diff --git a/.gitattributes b/.gitattributes index b7d98e49d..3975fc099 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,5 +8,4 @@ # Files that will always have CRLF line endings on checkout. *.bat eol=crlf -build/macosx/jAppleMenuBar.url eol=crlf - +*.url eol=crlf diff --git a/.github/issue_template.md b/.github/issue_template.md index 01a24196c..02fbadc04 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -1,5 +1,8 @@ - - + + + + + @@ -26,11 +29,11 @@ ## Your Environment - - + + * Processing version: * Operating System and OS version: * Other information: ## Possible Causes / Solutions - + diff --git a/.gitignore b/.gitignore index 95436daf3..e3b98dcf5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,17 @@ .DS_Store .AppleDouble -*.iml ._* *~ /build/shared/reference.zip *.x -# temporary, until we complete the move to IntelliJ #*.iml #/.idea +# may need to bring this back later +/.idea/codeStyles + # via https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 @@ -88,3 +89,7 @@ bin-test # dated folders /19* /20* + +# VS Code Java project files +.project +.vscode/ \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index b754b3d59..3d3ab27e9 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index ab31269e4..335d8c4b3 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,19 +3,17 @@ - - + - - \ No newline at end of file + diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 797acea53..000000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index afb13c6f1..ed3eae586 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,74 @@ ## Welcome to Processing! -### Bug Report? +## Bug Report? We have a page on [troubleshooting](https://github.com/processing/processing/wiki/Troubleshooting) common problems. Check there first! -We also host an [online forum](https://forum.processing.org) for coding questions, which is also helpful for general "getting started" queries. +We also host an [online forum](https://discourse.processing.org/) for coding questions, which is also helpful for general “getting started” queries. -If you don't find an answer, please let us know by [filing an issue](https://github.com/processing/processing/issues). We can only fix the things we've heard about. +If you don’t find an answer, please let us know by [filing an issue](https://github.com/processing/processing4/issues). We can only fix the things we’ve heard about! -Please keep the tone polite. This project is volunteer work done in our free time. We give it away at no cost. We do this because we think it's important for the community and enjoy it. Complaints that things *suck* are *annoying* or lectures about things that *must* be fixed are... weird things to hear from strangers at best, demotivating at worst. +Please keep the tone polite. This project is volunteer work done in our free time. We give it away at no cost. We do this because we think it’s important for the community, and enjoy working on it. Complaints that things *suck*, or are *annoying*, or lectures about things that *must* be fixed are… weird to hear from total strangers (at best), and demotivating (more likely). -### Want to Help? -Great! The number of contributors on this project is *tiny*, especially relative to the number of users. There are [only 2 or 3 people](https://github.com/processing/processing/graphs/contributors) who actively work on this repository, for instance. We need help! +## Want to Help? + +Great! The number of contributors on this project is *tiny*, especially relative to the number of users. There are [only one or two people](https://github.com/processing/processing4/graphs/contributors) who actively work on this repository, for instance. We need help! How to start: -* Issues marked [help](https://github.com/processing/processing/issues?q=is%3Aissue+is%3Aopen+label%3Ahelp) are a good place to start, because they're something that's isolated enough that someone can jump into it without significant reworking of other code. -* Mind the [style guidelines](https://github.com/processing/processing/wiki/Style-Guidelines) when submitting pull requests. Otherwise someone else will have to reformat your code so that it fits everything else (or we'll have to reject it if it'll take us too long to clean it up). +* **Help Wanted** – Most [issues marked help wanted](https://github.com/processing/processing4/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) are a good place to start. Issues are marked with this tag when: -### Other Details + * They are isolated enough that someone can jump into it without significant reworking of other code. + * Ben knows that it’s unlikely that he’ll have time to work on them. -This document was hastily thrown together in an attempt to improve the bug reporting process. It needs more detail about our intent with the project, the community behind it, our values, and an explanation of how the code itself is designed. \ No newline at end of file +* **The Old Repository** – There are also many “help wanted” [issues in the 3.x repository](https://github.com/processing/processing4/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22). Some of these are very old, so it may be a good idea to check in about the priority before putting in too much work! + +* **JavaFX** – There are several [active issues](https://github.com/processing/processing4-javafx/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) for the JavaFX renderer as well. + +* **The `todo.txt` File** – This is *not* a good place to start. It’s Ben’s rambling to-do list, and dates back to the very start of the project over twenty years ago. It shouldn’t be used as a guideline for work to be done, because there are lots of things there that are no longer relevant. Consider it the dusty attic of what’s inside his head. If you see something of interest there, open an issue to see if it’s still a priority, or how it should be approached. But also, there are *so many open issues*, which represent actual problems identified by community members. Those are by far the best place to start. + +* **Style Guidelines** – Keep the [style guidelines](https://github.com/processing/processing/wiki/Style-Guidelines) in mind when submitting pull requests. If you don’t, someone else will have to reformat your code so that it fits everything else (or we’ll have to reject it if it’ll take us too long to clean it up). + +* **Larger Projects** – If you’re looking for a larger project, check out the [Project List](https://github.com/processing/processing/wiki/Project-List#processing) for other ideas. + + +## New Features + +Nearly all new features are first introduced as a Library or a Mode, or even as an example. The current [OpenGL renderer](http://glgraphics.sourceforge.net/) and Video library began as separate projects by Andrés Colubri, who needed a more performant, more sophisticated version of what we had in Processing for work that he was creating. The original `loadShape()` implementation came from the “Candy” library by Michael Chang (“mflux“). + +Similarly, Tweak Mode began as a [separate project](http://galsasson.com/tweakmode/) by Gal Sasson before being incorporated. PDE X was a Google Summer of code [project](https://github.com/processing/processing-experimental) by Manindra Moharana that updated the PDE to include basic refactoring and better error checking. + +Developing features separately from the main software has several benefits: + +* It’s easier for the contributor to develop the software without it needing to work for tens of thousands of Processing users. +* It provides a way to get feedback on that code independently of everything else, and iterating on it rapidly. +* This feedback process also helps gauge the level of interest for the community, and how it should be prioritized for the software. +* We can delay the process of “normalizing” the features so that they’re consistent with the rest of Processing (function naming, structure, etc). + +A major consideration for any new feature is the level of maintenance that it might require in the future. If the original maintainer loses interest over time (which is normal), any ongoing work usually falls to Ben, or it sits on the issues list unfixed, which isn’t good for the community, or for Ben, who has plenty of issues of his own—whether Processing or otherwise. + +Processing is a massive project that has existed for more than 20 years. Part of its longevity comes from the effort that’s gone into keeping things as simple as we can, and making a lot of decisions about *what to leave out*. Adding a new feature always has to be weighed against the potential confusion of one more thing—whether it’s a menu item, a dialog box, a function that needs to be added to the reference, etc. Adding a new graphics function means making it work across all the renderers that we ship (Java2D, OpenGL, JavaFX, PDF, etc) and across platforms (macOS, Windows, Linux). + + +## Editor + +The current Editor, based on the ancient [JEditSyntax](http://syntax.jedit.org/) package has held up long past its expiration date. [Exhaustive work](https://github.com/processing/processing4/blob/master/app/src/processing/app/syntax/README.md) has been done to look at replacing the component with something more modern, like `RSyntaxArea`, but it’s simply not feasible without breaking a massive amount of code, and likely introducing a lot of regressions in the process. All for… code folding? An incrementally better experience? But with potential for major setbacks in low-level code? It’s simply not a path that makes sense. + +With that in mind, any work on updating the editor and adding new features should be focused on first [adapting the preprocessor and compiler](https://github.com/processing/processing4/issues/117) to be wrapped using the [Language Server Protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol), so that we can link to other existing editors (Visual Studio Code and many others). It should be possible to create a Java-only, headless implementation that wraps the current source in this repository and can communicate via LSP. + +Once that initial work is done, more people can use popular tools like VS Code and others, and we can start building a new PDE that’s as simple to use as the current application, based on something like [Theia](https://theia-ide.org/), a new editor platform that uses LSP as its basis. + +With that in mind, nearly all editor enhancement requests will be redirected to this aim. The current editor does what we want it to, for the intended audience, and improving it requires a better foundation as a starting point. + + +## Refactoring + +Refactoring is fun! There’s always more cleaning to do. It’s also often not very helpful. + +Broadly speaking, the code is built the way it is for a reason. There are so many things that can be improved, but those improvements need to come from an understanding of what’s been built so far. Changes that include refactoring are typically only accepted from contributors who have an established record of working on the code. With a better understanding of the software, the refactoring decisions come from a better, more useful place. + + +## Other Details + +This document was hastily thrown together in an attempt to improve the bug reporting and development/contribution process. It doesn’t yet include detail about our intent with the project, the community behind it, our values, and an explanation of how the code itself is designed. diff --git a/README.md b/README.md index f61f9ce64..2a1539301 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,91 @@ +This repository contains the source code for the [Processing](https://processing.org/) project for people who want to help improve the code. + +If you're interested in *using* Processing, get started at the [download](https://processing.org/download) page, or read more about the project at the [home page](https://processing.org/). There are also several [tutorials](https://processing.org/tutorials) that provide a helpful introduction. They are complemented by hundreds of examples that are included with the software itself. + + # Processing 4.0 -Processing 4 makes important updates to the code to prepare the platform for its future. Most significantly, this includes the move to JDK 11 and support for new Java language features. The changes should be transparent to most users, but because of the massive shift behind the scenes, this will be 4.0. +Processing 4 has [important updates](https://github.com/processing/processing4/wiki/Changes-in-4.0) that prepare the platform for its future. Most significantly, this includes the move to Java 17 as well as major changes to the range of platforms we support (Apple Silicon! Raspberry Pi on 32- and 64-bit ARM!) + +With any luck, many changes should be transparent to most users, in spite of how much is updated behind the scenes. More immediately visible changes include major work on the UI, including “themes” and the ability to change how sketches are named by default. -## Roadmap +## Building the Code -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. +[Instructions on how to build the code](https://github.com/processing/processing4/blob/master/build/README.md) are found in a README inside the `build` folder. -* 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. +We've also moved to a new repository for this release so that we could cull a lot of the accumulated mess of the last 20 years, which makes `git clone` (and most other `git` operations) a lot faster. -* 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). +The work on 4.0 was done by a [tiny number of people](https://github.com/processing/processing4/graphs/contributors?from=2019-10-01&to=2022-08-09&type=c) who continue working on it, unpaid, because they care about it. Please help! ## 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. +As with all releases, we'll do [everything possible](https://twitter.com/ben_fry/status/1426282574683516928) 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.) +The full list of changes can be seen in [the release notes for each version](https://github.com/processing/processing4/blob/master/build/shared/revisions.md). The list below only covers changes for developers working on this repository, or that may have an impact on Library, Mode, or Tool development. + + +### Beta 9 + +* Major changes to themes and some libraries too. Also changed the default branch. If you have an older checkout, do this: + + git pull + git checkout main + ant clean + ant clean-libs + ant run + + …or just do a fresh `git clone` and pull down the latest. + +* Apple Silicon support should be complete, as far as we know. If you find otherwise, file an issue. + +* Check out the long [revisions](https://github.com/processing/processing4/blob/master/build/shared/revisions.md) update for this one. Too much to cover here. + +* Now using Java 17.0.4+8 from [Adoptium](https://adoptium.net/). + + +### Beta 8 + +* [Renamed](https://github.com/processing/processing4/commit/409163986ff2ff4d2dbf69c79c7eced45950d1d0) the Add Mode, Add Library, and Add Tool menu items to Manage Modes, Manage Libraries, and Manage Tools. This will require translation updates for the new text: + * `toolbar.add_mode = Add Mode...` has been replaced with `toolbar.manage_modes = Manage Modes…` + * `menu.library.add_library = Add Library...` → `menu.library.manage_libraries = Manage Libraries…` + * `menu.tools.add_tool = Add Tool...` → `menu.tools.manage_tools = Manage Tools…` + + +### Beta 6 + +* Major rewrite of `handleOpen()`, now possible to use something besides the folder name for the main sketch file (see `revisions.md` for details). + +* Now using Java 17.0.2+8 from [Adoptium](https://adoptium.net/). + + +### Beta 3 + +* Now using JDK 17.0.1 and JavaFX 17.0.1. + +* Major changes to `theme.txt` and theme handling in general. Now rendering toolbar icons from SVG images. More documentation later. + +* Made `DrawListener` public in `PSurfaceJOGL`. + + +### Beta 2 + +* Added a workaround so that `color` can be part of package names, which gets some older code (i.e. toxiclibs) running again. + + +### Beta 1 + +* Now using JDK 11.0.12+7. + + +### Alpha 6 + +* `Editor.applyPreferences()` was `protected`, now it's `public`. + +* Removed `Editor.repaintErrorBar()` and `Editor.showConsole()`. Does not appear to be in use anywhere, easy to add back if we hear otherwise. + +* Renamed `TextAreaPainter.getCompositionTextpainter()` to `getCompositionTextPainter()`. This was an internal function and inconsistent with the rest of the function naming. ### Alpha 5 @@ -35,11 +104,16 @@ The full list of changes can be seen in the release notes for each version, this * `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 3 + +* `export.embed_java.for` changed to `export.include_java` which also embeds a string for the platform for better localization support. + + ### 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.~~ +* ~~See `revisions.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. @@ -58,53 +132,3 @@ The full list of changes can be seen in the release notes for each version, this * `Base.defaultFileMenu` is now `protected` instead of `static public` * 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 - -* `export.embed_java.for` changed to `export.include_java` which also embeds a string for the platform for better localization support. - - -## Building the Code - -We'll eventually create a new wiki page with the build instructions, but for the time being, the instructions are: - -1. Download and install JDK 11 from -2. Make sure `ant` is installed for your platform. -3. Open a Terminal window/Command Prompt/whatever and type: - - cd /path/to/processing4/build - ant run - -### Java version complaints - -You might have multiple versions of Java installed. Type `java -version` and if it says something other than 11, you'll need to set the `JAVA_HOME` environment variable. - -On macOS, you can use: - - export JAVA_HOME="`/usr/libexec/java_home -v 11`" - -If you need to go back to Java 8 (i.e. to build Processing 3), you can use: - - export JAVA_HOME="`/usr/libexec/java_home -v 1.8`" - -On Windows and Linux, you can set `JAVA_HOME` to point at the installation the way you would any other environment variable. - -On Linux (Ubuntu 20.04 in particular), the headless version of OpenJDK may be installed by default. If so, you may get errors when trying to run tests in core: - - java.lang.UnsatisfiedLinkError: Can't load library: /usr/lib/jvm/java-11-openjdk-amd64/lib/libawt_xawt.so - -If so, use `sudo apt install openjdk-11-jdk` to install a full version. (You could also make use of the version downloaded by Processing itself to avoid duplication, but that's a little trickier to get everything bootstrapped and (sym)linked properly.) - -And again, we'll have more complete instructions later once the dust settles. - -### Eclipse - -If you're using Eclipse, it'll complain about the lack of `jogl-all-src.jar`. Steps to create your own: - - git clone --recurse-submodules git://jogamp.org/srv/scm/jogl.git jogl - cd jogl - git checkout 0779f229b0e9538c640b18b9a4e095af1f5a35b3 - zip -r ../jogl-all-src.jar src - -Then copy that `jogl-all-src.jar` file to sit next to the `jogl-all.jar` folder inside `/path/to/processing/core/library`. diff --git a/app/.classpath b/app/.classpath index 3fb1a2481..fa715a50e 100644 --- a/app/.classpath +++ b/app/.classpath @@ -10,7 +10,8 @@ - + + diff --git a/app/build.xml b/app/build.xml index d667aa2c8..c48adeb02 100644 --- a/app/build.xml +++ b/app/build.xml @@ -1,30 +1,10 @@ - - - - - - - - - - - - - - - - - - - - - - + + @@ -71,11 +51,9 @@ - + - - @@ -120,8 +98,34 @@ + + + + + + + + + + + + + + + + + + + + + + + depends="download-ant, download-jna, download-flatlaf"> @@ -129,10 +133,12 @@ - + + + @@ -143,19 +149,18 @@ - @@ -167,18 +172,18 @@ - + - - - - + + + diff --git a/app/lib/.gitignore b/app/lib/.gitignore index 25b443b60..755a82b25 100644 --- a/app/lib/.gitignore +++ b/app/lib/.gitignore @@ -1,7 +1,7 @@ ant.jar ant-launcher.jar +flatlaf.jar + jna.jar jna-platform.jar - -VAqua*.jar \ No newline at end of file diff --git a/app/processing4-app.iml b/app/processing4-app.iml new file mode 100644 index 000000000..62e9a8c3a --- /dev/null +++ b/app/processing4-app.iml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 26ab0d06f..5167857e4 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-21 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -27,7 +27,6 @@ import java.awt.*; import java.awt.event.ActionListener; import java.io.*; import java.lang.reflect.InvocationTargetException; -import java.text.SimpleDateFormat; import java.util.*; import java.util.List; import java.util.Map.Entry; @@ -35,6 +34,9 @@ import java.util.Map.Entry; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; +import com.formdev.flatlaf.FlatDarkLaf; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.FlatLightLaf; import processing.app.contrib.*; import processing.app.tools.Tool; import processing.app.ui.*; @@ -52,9 +54,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 = 1277; + static private final int REVISION = 1287; /** This might be replaced by main() if there's a lib/version.txt file. */ - static private String VERSION_NAME = "1277"; //$NON-NLS-1$ + static private String VERSION_NAME = "1287"; //$NON-NLS-1$ static final public String SKETCH_BUNDLE_EXT = ".pdez"; static final public String CONTRIB_BUNDLE_EXT = ".pdex"; @@ -66,14 +68,19 @@ public class Base { */ static public boolean DEBUG; + /** True if running via Commander. */ static private boolean commandLine; + /** + * If settings.txt is present inside lib, it will be used to override + * the location of the settings folder so that "portable" versions + * of the software are possible. + */ + static private File settingsOverride; + // A single instance of the preferences window PreferencesFrame preferencesFrame; - // A single instance of the library manager window -// ContributionManagerDialog contributionManagerFrame; - // Location for untitled items static File untitledFolder; @@ -81,20 +88,30 @@ public class Base { 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; /** - * Starts with the last mode used with the environment, - * or the default mode if not used. + * The next Mode to be used with handleNew() or handleOpen() + * (unless it's overridden by something else). Starts with the last + * Mode used with the environment, or the default mode if not used. */ private Mode nextMode; - /** The built-in modes. coreModes[0] will be considered the 'default'. */ - private Mode[] coreModes; + /** Only one built-in Mode these days, removing the extra fluff. */ + private Mode coreMode; - protected List modeContribs; - protected List exampleContribs; + // TODO can these be Set objects, or are they expected to be in order? + protected List contribModes; + protected List contribExamples; + + /** These aren't even dynamically loaded, they're hard-wired here. */ + private List internalTools; + + // TODO can these be Set objects, or are they expected to be in order? + private List coreTools; + private List contribTools; // Used by handleOpen(), this saves the chooser to remember the directory. // Doesn't appear to be necessary with the AWT native file dialog. @@ -102,7 +119,6 @@ public class Base { private JFileChooser openChooser; static protected File sketchbookFolder; -// protected File toolsFolder; static public void main(final String[] args) { @@ -145,6 +161,7 @@ public class Base { static private void createAndShowGUI(String[] args) { + // these times are fairly negligible relative to Base. // long t1 = System.currentTimeMillis(); File versionFile = Platform.getContentFile("lib/version.txt"); @@ -157,6 +174,35 @@ public class Base { } } + // Detect settings.txt in the lib folder for portable versions + File settingsFile = Platform.getContentFile("lib/settings.txt"); + if (settingsFile != null && settingsFile.exists()) { + try { + Settings portable = new Settings(settingsFile); + String path = portable.get("settings.path"); + File folder = new File(path); + boolean success = true; + if (!folder.exists()) { + success = folder.mkdirs(); + if (!success) { + Messages.err("Could not create " + folder + " to store settings."); + } + } + if (success) { + if (!folder.canRead()) { + Messages.err("Cannot read from " + folder); + } else if (!folder.canWrite()) { + Messages.err("Cannot write to " + folder); + } else { + settingsOverride = folder.getAbsoluteFile(); + } + } + } catch (IOException e) { + Messages.err("Error while reading the settings.txt file", e); + } + } + + Platform.init(); // call after Platform.init() because we need the settings folder Console.startup(); @@ -172,7 +218,7 @@ public class Base { DEBUG = true; } - // Use native popups so they don't look crappy on macOS + // Use native popups to avoid looking crappy on macOS JPopupMenu.setDefaultLightWeightPopupEnabled(false); // Don't put anything above this line that might make GUI, @@ -199,11 +245,12 @@ public class Base { } // long t3 = System.currentTimeMillis(); -// long t4 = System.currentTimeMillis(); // Get the sketchbook path, and make sure it's set properly locateSketchbookFolder(); +// long t4 = System.currentTimeMillis(); + // Load colors for UI elements. This must happen after Preferences.init() // (so that fonts are set) and locateSketchbookFolder() so that a // theme.txt file in the user's sketchbook folder is picked up. @@ -211,8 +258,10 @@ public class Base { // Create a location for untitled sketches try { - untitledFolder = Util.createTempFolder("untitled", "sketches", null); - untitledFolder.deleteOnExit(); + //untitledFolder = Util.createTempFolder("untitled", "sketches", null); + //untitledFolder.deleteOnExit(); + untitledFolder = Util.getProcessingTemp(); + } catch (IOException e) { Messages.showError("Trouble without a name", "Could not create a place to store untitled sketches.\n" + @@ -220,11 +269,12 @@ public class Base { } // long t5 = System.currentTimeMillis(); -// long t6 = 0; +// long t6 = 0; // replaced below, just needs decl outside try { } Messages.log("About to create Base..."); //$NON-NLS-1$ try { final Base base = new Base(args); + base.updateTheme(); Messages.log("Base() constructor succeeded"); // t6 = System.currentTimeMillis(); @@ -232,17 +282,19 @@ public class Base { SingleInstance.startServer(base); handleWelcomeScreen(base); - //checkDriverBug(); // that was 2017, right? + handleCrustyDisplay(); + handleTempCleaning(); } catch (Throwable t) { // Catch-all to pick up badness during startup. + Throwable err = t; if (t.getCause() != null) { // Usually this is the more important piece of information. We'll // show this one so that it's not truncated in the error window. - t = t.getCause(); + err = t.getCause(); } Messages.showTrace("We're off on the wrong foot", - "An error occurred during startup.", t, true); + "An error occurred during startup.", err, true); } Messages.log("Done creating Base..."); //$NON-NLS-1$ @@ -252,7 +304,51 @@ public class Base { } + public void updateTheme() { + try { + //System.out.println("updating theme"); + FlatLaf laf = "dark".equals(Theme.get("laf.mode")) ? + new FlatDarkLaf() : new FlatLightLaf(); + laf.setExtraDefaults(Collections.singletonMap("@accentColor", + Theme.get("laf.accent.color"))); + //System.out.println(laf.getExtraDefaults()); + //UIManager.setLookAndFeel(laf); + FlatLaf.setup(laf); + // updateUI() will wipe out our custom components + // even if we do a setUI() call and invalidate/revalidate/repaint +// FlatLaf.updateUI(); +// System.out.println("FlatLaf.updateUI() should be done"); +// FlatLaf.updateUILater(); + + } catch (Exception e) { + e.printStackTrace(); + } + + if (preferencesFrame != null) { + preferencesFrame.updateTheme(); + } + + ContributionManager.updateTheme(); + for (Editor editor : getEditors()) { + editor.updateTheme(); + +// Component sb = editor.getPdeTextArea(); +// sb.invalidate(); +// sb.revalidate(); +// sb.repaint(); + } + + /* + Window[] windows = Window.getWindows(); + for (Window w : windows) { + SwingUtilities.updateComponentTreeUI(w); + } + */ + } + + static private void handleWelcomeScreen(Base base) { + /* boolean sketchbookPrompt = false; if (Preferences.getBoolean("welcome.four.beta.show")) { // only ask once about split sketchbooks @@ -274,58 +370,86 @@ public class Base { } } } + */ // Needs to be shown after the first editor window opens, so that it // shows up on top, and doesn't prevent an editor window from opening. - if (Preferences.getBoolean("welcome.four.beta.show")) { + if (Preferences.getBoolean("welcome.four.show")) { try { - new Welcome(base, sketchbookPrompt); + //new Welcome(base, sketchbookPrompt); + new Welcome(base); } catch (IOException e) { Messages.showTrace("Unwelcoming", "Please report this error to\n" + - "https://github.com/processing/processing/issues", e, false); + "https://github.com/processing/processing4/issues", e, false); } } } - /* - // Remove this code in a couple of months [fry 170211] - // https://github.com/processing/processing/issues/4853 - // Or maybe not, if NVIDIA keeps doing this [fry 170423] - // https://github.com/processing/processing/issues/4997 - @SuppressWarnings("SpellCheckingInspection") - static private void checkDriverBug() { - if (System.getProperty("os.name").contains("Windows 10")) { - 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.err("Problem checking NVIDIA driver", e); + /** + * Temporary workaround as we try to sort out + * https://github.com/processing/processing4/issues/231 + * and https://github.com/processing/processing4/issues/226 + */ + static private void handleCrustyDisplay() { + /* + System.out.println("retina is " + Toolkit.isRetina()); + System.out.println("system zoom " + Platform.getSystemZoom()); + System.out.println("java2d param is " + System.getProperty("sun.java2d.uiScale.enabled")); + System.out.println("toolkit res is " + java.awt.Toolkit.getDefaultToolkit().getScreenResolution()); + */ + if (Platform.isWindows()) { // only an issue on Windows + if (!Toolkit.isRetina() && !Splash.getDisableHiDPI()) { + int res = java.awt.Toolkit.getDefaultToolkit().getScreenResolution(); + if (res % 96 != 0) { + // fractional dpi scaling on a low-res screen + System.out.println("If the editor cursor is in the wrong place or the interface is blocky or fuzzy,"); + System.out.println("open Preferences and select the “Disable HiDPI Scaling” option to fix it."); } - }).start(); + } + } + } + + + static private void handleTempCleaning() { + new Thread(() -> { + Console.cleanTempFiles(); + cleanTempFolders(); + }).start(); + } + + + /** + * Clean folders and files from the Processing subdirectory + * of the user's temp folder (java.io.tmpdir). + */ + static public void cleanTempFolders() { + try { + final File tempDir = Util.getProcessingTemp(); + final int days = Preferences.getInteger("temp.days"); + + if (days > 0) { + final long now = new Date().getTime(); + final long diff = days * 24 * 60 * 60 * 1000L; + File[] expiredFiles = + tempDir.listFiles(file -> (now - file.lastModified()) > diff); + if (expiredFiles != null) { + // Remove the files approved for deletion + for (File file : expiredFiles) { + //file.delete(); // not as safe + try { + Platform.deleteFile(file); // move to trash + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } catch (IOException e) { + e.printStackTrace(); } } - */ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -362,29 +486,23 @@ public class Base { public Base(String[] args) throws Exception { -// long t1 = System.currentTimeMillis(); - // TODO Taking 3-5 seconds with several things installed, which is unacceptable. - // Will take longer to optimize because most needs to run on the EDT. + long t1 = System.currentTimeMillis(); ContributionManager.init(this); -// } catch (Exception e) { -// Messages.showWarning("Contribution Manager Error", -// "Error while setting up the Contribution Manager. Please report.", e); -// } -// }); -// long t2 = System.currentTimeMillis(); + long t2 = System.currentTimeMillis(); buildCoreModes(); + long t2b = System.currentTimeMillis(); rebuildContribModes(); + long t2c = System.currentTimeMillis(); rebuildContribExamples(); -// long t3 = System.currentTimeMillis(); + long t3 = System.currentTimeMillis(); // Needs to happen after the sketchbook folder has been located. - // Also relies on the modes to be loaded so it knows what can be + // Also relies on the modes to be loaded, so it knows what can be // marked as an example. -// recent = new Recent(this); Recent.init(this); -// long t4 = System.currentTimeMillis(); + long t4 = System.currentTimeMillis(); String lastModeIdentifier = Preferences.get("mode.last"); //$NON-NLS-1$ if (lastModeIdentifier == null) { nextMode = getDefaultMode(); @@ -404,7 +522,7 @@ public class Base { //contributionManagerFrame = new ContributionManagerDialog(); -// long t5 = System.currentTimeMillis(); + long t5 = System.currentTimeMillis(); // Make sure ThinkDifferent has library examples too nextMode.rebuildLibraryList(); @@ -413,7 +531,7 @@ public class Base { // menu works on Mac OS X (since it needs examplesFolder to be set). Platform.initBase(this); -// long t6 = System.currentTimeMillis(); + long t6 = System.currentTimeMillis(); // // Check if there were previously opened sketches to be restored // boolean opened = restoreSketches(); @@ -427,7 +545,7 @@ public class Base { // Fix a problem with systems that use a non-ASCII languages. Paths are // being passed in with 8.3 syntax, which makes the sketch loader code // unhappy, since the sketch folder naming doesn't match up correctly. - // http://dev.processing.org/bugs/show_bug.cgi?id=1089 + // https://download.processing.org/bugzilla/1089.html if (Platform.isWindows()) { try { File file = new File(args[i]); @@ -442,7 +560,7 @@ public class Base { } } -// long t7 = System.currentTimeMillis(); + long t7 = System.currentTimeMillis(); // Create a new empty window (will be replaced with any files to be opened) if (!opened) { @@ -452,16 +570,18 @@ public class Base { Messages.log("No handleNew(), something passed on the command line"); } -// long t8 = System.currentTimeMillis(); + long t8 = System.currentTimeMillis(); // check for updates new UpdateCheck(this); ContributionListing cl = ContributionListing.getInstance(); - cl.downloadAvailableList(this, new ContribProgressMonitor() { }); -// long t9 = System.currentTimeMillis(); + cl.downloadAvailableList(this, new ContribProgress(null)); + long t9 = System.currentTimeMillis(); + +// System.out.println("core modes: " + (t2b-t2) + ", contrib modes: " + (t2c-t2b) + ", contrib ex: " + (t2c-t2b)); // System.out.println("base took " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3) + -// " " + (t5-t4) + " 6-5=" + (t6-t5) + " " + (t7-t6) + " " + (t8-t7) + " " + (t9-t8) + " ms"); +// " " + (t5-t4) + " t6-t5=" + (t6-t5) + " " + (t7-t6) + " handleNew=" + (t8-t7) + " " + (t9-t8) + " ms"); } @@ -483,7 +603,7 @@ public class Base { defaultFileMenu.add(item); item = Toolkit.newJMenuItemShift(Language.text("menu.file.sketchbook"), 'K'); - item.addActionListener(e -> getNextMode().showSketchbookFrame()); + item.addActionListener(e -> showSketchbookFrame()); defaultFileMenu.add(item); item = Toolkit.newJMenuItemShift(Language.text("menu.file.examples"), 'O'); @@ -505,19 +625,20 @@ public class Base { } else { // PDE X calls getModeList() while it's loading, so coreModes must be set - coreModes = new Mode[] { javaModeContrib.getMode() }; + //coreModes = new Mode[] { javaModeContrib.getMode() }; + coreMode = javaModeContrib.getMode(); } } /** * Instantiates and adds new contributed modes to the contribModes list. - * Checks for duplicates so the same mode isn't instantiates twice. Does not + * Checks for duplicates so the same mode isn't instantiated twice. Does not * remove modes because modes can't be removed once they are instantiated. */ void rebuildContribModes() { - if (modeContribs == null) { - modeContribs = new ArrayList<>(); + if (contribModes == null) { + contribModes = new ArrayList<>(); } File modesFolder = getSketchbookModesFolder(); List contribModes = getModeContribs(); @@ -553,7 +674,7 @@ public class Base { } } - // This allows you to build and test your Mode code from Eclipse. + // This allows you to build and test a Mode from Eclipse // -Dusemode=com.foo.FrobMode:/path/to/FrobMode final String useMode = System.getProperty("usemode"); if (useMode != null) { @@ -593,8 +714,8 @@ public class Base { * remove modes because modes can't be removed once they are instantiated. */ void rebuildContribExamples() { - if (exampleContribs == null) { - exampleContribs = new ArrayList<>(); + if (contribExamples == null) { + contribExamples = new ArrayList<>(); } ExamplesContribution.loadMissing(this); } @@ -697,11 +818,6 @@ public class Base { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - List internalTools; - List coreTools; - List contribTools; - - public List getCoreTools() { return coreTools; } @@ -724,13 +840,16 @@ public class Base { if (internalTools == null) { internalTools = new ArrayList<>(); - initInternalTool("processing.app.tools.CreateFont"); - initInternalTool("processing.app.tools.ColorSelector"); initInternalTool("processing.app.tools.Archiver"); + initInternalTool("processing.app.tools.ColorSelector"); + initInternalTool("processing.app.tools.CreateFont"); if (Platform.isMacOS()) { initInternalTool("processing.app.tools.InstallCommander"); } + + initInternalTool("processing.app.tools.ThemeSelector"); + //initInternalTool("processing.app.tools.UpdateTheme"); } // No need to reload these either @@ -824,9 +943,10 @@ public class Base { toolsMenu.addSeparator(); } - JMenuItem item = new JMenuItem(Language.text("menu.tools.add_tool")); - item.addActionListener(e -> ContributionManager.openTools()); - toolsMenu.add(item); + JMenuItem manageTools = + new JMenuItem(Language.text("menu.tools.manage_tools")); + manageTools.addActionListener(e -> ContributionManager.openTools()); + toolsMenu.add(manageTools); } @@ -882,48 +1002,48 @@ public class Base { public List getModeContribs() { - return modeContribs; + return contribModes; } public List getModeList() { - List allModes = new ArrayList<>(Arrays.asList(coreModes)); - if (modeContribs != null) { - for (ModeContribution contrib : modeContribs) { - allModes.add(contrib.getMode()); + //List outgoing = new ArrayList<>(Arrays.asList(coreModes)); + List outgoing = new ArrayList<>(); + outgoing.add(coreMode); + if (contribModes != null) { + for (ModeContribution contrib : contribModes) { + outgoing.add(contrib.getMode()); } } - return allModes; + return outgoing; } - public List getExampleContribs() { - return exampleContribs; + public List getContribExamples() { + return contribExamples; } - private List getInstalledContribs() { + private Set getInstalledContribs() { List modeContribs = getModeContribs(); - List contributions = new ArrayList<>(modeContribs); + Set contributions = new HashSet<>(modeContribs); for (ModeContribution modeContrib : modeContribs) { Mode mode = modeContrib.getMode(); contributions.addAll(new ArrayList<>(mode.contribLibraries)); } + // how is this different from getToolContribs()? // TODO this duplicates code in Editor, but it's not editor-specific -// List toolContribs = -// ToolContribution.loadAll(Base.getSketchbookToolsFolder()); -// contributions.addAll(toolContribs); contributions.addAll(ToolContribution.loadAll(getSketchbookToolsFolder())); - contributions.addAll(getExampleContribs()); + contributions.addAll(getContribExamples()); return contributions; } public byte[] getInstalledContribsInfo() { - List contribs = getInstalledContribs(); + Set contribs = getInstalledContribs(); StringList entries = new StringList(); for (Contribution c : contribs) { String entry = c.getTypeName() + "=" + @@ -934,22 +1054,6 @@ public class Base { } String joined = "id=" + UpdateCheck.getUpdateID() + "&" + entries.join("&"); -// StringBuilder sb = new StringBuilder(); -// try { -// // Truly ridiculous attempt to shove everything into a GET request. -// // More likely to be seen as part of a grand plot. -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// GZIPOutputStream output = new GZIPOutputStream(baos); -// PApplet.saveStream(output, new ByteArrayInputStream(joined.getBytes())); -// output.close(); -// byte[] b = baos.toByteArray(); -// for (int i = 0; i < b.length; i++) { -// sb.append(PApplet.hex(b[i], 2)); -// } -// } catch (IOException e) { -// e.printStackTrace(); -// } -// return sb.toString(); return joined.getBytes(); } @@ -957,34 +1061,15 @@ public class Base { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /** - * Create or modify a sketch.properties file to specify the given Mode. - */ - private void saveModeSettings(final File sketchProps, final Mode mode) { - try { - final Settings settings = new Settings(sketchProps); - settings.set("mode", mode.getTitle()); - settings.set("mode.id", mode.getIdentifier()); - settings.save(); - } catch (IOException e) { - System.err.println("While creating " + sketchProps + ": " + e.getMessage()); - } - } - - String getDefaultModeIdentifier() { + // Used to initialize coreModes, so cannot use coreModes[0].getIdentifier() return "processing.mode.java.JavaMode"; } public Mode getDefaultMode() { - return coreModes[0]; - } - - - /** Used by ThinkDifferent so that it can have a Sketchbook menu. */ - public Mode getNextMode() { - return nextMode; + //return coreModes[0]; + return coreMode; } @@ -998,30 +1083,34 @@ public class Base { nextMode = mode; if (sketch.isModified()) { - handleNew(); // don't bother with error messages, just switch + // If sketch is modified, simpler to just open a new window. + // https://github.com/processing/processing4/issues/189 + handleNew(); return false; } else if (sketch.isUntitled()) { // The current sketch is empty, just close and start fresh. // (Otherwise the editor would lose its 'untitled' status.) + // Safe to do here because of the 'modified' check above. handleClose(activeEditor, true); handleNew(); } 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 the current sketch contains file extensions that the new Mode + // can handle, then write a sketch.properties file with that Mode + // specified, and reopen. Currently, only used for Java <-> Android. if (mode.canEdit(sketch)) { - final File props = new File(sketch.getCodeFolder(), "sketch.properties"); - saveModeSettings(props, nextMode); + //final File props = new File(sketch.getFolder(), "sketch.properties"); + //saveModeSettings(props, nextMode); + sketch.updateModeProperties(nextMode, getDefaultMode()); handleClose(activeEditor, true); - Editor editor = handleOpen(sketch.getMainFilePath()); + Editor editor = handleOpen(sketch.getMainPath()); if (editor == null) { // the Mode change failed (probably code that's out of date) // re-open the sketch using the mode we were in before - saveModeSettings(props, oldMode); - handleOpen(sketch.getMainFilePath()); + //saveModeSettings(props, oldMode); + sketch.updateModeProperties(oldMode, getDefaultMode()); + handleOpen(sketch.getMainPath()); return false; } } else { @@ -1044,7 +1133,6 @@ public class Base { } return true; } - */ private static class ModeInfo { @@ -1078,51 +1166,48 @@ public class Base { } return null; } + */ - private Mode promptForMode(final File sketch, final ModeInfo preferredMode) { + /* + private Mode promptForMode(final File passedFile) { + final String filename = passedFile.getName(); final String extension = - sketch.getName().substring(sketch.getName().lastIndexOf('.') + 1); + filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); final List possibleModes = new ArrayList<>(); for (final Mode mode : getModeList()) { - if (mode.canEdit(sketch)) { + if (mode.canEdit(passedFile)) { possibleModes.add(mode); } } - if (possibleModes.size() == 1 && - possibleModes.get(0).getIdentifier().equals(getDefaultModeIdentifier())) { - // If default mode can open it, then do so without prompting. - return possibleModes.get(0); - } if (possibleModes.size() == 0) { - if (preferredMode == null) { - final String msg = - "I don't know how to open a sketch with the \"" + extension + "\"\n" + - "file extension. You'll have to install a different\n" + - "Mode for that."; - Messages.showWarning("Modeless Dialog", msg); - } else { - Messages.showWarning("Modeless Dialog", - "Install " + preferredMode.title + " Mode " + - "to open this sketch."); - } + final String msg = + "I don't know how to open a sketch with the \"" + extension + "\"\n" + + "file extension. You'll have to install a different\n" + + "Mode for that."; + Messages.showWarning("Modeless Dialog", msg); return null; + + } else if (possibleModes.size() == 1) { + // If there's one Mode that can open this sketch, just open it + return possibleModes.get(0); + + } else { + // More than one Mode possible, prompt the user + Mode[] modes = possibleModes.toArray(new Mode[0]); + return (Mode) JOptionPane.showInputDialog(null, + (nextMode.getTitle() + " Mode can't open ." + extension + " files, " + + "but you have more than one Mode\ninstalled that can. " + + "Select which you would like to use:"), + "Choose Wisely", + JOptionPane.QUESTION_MESSAGE, + null, modes, modes[0]); } - 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. " + - "Would you like to try one?") : - ("That's a " + preferredMode.title + " Mode sketch, " + - "but you don't have " + preferredMode.title + " installed.\n" + - "Would you like to try a different mode for opening a " + - "." + extension + " sketch?"); - return (Mode) JOptionPane.showInputDialog(null, message, "Choose Wisely", - JOptionPane.QUESTION_MESSAGE, - null, modes, modes[0]); } + */ + /* private Mode selectMode(final File sketch) { final ModeInfo modeInfo = modeInfoFor(sketch); final Mode specifiedMode = modeInfo == null ? null : findMode(modeInfo.id); @@ -1131,6 +1216,7 @@ public class Base { } return promptForMode(sketch, modeInfo); } + */ protected Mode findMode(String id) { @@ -1143,92 +1229,57 @@ public class Base { } + /** + * Called when a Mode is uninstalled, in case it's the current Mode. + */ + public void modeRemoved(Mode mode) { + if (nextMode == mode) { + nextMode = getDefaultMode(); + } + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - boolean breakTime = false; - String[] months = { - "jan", "feb", "mar", "apr", "may", "jun", - "jul", "aug", "sep", "oct", "nov", "dec" - }; - - /** * Create a new untitled document in a new sketch window. */ public void handleNew() { // long t1 = System.currentTimeMillis(); try { - 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. -// File sketchbookDir = getSketchbookFolder(); - File newbieParentDir = untitledFolder; + //File sketchbookDir = getSketchbookFolder(); + File newbieDir = SketchName.nextFolder(untitledFolder); - String prefix = Preferences.get("editor.untitled.prefix"); - - // Use a generic name like sketch_031008a, the date plus a char - int index = 0; - String format = Preferences.get("editor.untitled.suffix"); - String suffix; - if (format == null) { - Calendar cal = Calendar.getInstance(); - int day = cal.get(Calendar.DAY_OF_MONTH); // 1..31 - int month = cal.get(Calendar.MONTH); // 0..11 - suffix = months[month] + PApplet.nf(day, 2); - } else { - SimpleDateFormat formatter = new SimpleDateFormat(format); - suffix = formatter.format(new Date()); - } - do { - if (index == 26) { - // In 0159, avoid running past z by sending people outdoors. - if (!breakTime) { - Messages.showWarning("Time for a Break", - "You've reached the limit for auto naming of new sketches\n" + - "for the day. How about going for a walk instead?", null); - breakTime = true; - } else { - Messages.showWarning("Sunshine", - "No really, time for some fresh air for you.", null); - } - return; - } - 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. - // https://github.com/processing/processing/issues/322 - newbieName = Sketch.sanitizeName(newbieName); - newbieDir = new File(newbieParentDir, newbieName); - index++; - // Make sure it's not in the temp folder *and* it's not in the sketchbook - } while (newbieDir.exists() || new File(sketchbookFolder, newbieName).exists()); + // User was told to go outside or other problem happened inside naming. + if (newbieDir == null) return; // Make the directory for the new sketch if (!newbieDir.mkdirs()) { throw new IOException("Could not create directory " + newbieDir); } + // Retrieve the sketch name from the folder name (not a great + // assumption for the future, but overkill to do otherwise for now.) + String newbieName = newbieDir.getName(); + // Add any template files from the Mode itself File newbieFile = nextMode.addTemplateFiles(newbieDir, newbieName); // Create sketch properties file if it's not the default mode. if (!nextMode.equals(getDefaultMode())) { - saveModeSettings(new File(newbieDir, "sketch.properties"), nextMode); + Sketch.updateModeProperties(newbieDir, nextMode, getDefaultMode()); } String path = newbieFile.getAbsolutePath(); -// long t2 = System.currentTimeMillis(); - /*Editor editor =*/ handleOpen(path, true); -// long t3 = System.currentTimeMillis(); -// System.out.println("handleNew " + (t2-t1) + " " + (t3-t2)); + handleOpenUntitled(path); } catch (IOException e) { - Messages.showWarning("That's new to me", - "A strange and unexplainable error occurred\n" + - "while trying to create a new sketch.", e); + Messages.showTrace("That's new to me", + "A strange and unexplainable error occurred\n" + + "while trying to create a new sketch.", e, false); } } @@ -1245,6 +1296,7 @@ public class Base { // Add the extensions for each installed Mode for (Mode mode : getModeList()) { extensions.append(mode.getDefaultExtension()); + // not adding aux extensions b/c we're looking for the main } final String prompt = Language.text("open"); @@ -1284,7 +1336,7 @@ public class Base { public boolean accept(File file) { // JFileChooser requires you to explicitly say yes to directories // as well (unlike the AWT chooser). Useful, but... different. - // http://code.google.com/p/processing/issues/detail?id=1151 + // https://github.com/processing/processing/issues/1189 if (file.isDirectory()) { return true; } @@ -1307,147 +1359,367 @@ public class Base { } + private Editor openSketchBundle(String path) { + File zipFile = new File(path); + try { + File destFolder = File.createTempFile("zip", "tmp", untitledFolder); + if (!destFolder.delete() || !destFolder.mkdirs()) { + // Hard to imagine why this would happen, but... + System.err.println("Could not create temporary folder " + destFolder); + return null; + } + Util.unzip(zipFile, destFolder); + File[] fileList = destFolder.listFiles(File::isDirectory); + if (fileList != null) { + if (fileList.length == 1) { + File sketchFile = Sketch.findMain(fileList[0], getModeList()); + if (sketchFile != null) { + return handleOpenUntitled(sketchFile.getAbsolutePath()); + } + } else { + System.err.println("Expecting one folder inside " + + SKETCH_BUNDLE_EXT + " file, found " + fileList.length + "."); + } + } else { + System.err.println("Could not read " + destFolder); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; // no luck + } + + + private void openContribBundle(String path) { + EventQueue.invokeLater(() -> { + Editor editor = getActiveEditor(); + if (editor == null) { + // Shouldn't really happen, but if it's still null, it's a no-go + Messages.showWarning("Failure is the only option", + "Please open an Editor window before installing an extension."); + } else { + File contribFile = new File(path); + String baseName = contribFile.getName(); + baseName = baseName.substring(0, baseName.length() - CONTRIB_BUNDLE_EXT.length()); + int result = + Messages.showYesNoQuestion(editor, "How to Handle " + CONTRIB_BUNDLE_EXT, + "Install " + baseName + "?", + "Libraries, Modes, and Tools should
" + + "only be installed from trusted sources."); + + if (result == JOptionPane.YES_OPTION) { + editor.statusNotice("Installing " + baseName + "..."); + editor.startIndeterminate(); + + new Thread(() -> { + try { + // do the work of the actual install + LocalContribution contrib = + AvailableContribution.install(this, new File(path)); + + EventQueue.invokeLater(() -> { + editor.stopIndeterminate(); + + if (contrib != null) { + editor.statusEmpty(); + } else { + editor.statusError("Could not install " + path); + } + }); + } catch (IOException e) { + EventQueue.invokeLater(() -> + Messages.showWarning("Exception During Installation", + "Could not install contrib from " + path, e)); + } + }).start(); + } + } + }); + } + + + /** + * Return true if it's an obvious sketch folder: only .pde files, + * and maybe a data folder. Dot files (.DS_Store, ._blah) are ignored. + */ + private boolean smellsLikeSketchFolder(File folder) { + File[] files = folder.listFiles(); + if (files == null) { // unreadable, assume badness + return false; + } + for (File file : files) { + String name = file.getName(); + if (!(name.startsWith(".") || + name.toLowerCase().endsWith(".pde")) || + (file.isDirectory() && name.equals("data"))) { + return false; + } + } + return true; + } + + + private File moveLikeSketchFolder(File pdeFile, String baseName) throws IOException { + Object[] options = { + "Keep", "Move", "Cancel" + }; + String prompt = + "Would you like to keep “" + pdeFile.getParentFile().getName() + "” as the sketch folder,\n" + + "or move “" + pdeFile.getName() + "” to its own folder?\n" + + "(Usually, “" + pdeFile.getName() + "” would be stored inside a\n" + + "sketch folder named “" + baseName + "”.)"; + + int result = JOptionPane.showOptionDialog(null, + prompt, + "Keep it? Move it?", + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + + if (result == JOptionPane.YES_OPTION) { // keep + return pdeFile; + + } else if (result == JOptionPane.NO_OPTION) { // move + // create properly named folder + File properFolder = new File(pdeFile.getParent(), baseName); + if (properFolder.exists()) { + throw new IOException("A folder named \"" + baseName + "\" " + + "already exists. Cannot open sketch."); + } + if (!properFolder.mkdirs()) { + throw new IOException("Could not create the sketch folder."); + } + // copy the sketch inside + File properPdeFile = new File(properFolder, pdeFile.getName()); + Util.copyFile(pdeFile, properPdeFile); + + // remove the original file, so user doesn't get confused + if (!pdeFile.delete()) { + Messages.err("Could not delete " + pdeFile); + } + + // update with the new path + return properPdeFile; + } + + // Catch all other cases, including Cancel or ESC + return null; + } + + /** * Open a sketch from the path specified. Do not use for untitled sketches. + * Note that the user may have selected/double-clicked any .pde in a sketch. */ public Editor handleOpen(String path) { if (path.endsWith(SKETCH_BUNDLE_EXT)) { - File zipFile = new File(path); - try { - File destFolder = File.createTempFile("zip", "tmp", untitledFolder); - if (!destFolder.delete() || !destFolder.mkdirs()) { - // Hard to imagine why this would happen, but... - System.err.println("Could not create temporary folder " + destFolder); - return null; - } - Util.unzip(zipFile, destFolder); - File[] fileList = destFolder.listFiles(File::isDirectory); - if (fileList != null) { - if (fileList.length == 1) { - File sketchFile = checkSketchFolder(fileList[0]); - if (sketchFile != null) { - return handleOpen(sketchFile.getAbsolutePath(), true); - } - } else { - System.err.println("Expecting one folder inside " + - SKETCH_BUNDLE_EXT + " file, found " + fileList.length + "."); - } - } else { - System.err.println("Could not read " + destFolder); - } - } catch (IOException e) { - e.printStackTrace(); - } - return null; // no luck + return openSketchBundle(path); } else if (path.endsWith(CONTRIB_BUNDLE_EXT)) { - EventQueue.invokeLater(() -> { - Editor editor = getActiveEditor(); - if (editor == null) { - // Shouldn't really happen, but if it's still null, it's a no-go - Messages.showWarning("Failure is the only option", - "Please open an Editor window before installing an extension."); - } else { - File contribFile = new File(path); - String baseName = contribFile.getName(); - baseName = baseName.substring(0, baseName.length() - CONTRIB_BUNDLE_EXT.length()); - int result = - Messages.showYesNoQuestion(editor, "How to Handle " + CONTRIB_BUNDLE_EXT, - "Install " + baseName + "?", - "Libraries, Modes, and Tools should
" + - "only be installed from trusted sources."); + openContribBundle(path); + return null; // never returning an Editor for a contrib + } - if (result == JOptionPane.YES_OPTION) { - editor.statusNotice("Installing " + baseName + "..."); - editor.startIndeterminate(); - - new Thread(() -> { - try { - // do the work of the actual install - LocalContribution contrib = - AvailableContribution.install(this, new File(path)); - - EventQueue.invokeLater(() -> { - editor.stopIndeterminate(); - - if (contrib != null) { - editor.statusEmpty(); - } else { - editor.statusError("Could not install " + path); - } - }); - } catch (IOException e) { - EventQueue.invokeLater(() -> Messages.showWarning("Exception During Installation", "Could not install contrib from " + path, e)); - } - }).start(); - } - } - }); + File pdeFile = new File(path); + if (!pdeFile.exists()) { + System.err.println(path + " does not exist"); return null; } - return handleOpen(path, false); - } + // Cycle through open windows to make sure that it's not already open. + for (Editor editor : editors) { + // User may have double-clicked any PDE in the sketch folder, + // so we have to check each open tab (not just the main one). + // https://github.com/processing/processing/issues/2506 + for (SketchCode tab : editor.getSketch().getCode()) { + if (tab.getFile().equals(pdeFile)) { + editor.toFront(); + // move back to the top of the recent list + Recent.append(editor); + return editor; + } + } + } + File parentFolder = pdeFile.getParentFile(); - /** - * Open a sketch in a new window. - * @param path Path to the pde file for the sketch in question - * @return the Editor object, so that properties (like 'untitled') - * can be set by the caller - */ - public Editor handleOpen(String path, boolean untitled) { - return handleOpen(path, untitled, EditorState.nextEditor(editors)); - } - - - protected Editor handleOpen(String path, boolean untitled, - EditorState state) { try { - // System.err.println("entering handleOpen " + path); + // read the sketch.properties file or get an empty Settings object + Settings props = Sketch.loadProperties(parentFolder); + if (!props.isEmpty()) { + // First check for the Mode, because it may not even be available + String modeIdentifier = props.get("mode.id"); + if (modeIdentifier != null) { + if (modeIdentifier.equals("galsasson.mode.tweak.TweakMode")) { + // Tweak Mode has been built into Processing since 2015, + // but there are some old sketch.properties files out there. + // https://github.com/processing/processing4/issues/415 + nextMode = getDefaultMode(); - final File file = new File(path); - if (!file.exists()) { + // Clean up sketch.properties and re-save or remove if necessary + props.remove("mode"); + props.remove("mode.id"); + props.reckon(); + + } else { + // sketch.properties specifies a Mode, see if it's available + Mode mode = findMode(modeIdentifier); + if (mode != null) { + nextMode = mode; + } else { + ContributionManager.openModes(); + Messages.showWarning("Missing Mode", + "You must first install " + props.get("mode") + " Mode to use this sketch."); + return null; + } + } + } + + String main = props.get("main"); + if (main != null) { + // this may be exactly what was passed, but override anyway + String mainPath = new File(parentFolder, main).getAbsolutePath(); + if (!path.equals(mainPath)) { + // for now, post a warning if the main was different + System.out.println(path + " selected, but main is " + mainPath); + } + return handleOpenInternal(mainPath, false); + } + } else { + // Switch back to defaultMode, because a sketch.properties + // file is required whenever not using the default Mode. + // (Unless being called from, say, the Examples frame, which + // uses a version of this function that takes a Mode object.) + nextMode = getDefaultMode(); + } + + // Do some checks to make sure the file can be opened, and identify the + // Mode that it's using. (In 4.0b8, this became the fall-through case.) + + if (!Sketch.isSanitaryName(pdeFile.getName())) { + Messages.showWarning("You're tricky, but not tricky enough", + pdeFile.getName() + " is not a valid name for sketch code.\n" + + "Better to stick to ASCII, no spaces, and make sure\n" + + "it doesn't start with a number.", null); return null; } - // Cycle through open windows to make sure that it's not already open. - for (Editor editor : editors) { - // User may have double-clicked any PDE in the sketch folder, - // so we have to check each open tab (not just the main one). - // https://github.com/processing/processing/issues/2506 - for (SketchCode tab : editor.getSketch().getCode()) { - if (tab.getFile().equals(file)) { - editor.toFront(); - // move back to the top of the recent list - Recent.append(editor); - return editor; + // Check if the name of the file matches the parent folder name. + String baseName = pdeFile.getName(); + int dot = baseName.lastIndexOf('.'); + if (dot == -1) { + // Shouldn't really be possible, right? + System.err.println(pdeFile + " does not have an extension."); + return null; + } + baseName = baseName.substring(0, dot); + if (!baseName.equals(parentFolder.getName())) { + // Parent folder name does not match, and because sketch.properties + // did not exist or did not specify it above, need to determine main. + + // Check whether another .pde file has a matching name, and if so, + // switch to using that instead. Handles when a user selects a .pde + // file in the open dialog box that isn't the main tab. + // (Also important to use nextMode here, because the Mode + // may be set by sketch.properties when it's loaded above.) + String filename = + parentFolder.getName() + "." + nextMode.getDefaultExtension(); + File mainFile = new File(parentFolder, filename); + if (mainFile.exists()) { + // User was opening the wrong file in a legit sketch folder. + pdeFile = mainFile; + + } else if (smellsLikeSketchFolder(parentFolder)) { + // Looks like a sketch folder, set this as the main. + props.set("main", pdeFile.getName()); + // Save for later use, then fall through. + props.save(); + + } else { + // If it's not an obvious sketch folder (just .pde files, + // maybe a data folder) prompt the user whether to + // 1) move sketch into its own folder, or + // 2) call this the main, and write sketch.properties. + File newFile = moveLikeSketchFolder(pdeFile, baseName); + + if (newFile == pdeFile) { + // User wanted to keep this sketch folder, so update the + // property for the main tab and write sketch.properties. + props.set("main", newFile.getName()); + props.save(); + + } else if (newFile == null) { + // User canceled, so exit handleOpen() + return null; + + } else { + // User asked to move the sketch file + pdeFile = newFile; } } } - if (!Sketch.isSanitaryName(file.getName())) { - Messages.showWarning("You're tricky, but not tricky enough", - file.getName() + " is not a valid name for a sketch.\n" + - "Better to stick to ASCII, no spaces, and make sure\n" + - "it doesn't start with a number.", null); - return null; - } + // TODO Remove this selector? Seems too messy/precious. [fry 220205] + // Opting to remove in beta 7, because sketches that use another + // Mode should have a working sketch.properties. [fry 220302] + /* + // If the current Mode cannot open this file, try to find another. + if (!nextMode.canEdit(pdeFile)) { - if (!nextMode.canEdit(file)) { - final Mode mode = selectMode(file); + final Mode mode = promptForMode(pdeFile); if (mode == null) { return null; } nextMode = mode; } + */ + return handleOpenInternal(pdeFile.getAbsolutePath(), false); + } catch (IOException e) { + Messages.showWarning("sketch.properties", + "Error while reading sketch.properties from\n" + parentFolder, e); + return null; + } + } + + + /** + * Open a (vetted) sketch location using a particular Mode. Used by the + * Examples window, because Modes like Python and Android do not have + * "sketch.properties" files in each example folder. + */ + public Editor handleOpen(String path, Mode mode) { + nextMode = mode; + return handleOpenInternal(path, false); + } + + + /** + * Open the sketch associated with this .pde file in a new window + * as an "Untitled" sketch. + * @param path Path to the pde file for the sketch in question + * @return the Editor object, so that properties (like 'untitled') + * can be set by the caller + */ + protected Editor handleOpenUntitled(String path) { + return handleOpenInternal(path, true); + } + + + /** + * Internal function to actually open the sketch. At this point, the + * sketch file/folder must have been vetted, and nextMode set properly. + */ + protected Editor handleOpenInternal(String path, boolean untitled) { + try { try { + EditorState state = EditorState.nextEditor(editors); Editor editor = nextMode.createEditor(this, path, state); - editor.setUpdatesAvailable(updatesAvailable); - // opened successfully, let's go to work + editor.setUpdatesAvailable(updatesAvailable); editor.getSketch().setUntitled(untitled); editors.add(editor); Recent.append(editor); @@ -1462,10 +1734,10 @@ public class Base { if (ee.getMessage() != null) { // null if the user canceled Messages.showWarning("Error opening sketch", ee.getMessage(), ee); } - } catch (NoSuchMethodError nsme) { + } catch (NoSuchMethodError me) { Messages.showWarning("Mode out of date", nextMode.getTitle() + " is not compatible with this version of Processing.\n" + - "Try updating the Mode or contact its author for a new version.", nsme); + "Try updating the Mode or contact its author for a new version.", me); } catch (Throwable t) { if (nextMode.equals(getDefaultMode())) { Messages.showTrace("Serious Problem", @@ -1502,24 +1774,6 @@ public class Base { } } } - - /* - if (editors.isEmpty()) { - // if the bad mode is the default mode, don't go into an infinite loop - // trying to recreate a window with the default mode. - Mode defaultMode = getDefaultMode(); - if (nextMode == defaultMode) { - Base.showError("Editor Problems", - "An error occurred while trying to change modes.\n" + - "We'll have to quit for now because it's an\n" + - "unfortunate bit of indigestion with the default Mode.", - null); - } else { - editor = defaultMode.createEditor(this, path, state); - } - } - */ - } catch (Throwable t) { Messages.showTrace("Terrible News", "A serious error occurred while " + @@ -1537,13 +1791,12 @@ public class Base { /** * Close a sketch as specified by its editor window. * @param editor Editor object of the sketch to be closed. - * @param modeSwitch Whether this close is being done in the context of a - * mode switch. + * @param preventQuit For platforms that must have a window open, + * prevent a quit because a new window will be opened + * (i.e. when upgrading or changing the Mode) * @return true if succeeded in closing, false if canceled. */ - public boolean handleClose(Editor editor, boolean modeSwitch) { - // Check if modified -// boolean immediate = editors.size() == 1; + public boolean handleClose(Editor editor, boolean preventQuit) { if (!editor.checkModified()) { return false; } @@ -1551,28 +1804,16 @@ public class Base { // Close the running window, avoid window boogers with multiple sketches editor.internalCloseRunner(); -// System.out.println("editors size is " + editors.size()); if (editors.size() == 1) { - // For 0158, when closing the last window /and/ it was already an - // untitled sketch, just give up and let the user quit. -// if (Preferences.getBoolean("sketchbook.closing_last_window_quits") || -// (editor.untitled && !editor.getSketch().isModified())) { if (Platform.isMacOS()) { // If the central menu bar isn't supported on this macOS JVM, // we have to do the old behavior. Yuck! if (defaultFileMenu == null) { Object[] options = { Language.text("prompt.ok"), Language.text("prompt.cancel") }; - String prompt = - " " + - " " + - "Are you sure you want to Quit?" + - "

Closing the last open sketch will quit Processing."; int result = JOptionPane.showOptionDialog(editor, - prompt, + Toolkit.formatMessage("Are you sure you want to Quit?", + "Closing the last open sketch will quit Processing."), "Quit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, @@ -1586,19 +1827,20 @@ public class Base { } } + /* + // wow, this is wrong (should only be called after the last window) + // but also outdated, because it's instance_server.* not server.* + // and Preferences.save() is also about restoring sketches. + Preferences.unset("server.port"); //$NON-NLS-1$ Preferences.unset("server.key"); //$NON-NLS-1$ -// // This will store the sketch count as zero -// editors.remove(editor); -// System.out.println("editors size now " + editors.size()); -// storeSketches(); - // Save out the current prefs state Preferences.save(); + */ if (defaultFileMenu == null) { - if (modeSwitch) { + if (preventQuit) { // need to close this editor, ever so temporarily editor.setVisible(false); editor.dispose(); @@ -1698,18 +1940,68 @@ public class Base { } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + protected SketchbookFrame sketchbookFrame; + + public DefaultMutableTreeNode buildSketchbookTree() { + DefaultMutableTreeNode sbNode = + new DefaultMutableTreeNode(Language.text("sketchbook.tree")); + try { + addSketches(sbNode, Base.getSketchbookFolder(), false); + } catch (IOException e) { + e.printStackTrace(); + } + return sbNode; + } + + + /** Sketchbook has changed, update it on next viewing. */ + public void rebuildSketchbookFrame() { + if (sketchbookFrame != null) { + sketchbookFrame.rebuild(); + /* + boolean visible = sketchbookFrame.isVisible(); + Rectangle bounds = null; + if (visible) { + bounds = sketchbookFrame.getBounds(); + sketchbookFrame.setVisible(false); + sketchbookFrame.dispose(); + } + sketchbookFrame = null; + if (visible) { + showSketchbookFrame(); + sketchbookFrame.setBounds(bounds); + } + */ + } + } + + + public void showSketchbookFrame() { + if (sketchbookFrame == null) { + sketchbookFrame = new SketchbookFrame(this); + } + sketchbookFrame.setVisible(); + } + + /** * Synchronous version of rebuild, used when the sketchbook folder has * changed, so that the libraries are properly re-scanned before those menus * (and the examples window) are rebuilt. */ - protected void rebuildSketchbookMenus() { + public void rebuildSketchbook() { for (Mode mode : getModeList()) { mode.rebuildImportMenu(); // calls rebuildLibraryList mode.rebuildToolbarMenu(); mode.rebuildExamplesFrame(); - mode.rebuildSketchbookFrame(); } + // Unlike libraries, examples, etc. the sketchbook is global + // (because you need to be able to open sketches from the Mode + // that you're not currently using). + rebuildSketchbookFrame(); } @@ -1736,7 +2028,7 @@ public class Base { * sketch should open in a new window. */ protected boolean addSketches(JMenu menu, File folder) { - // skip .DS_Store files, etc (this shouldn't actually be necessary) + // skip .DS_Store files, etc. (this shouldn't actually be necessary) if (!folder.isDirectory()) { return false; } @@ -1746,7 +2038,7 @@ public class Base { } if (folder.getName().equals("sdk")) { - // This could be Android's SDK folder. Let's double check: + // This could be Android's SDK folder. Let's double-check: File suspectSDKPath = new File(folder.getParent(), folder.getName()); File expectedSDKPath = new File(sketchbookFolder, "android" + File.separator + "sdk"); if (expectedSDKPath.getAbsolutePath().equals(suspectSDKPath.getAbsolutePath())) { @@ -1792,7 +2084,7 @@ public class Base { File entry = new File(folder, name); File sketchFile = null; if (entry.isDirectory()) { - sketchFile = checkSketchFolder(entry); + sketchFile = Sketch.findMain(entry, getModeList()); } else if (name.toLowerCase().endsWith(SKETCH_BUNDLE_EXT)) { name = name.substring(0, name.length() - SKETCH_BUNDLE_EXT.length()); sketchFile = entry; @@ -1826,7 +2118,7 @@ public class Base { */ public boolean addSketches(DefaultMutableTreeNode node, File folder, boolean examples) throws IOException { - // skip .DS_Store files, etc (this shouldn't actually be necessary) + // skip .DS_Store files, etc. (this shouldn't actually be necessary) if (!folder.isDirectory()) { return false; } @@ -1871,7 +2163,7 @@ public class Base { File entry = new File(folder, name); File sketchFile = null; if (entry.isDirectory()) { - sketchFile = checkSketchFolder(entry); + sketchFile = Sketch.findMain(entry, getModeList()); } else if (name.toLowerCase().endsWith(SKETCH_BUNDLE_EXT)) { name = name.substring(0, name.length() - SKETCH_BUNDLE_EXT.length()); sketchFile = entry; @@ -1899,21 +2191,33 @@ public class Base { } - /** - * Check through the various modes and see if this is a legit sketch. - * Because the default mode will be the first in the list, this will always - * prefer that one over the others. - */ - private File checkSketchFolder(File folder) { - for (Mode mode : getModeList()) { + /* + static private Mode findSketchMode(File folder, List modeList) { + try { + Settings props = Sketch.loadProperties(folder); + if (props != null) { + String id = props.get("mode.id"); + if (id != null) { + Mode mode = findMode(id); + if (mode != null) { + return mode; + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + for (Mode mode : modeList) { // Test whether a .pde file of the same name as its parent folder exists. - File entry = new File(folder, folder.getName() + "." + mode.getDefaultExtension()); //$NON-NLS-1$ + String defaultName = folder.getName() + "." + mode.getDefaultExtension(); + File entry = new File(folder, defaultName); if (entry.exists()) { - return entry; + return mode; } } return null; } + */ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -1953,7 +2257,8 @@ public class Base { /** * Get the directory that can store settings. (Library on OS X, App Data or * something similar on Windows, a dot folder on Linux.) Removed this as a - * preference for 3.0a3 because we need this to be stable. + * preference for 3.0a3 because we need this to be stable, but adding back + * for 4.0 beta 4 so that folks can do 'portable' versions again. */ static public File getSettingsFolder() { File settingsFolder = null; @@ -1980,6 +2285,11 @@ public class Base { } + static public File getSettingsOverride() { + return settingsOverride; + } + + /** * Convenience method to get a File object for the specified filename inside * the settings folder. Used to get preferences and recent sketch files. @@ -2033,7 +2343,7 @@ public class Base { public void setSketchbookFolder(File folder) { sketchbookFolder = folder; Preferences.setSketchbookPath(folder.getAbsolutePath()); - rebuildSketchbookMenus(); + rebuildSketchbook(); makeSketchbookSubfolders(); } diff --git a/app/src/processing/app/Console.java b/app/src/processing/app/Console.java index b63add2fe..badc4bb6d 100644 --- a/app/src/processing/app/Console.java +++ b/app/src/processing/app/Console.java @@ -88,27 +88,7 @@ public class Console { final String stamp = formatter.format(new Date()); File consoleDir = Base.getSettingsFile("console"); - if (consoleDir.exists()) { - // clear old debug files - File[] stdFiles = consoleDir.listFiles(new FileFilter() { - final String todayPrefix = stamp.substring(0, 4); - - public boolean accept(File file) { - if (!file.isDirectory()) { - String name = file.getName(); - if (name.endsWith(".err") || name.endsWith(".out")) { - // don't delete any of today's debug messages - return !name.startsWith(todayPrefix); - } - } - return false; - } - }); - // Remove any files that aren't from today - for (File file : stdFiles) { - file.delete(); - } - } else { + if (!consoleDir.exists()) { consoleDir.mkdirs(); consoleDir.setWritable(true, false); } @@ -141,6 +121,36 @@ public class Console { } + static public void cleanTempFiles() { + final File consoleDir = Base.getSettingsFile("console"); + final int days = Preferences.getInteger("console.temp.days"); + + if (days > 0) { + final long now = new Date().getTime(); + final long diff = days * 24 * 60 * 60 * 1000L; + File[] expiredFiles = consoleDir.listFiles(file -> { + if (!file.isDirectory()) { + String name = file.getName(); + // Not really + if (name.endsWith(".err") || name.endsWith(".out")) { + return now - file.lastModified() > diff; + } + } + return false; + }); + // Remove the files approved for deletion + for (File file : expiredFiles) { + //file.delete(); // not as safe + try { + Platform.deleteFile(file); // move to trash + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + static public void setEditor(OutputStream out, OutputStream err) { editorOut = out; editorErr = err; diff --git a/app/src/processing/app/Language.java b/app/src/processing/app/Language.java index 06be82fc5..263ab16b3 100644 --- a/app/src/processing/app/Language.java +++ b/app/src/processing/app/Language.java @@ -28,7 +28,7 @@ import processing.core.PApplet; /** - * Internationalization (i18n) + * Internationalization (I18N) and Localization (L10N) */ public class Language { // Store the language information in a file separate from the preferences, @@ -43,7 +43,7 @@ public class Language { private String language; /** Available languages */ - private HashMap languages; + private final Map languages; private LanguageBundle bundle; @@ -59,7 +59,7 @@ public class Language { } // Set available languages - languages = new HashMap(); + languages = new HashMap<>(); for (String code : listSupported()) { Locale locale = Locale.forLanguageTag(code); languages.put(code, locale.getDisplayLanguage(locale)); @@ -88,8 +88,9 @@ public class Language { static private String[] listSupported() { // List of languages in alphabetical order. (Add yours here.) // Also remember to add it to build/shared/lib/languages/languages.txt. - final String[] SUPPORTED = { + return new String[] { "ar", // Arabic + "ca", // Catalan "de", // German, Deutsch "en", // English "el", // Greek @@ -105,7 +106,6 @@ public class Language { "uk", // Ukrainian "zh" // Chinese }; - return SUPPORTED; /* // come back to this when bundles are placed outside the JAR @@ -129,11 +129,14 @@ public class Language { static private String loadLanguage() { try { if (prefFile.exists()) { - String language = PApplet.loadStrings(prefFile)[0]; - language = language.trim().toLowerCase(); - if (language.trim().length() != 0) { - return language; + String[] lines = PApplet.loadStrings(prefFile); + if (lines != null && lines.length > 0) { + String language = lines[0].trim().toLowerCase(); + if (language.length() != 0) { + return language; + } } + System.err.println("Using default language because of a problem while reading " + prefFile); } } catch (Exception e) { e.printStackTrace(); @@ -149,7 +152,10 @@ public class Language { static public void saveLanguage(String language) { try { Util.saveFile(language, prefFile); - prefFile.setWritable(true, false); + boolean ok = prefFile.setWritable(true, false); + if (!ok) { + System.err.println("Warning: could not set " + prefFile + " to writable"); + } } catch (Exception e) { e.printStackTrace(); } @@ -178,7 +184,7 @@ public class Language { if (value != null) { return value; } - } catch (MissingResourceException e) { } + } catch (MissingResourceException ignored) { } return null; } @@ -200,7 +206,6 @@ public class Language { if (value == null) { return key; } -// System.out.println(" interp for " + key + " is " + String.format(value, arguments)); return String.format(value, arguments); } @@ -231,9 +236,22 @@ public class Language { } + /* + static public String nameToCode(String languageName) { + Map languages = Language.getLanguages(); + for (Map.Entry lang : languages.entrySet()) { + if (lang.getValue().equals(languageName)) { + return lang.getKey().trim().toLowerCase(); + } + } + return null; // not found + } + */ + + /** * Get the current language. - * @return two digit ISO code (lowercase) + * @return two-digit ISO code (lowercase) */ static public String getLanguage() { return init().language; @@ -252,6 +270,22 @@ public class Language { } + @SuppressWarnings("unused") + static public void addModeStrings(Mode mode) { + String baseFilename = "languages/mode.properties"; + File modeBaseFile = new File(mode.getFolder(), baseFilename); + if (modeBaseFile.exists()) { + init().bundle.read(modeBaseFile, true); + } + + String langFilename = "languages/mode_" + instance.language + ".properties"; + File modeLangFile = new File(mode.getFolder(), langFilename); + if (modeLangFile.exists()) { + init().bundle.read(modeLangFile, true); + } + } + + // /** Set new language (called by Preferences) */ // static public void setLanguage(String language) { // this.language = language; @@ -307,7 +341,7 @@ public class Language { Map table; LanguageBundle(String language) throws IOException { - table = new HashMap(); + table = new HashMap<>(); // Check to see if the user is working on localization, // and has their own .properties files in their sketchbook. @@ -331,41 +365,51 @@ public class Language { } void read(File additions) { + read(additions, false); + } + + void read(File additions, boolean enforcePrefix) { + String prefix = null; + String[] lines = PApplet.loadStrings(additions); - if (lines == null) { - throw new NullPointerException("File not found:\n" + additions.getAbsolutePath()); - } - //for (String line : lines) { - for (int i = 0; i < lines.length; i++) { - String line = lines[i]; - 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 inside in the text - int equals = line.indexOf('='); - if (equals != -1) { - String key = line.substring(0, equals).trim(); - String value = line.substring(equals + 1).trim(); + // this won't properly handle = signs inside in the text + int equals = line.indexOf('='); + if (equals != -1) { + String key = line.substring(0, equals).trim(); - /* - // Support for backslashes to continue lines... Nah. - while (line.endsWith("\\")) { - // remove the backslash from the previous - value = value.substring(0, value.length() - 1); - // get the next line - line = lines[++i].trim(); - // append the new line to the value (with a space) - // This is imperfect since the prev may end
- value += " " + line; + boolean ignore = false; + if (enforcePrefix) { + if (prefix == null) { + prefix = key.substring(0, key.indexOf('.') + 1); + if (prefix.length() == 0) { + System.err.println("Language strings in Modes must include a prefix for all entries."); + System.err.println(additions + " will be ignored."); + return; // exit read() + } + } else if (!key.startsWith(prefix)) { + System.err.println("Ignoring " + key + " because all entries in " + additions + " must begin with " + prefix); + ignore = true; + } + } + + if (!ignore) { + String value = line.substring(equals + 1).trim(); + + // Replace \n and \' with their actual values + value = value.replaceAll("\\\\n", "\n"); + value = value.replaceAll("\\\\'", "'"); + + table.put(key, value); + } } - */ - - // fix \n and \' - value = value.replaceAll("\\\\n", "\n"); - value = value.replaceAll("\\\\'", "'"); - - table.put(key, value); } + } else { + System.err.println("Unable to read " + additions); } } @@ -373,8 +417,12 @@ public class Language { return table.get(key); } + + /* + // removing in 4.0 beta 5; not known to be in use [fry 220130] boolean containsKey(String key) { return table.containsKey(key); } + */ } } diff --git a/app/src/processing/app/Library.java b/app/src/processing/app/Library.java index 24cd3c636..ce7c8aa4e 100644 --- a/app/src/processing/app/Library.java +++ b/app/src/processing/app/Library.java @@ -11,7 +11,15 @@ import processing.data.StringList; public class Library extends LocalContribution { - static final String[] platformNames = PConstants.platformNames; +// static final String[] platformNames = PConstants.platformNames; + + static StringDict newToOld = new StringDict(new String[][] { + { "macos-x86_64", "macosx" }, + { "windows-amd64", "windows64" }, + { "linux-amd64", "linux64" }, + { "linux-arm", "linux-armv6hf" }, + { "linux-aarch64", "linux-arm64" } + }); protected File libraryFolder; // shortname/library protected File examplesFolder; // shortname/examples @@ -30,14 +38,15 @@ public class Library extends LocalContribution { /** Per-platform exports for this library. */ Map exportList; - /** Applet exports (cross-platform by definition). */ - String[] appletExportList; + /** List of default exports */ + String[] baseList; /** Android exports (single platform for now, may not exist). */ String[] androidExportList; - /** True if there are separate 32/64 bit for the specified platform index. */ - boolean[] multipleArch = new boolean[platformNames.length]; +// /** True if there are separate 32/64 bit for the specified platform index. */ +// boolean[] multipleArch = new boolean[platformNames.length]; +// Map multipleArch = new HashMap<>(); /** * For runtime, the native library path for this platform. e.g. on Windows 64, @@ -45,46 +54,48 @@ public class Library extends LocalContribution { */ String nativeLibraryPath; +// /** True if */ +// boolean variants; + static public final String propertiesFileName = "library.properties"; /** * Filter to pull out just files and none of the platform-specific - * directories, and to skip export.txt. As of 2.0a2, other directories are - * included, because we need things like the 'plugins' subfolder w/ video. + * directories, and to skip export.txt. + * + * As of 2.0a2, other directories are included, because we need + * things like the 'plugins' subfolder w/ video. + * + * As of 4.0b4, only checking whether macos, windows, linux, or + * android are the prefix of the folder name, so that we can avoid + * explicitly listing all possible architectures, and so that + * macos-blah as well as and macosx will be handled properly. */ - static FilenameFilter standardFilter = new FilenameFilter() { - public boolean accept(File dir, String name) { - // skip .DS_Store files, .svn folders, etc - if (name.charAt(0) == '.') return false; - if (name.equals("CVS")) return false; - if (name.equals("export.txt")) return false; - File file = new File(dir, name); -// return (!file.isDirectory()); - if (file.isDirectory()) { - if (name.equals("macosx")) return false; - if (name.equals("macosx32")) return false; - if (name.equals("macosx64")) return false; - if (name.equals("windows")) return false; - if (name.equals("windows32")) return false; - if (name.equals("windows64")) return false; - if (name.equals("linux")) return false; - if (name.equals("linux32")) return false; - if (name.equals("linux64")) return false; - if (name.equals("linux-armv6hf")) return false; - if (name.equals("linux-arm64")) return false; - if (name.equals("android")) return false; + static FilenameFilter libraryFolderFilter = (dir, name) -> { + // skip .DS_Store files, .svn folders, etc + if (name.charAt(0) == '.') return false; + // ha, the sftp library still has one [fry 220121] + if (name.equals("CVS")) return false; + if (name.equals("export.txt")) return false; + + File file = new File(dir, name); + if (file.isDirectory()) { + //noinspection RedundantIfStatement + if (name.startsWith("macos") || + name.startsWith("windows") || + name.startsWith("linux")) { + //name.startsWith("android")) { // no libraries use this + return false; } - return true; } + return true; }; - static FilenameFilter jarFilter = new FilenameFilter() { - public boolean accept(File dir, String name) { - if (name.charAt(0) == '.') return false; // skip ._blah.jar crap on OS X - if (new File(dir, name).isDirectory()) return false; - String lc = name.toLowerCase(); - return lc.endsWith(".jar") || lc.endsWith(".zip"); - } + static FilenameFilter jarFilter = (dir, name) -> { + if (name.charAt(0) == '.') return false; // skip ._blah.jar crap on OS X + if (new File(dir, name).isDirectory()) return false; + String lc = name.toLowerCase(); + return lc.endsWith(".jar") || lc.endsWith(".zip"); }; @@ -96,8 +107,6 @@ public class Library extends LocalContribution { } catch (Error err) { // Handles UnsupportedClassVersionError and others err.printStackTrace(); - } catch (Exception ex) { - ex.printStackTrace(); } return null; } @@ -124,66 +133,94 @@ public class Library extends LocalContribution { * Handles all the Java-specific parsing for library handling. */ protected void handle() { + handleNative(); + handleExports(); + } + + + /** + * Identify nativeLibraryFolder location for the current platform. + */ + private void handleNative() { + String variant = Platform.getVariant(); + + // use the root of the library folder as the default + File nativeLibraryFolder = libraryFolder; + + /* + String hostPlatform = Platform.getName(); + // see if there's a 'windows', 'macosx', or 'linux' folder + File hostLibrary = new File(libraryFolder, hostPlatform); + if (hostLibrary.exists()) { + nativeLibraryFolder = hostLibrary; + } + */ + + // see if there's a {platform}-{arch} folder + File hostLibrary = new File(libraryFolder, variant); + if (hostLibrary.exists()) { + nativeLibraryFolder = hostLibrary; + + } else { + // if not found, try the old-style naming + String oldName = newToOld.get(variant); + if (oldName != null) { + hostLibrary = new File(libraryFolder, oldName); + if (hostLibrary.exists()) { + nativeLibraryFolder = hostLibrary; + } + } + } + + // save that folder for later use + nativeLibraryPath = nativeLibraryFolder.getAbsolutePath(); + } + + + private void handleExports() { + /* File exportSettings = new File(libraryFolder, "export.txt"); StringDict exportTable = exportSettings.exists() ? Util.readSettings(exportSettings) : new StringDict(); + */ exportList = new HashMap<>(); // get the list of files just in the library root - String[] baseList = libraryFolder.list(standardFilter); -// System.out.println("Loading " + name + "..."); -// PApplet.println(baseList); + baseList = libraryFolder.list(libraryFolderFilter); - String appletExportStr = exportTable.get("applet"); - if (appletExportStr != null) { - appletExportList = PApplet.splitTokens(appletExportStr, ", "); - } else { - appletExportList = baseList; + for (String variant : Platform.getSupportedVariants().keys()) { + File variantFolder = new File(libraryFolder, variant); + if (!variantFolder.exists()) { + // check to see if old naming is in use + String oldName = newToOld.get(variant, null); + if (oldName != null) { + variantFolder = new File(libraryFolder, variant); + if (variantFolder.exists()) { + Messages.log("Please update " + getName() + " for Processing 4. " + + variantFolder + " is the older naming scheme."); + } + } + } + if (variantFolder.exists()) { + String[] entries = listPlatformEntries(libraryFolder, variant, baseList); + if (entries != null) { + exportList.put(variant, entries); + } + } } + /* + // not actually used in any libraries String androidExportStr = exportTable.get("android"); if (androidExportStr != null) { androidExportList = PApplet.splitTokens(androidExportStr, ", "); } else { androidExportList = baseList; } + */ - // for the host platform, need to figure out what's available - File nativeLibraryFolder = libraryFolder; - String hostPlatform = Platform.getName(); -// System.out.println("1 native lib folder now " + nativeLibraryFolder); - // see if there's a 'windows', 'macosx', or 'linux' folder - File hostLibrary = new File(libraryFolder, hostPlatform); - if (hostLibrary.exists()) { - nativeLibraryFolder = hostLibrary; - } -// System.out.println("2 native lib folder now " + nativeLibraryFolder); - // check for bit-specific version, e.g. on windows, check if there - // is a window32 or windows64 folder (on windows) - hostLibrary = - new File(libraryFolder, hostPlatform + Platform.getNativeBits()); - if (hostLibrary.exists()) { - nativeLibraryFolder = hostLibrary; - } -// System.out.println("3 native lib folder now " + nativeLibraryFolder); - - if (hostPlatform.equals("linux") && System.getProperty("os.arch").equals("arm")) { - hostLibrary = new File(libraryFolder, "linux-armv6hf"); - if (hostLibrary.exists()) { - nativeLibraryFolder = hostLibrary; - } - } - if (hostPlatform.equals("linux") && System.getProperty("os.arch").equals("aarch64")) { - hostLibrary = new File(libraryFolder, "linux-arm64"); - if (hostLibrary.exists()) { - nativeLibraryFolder = hostLibrary; - } - } - - // save that folder for later use - nativeLibraryPath = nativeLibraryFolder.getAbsolutePath(); - + /* // for each individual platform that this library supports, figure out what's around for (int i = 1; i < platformNames.length; i++) { String platformName = platformNames[i]; @@ -223,7 +260,8 @@ public class Library extends LocalContribution { } if (platformList32 != null || platformList64 != null || platformListArmv6hf != null || platformListArm64 != null) { - multipleArch[i] = true; + //multipleArch[i] = true; + multipleArch.put(platformName, true); } // if there aren't any relevant imports specified or in their own folders, @@ -251,6 +289,8 @@ public class Library extends LocalContribution { } } } + */ + // for (String p : exportList.keySet()) { // System.out.println(p + " -> "); // PApplet.println(exportList.get(p)); @@ -267,7 +307,7 @@ public class Library extends LocalContribution { static String[] listPlatformEntries(File libraryFolder, String folderName, String[] baseList) { File folder = new File(libraryFolder, folderName); if (folder.exists()) { - String[] entries = folder.list(standardFilter); + String[] entries = folder.list((dir, name) -> name.charAt(0) != '.'); if (entries != null) { String[] outgoing = new String[entries.length + baseList.length]; for (int i = 0; i < entries.length; i++) { @@ -282,7 +322,7 @@ public class Library extends LocalContribution { } - static protected HashMap packageWarningMap = new HashMap<>(); + //static protected HashMap packageWarningMap = new HashMap<>(); /** * Add the packages provided by this library to the master list that maps @@ -351,41 +391,37 @@ public class Library extends LocalContribution { } - // this prepends a colon so that it can be appended to other paths safely + // the returned value begins with File.pathSeparatorChar + // so that it can be appended to other paths safely public String getClassPath() { StringBuilder cp = new StringBuilder(); -// PApplet.println(libraryFolder.getAbsolutePath()); -// PApplet.println(libraryFolder.list()); String[] jarHeads = libraryFolder.list(jarFilter); - for (String jar : jarHeads) { - cp.append(File.pathSeparatorChar); - cp.append(new File(libraryFolder, jar).getAbsolutePath()); + if (jarHeads != null) { + for (String jar : jarHeads) { + cp.append(File.pathSeparatorChar); + cp.append(new File(libraryFolder, jar).getAbsolutePath()); + } } File nativeLibraryFolder = new File(nativeLibraryPath); if (!libraryFolder.equals(nativeLibraryFolder)) { jarHeads = new File(nativeLibraryPath).list(jarFilter); - for (String jar : jarHeads) { - cp.append(File.pathSeparatorChar); - cp.append(new File(nativeLibraryPath, jar).getAbsolutePath()); + if (jarHeads != null) { + for (String jar : jarHeads) { + cp.append(File.pathSeparatorChar); + cp.append(new File(nativeLibraryPath, jar).getAbsolutePath()); + } } } - //cp.setLength(cp.length() - 1); // remove the last separator return cp.toString(); } public String getNativePath() { -// PApplet.println("native lib folder " + nativeLibraryPath); return nativeLibraryPath; } -// public String[] getAppletExports() { -// return appletExportList; -// } - - protected File[] wrapFiles(String[] list) { File[] outgoing = new File[list.length]; for (int i = 0; i < list.length; i++) { @@ -395,28 +431,22 @@ public class Library extends LocalContribution { } - /** - * Applet exports don't go by platform, since by their nature applets are - * meant to be cross-platform. Technically, you could have a situation where - * you want to export applet code for different platforms, but it's too - * obscure a case that we're not interested in supporting it. - */ - public File[] getAppletExports() { - return wrapFiles(appletExportList); + public File[] getApplicationExports(String variant) { + String[] list = getApplicationExportList(variant); + return wrapFiles(list); } + /* public File[] getApplicationExports(int platform, String variant) { String[] list = getApplicationExportList(platform, variant); return wrapFiles(list); } - /** - * Returns the necessary exports for the specified platform. - * If no 32 or 64-bit version of the exports exists, it returns the version - * that doesn't specify bit depth. - */ +// * Returns the necessary exports for the specified platform. +// * If no 32 or 64-bit version of the exports exists, it returns the version +// * that doesn't specify bit depth. public String[] getApplicationExportList(int platform, String variant) { String platformName = PConstants.platformNames[platform]; if (variant.equals("32")) { @@ -434,23 +464,31 @@ public class Library extends LocalContribution { } return exportList.get(platformName); } + */ + + public String[] getApplicationExportList(String variant) { + if (exportList.isEmpty()) { + return baseList; + } + return exportList.get(variant); + } + @SuppressWarnings("unused") public File[] getAndroidExports() { return wrapFiles(androidExportList); } -// public boolean hasMultiplePlatforms() { -// return false; -// } - - + /* public boolean hasMultipleArch(int platform) { - return multipleArch[platform]; + //return multipleArch[platform]; + return multipleArch.getOrDefault(platform, false); } + */ + /* public boolean supportsArch(int platform, String variant) { // If this is a universal library, or has no natives, then we're good. if (multipleArch[platform] == false) { @@ -459,37 +497,59 @@ public class Library extends LocalContribution { return getApplicationExportList(platform, variant) != null; } + public boolean isUnsupported(int platform, String variant) { + // If this is a universal library, or has no natives, then we're good. + if (!multipleArch.containsKey(platformNames[platform])) { + return false; + } + return getApplicationExportList(platform, variant) == null; + } + */ + + public boolean isUnsupported(String variant) { + if (exportList.isEmpty()) { + // if no per-platform exports, then nothing to worry about + return false; + } + return getApplicationExportList(variant) == null; + } + + + /* static public boolean hasMultipleArch(int platform, List libraries) { return libraries.stream().anyMatch(library -> library.hasMultipleArch(platform)); } + */ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - static protected FilenameFilter junkFolderFilter = new FilenameFilter() { - public boolean accept(File dir, String name) { - // skip .DS_Store files, .svn and .git folders, etc - if (name.charAt(0) == '.') return false; - if (name.equals("CVS")) return false; // old skool - return new File(dir, name).isDirectory(); - } + static protected FilenameFilter junkFolderFilter = (dir, name) -> { + // skip .DS_Store files, .svn and .git folders, etc + if (name.charAt(0) == '.') return false; + if (name.equals("CVS")) return false; // old skool + return new File(dir, name).isDirectory(); }; static public String findCollision(File folder) { File[] jars = PApplet.listFiles(folder, "recursive", "extension=jar"); - for (File file : jars) { - try { - ZipFile zf = new ZipFile(file); - if (zf.getEntry("processing/core/PApplet.class") != null) { - return "processing.core"; + if (jars != null) { + for (File file : jars) { + try { + ZipFile zf = new ZipFile(file); + if (zf.getEntry("processing/core/PApplet.class") != null) { + return "processing.core"; + } + if (zf.getEntry("processing/app/Base.class") != null) { + return "processing.app"; + } + } catch (IOException e) { + // ignored } - if (zf.getEntry("processing/app/Base.class") != null) { - return "processing.app"; - } - } catch (IOException e) { } + } } return null; } @@ -506,8 +566,8 @@ public class Library extends LocalContribution { Arrays.sort(folderNames, String.CASE_INSENSITIVE_ORDER); // TODO a little odd because ContributionType.LIBRARY.isCandidate() - // handles some, but not all, of this; and the rules of selection - // should probably be consolidated in a sensible way [fry 200116] + // handles some, but not all, of this; and the rules of selection + // should probably be consolidated in a sensible way [fry 200116] for (String potentialName : folderNames) { File baseFolder = new File(folder, potentialName); File libraryFolder = new File(baseFolder, "library"); @@ -534,13 +594,19 @@ public class Library extends LocalContribution { // Move the folder out of the way File badFolder = new File(baseFolder.getParentFile(), "disabled"); + boolean success = true; if (!badFolder.exists()) { - badFolder.mkdirs(); + success = badFolder.mkdirs(); + } + if (success) { + File hideFolder = new File(badFolder, baseFolder.getName()); + success = baseFolder.renameTo(hideFolder); + if (success) { + System.out.println("Moved " + baseFolder + " to " + hideFolder); + } else { + System.err.println("Could not move " + baseFolder + " to " + hideFolder); + } } - File hideFolder = new File(badFolder, baseFolder.getName()); - System.out.println("moving " + baseFolder); - System.out.println("to " + hideFolder); - baseFolder.renameTo(hideFolder); } else { libraries.add(baseFolder); @@ -555,31 +621,11 @@ public class Library extends LocalContribution { static public List list(File folder) { List libraries = new ArrayList<>(); - List librariesFolders = new ArrayList<>(); - librariesFolders.addAll(discover(folder)); + List librariesFolders = new ArrayList<>(discover(folder)); for (File baseFolder : librariesFolders) { libraries.add(new Library(baseFolder)); } - - /* - // Support libraries inside of one level of subfolders? I believe this was - // the compromise for supporting library groups, but probably a bad idea - // because it's not compatible with the Manager. - String[] folderNames = folder.list(junkFolderFilter); - if (folderNames != null) { - for (String subfolderName : folderNames) { - File subfolder = new File(folder, subfolderName); - - if (!librariesFolders.contains(subfolder)) { - List discoveredLibFolders = discover(subfolder); - for (File discoveredFolder : discoveredLibFolders) { - libraries.add(new Library(discoveredFolder, subfolderName)); - } - } - } - } - */ return libraries; } diff --git a/app/src/processing/app/Messages.java b/app/src/processing/app/Messages.java index 5257491ca..f79af6714 100644 --- a/app/src/processing/app/Messages.java +++ b/app/src/processing/app/Messages.java @@ -21,6 +21,8 @@ package processing.app; +import processing.app.ui.Toolkit; + import java.awt.Frame; import java.io.PrintWriter; import java.io.StringWriter; @@ -84,28 +86,9 @@ public class Messages { System.out.println(title + ": " + message); } else { - if (!Platform.isMacOS()) { - JOptionPane.showMessageDialog(new JFrame(), - "" + - "" + primary + "" + - "
" + secondary, title, - JOptionPane.WARNING_MESSAGE); - } else { - // Pane formatting adapted from the Quaqua guide - // http://www.randelshofer.ch/quaqua/guide/joptionpane.html - JOptionPane pane = - new JOptionPane(" " + - " " + - "" + primary + "" + - "

" + secondary + "

", - JOptionPane.WARNING_MESSAGE); - - JDialog dialog = pane.createDialog(new JFrame(), null); - dialog.setVisible(true); - } + JOptionPane.showMessageDialog(new JFrame(), + Toolkit.formatMessage(primary, secondary), + title, JOptionPane.WARNING_MESSAGE); } if (e != null) e.printStackTrace(); } @@ -132,7 +115,7 @@ public class Messages { /** - * Testing a new warning window that includes the stack trace. + * Warning window that includes the stack trace. */ static public void showTrace(String title, String message, Throwable t, boolean fatal) { @@ -147,13 +130,12 @@ public class Messages { } else { StringWriter sw = new StringWriter(); t.printStackTrace(new PrintWriter(sw)); - // Necessary to replace \n with
(even if pre) otherwise Java - // treats it as a closed tag and reverts to plain formatting. - message = ("" + message + - "

" + - sw + "").replaceAll("\n", "
"); - JOptionPane.showMessageDialog(new Frame(), message, title, + JOptionPane.showMessageDialog(new Frame(), + // first
clears to the next line + // second
is a shorter height blank space before the trace + Toolkit.formatMessage(message + "

" + sw + "
"), + title, fatal ? JOptionPane.ERROR_MESSAGE : JOptionPane.WARNING_MESSAGE); @@ -169,9 +151,11 @@ public class Messages { String primary, String secondary) { if (!Platform.isMacOS()) { return JOptionPane.showConfirmDialog(editor, - "" + - "" + primary + "" + - "
" + secondary, title, + Toolkit.formatMessage(primary, secondary), + //"" + + //"" + primary + "" + + //"
" + secondary, + title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); } else { @@ -199,20 +183,12 @@ public class Messages { Object result; if (!Platform.isMacOS()) { return JOptionPane.showOptionDialog(editor, - "" + primary + "
" + secondary, title, + Toolkit.formatMessage(primary, secondary), title, JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[highlight]); } else { - // Pane formatting adapted from the Quaqua guide - // http://www.randelshofer.ch/quaqua/guide/joptionpane.html JOptionPane pane = - new JOptionPane(" " + - " " + - "" + primary + "" + - "

" + secondary, // + "

", + new JOptionPane(Toolkit.formatMessage(primary, secondary), JOptionPane.QUESTION_MESSAGE); pane.setOptions(options); diff --git a/app/src/processing/app/Mode.java b/app/src/processing/app/Mode.java index 73d546619..67ec35259 100644 --- a/app/src/processing/app/Mode.java +++ b/app/src/processing/app/Mode.java @@ -30,7 +30,6 @@ import java.util.*; import java.util.List; import javax.swing.*; -import javax.swing.tree.*; import processing.app.contrib.ContributionManager; import processing.app.syntax.*; @@ -39,7 +38,6 @@ import processing.app.ui.EditorException; import processing.app.ui.EditorState; import processing.app.ui.ExamplesFrame; import processing.app.ui.Recent; -import processing.app.ui.SketchbookFrame; import processing.app.ui.Toolkit; import processing.core.PApplet; @@ -63,7 +61,6 @@ public abstract class Mode { protected JMenu importMenu; protected ExamplesFrame examplesFrame; - protected SketchbookFrame sketchbookFrame; // popup menu used for the toolbar protected JMenu toolbarMenu; @@ -86,14 +83,6 @@ public abstract class Mode { */ protected ClassLoader classLoader; -// static final int BACKGROUND_WIDTH = 1025; -// static final int BACKGROUND_HEIGHT = 65; -// protected Image backgroundImage; - -// public Mode(Base base, File folder) { -// this(base, folder, base.getSketchbookLibrariesFolder()); -// } - public Mode(Base base, File folder) { this.base = base; @@ -138,42 +127,44 @@ public abstract class Mode { @SuppressWarnings("SameParameterValue") protected void loadKeywords(File keywordFile, String commentPrefix) throws IOException { - BufferedReader reader = PApplet.createReader(keywordFile); - String line; - while ((line = reader.readLine()) != null) { - if (!line.trim().startsWith(commentPrefix)) { - // Was difficult to make sure that mode authors were properly doing - // tab-separated values. By definition, there can't be additional - // spaces inside a keyword (or filename), so just splitting on tokens. - String[] pieces = PApplet.splitTokens(line); - if (pieces.length >= 2) { - String keyword = pieces[0]; - String coloring = pieces[1]; + String[] lines = PApplet.loadStrings(keywordFile); + if (lines != null) { + for (String line : lines) { + if (!line.trim().startsWith(commentPrefix)) { + // Was difficult to make sure that mode authors were properly doing + // tab-separated values. By definition, there can't be additional + // spaces inside a keyword (or filename), so just splitting on tokens. + String[] pieces = PApplet.splitTokens(line); + if (pieces.length >= 2) { + String keyword = pieces[0]; + String coloring = pieces[1]; - if (coloring.length() > 0) { - tokenMarker.addColoring(keyword, coloring); - } - if (pieces.length == 3) { - String htmlFilename = pieces[2]; - if (htmlFilename.length() > 0) { - // if the file is for the version with parens, - // add a paren to the keyword - if (htmlFilename.endsWith("_")) { - keyword += "_"; - } - // Allow the bare size() command to override the lookup - // for StringList.size() and others, but not vice-versa. - // https://github.com/processing/processing/issues/4224 - boolean seen = keywordToReference.containsKey(keyword); - if (!seen || keyword.equals(htmlFilename)) { - keywordToReference.put(keyword, htmlFilename); + if (coloring.length() > 0) { + tokenMarker.addColoring(keyword, coloring); + } + if (pieces.length == 3) { + String htmlFilename = pieces[2]; + if (htmlFilename.length() > 0) { + // if the file is for the version with parens, + // add a paren to the keyword + if (htmlFilename.endsWith("_")) { + keyword += "_"; + } + // Allow the bare size() command to override the lookup + // for StringList.size() and others, but not vice-versa. + // https://github.com/processing/processing/issues/4224 + boolean seen = keywordToReference.containsKey(keyword); + if (!seen || keyword.equals(htmlFilename)) { + keywordToReference.put(keyword, htmlFilename); + } } } } } } + } else { + System.err.println("Could not read " + keywordFile); } - reader.close(); } @@ -187,51 +178,12 @@ public abstract class Mode { } -// /** -// * Setup additional elements that are only required when running with a GUI, -// * rather than from the command-line. Note that this will not be called when -// * the Mode is used from the command line (because Base will be null). -// */ - /* - public void setupGUI() { - try { - // First load the default theme data for the whole PDE. - theme = new Settings(Platform.getContentFile("lib/theme.txt")); - - // The mode-specific theme.txt file should only contain additions, - // and in extremely rare cases, it might override entries from the - // main theme. Do not override for style changes unless they are - // objectively necessary for your Mode. - File modeTheme = new File(folder, "theme/theme.txt"); - if (modeTheme.exists()) { - // Override the built-in settings with what the theme provides - theme.load(modeTheme); - } - - // Against my better judgment, adding the ability to override themes - // https://github.com/processing/processing/issues/5445 - File sketchbookTheme = - new File(Base.getSketchbookFolder(), "theme.txt"); - if (sketchbookTheme.exists()) { - theme.load(sketchbookTheme); - } - - // other things that have to be set explicitly for the defaults - theme.setColor("run.window.bgcolor", SystemColor.control); - - } catch (IOException e) { - Messages.showError("Problem loading theme.txt", - "Could not load theme.txt, please re-install Processing", e); - } - } - */ - - public File getContentFile(String path) { return new File(folder, path); } + @SuppressWarnings("unused") public InputStream getContentStream(String path) throws FileNotFoundException { return new FileInputStream(getContentFile(path)); } @@ -430,6 +382,7 @@ public abstract class Mode { // abstract public EditorToolbar createToolbar(Editor editor); + @SuppressWarnings("unused") public JMenu getToolbarMenu() { if (toolbarMenu == null) { rebuildToolbarMenu(); @@ -544,9 +497,9 @@ public abstract class Mode { importMenu.removeAll(); } - JMenuItem addLib = new JMenuItem(Language.text("menu.library.add_library")); - addLib.addActionListener(e -> ContributionManager.openLibraries()); - importMenu.add(addLib); + JMenuItem manageLibs = new JMenuItem(Language.text("menu.library.manage_libraries")); + manageLibs.addActionListener(e -> ContributionManager.openLibraries()); + importMenu.add(manageLibs); importMenu.addSeparator(); rebuildLibraryList(); @@ -563,6 +516,7 @@ public abstract class Mode { JMenuItem item = new JMenuItem(getTitle() + " " + Language.text("menu.library.no_core_libraries")); item.setEnabled(false); importMenu.add(item); + } else { for (Library library : coreLibraries) { JMenuItem item = new JMenuItem(library.getName()); @@ -581,7 +535,7 @@ public abstract class Mode { contrib.setEnabled(false); importMenu.add(contrib); - HashMap subfolders = new HashMap<>(); + Map subfolders = new HashMap<>(); for (Library library : contribLibraries) { JMenuItem item = new JMenuItem(library.getName()); @@ -657,48 +611,6 @@ public abstract class Mode { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - public DefaultMutableTreeNode buildSketchbookTree() { - DefaultMutableTreeNode sbNode = - new DefaultMutableTreeNode(Language.text("sketchbook.tree")); - try { - base.addSketches(sbNode, Base.getSketchbookFolder(), false); - } catch (IOException e) { - e.printStackTrace(); - } - return sbNode; - } - - - /** Sketchbook has changed, update it on next viewing. */ - public void rebuildSketchbookFrame() { - if (sketchbookFrame != null) { - boolean visible = sketchbookFrame.isVisible(); - Rectangle bounds = null; - if (visible) { - bounds = sketchbookFrame.getBounds(); - sketchbookFrame.setVisible(false); - sketchbookFrame.dispose(); - } - sketchbookFrame = null; - if (visible) { - showSketchbookFrame(); - sketchbookFrame.setBounds(bounds); - } - } - } - - - public void showSketchbookFrame() { - if (sketchbookFrame == null) { - sketchbookFrame = new SketchbookFrame(base, this); - } - sketchbookFrame.setVisible(); - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - /** * Get an ImageIcon object from the Mode folder. * Or when prefixed with /lib, load it from the main /lib folder. @@ -710,10 +622,8 @@ public abstract class Mode { } File file = new File(folder, filename); if (!file.exists()) { -// EditorConsole.systemErr.println("file does not exist: " + file.getAbsolutePath()); return null; } -// EditorConsole.systemErr.println("found: " + file.getAbsolutePath()); return new ImageIcon(file.getAbsolutePath()); } @@ -732,19 +642,20 @@ public abstract class Mode { public Image loadImageX(String filename) { - final int res = Toolkit.highResImages() ? 2 : 1; - return loadImage(filename + "-" + res + "x.png"); + return loadImage(filename + "-" + Toolkit.highResMultiplier() + "x.png"); } -// public EditorButton loadButton(String name) { -// return new EditorButton(this, name); -// } - - - //public Settings getTheme() { - // return theme; - //} + public String loadString(String filename) { + File file; + if (filename.startsWith("/lib/")) { + // remove the slash from the front + file = Platform.getContentFile(filename.substring(1)); + } else { + file = new File(folder, filename); + } + return Util.loadFile(file); + } /** @@ -778,112 +689,8 @@ public abstract class Mode { } -// abstract public Formatter createFormatter(); - - -// public Formatter getFormatter() { -// return formatter; -// } - - -// public Tool getFormatter() { -// return formatter; -// } - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /* - // Get attributes/values from the theme.txt file. To discourage burying this - // kind of information in code where it doesn't belong (and is difficult to - // track down), these don't have a "default" option as a second parameter. - - - // removing, this doesn't seem to have been in use - /// @since 3.0a6 - public String getString(String attribute) { - return theme.get(attribute); - } - - - public boolean getBoolean(String attribute) { - return theme.getBoolean(attribute); - } - - - public int getInteger(String attribute) { - return theme.getInteger(attribute); - } - - - public Color getColor(String attribute) { - return theme.getColor(attribute); - } - - - public Font getFont(String attribute) { - return theme.getFont(attribute); - } - - - public SyntaxStyle getStyle(String attribute) { - String str = Preferences.get("editor.token." + attribute + ".style"); - if (str == null) { - throw new IllegalArgumentException("No style found for " + attribute); - } - - StringTokenizer st = new StringTokenizer(str, ","); - - String s = st.nextToken(); - if (s.indexOf("#") == 0) s = s.substring(1); - Color color = new Color(Integer.parseInt(s, 16)); - - s = st.nextToken(); - boolean bold = s.contains("bold"); -// boolean italic = (s.indexOf("italic") != -1); - -// return new SyntaxStyle(color, italic, bold); - return new SyntaxStyle(color, bold); - } - - - public Image makeGradient(String attribute, int wide, int high) { - int top = getColor(attribute + ".gradient.top").getRGB(); - int bot = getColor(attribute + ".gradient.bottom").getRGB(); - -// float r1 = (top >> 16) & 0xff; -// float g1 = (top >> 8) & 0xff; -// float b1 = top & 0xff; -// float r2 = (bot >> 16) & 0xff; -// float g2 = (bot >> 8) & 0xff; -// float b2 = bot & 0xff; - - BufferedImage outgoing = - new BufferedImage(wide, high, BufferedImage.TYPE_INT_RGB); - int[] row = new int[wide]; - WritableRaster wr = outgoing.getRaster(); - for (int i = 0; i < high; i++) { -// Arrays.fill(row, (255 - (i + GRADIENT_TOP)) << 24); -// int r = (int) PApplet.map(i, 0, high-1, r1, r2); - int rgb = PApplet.lerpColor(top, bot, i / (float)(high-1), PConstants.RGB); - Arrays.fill(row, rgb); -// System.out.println(PApplet.hex(row[0])); - wr.setDataElements(0, i, wide, 1, row); - } -// Graphics g = outgoing.getGraphics(); -// for (int i = 0; i < steps; i++) { -// g.setColor(new Color(1, 1, 1, 255 - (i + GRADIENT_TOP))); -// //g.fillRect(0, i, EditorButton.DIM, 10); -// g.drawLine(0, i, EditorButton.DIM, i); -// } - return outgoing; - } - */ - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - // Breaking out extension types in order to clean up the code, and make it // easier for other environments (like Arduino) to incorporate changes. @@ -893,8 +700,8 @@ public abstract class Mode { * For Processing, this is true for .pde files. (Broken out for subclasses.) * You can override this in your Mode subclass to handle it differently. */ - public boolean hideExtension(String what) { - return what.equals(getDefaultExtension()); + public boolean hideExtension(String ext) { + return ext.equals(getDefaultExtension()); } @@ -909,21 +716,21 @@ public abstract class Mode { /** * True if the specified extension is the default file extension. */ - public boolean isDefaultExtension(String what) { - return what.equals(getDefaultExtension()); + public boolean isDefaultExtension(String ext) { + return ext.equals(getDefaultExtension()); } /** - * @param f File to be checked against this mode's accepted extensions. - * @return Whether or not the given file name features an extension supported by this mode. + * True if this Mode can edit this file (usually meaning that + * its extension matches one that is supported by the Mode). */ - public boolean canEdit(final File f) { - final int dot = f.getName().lastIndexOf('.'); + public boolean canEdit(final File file) { + final int dot = file.getName().lastIndexOf('.'); if (dot < 0) { return false; } - return validExtension(f.getName().substring(dot + 1)); + return validExtension(file.getName().substring(dot + 1)); } @@ -957,12 +764,12 @@ public abstract class Mode { /** - * Returns the appropriate file extension to use for auxilliary source files in a sketch. - * For example, in a Java-mode sketch, auxilliary files should be name "Foo.java"; in - * Python mode, they should be named "foo.py". + * Returns the appropriate file extension to use for auxiliary source + * files in a sketch. For example, in a Java-mode sketch, auxiliary files + * can be named "Foo.java"; in Python mode, they should be named "foo.py". * - *

Modes that do not override this function will get the default behavior of returning the - * default extension. + * Modes that do not override this function will get the + * default behavior of returning the default extension. */ public String getModuleExtension() { return getDefaultExtension(); @@ -983,33 +790,32 @@ public abstract class Mode { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + /** * Checks coreLibraries and contribLibraries for a library with the specified name * @param libName the name of the library to find * @return the Library or null if not found */ public Library findLibraryByName(String libName) { - for (Library lib : this.coreLibraries) { if (libName.equals(lib.getName())) return lib; } - for (Library lib : this.contribLibraries) { if (libName.equals(lib.getName())) return lib; } - return null; } + /** - * Create a fresh applet/application folder if the 'delete target folder' + * Create a fresh application folder if the 'delete target folder' * pref has been set in the preferences. */ public void prepareExportFolder(File targetFolder) { if (targetFolder != null) { - // Nuke the old applet/application folder because it can cause trouble + // Nuke the old application folder because it can cause trouble if (Preferences.getBoolean("export.delete_target_folder")) { if (targetFolder.exists()) { try { @@ -1029,21 +835,6 @@ public abstract class Mode { } } -// public void handleNew() { -// base.handleNew(); -// } -// -// -// public void handleNewReplace() { -// base.handleNewReplace(); -// } - - - // this is Java-specific, so keeping it in JavaMode -// public String getSearchPath() { -// return null; -// } - @Override public String toString() { diff --git a/app/src/processing/app/Platform.java b/app/src/processing/app/Platform.java index 59991bf7c..d6c73be9e 100644 --- a/app/src/processing/app/Platform.java +++ b/app/src/processing/app/Platform.java @@ -35,22 +35,26 @@ import com.sun.jna.platform.FileUtils; import processing.app.platform.DefaultPlatform; import processing.core.PApplet; import processing.core.PConstants; +import processing.data.StringDict; public class Platform { static DefaultPlatform inst; + /* static Map platformNames = new HashMap<>(); static { platformNames.put(PConstants.WINDOWS, "windows"); //$NON-NLS-1$ - platformNames.put(PConstants.MACOS, "macosx"); //$NON-NLS-1$ + platformNames.put(PConstants.MACOS, "macos"); //$NON-NLS-1$ platformNames.put(PConstants.LINUX, "linux"); //$NON-NLS-1$ } + */ + // TODO only used in one place, probably overkill for this to be a map static Map platformIndices = new HashMap<>(); static { platformIndices.put("windows", PConstants.WINDOWS); //$NON-NLS-1$ - platformIndices.put("macosx", PConstants.MACOS); //$NON-NLS-1$ + platformIndices.put("macos", PConstants.MACOS); //$NON-NLS-1$ platformIndices.put("linux", PConstants.LINUX); //$NON-NLS-1$ } @@ -82,15 +86,20 @@ public class Platform { static public void init() { try { - Class platformClass = Class.forName("processing.app.Platform"); //$NON-NLS-1$ + // Start with DefaultPlatform, but try to upgrade to a known platform + final String packageName = DefaultPlatform.class.getPackageName(); + Class platformClass = + Class.forName(packageName + ".DefaultPlatform"); + if (Platform.isMacOS()) { - platformClass = Class.forName("processing.app.platform.MacPlatform"); //$NON-NLS-1$ + platformClass = Class.forName(packageName + ".MacPlatform"); } else if (Platform.isWindows()) { - platformClass = Class.forName("processing.app.platform.WindowsPlatform"); //$NON-NLS-1$ + platformClass = Class.forName(packageName + ".WindowsPlatform"); } else if (Platform.isLinux()) { - platformClass = Class.forName("processing.app.platform.LinuxPlatform"); //$NON-NLS-1$ + platformClass = Class.forName(packageName + ".LinuxPlatform"); } inst = (DefaultPlatform) platformClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { Messages.showError("Problem Setting the Platform", "An unknown error occurred while trying to load\n" + @@ -136,12 +145,11 @@ public class Platform { /** * Implements the cross-platform headache of opening URLs. - * - * For 2.0a8 and later, this requires the parameter to be an actual URL, - * meaning that you can't send it a file:// path without a prefix. It also - * just calls into Platform, which now uses java.awt.Desktop (where - * possible, meaning not on Linux) now that we're requiring Java 6. - * As it happens the URL must also be properly URL-encoded. + *

+ * Since 2.0a8, this requires the parameter to be an actual URL, + * meaning that you can't send it a file:// path without a prefix. + * It also just calls into Platform, which now uses java.awt.Desktop + * (where possible). The URL must also be properly URL-encoded. */ static public void openURL(String url) { try { @@ -181,13 +189,13 @@ public class Platform { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /** - * Return whether sketches will run as 32- or 64-bits based - * on the JVM that's in use. - */ - static public int getNativeBits() { - return nativeBits; - } +// /** +// * Return whether sketches will run as 32- or 64-bits based +// * on the JVM that's in use. +// */ +// static public int getNativeBits() { +// return nativeBits; +// } /** @@ -202,10 +210,32 @@ public class Platform { } - /* - * Return a string that identifies the variant of a platform - * e.g. "32" or "64" on Intel + static public String getVariant() { + return getName() + "-" + getNativeArch(); + } + + + static StringDict supportedVariants = new StringDict(new String[][] { + { "macos-x86_64", "macOS (Intel 64-bit)" }, + { "macos-aarch64", "macOS (Apple Silicon)" }, + { "windows-amd64", "Windows (Intel 64-bit)" }, + { "linux-amd64", "Linux (Intel 64-bit)" }, + { "linux-arm", "Linux (Raspberry Pi 32-bit)" }, + { "linux-aarch64", "Linux (Raspberry Pi 64-bit)" } + }); + + /** + * List of variants that are supported by this release of the PDE. */ + static public StringDict getSupportedVariants() { + return supportedVariants; + } + +// /* +// * Return a string that identifies the variant of a platform +// * e.g. "32" or "64" on Intel +// */ + /* static public String getVariant() { return getVariant(PApplet.platform, getNativeArch(), getNativeBits()); } @@ -222,26 +252,41 @@ public class Platform { return Integer.toString(bits); // 32 or 64 } + */ + /** + * Returns one of macos, windows, linux, or other. + * Changed in 4.0b4 to return macos instead of macosx. + * Only used inside processing.app.java. + */ static public String getName() { return PConstants.platformNames[PApplet.platform]; } - /** - * Map a platform constant to its name. - * @param which PConstants.WINDOWS, PConstants.MACOSX, PConstants.LINUX - * @return one of "windows", "macosx", or "linux" - */ - static public String getName(int which) { - return platformNames.get(which); + static public String getPrettyName() { + return supportedVariants.get(getVariant()); } - static public int getIndex(String what) { - Integer entry = platformIndices.get(what); - return (entry == null) ? -1 : entry; +// /** +// * Map a platform constant to its name. +// * @param which PConstants.WINDOWS, PConstants.MACOSX, PConstants.LINUX +// * @return one of "windows", "macosx", or "linux" +// */ +// static public String getName(int which) { +// return platformNames.get(which); +// } + + + static public int getIndex(String platformName) { + // if this has os.arch at the end, remove it + int index = platformName.indexOf('-'); + if (index != -1) { + platformName = platformName.substring(0, index); + } + return platformIndices.getOrDefault(platformName, -1); } @@ -304,7 +349,7 @@ public class Platform { if (decodedPath.contains("/app/bin")) { // This means we're in Eclipse final File build = new File(decodedPath, "../../build").getAbsoluteFile(); if (Platform.isMacOS()) { - processingRoot = new File(build, "macosx/work/Processing.app/Contents/Java"); + processingRoot = new File(build, "macos/work/Processing.app/Contents/Java"); } else if (Platform.isWindows()) { processingRoot = new File(build, "windows/work"); } else if (Platform.isLinux()) { @@ -363,11 +408,18 @@ public class Platform { /** - * Attempts to move to the Trash on OS X, or the Recycle Bin on Windows. + * Delete a file or directory in a platform-specific manner. Removes a File + * object (a file or directory) from the system by placing it in the Trash + * or Recycle Bin (if available) or simply deleting it (if not). * Also tries to find a suitable Trash location on Linux. - * If not possible, just deletes the file or folder instead. - * @param file the folder or file to be removed/deleted - * @return true if the folder was successfully removed + *

+ * When the file/folder is on another file system, it may simply be removed + * immediately, without additional warning. So only use this if you want to, + * you know, "delete" the subject in question. + * + * @param file the victim (a directory or individual file) + * @return true if all ends well + * @throws IOException what went wrong */ static public boolean deleteFile(File file) throws IOException { try { diff --git a/app/src/processing/app/Settings.java b/app/src/processing/app/Settings.java index 403d6587f..020c20d38 100644 --- a/app/src/processing/app/Settings.java +++ b/app/src/processing/app/Settings.java @@ -44,7 +44,7 @@ public class Settings { * defaults, then replace those with what was in the user's preferences file. * Problem is, if something like a font entry in the user's file no longer * parses properly, we need to be able to get back to a clean version of that - * setting so we can recover. + * setting so that we can recover. */ Map defaults; @@ -254,4 +254,43 @@ public class Settings { } return new Font("Dialog", Font.PLAIN, 12); } + + + public String remove(String key) { + return table.remove(key); + } + + + /** + * The day of reckoning: save() a file if it has entries, or delete + * if the file exists but the table no longer has any entries. + */ + public void reckon() { + if (table.isEmpty()) { + if (file.exists() && !file.delete()) { + System.err.println("Could not remove empty " + file); + } + } else { + save(); + } + } + + + public boolean isEmpty() { + return table.isEmpty(); + } + + + public void print() { + String[] keys = table.keySet().toArray(new String[0]); + Arrays.sort(keys); + for (String key : keys) { + System.out.println(key + " = " + table.get(key)); + } + } + + + public Map getMap() { + return table; + } } \ No newline at end of file diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index eac622bea..ea8c93262 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -3,6 +3,7 @@ /* Part of the Processing project - http://processing.org + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2004-11 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -36,10 +37,9 @@ import java.awt.FileDialog; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; import java.io.*; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -51,15 +51,15 @@ import javax.swing.border.EmptyBorder; * Stores information about files in the current sketch. */ public class Sketch { - private Editor editor; - private Mode mode; + private final Editor editor; + private final Mode mode; /** main pde file for this sketch. */ - private File primaryFile; + private File mainFile; /** - * Name of sketch, which is the name of main file - * (without .pde or .java extension) + * Name of the sketch, which is the name of the folder since 4.0 beta 6. + * Prior, it was the "pretty" name of the first tab (they were synonymous). */ private String name; @@ -79,11 +79,11 @@ public class Sketch { private int currentIndex; /** - * Number of sketchCode objects (tabs) in the current sketch. Note that this + * Number of SketchCode objects (tabs) in the current sketch. Note that this * will be the same as code.length, because the getCode() method returns * just the code[] array, rather than a copy of it, or an array that's been * resized to just the relevant files themselves. - * http://dev.processing.org/bugs/show_bug.cgi?id=940 + * https://download.processing.org/bugzilla/940.html */ private int codeCount; private SketchCode[] code; @@ -94,6 +94,7 @@ public class Sketch { /** true if we've posted a "sketch disappeared" warning */ private boolean disappearedWarning; + /** * Used by the command-line version to create a sketch object. * @param path location of the main .pde file @@ -118,13 +119,17 @@ public class Sketch { protected void load(String path) { - primaryFile = new File(path); + mainFile = new File(path); + folder = mainFile.getParentFile(); + /* // get the name of the sketch by chopping .pde or .java // off of the main file name String mainFilename = primaryFile.getName(); int suffixLength = mode.getDefaultExtension().length() + 1; name = mainFilename.substring(0, mainFilename.length() - suffixLength); - folder = new File(new File(path).getParent()); + */ + // starting in 4.0 beta 6, use the folder name instead of the main tab + name = folder.getName(); disappearedWarning = false; load(); } @@ -166,7 +171,7 @@ public class Sketch { // start at 1, if it's at zero, don't bother for (int i = 1; i < codeCount; i++) { //if (code[i].file.getName().equals(mainFilename)) { - if (code[i].getFile().equals(primaryFile)) { + if (code[i].getFile().equals(mainFile)) { SketchCode temp = code[0]; code[0] = code[i]; code[i] = temp; @@ -187,29 +192,30 @@ public class Sketch { public void getSketchCodeFiles(List outFilenames, List outExtensions) { // get list of files in the sketch folder - String list[] = folder.list(); + String[] list = folder.list(); + if (list != null) { + for (String filename : list) { + // Ignoring the dot prefix files is especially important to avoid files + // with the ._ prefix on Mac OS X. (You'll see this with Mac files on + // non-HFS drives, i.e. a thumb drive formatted FAT32.) + if (filename.startsWith(".")) continue; - for (String filename : list) { - // Ignoring the dot prefix files is especially important to avoid files - // with the ._ prefix on Mac OS X. (You'll see this with Mac files on - // non-HFS drives, i.e. a thumb drive formatted FAT32.) - if (filename.startsWith(".")) continue; + // Don't let some wacko name a directory blah.pde or bling.java. + if (new File(folder, filename).isDirectory()) continue; - // Don't let some wacko name a directory blah.pde or bling.java. - if (new File(folder, filename).isDirectory()) continue; + // figure out the name without any extension + String base = filename; + // now strip off the .pde and .java extensions + for (String extension : mode.getExtensions()) { + if (base.toLowerCase().endsWith("." + extension)) { + base = base.substring(0, base.length() - (extension.length() + 1)); - // figure out the name without any extension - String base = filename; - // now strip off the .pde and .java extensions - for (String extension : mode.getExtensions()) { - if (base.toLowerCase().endsWith("." + extension)) { - base = base.substring(0, base.length() - (extension.length() + 1)); - - // Don't allow people to use files with invalid names, since on load, - // it would be otherwise possible to sneak in nasty filenames. [0116] - if (isSanitaryName(base)) { - if (outFilenames != null) outFilenames.add(filename); - if (outExtensions != null) outExtensions.add(extension); + // Don't allow people to use files with invalid names, since on load, + // it would be otherwise possible to sneak in nasty filenames. [0116] + if (isSanitaryName(base)) { + if (outFilenames != null) outFilenames.add(filename); + if (outExtensions != null) outExtensions.add(extension); + } } } } @@ -223,7 +229,7 @@ public class Sketch { */ public void reload() { // set current to null so that the tab gets updated - // http://dev.processing.org/bugs/show_bug.cgi?id=515 + // https://download.processing.org/bugzilla/515.html current = null; // nuke previous files and settings load(); @@ -317,7 +323,7 @@ public class Sketch { /** - * Handler for the Rename Code menu option. + * Handler for the "Rename Code" menu option. */ public void handleRenameCode() { // make sure the user didn't hide the sketch folder @@ -344,9 +350,9 @@ public class Sketch { } // ask for new name of file (internal to window) - // TODO maybe just popup a text area? + // TODO maybe just pop up a text area? renamingCode = true; - String prompt = (currentIndex == 0) ? + String prompt = (currentIndex == 0 && Preferences.getBoolean("editor.sync_folder_and_filename")) ? Language.text("editor.sketch.rename.description") : Language.text("editor.tab.rename.description"); String oldName = (current.isExtension(mode.getDefaultExtension())) ? @@ -365,14 +371,17 @@ public class Sketch { // Forget ESC, the JDialog should handle it. // Use keyTyped to catch when the feller is actually added to the text // field. With keyTyped, as opposed to keyPressed, the keyCode will be - // zero, even if it's enter or backspace or whatever, so the keychar + // zero, even if it's enter or backspace or whatever, so the key char // should be used instead. Grr. public void keyTyped(KeyEvent event) { //System.out.println("got event " + event); char ch = event.getKeyChar(); + + //noinspection StatementWithEmptyBody if ((ch == '_') || (ch == '.') || // allow.pde and .java (('A' <= ch) && (ch <= 'Z')) || (('a' <= ch) && (ch <= 'z'))) { // These events are allowed straight through. + } else if (ch == ' ') { String t = field.getText(); int start = field.getSelectionStart(); @@ -380,6 +389,7 @@ public class Sketch { field.setText(t.substring(0, start) + "_" + t.substring(end)); field.setCaretPosition(start + 1); event.consume(); + } else if ((ch >= '0') && (ch <= '9')) { // getCaretPosition == 0 means that it's the first char // and the field is empty. @@ -438,7 +448,6 @@ public class Sketch { } - /** * This is called upon return from entering a new file name. * (that is, from either newCode or renameCode after the prompt) @@ -449,19 +458,20 @@ public class Sketch { return; } - // make sure the user didn't hide the sketch folder + // Make sure the sketch folder is still available and exists. ensureExistence(); - // Add the extension here, this simplifies some of the logic below. + // Add the extension here, this simplifies some logic below. if (newName.indexOf('.') == -1) { newName += "." + (renamingCode ? mode.getDefaultExtension() : mode.getModuleExtension()); } - // if renaming to the same thing as before, just ignore. - // also ignoring case here, because i don't want to write - // a bunch of special stuff for each platform - // (osx is case insensitive but preserving, windows insensitive, - // *nix is sensitive and preserving.. argh) + // If renaming to the same thing as before, just ignore. + // Also ignoring case here, because I don't want to write/maintain/debug + // a bunch of platform-specific quirks: macOS is case-insensitive but + // preserving, Windows is insensitive, *nix is sensitive and preserving, + // and someday we're all gonna die, and I'm comfortable that writing the + // necessary code is not essential to the story of my life on Earth. if (renamingCode) { if (newName.equalsIgnoreCase(current.getFileName())) { // exit quietly for the 'rename' case. @@ -497,9 +507,9 @@ public class Sketch { } } - // dots are allowed for the .pde and .java, but not in the name - // make sure the user didn't name things poo.time.pde - // or something like that (nothing against poo time) + // Dots are allowed for the .pde and .java, but not in the name. + // Make sure the user didn't name the file poo.time.pde or anything + // else with a dot inside it (nothing against poo time). String shortName = newName.substring(0, dot); String sanitaryName = Sketch.sanitizeName(shortName); if (!shortName.equals(sanitaryName)) { @@ -507,8 +517,8 @@ public class Sketch { } // If changing the extension of a file from .pde to .java, then it's ok. - // http://code.google.com/p/processing/issues/detail?id=776 - // A regression introduced by Florian's bug report (below) years earlier. + // https://github.com/processing/processing/issues/814 + // (That regression was introduced years earlier by the bug report below.) if (!(renamingCode && sanitaryName.equals(current.getPrettyName()))) { // Make sure no .pde *and* no .java files with the same name already exist // (other than the one we are currently attempting to rename) @@ -526,77 +536,21 @@ public class Sketch { File newFile = new File(folder, newName); if (renamingCode) { - if (currentIndex == 0) { - // get the new folder name/location - String folderName = newName.substring(0, newName.indexOf('.')); - File newFolder = new File(folder.getParentFile(), folderName); - if (newFolder.exists()) { - Messages.showWarning(Language.text("name.messages.new_folder_exists"), - Language.interpolate("name.messages.new_folder_exists.description", - newName)); - return; - } + if (currentIndex == 0 && + Preferences.getBoolean("editor.sync_folder_and_filename")) { + if (!renameSketch(newName, newExtension)) return; - // renaming the containing sketch folder - boolean success = folder.renameTo(newFolder); - if (!success) { - Messages.showWarning(Language.text("name.messages.error"), - Language.text("name.messages.no_rename_folder.description")); - return; - } - // let this guy know where he's living (at least for a split second) - current.setFolder(newFolder); - // folder will be set to newFolder by updateInternal() - - // unfortunately this can't be a "save as" because that - // only copies the sketch files and the data folder - // however this *will* first save the sketch, then rename - - // moved this further up in the process (before prompting for the name) -// if (isModified()) { -// Base.showMessage("Save", "Please save the sketch before renaming."); -// return; -// } - - // This isn't changing folders, just changes the name - newFile = new File(newFolder, newName); + } else { // else if something besides code[0], or ok to decouple name if (!current.renameTo(newFile, newExtension)) { Messages.showWarning(Language.text("name.messages.error"), Language.interpolate("name.messages.no_rename_file.description", current.getFileName(), newFile.getName())); return; } - - // Tell each code file the good news about their new home. - // current.renameTo() above already took care of the main tab. - for (int i = 1; i < codeCount; i++) { - code[i].setFolder(newFolder); - } - // Update internal state to reflect the new location - updateInternal(sanitaryName, newFolder, renamingCode); - -// File newMainFile = new File(newFolder, newName + ".pde"); -// String newMainFilePath = newMainFile.getAbsolutePath(); -// -// // having saved everything and renamed the folder and the main .pde, -// // use the editor to re-open the sketch to re-init state -// // (unfortunately this will kill positions for carets etc) -// editor.handleOpenUnchecked(newMainFilePath, -// currentIndex, -// editor.getSelectionStart(), -// editor.getSelectionStop(), -// editor.getScrollPosition()); -// -// // get the changes into the sketchbook menu -// // (re-enabled in 0115 to fix bug #332) -// editor.base.rebuildSketchbookMenusAsync(); - - } else { // else if something besides code[0] - if (!current.renameTo(newFile, newExtension)) { - Messages.showWarning(Language.text("name.messages.error"), - Language.interpolate("name.messages.no_rename_file.description", - current.getFileName(), newFile.getName())); - return; + if (currentIndex == 0) { + // If the main tab was renamed, check sketch.properties + mainFile = newFile; //code[0].getFile(); + updateNameProperties(); } } @@ -613,7 +567,6 @@ public class Sketch { return; } SketchCode newCode = new SketchCode(newFile, newExtension); - //System.out.println("new code is named " + newCode.getPrettyName() + " " + newCode.getFile()); insertCode(newCode); } @@ -628,6 +581,65 @@ public class Sketch { } + /** + * Pre-4.0b6 style rename where the sketch name must be identical + * to the name of the first (main) tab with the extension removed. + */ + protected boolean renameSketch(String newName, String newExtension) { + // get the new folder name/location + String folderName = newName.substring(0, newName.indexOf('.')); + File newFolder = new File(folder.getParentFile(), folderName); + if (newFolder.exists()) { + Messages.showWarning(Language.text("name.messages.new_folder_exists"), + Language.interpolate("name.messages.new_folder_exists.description", newName)); + return false; + } + + // renaming the containing sketch folder + boolean success = folder.renameTo(newFolder); + if (!success) { + Messages.showWarning(Language.text("name.messages.error"), + Language.text("name.messages.no_rename_folder.description")); + return false; + } + // let this guy know where he's living (at least for a split second) + current.setFolder(newFolder); + // folder will be set to newFolder by updateInternal() + + // unfortunately this can't be a "save as" because that + // only copies the sketch files and the data folder + // however this *will* first save the sketch, then rename + + // This isn't changing folders, just changes the name + File newFile = new File(newFolder, newName); + if (!current.renameTo(newFile, newExtension)) { + Messages.showWarning(Language.text("name.messages.error"), + Language.interpolate("name.messages.no_rename_file.description", + current.getFileName(), newFile.getName())); + return false; + } + + // Tell each code file the good news about their new home. + // current.renameTo() above already took care of the main tab. + for (int i = 1; i < codeCount; i++) { + code[i].setFolder(newFolder); + } + // Save the path in case we need to remove it from the Recent menu + String oldPath = getMainPath(); + + // Update internal state to reflect the new location + updateInternal(newFolder); + + if (renamingCode) { + // Update the Recent menu if a Rename event (but not Save As) + // https://github.com/processing/processing/issues/5902 + Recent.rename(editor, oldPath); + } + + return true; + } + + /** * Remove a piece of code from the sketch and from the disk. */ @@ -653,8 +665,8 @@ public class Sketch { // confirm deletion with user, yes/no Object[] options = { Language.text("prompt.ok"), Language.text("prompt.cancel") }; String prompt = (currentIndex == 0) ? - Language.text("warn.delete.sketch") : - Language.interpolate("warn.delete.file", current.getPrettyName()); + Language.interpolate("warn.delete.sketch_folder", getName()) : + Language.interpolate("warn.delete.sketch_file", current.getPrettyName()); int result = JOptionPane.showOptionDialog(editor, prompt, Language.text("warn.delete"), @@ -664,24 +676,22 @@ public class Sketch { options, options[0]); if (result == JOptionPane.YES_OPTION) { - if (currentIndex == 0) { + if (currentIndex == 0) { // delete the entire sketch // need to unset all the modified flags, otherwise tries // to do a save on the handleNew() - // delete the entire sketch - Util.removeDir(folder); - - // get the changes into the sketchbook menu - //sketchbook.rebuildMenus(); + // Attempt to move to the trash (falls back to removeDir) + try { + Platform.deleteFile(folder); + } catch (IOException e) { + e.printStackTrace(); + } // make a new sketch and rebuild the sketch menu - //editor.handleNewUnchecked(); - //editor.handleClose2(); - editor.getBase().rebuildSketchbookMenus(); + editor.getBase().rebuildSketchbook(); editor.getBase().handleClose(editor, false); - } else { - // delete the file + } else { // delete a single tab if (!current.deleteFile()) { Messages.showMessage(Language.text("delete.messages.cannot_delete.file"), Language.text("delete.messages.cannot_delete.file.description")+" \"" + @@ -720,7 +730,11 @@ public class Sketch { return; } } - System.err.println("removeCode: internal error.. could not find code"); + + if (Base.DEBUG) { + // This can happen with the change detector, but need not be reported. + System.err.println("removeCode: could not find " + which.getFileName()); + } } @@ -743,11 +757,9 @@ public class Sketch { /** - * Sets the modified value for the code in the frontmost tab. + * Sets the modified value for the code in the front-most tab. */ public void setModified(boolean state) { - //System.out.println("setting modified to " + state); - //new Exception().printStackTrace(System.out); if (current.isModified() != state) { current.setModified(state); calcModified(); @@ -783,9 +795,7 @@ public class Sketch { * Ensure that all SketchCodes are up-to-date, so that sc.save() works. */ public void updateSketchCodes() { -// if (current.isModified()) { current.setProgram(editor.getText()); -// } } @@ -827,19 +837,17 @@ public class Sketch { *

* This basically just duplicates the current sketch folder to * a new location, and then calls 'Save'. (needs to take the current - * state of the open files and save them to the new folder.. - * but not save over the old versions for the old sketch..) + * state of the open files and save them to the new folder, + * but not save over the old versions for the old sketch...) *

* Also removes the previously-generated .class and .jar files, * because they can cause trouble. */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean saveAs() throws IOException { String newParentDir = null; - String newName = null; - String oldName = folder.getName(); + String newSketchName = null; - // TODO rewrite this to use shared version from PApplet (But because that - // specifies a callback function, this needs to wait until the refactoring) final String PROMPT = Language.text("save"); // https://github.com/processing/processing4/issues/77 @@ -858,7 +866,7 @@ public class Sketch { fd.setFile(oldFolderName); fd.setVisible(true); newParentDir = fd.getDirectory(); - newName = fd.getFile(); + newSketchName = fd.getFile(); } else { JFileChooser fc = new JFileChooser(); fc.setDialogTitle(PROMPT); @@ -875,39 +883,42 @@ public class Sketch { if (result == JFileChooser.APPROVE_OPTION) { File selection = fc.getSelectedFile(); newParentDir = selection.getParent(); - newName = selection.getName(); + newSketchName = selection.getName(); } } // user canceled selection - if (newName == null) return false; + if (newSketchName == null) return false; - // check on the sanity of the name - String sanitaryName = Sketch.checkName(newName); - File newFolder = new File(newParentDir, sanitaryName); - if (!sanitaryName.equals(newName) && newFolder.exists()) { - Messages.showMessage(Language.text("save_file.messages.sketch_exists"), - Language.interpolate("save_file.messages.sketch_exists.description", - sanitaryName)); - return false; + boolean sync = Preferences.getBoolean("editor.sync_folder_and_filename"); + String newMainFileName = null; // only set with !sync + File newFolder; + if (sync) { + // before 4.0 beta 6 + //String sanitaryName = Sketch.checkName(newSketchName); + String newMainName = sanitizeName(newSketchName); + newFolder = new File(newParentDir, newMainName); + if (!newMainName.equals(newSketchName) && newFolder.exists()) { + Messages.showMessage(Language.text("save_file.messages.sketch_exists"), + Language.interpolate("save_file.messages.sketch_exists.description", + newMainName)); + return false; + } + newSketchName = newMainName; + newMainFileName = newMainName + "." + mode.getDefaultExtension(); + + } else { + newFolder = new File(newParentDir, newSketchName); // sketch folder name can be different } - newName = sanitaryName; - -// String newPath = newFolder.getAbsolutePath(); -// String oldPath = folder.getAbsolutePath(); - -// if (newPath.equals(oldPath)) { -// return false; // Can't save a sketch over itself -// } // make sure there doesn't exist a tab with that name already // but ignore this situation for the first tab, since it's probably being - // resaved (with the same name) to another location/folder. + // re-saved (with the same name) to another location/folder. for (int i = 1; i < codeCount; i++) { - if (newName.equalsIgnoreCase(code[i].getPrettyName())) { + if (newSketchName.equalsIgnoreCase(code[i].getPrettyName())) { Messages.showMessage(Language.text("save_file.messages.tab_exists"), Language.interpolate("save_file.messages.tab_exists.description", - newName)); + newSketchName)); return false; } } @@ -917,10 +928,12 @@ public class Sketch { // just use "save" here instead, because the user will have received a // message (from the operating system) about "do you want to replace?" return save(); - } + } // check to see if the user is trying to save this sketch inside itself try { + // Includes the separator so that a/b/c is different from a/b/c2. + // (a/b/c matches a/b/c2, but a/b/c/ does not match a/b/c2/) String newPath = newFolder.getCanonicalPath() + File.separator; String oldPath = folder.getCanonicalPath() + File.separator; @@ -929,76 +942,87 @@ public class Sketch { Language.text("save_file.messages.recursive_save.description")); return false; } - } catch (IOException e) { } + } catch (IOException ignored) { } // if the new folder already exists, then first remove its contents before // copying everything over (user will have already been warned). if (newFolder.exists()) { - Util.removeDir(newFolder); + //Util.removeDir(newFolder); + try { + Platform.deleteFile(newFolder); + } catch (IOException e) { + e.printStackTrace(); + } } // in fact, you can't do this on Windows because the file dialog // will instead put you inside the folder, but it happens on OS X a lot. // now make a fresh copy of the folder - newFolder.mkdirs(); + if (!newFolder.mkdirs()) { + // mkdirs() returns true when the folders are created, which should + // be the case here because we removed any existing 'newFolder' above. + // If this fails, then it probably means the removeDir() failed, + // or at least left things behind, which could mean badness later. + System.err.println("Error creating path " + newFolder); + } // grab the contents of the current tab before saving // first get the contents of the editor text area updateSketchCodes(); - File[] copyItems = folder.listFiles(new FileFilter() { - public boolean accept(File file) { - String name = file.getName(); - // just in case the OS likes to return these as if they're legit - if (name.equals(".") || name.equals("..")) { - return false; - } - // list of files/folders to be ignored during "save as" - String[] ignorable = mode.getIgnorable(); - if (ignorable != null) { - for (String ignore : ignorable) { - if (name.equals(ignore)) { - return false; - } - } - } - // ignore the extensions for code, since that'll be copied below - for (String ext : mode.getExtensions()) { - if (name.endsWith(ext)) { + File[] copyItems = folder.listFiles(file -> { + String name = file.getName(); + // just in case the OS likes to return these as if they're legit + if (name.equals(".") || name.equals("..")) { + return false; + } + // list of files/folders to be ignored during "save as" + String[] ignorable = mode.getIgnorable(); + if (ignorable != null) { + for (String ignore : ignorable) { + if (name.equals(ignore)) { return false; } } - // don't do screen captures, since there might be thousands. kind of - // a hack, but seems harmless. hm, where have i heard that before... - if (name.startsWith("screen-")) { + } + // ignore the extensions for code, since that'll be copied below + for (String ext : mode.getExtensions()) { + if (name.endsWith(ext)) { return false; } - return true; } + // don't do screen captures, since there might be thousands. kind of + // a hack, but seems harmless. hm, where have i heard that before... + //noinspection RedundantIfStatement + if (name.startsWith("screen-")) { + return false; + } + return true; }); - startSaveAsThread(oldName, newName, newFolder, copyItems); + startSaveAsThread(newFolder, copyItems); - // save the other tabs to their new location (main tab saved below) - for (int i = 1; i < codeCount; i++) { + // Save each tab to its new location + for (int i = 0; i < codeCount; i++) { File newFile = new File(newFolder, code[i].getFileName()); + if (i == 0 && sync) { + newFile = new File(newFolder, newMainFileName); + } code[i].saveAs(newFile); } - // While the old path to the main .pde is still set, remove the entry from - // the Recent menu so that it's not sticking around after the rename. - // If untitled, it won't be in the menu, so there's no point. -// if (!isUntitled()) { -// Recent.remove(editor); -// } - // Folks didn't like this behavior, so shutting it off + // We were removing the old folder from the Recent menu, but folks + // did not like that behavior because they expected to have older + // versions readily available, so we shut it off in 3.5.4 and 4.x. // https://github.com/processing/processing/issues/5902 - // save the main tab with its new name - File newFile = new File(newFolder, newName + "." + mode.getDefaultExtension()); - code[0].saveAs(newFile); +// if (sync) { +// // save the main tab with its new name +// File newFile = new File(newFolder, newMainName + "." + mode.getDefaultExtension()); +// code[0].saveAs(newFile); +// } - updateInternal(newName, newFolder, false); + updateInternal(newFolder); // Make sure that it's not an untitled sketch setUntitled(false); @@ -1034,130 +1058,131 @@ public class Sketch { * * 3843 */ - void startSaveAsThread(final String oldName, final String newName, - final File newFolder, final File[] copyItems) { + void startSaveAsThread(final File newFolder, final File[] copyItems) { saving.set(true); - EventQueue.invokeLater(new Runnable() { - public void run() { - final JFrame frame = - new JFrame("Saving \u201C" + newName + "\u201C..."); - frame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); + EventQueue.invokeLater(() -> { + final JFrame frame = + new JFrame("Saving \u201C" + newFolder.getName() + "\u201C\u2026"); + frame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); - Box box = Box.createVerticalBox(); - box.setBorder(new EmptyBorder(16, 16, 16, 16)); + Box box = Box.createVerticalBox(); + box.setBorder(new EmptyBorder(16, 16, 16, 16)); - if (Platform.isMacOS()) { - frame.setBackground(Color.WHITE); + if (Platform.isMacOS()) { + frame.setBackground(Color.WHITE); + } + + JLabel label = + new JLabel("Saving additional files from the sketch folder..."); + box.add(label); + box.add(Box.createVerticalStrut(8)); + + final JProgressBar progressBar = new JProgressBar(0, 100); + // no luck, stuck with ugly on OS X + //progressBar.putClientProperty("JComponent.sizeVariant", "regular"); + progressBar.setValue(0); + progressBar.setStringPainted(true); + box.add(progressBar); + + frame.getContentPane().add(box); + frame.pack(); + frame.setLocationRelativeTo(editor); + Toolkit.setIcon(frame); + frame.setVisible(true); + + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + addPropertyChangeListener(evt -> { + if ("progress".equals(evt.getPropertyName())) { + progressBar.setValue((Integer) evt.getNewValue()); + } + }); + + long totalSize = 0; + for (File copyable : copyItems) { + totalSize += Util.calcSize(copyable); + } + + long progress = 0; + setProgress(0); + for (File copyable : copyItems) { + if (copyable.isDirectory()) { + copyDir(copyable, + new File(newFolder, copyable.getName()), + progress, totalSize); + progress += Util.calcSize(copyable); + } else { + copyFile(copyable, + new File(newFolder, copyable.getName()), + progress, totalSize); + if (Util.calcSize(copyable) < 512 * 1024) { + // If the file length > 0.5MB, the copyFile() function has + // been redesigned to change progress every 0.5MB so that + // the progress bar doesn't stagnate during that time + progress += Util.calcSize(copyable); + setProgress((int) (progress * 100L / totalSize)); + } + } + } + saving.set(false); + return null; } - JLabel label = - new JLabel("Saving additional files from the sketch folder..."); - box.add(label); - box.add(Box.createVerticalStrut(8)); - final JProgressBar progressBar = new JProgressBar(0, 100); - // no luck, stuck with ugly on OS X - //progressBar.putClientProperty("JComponent.sizeVariant", "regular"); - progressBar.setValue(0); - progressBar.setStringPainted(true); - box.add(progressBar); - - frame.getContentPane().add(box); - frame.pack(); - frame.setLocationRelativeTo(editor); - Toolkit.setIcon(frame); - frame.setVisible(true); - - new SwingWorker() { - - @Override - protected Void doInBackground() throws Exception { - addPropertyChangeListener(new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent evt) { - if ("progress".equals(evt.getPropertyName())) { - progressBar.setValue((Integer) evt.getNewValue()); - } - } - }); - - long totalSize = 0; - for (File copyable : copyItems) { - totalSize += Util.calcSize(copyable); + /** + * Overloaded copyFile that is called whenever a Save As is being done, + * so that the ProgressBar is updated for very large files as well. + */ + void copyFile(File sourceFile, File targetFile, + long progress, long totalSize) throws IOException { + BufferedInputStream from = + new BufferedInputStream(new FileInputStream(sourceFile)); + BufferedOutputStream to = + new BufferedOutputStream(new FileOutputStream(targetFile)); + byte[] buffer = new byte[16 * 1024]; + int bytesRead; + int progRead = 0; + while ((bytesRead = from.read(buffer)) != -1) { + to.write(buffer, 0, bytesRead); + progRead += bytesRead; + if (progRead >= 512 * 1024) { // to update progress bar every 0.5MB + progress += progRead; + //progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100)); + setProgress((int) (100L * progress / totalSize)); + progRead = 0; } - - long progress = 0; - setProgress(0); - for (File copyable : copyItems) { - if (copyable.isDirectory()) { - copyDir(copyable, - new File(newFolder, copyable.getName()), - progress, totalSize); - progress += Util.calcSize(copyable); - } else { - copyFile(copyable, - new File(newFolder, copyable.getName()), - progress, totalSize); - if (Util.calcSize(copyable) < 512 * 1024) { - // If the file length > 0.5MB, the copyFile() function has - // been redesigned to change progress every 0.5MB so that - // the progress bar doesn't stagnate during that time - progress += Util.calcSize(copyable); - setProgress((int) (progress * 100L / totalSize)); - } - } - } - saving.set(false); - return null; } + // Final update to progress bar + setProgress((int) (100L * progress / totalSize)); + from.close(); + to.flush(); + to.close(); - /** - * Overloaded copyFile that is called whenever a Save As is being done, - * so that the ProgressBar is updated for very large files as well. - */ - void copyFile(File sourceFile, File targetFile, - long progress, long totalSize) throws IOException { - BufferedInputStream from = - new BufferedInputStream(new FileInputStream(sourceFile)); - BufferedOutputStream to = - new BufferedOutputStream(new FileOutputStream(targetFile)); - byte[] buffer = new byte[16 * 1024]; - int bytesRead; - int progRead = 0; - while ((bytesRead = from.read(buffer)) != -1) { - to.write(buffer, 0, bytesRead); - progRead += bytesRead; - if (progRead >= 512 * 1024) { // to update progress bar every 0.5MB - progress += progRead; - //progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100)); - setProgress((int) (100L * progress / totalSize)); - progRead = 0; - } - } - // Final update to progress bar - setProgress((int) (100L * progress / totalSize)); - - from.close(); - from = null; - to.flush(); - to.close(); - to = null; - - targetFile.setLastModified(sourceFile.lastModified()); - targetFile.setExecutable(sourceFile.canExecute()); + if (!targetFile.setLastModified(sourceFile.lastModified())) { + System.err.println("Warning: Could not set modification date/time for " + targetFile); } - - - long copyDir(File sourceDir, File targetDir, - long progress, long totalSize) throws IOException { - // Overloaded copyDir so that the Save As progress bar gets updated when the - // files are in folders as well (like in the data folder) - if (sourceDir.equals(targetDir)) { - final String urDum = "source and target directories are identical"; - throw new IllegalArgumentException(urDum); + if (!targetFile.setExecutable(sourceFile.canExecute())) { + if (!Platform.isWindows()) { // more of a UNIX thing + System.err.println("Warning: Could not set permissions for " + targetFile); } - targetDir.mkdirs(); - String files[] = sourceDir.list(); + } + } + + + long copyDir(File sourceDir, File targetDir, + long progress, long totalSize) throws IOException { + // Overloaded copyDir so that the Save As progress bar gets updated when the + // files are in folders as well (like in the data folder) + if (sourceDir.equals(targetDir)) { + final String urDum = "source and target directories are identical"; + throw new IllegalArgumentException(urDum); + } + targetDir.mkdirs(); + String[] files = sourceDir.list(); + if (files != null) { for (String filename : files) { // Ignore dot files (.DS_Store), dot folders (.svn) while copying if (filename.charAt(0) == '.') { @@ -1178,17 +1203,19 @@ public class Sketch { setProgress((int) (100L * progress / totalSize)); } } - return progress; + } else { + throw new IOException("Could not list files inside " + sourceDir); } + return progress; + } - @Override - public void done() { - frame.dispose(); - editor.statusNotice(Language.text("editor.status.saving.done")); - } - }.execute(); - } + @Override + public void done() { + frame.dispose(); + editor.statusNotice(Language.text("editor.status.saving.done")); + } + }.execute(); }); } @@ -1196,35 +1223,136 @@ public class Sketch { /** * Update internal state for new sketch name or folder location. */ - protected void updateInternal(String sketchName, File sketchFolder, - boolean renaming) { + protected void updateInternal(File sketchFolder) { // reset all the state information for the sketch object - String oldPath = getMainFilePath(); - primaryFile = code[0].getFile(); -// String newPath = getMainFilePath(); -// editor.base.renameRecent(oldPath, newPath); + mainFile = code[0].getFile(); - name = sketchName; + name = sketchFolder.getName(); folder = sketchFolder; disappearedWarning = false; codeFolder = new File(folder, "code"); dataFolder = new File(folder, "data"); - // set the main file to be the current tab - //setCurrentCode(0); - // nah, this might just annoy people + updateNameProperties(); // Name changed, rebuild the sketch menus calcModified(); -// System.out.println("modified is now " + modified); editor.updateTitle(); - editor.getBase().rebuildSketchbookMenus(); - if (renaming) { - // only update the Recent menu if it's a rename, not a Save As - // https://github.com/processing/processing/issues/5902 - Recent.rename(editor, oldPath); + editor.getBase().rebuildSketchbook(); + } + + + protected void updateModeProperties(Mode mode, Mode defaultMode) { + updateModeProperties(folder, mode, defaultMode); + } + + + /** + * Create or modify a sketch.properties file to specify the given Mode. + */ + static protected void updateModeProperties(File folder, Mode mode, Mode defaultMode) { + try { + // Read the old sketch.properties file if it already exists + Settings props = loadProperties(folder); + + // If changing to the default Mode, + // remove those entries from sketch.properties + if (mode == defaultMode) { + props.remove("mode"); + props.remove("mode.id"); + } else { + // Setting to something other than the default Mode, + // write that and any other params already in the file. + props.set("mode", mode.getTitle()); + props.set("mode.id", mode.getIdentifier()); + } + props.reckon(); + + } catch (IOException e) { + System.err.println("Error while writing sketch.properties"); + e.printStackTrace(); + } + } + + + /* + protected Settings loadProperties() throws IOException { + return loadProperties(folder); + } + */ + + + /** + * Opens and parses sketch.properties. If it does not exist, returns an + * empty Settings object that can be written back to the same location. + */ + static protected Settings loadProperties(File folder) throws IOException { + /* + File propsFile = new File(folder, "sketch.properties"); + if (propsFile.exists()) { + return new Settings(propsFile); + } + return null; + */ + return new Settings(new File(folder, "sketch.properties")); + } + + + /** + * Check through the various modes and see if this is a legit sketch. + * Because the default mode will be the first in the list, this will always + * prefer that one over the others. + */ + static protected File findMain(File folder, List modeList) { + try { + Settings props = Sketch.loadProperties(folder); + String main = props.get("main"); + if (main != null) { + File mainFile = new File(folder, main); + if (!mainFile.exists()) { + System.err.println(main + " does not exist inside " + folder); + // Fall through to the code below in case we can recover. + // Not removing the bad entry since this is a find() method. + } + } + } catch (IOException e) { + e.printStackTrace(); + } + for (Mode mode : modeList) { + // Test whether a .pde file of the same name as its parent folder exists. + String defaultName = folder.getName() + "." + mode.getDefaultExtension(); + File entry = new File(folder, defaultName); + if (entry.exists()) { + return entry; + } + } + return null; + } + + + private void updateNameProperties() { + // If the main file and the sketch name are not identical, + // update sketch.properties. + String mainName = mainFile.getName(); + String defaultName = name + "." + mode.getDefaultExtension(); + //System.out.println("main name is " + mainName + " and default name is " + defaultName); + + try { + // Read the old sketch.properties file if it already exists + Settings props = loadProperties(folder); + + if (mainName.equals(defaultName)) { + props.remove("main"); + } else { + props.set("main", mainName); + } + //System.out.println("props size is now " + props.getMap().size()); + props.reckon(); + + } catch (IOException e) { + System.err.println("Error while updating sketch.properties"); + e.printStackTrace(); } -// editor.header.rebuild(); } @@ -1255,7 +1383,7 @@ public class Sketch { if (filename == null) return; // copy the file into the folder. if people would rather - // it move instead of copy, they can do it by hand + // move instead of copy, they can do it by hand File sourceFile = new File(directory, filename); // now do the work of adding the file @@ -1282,6 +1410,11 @@ public class Sketch { * @return true if successful. */ public boolean addFile(File sourceFile) { + if (sourceFile.isDirectory()) { + System.err.println("Skipping folder " + sourceFile); + System.err.println("Dragging and dropping a folder is not supported."); + return false; + } String filename = sourceFile.getName(); File destFile = null; String codeExtension = null; @@ -1298,8 +1431,13 @@ public class Sketch { filename.toLowerCase().endsWith(".jnilib") || filename.toLowerCase().endsWith(".so")) { - //if (!codeFolder.exists()) codeFolder.mkdirs(); - prepareCodeFolder(); + if (!codeFolder.exists()) { + boolean success = codeFolder.mkdirs(); + if (!success) { + System.err.println("Could not create " + codeFolder); + return false; + } + } destFile = new File(codeFolder, filename); isCode = true; } else { @@ -1338,7 +1476,7 @@ public class Sketch { // If it's a replacement, delete the old file first, // otherwise case changes will not be preserved. - // http://dev.processing.org/bugs/show_bug.cgi?id=969 + // https://download.processing.org/bugzilla/969.html if (replacement) { boolean muchSuccess = destFile.delete(); if (!muchSuccess) { @@ -1355,7 +1493,7 @@ public class Sketch { return false; } - // Handles "Add File" when a .pde is used. For beta 1, this no longer runs + // Handles "Add File" when a .pde is used. For 3.0b1, this no longer runs // on a separate thread because it's totally unnecessary (a .pde file is // not going to be so large that it's ever required) and otherwise we have // to introduce a threading block here. @@ -1412,11 +1550,6 @@ public class Sketch { * */ public void setCurrentCode(int which) { -// // for the tab sizing -// if (current != null) { -// current.visited = System.currentTimeMillis(); -// System.out.println(current.visited); -// } // if current is null, then this is the first setCurrent(0) if (which < 0 || which >= codeCount || ((currentIndex == which) && (current == code[currentIndex]))) { @@ -1470,47 +1603,6 @@ public class Sketch { } - /** - * When running from the editor, take care of preparations before running - * a build or an export. Also erases and/or creates 'targetFolder' if it's - * not null, and if preferences say to do so when exporting. - * @param targetFolder is something like applet, application, android... - */ - /* - public void prepareBuild(File targetFolder) throws SketchException { - // make sure the user didn't hide the sketch folder - ensureExistence(); - - // don't do from the command line - if (editor != null) { - // make sure any edits have been stored - current.setProgram(editor.getText()); - - // if an external editor is being used, need to grab the - // latest version of the code from the file. - if (Preferences.getBoolean("editor.external")) { - // set current to null so that the tab gets updated - // http://dev.processing.org/bugs/show_bug.cgi?id=515 - current = null; - // nuke previous files and settings - load(); - } - } - - if (targetFolder != null) { - // Nuke the old applet/application folder because it can cause trouble - if (Preferences.getBoolean("export.delete_target_folder")) { - System.out.println("temporarily skipping deletion of " + targetFolder); -// Base.removeDir(targetFolder); -// targetFolder.renameTo(dest); - } - // Create a fresh output folder (needed before preproc is run next) - targetFolder.mkdirs(); - } - } - */ - - /** * Make sure the sketch hasn't been moved or deleted by a nefarious user. * If they did, try to re-create it and save. Only checks whether the @@ -1547,28 +1639,27 @@ public class Sketch { /** * Returns true if this is a read-only sketch. Used for the - * examples directory, or when sketches are loaded from read-only + * "examples" directory, or when sketches are loaded from read-only * volumes or folders without appropriate permissions. */ public boolean isReadOnly() { - String apath = folder.getAbsolutePath(); + String path = folder.getAbsolutePath(); List modes = editor.getBase().getModeList(); // Make sure it's not read-only for another Mode besides this one // https://github.com/processing/processing/issues/773 for (Mode mode : modes) { - if (apath.startsWith(mode.getExamplesFolder().getAbsolutePath()) || - apath.startsWith(mode.getLibrariesFolder().getAbsolutePath())) { + if (path.startsWith(mode.getExamplesFolder().getAbsolutePath()) || + path.startsWith(mode.getLibrariesFolder().getAbsolutePath())) { return true; } } - // check to see if each modified code file can be written to - // canWrite() doesn't work on directories + // Check to see if each modified code file can be written. + // Note: canWrite() does not work on directories. for (int i = 0; i < codeCount; i++) { if (code[i].isModified() && - code[i].fileReadOnly() && - code[i].fileExists()) { - //System.err.println("found a read-only file " + code[i].file); + code[i].fileReadOnly() && + code[i].fileExists()) { return true; } } @@ -1591,19 +1682,29 @@ public class Sketch { } +// /** +// * Returns a File object for the main .pde file for this sketch. +// */ +// public File getMainFile() { +// return mainFile; +// } + + /** - * Returns a File object for the main .pde file for this sketch. + * Returns the name (without extension) of the main tab. + * Most uses of getName() prior to 4.0 beta 6 were to get the main class, + * but this allows the sketch to be decoupled from the main tab name. */ - public File getMainFile() { - return primaryFile; + public String getMainName() { + return code[0].getPrettyName(); } /** * Returns path to the main .pde file for this sketch. */ - public String getMainFilePath() { - return primaryFile.getAbsolutePath(); + public String getMainPath() { + return mainFile.getAbsolutePath(); } @@ -1653,28 +1754,6 @@ public class Sketch { } - /** - * Create the code folder if it does not exist already. As a convenience, - * it also returns the code folder, since it's likely about to be used. - */ - public File prepareCodeFolder() { - if (!codeFolder.exists()) { - codeFolder.mkdirs(); - } - return codeFolder; - } - - -// public String getClassPath() { -// return classPath; -// } - - -// public String getLibraryPath() { -// return javaLibraryPath; -// } - - public SketchCode[] getCode() { return code; } @@ -1690,6 +1769,8 @@ public class Sketch { } + // Cannot remove because this is used by GUI Builder for Processing + // https://github.com/processing/processing4/issues/545 public int getCodeIndex(SketchCode who) { for (int i = 0; i < codeCount; i++) { if (who == code[i]) { @@ -1710,20 +1791,23 @@ public class Sketch { } + /** + * Tried to remove in beta 6, but in use by Python Mode. + * When it's removed there, let me know and I'll remove it here. + */ + @Deprecated public String getMainProgram() { return getCode(0).getProgram(); } public void setUntitled(boolean untitled) { -// editor.untitled = u; this.untitled = untitled; editor.updateTitle(); } public boolean isUntitled() { -// return editor.untitled; return untitled; } @@ -1731,25 +1815,25 @@ public class Sketch { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /** - * Convert to sanitized name and alert the user - * if changes were made. - */ - static public String checkName(String origName) { - String newName = sanitizeName(origName); - - if (!newName.equals(origName)) { - String msg = - Language.text("check_name.messages.is_name_modified"); - System.out.println(msg); - } - return newName; - } +// /** +// * Convert to sanitized name and alert the user +// * if changes were made. +// */ +// static public String checkName(String origName) { +// String newName = sanitizeName(origName); +// +// if (!newName.equals(origName)) { +// String msg = +// Language.text("check_name.messages.is_name_modified"); +// System.out.println(msg); +// } +// return newName; +// } /** - * Return true if the name is valid for a Processing sketch. Extensions of the form .foo are - * ignored. + * Return true if the name is valid for a Processing sketch. + * Extensions of the form .foo are ignored. */ public static boolean isSanitaryName(String name) { final int dot = name.lastIndexOf('.'); @@ -1760,7 +1844,7 @@ public class Sketch { } - static final boolean asciiLetter(char c) { + static boolean isAsciiLetter(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } @@ -1783,24 +1867,24 @@ public class Sketch { * because these aren't valid class names on Android. */ static public String sanitizeName(String origName) { - char orig[] = origName.toCharArray(); + char[] orig = origName.toCharArray(); StringBuilder sb = new StringBuilder(); // Can't lead with a digit (or anything besides a letter), so prefix with // "sketch_". In 1.x this prefixed with an underscore, but those get shaved // off later, since you can't start a sketch name with underscore anymore. - if (!asciiLetter(orig[0])) { + if (!isAsciiLetter(orig[0])) { sb.append("sketch_"); } // for (int i = 0; i < orig.length; i++) { for (char c : orig) { - if (asciiLetter(c) || (c >= '0' && c <= '9')) { + if (isAsciiLetter(c) || (c >= '0' && c <= '9')) { sb.append(c); } else { // Tempting to only add if prev char is not underscore, but that // might be more confusing if lots of chars are converted and the - // result is a very short string thats nothing like the original. + // result is a very short string that's nothing like the original. sb.append('_'); } } @@ -1837,7 +1921,7 @@ public class Sketch { @Override public boolean equals(Object another) { if (another instanceof Sketch) { - return getMainFilePath().equals(((Sketch) another).getMainFilePath()); + return getMainPath().equals(((Sketch) another).getMainPath()); } return false; } diff --git a/app/src/processing/app/SketchCode.java b/app/src/processing/app/SketchCode.java index e66fe7a88..f51f142f4 100644 --- a/app/src/processing/app/SketchCode.java +++ b/app/src/processing/app/SketchCode.java @@ -36,9 +36,6 @@ import javax.swing.undo.*; * Represents a single tab of a sketch. */ public class SketchCode { - /** Pretty name (no extension), not the full file name */ - private String prettyName; - /** File object for where this code is located */ private File file; @@ -51,7 +48,7 @@ public class SketchCode { /** Last version of the program on disk. */ private String savedProgram; - /** Document object for this tab. Currently this is a SyntaxDocument. */ + /** Document object for this tab. This is currently a SyntaxDocument. */ private Document document; /** Last time this tab was visited */ @@ -61,9 +58,8 @@ public class SketchCode { private long lastModified; /** - * Undo Manager for this tab, each tab keeps track of their own - * Editor.undo will be set to this object when this code is the tab - * that's currently the front. + * Undo Manager for this tab; each tab keeps track of their own. + * Editor.undo will be set to this object when this tab is current. */ private final UndoManager undo = new UndoManager(); @@ -75,9 +71,6 @@ public class SketchCode { private final Stack caretUndoStack = new Stack<>(); private final Stack caretRedoStack = new Stack<>(); - /** What was on top of the undo stack when last saved. */ -// private UndoableEdit lastEdit; - // saved positions from last time this tab was used private int selectionStart; private int selectionStop; @@ -85,9 +78,7 @@ public class SketchCode { private boolean modified; - /** name of .java file after preproc */ -// private String preprocName; - /** where this code starts relative to the concat'd code */ + /** where this code starts relative to the concatenated code */ private int preprocOffset; @@ -95,8 +86,6 @@ public class SketchCode { this.file = file; this.extension = extension; - makePrettyName(); - try { load(); } catch (IOException e) { @@ -105,13 +94,6 @@ public class SketchCode { } - protected void makePrettyName() { - prettyName = file.getName(); - int dot = prettyName.lastIndexOf('.'); - prettyName = prettyName.substring(0, dot); - } - - public File getFile() { return file; } @@ -133,13 +115,11 @@ public class SketchCode { protected boolean renameTo(File what, String ext) { -// System.out.println("renaming " + file); -// System.out.println(" to " + what); boolean success = file.renameTo(what); if (success) { - this.file = what; // necessary? + // reassign file because renameTo() (ironically?) does not update + this.file = what; this.extension = ext; - makePrettyName(); } return success; } @@ -155,8 +135,19 @@ public class SketchCode { } + /** + * Get the name of this file with its extension removed. + * Because of other uses of this method, this is not a good place + * to do things like swap underscores for spaces. + */ public String getPrettyName() { - return prettyName; + String name = file.getName(); + + // remove the extension from the name + int dot = name.lastIndexOf('.'); + // should have a dot in all current scenarios, but better not to make + // that assumption in case we later decide to allow things like README + return (dot != -1) ? name.substring(0, dot) : name; } @@ -203,16 +194,6 @@ public class SketchCode { } -// public void setPreprocName(String preprocName) { -// this.preprocName = preprocName; -// } -// -// -// public String getPreprocName() { -// return preprocName; -// } - - public void setPreprocOffset(int preprocOffset) { this.preprocOffset = preprocOffset; } @@ -351,7 +332,6 @@ public class SketchCode { Util.saveFile(program, newFile); savedProgram = program; file = newFile; - makePrettyName(); setLastModified(); setModified(false); } diff --git a/app/src/processing/app/SketchName.java b/app/src/processing/app/SketchName.java new file mode 100644 index 000000000..8a0bded01 --- /dev/null +++ b/app/src/processing/app/SketchName.java @@ -0,0 +1,172 @@ +package processing.app; + +import processing.core.PApplet; +import processing.data.JSONArray; +import processing.data.JSONObject; +import processing.data.StringList; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + + +public class SketchName { + static final String FILENAME = "naming.json"; + static final public String CLASSIC = "Classic (sketch_220809a)"; + static boolean breakTime = false; + + static Map wordLists; + + + /** + * Return a new File object for the next sketch directory. + * The name of this directory will also be used as the sketch name. + * (i.e. in Java Mode, dirname.pde will be created as the main tab) + * If returning null, it's up to *this* class to report errors to the user. + * @param parentDir Parent directory where the sketch folder will live + * @return File object for safe new path, or null if there were problems + */ + static File nextFolder(File parentDir) { + String approach = Preferences.get("sketch.name.approach"); + if (!CLASSIC.equals(approach)) { + File folder = wordsFolder(parentDir, approach); + if (folder != null) { + return folder; + } + } + // classic was selected, or fallback due to an error + return classicFolder(parentDir); + } + + + /** + * Use a generic name like sketch_031008a, the date plus a char. + */ + static File classicFolder(File parentDir) { + File newbieDir; + String newbieName; + + int index = 0; + String prefix = Preferences.get("editor.untitled.prefix"); + String format = Preferences.get("editor.untitled.suffix"); + String suffix; + if (format == null) { + // If no format is specified, uses this ancient format + Calendar cal = Calendar.getInstance(); + int day = cal.get(Calendar.DAY_OF_MONTH); // 1..31 + int month = cal.get(Calendar.MONTH); // 0..11 + final String[] months = { + "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" + }; + suffix = months[month] + PApplet.nf(day, 2); + } else { + SimpleDateFormat formatter = new SimpleDateFormat(format); + suffix = formatter.format(new Date()); + } + do { + if (index == 26) { + // In 0159, avoid running past z by sending people outdoors. + if (!breakTime) { + Messages.showWarning("Time for a Break", + "You've reached the limit for auto naming of new sketches\n" + + "for the day. How about going for a walk instead?", null); + breakTime = true; + } else { + Messages.showWarning("Sunshine", + "No really, time for some fresh air for you.", null); + } + return null; + } + 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. + // https://github.com/processing/processing/issues/322 + newbieName = Sketch.sanitizeName(newbieName); + newbieDir = new File(parentDir, newbieName); + index++; + // Make sure it's not in the temp folder *and* it's not in the sketchbook + } while (newbieDir.exists() || new File(Base.getSketchbookFolder(), newbieName).exists()); + + return newbieDir; + } + + + static class WordList { + String name; + String notes; + StringList prefixes; + StringList suffixes; + + WordList(JSONObject source) { + name = source.getString("name"); + notes = source.getString("notes"); + prefixes = source.getStringList("prefixes"); + suffixes = source.getStringList("suffixes"); + } + + String getPair() { + return (prefixes.random() + " " + suffixes.random()).replace(' ', '_'); + } + } + + + static File wordsFolder(File parentDir, String setName) { + WordList wl = getWordLists().get(setName); + File outgoing = null; + if (wl != null) { + do { + // Clean up the name in case a user-supplied word list breaks the rules + String name = Sketch.sanitizeName(wl.getPair()); + outgoing = new File(parentDir, name); + } while (outgoing.exists()); + } + return outgoing; + } + + + static private void load(File namingFile) { + JSONArray array = PApplet.loadJSONArray(namingFile); + for (int i = 0; i < array.size(); i++) { + JSONObject obj = array.getJSONObject(i); + WordList wl = new WordList(obj); + wordLists.put(wl.name, wl); + } + } + + static Map getWordLists() { + if (wordLists == null) { + wordLists = new HashMap<>(); + try { + File namingFile = Base.getLibFile(FILENAME); + load(namingFile); + } catch (Exception e) { + Messages.showWarning("Naming Error", + "Could not load word lists from " + FILENAME, e); + } + File sketchbookFile = new File(Base.getSketchbookFolder(), FILENAME); + if (sketchbookFile.exists()) { + try { + load(sketchbookFile); + } catch (Exception e) { + Messages.showWarning("Naming Error", + "Error while reading " + FILENAME + " from sketchbook folder", e); + } + } + } + return wordLists; + } + + + static public String[] getOptions() { + StringList outgoing = new StringList(); + outgoing.append(CLASSIC); + for (String approach : getWordLists().keySet()) { + outgoing.append(approach); + } + return outgoing.toArray(); + } +} \ No newline at end of file diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java index 5103f5d85..05d95bdae 100644 --- a/app/src/processing/app/UpdateCheck.java +++ b/app/src/processing/app/UpdateCheck.java @@ -31,7 +31,6 @@ import java.util.Random; import javax.swing.JOptionPane; -import processing.app.contrib.ContributionManager; import processing.core.PApplet; @@ -59,24 +58,20 @@ public class UpdateCheck { static private final long ONE_DAY = 24 * 60 * 60 * 1000; - static boolean allowed; - public UpdateCheck(Base base) { this.base = base; if (isAllowed()) { - new Thread(new Runnable() { - public void run() { - try { - Thread.sleep(5 * 1000); // give the PDE time to get rolling - updateCheck(); + new Thread(() -> { + try { + Thread.sleep(5 * 1000); // give the PDE time to get rolling + updateCheck(); - } catch (Exception e) { - // This can safely be ignored, too many situations where no net - // connection is available that behave in strange ways. - // Covers likely IOException, InterruptedException, and any others. - } + } catch (Exception e) { + // This can safely be ignored, too many situations where no net + // connection is available that behave in strange ways. + // Covers likely IOException, InterruptedException, and any others. } }, "Update Checker").start(); } @@ -84,7 +79,7 @@ public class UpdateCheck { /** - * Turned into a separate method so that anyone needed update.id will get + * Turned into a separate method so that anyone needed "update.id" will get * a legit answer. Had a problem with the contribs script where the id * wouldn't be set so a null id would be sent to the contribs server. */ @@ -103,7 +98,7 @@ public class UpdateCheck { } - public void updateCheck() throws IOException, InterruptedException { + public void updateCheck() throws IOException { String info = PApplet.urlEncode(getUpdateID() + "\t" + PApplet.nf(Base.getRevision(), 4) + "\t" + System.getProperty("java.version") + "\t" + @@ -152,7 +147,7 @@ public class UpdateCheck { } - protected boolean promptToVisitDownloadPage() { + protected void promptToVisitDownloadPage() { String prompt = Language.text("update_check.updates_available.core"); Object[] options = { Language.text("prompt.yes"), Language.text("prompt.no") }; @@ -166,13 +161,11 @@ public class UpdateCheck { options[0]); if (result == JOptionPane.YES_OPTION) { Platform.openURL(DOWNLOAD_URL); - return true; } - - return false; } + /* protected boolean promptToOpenContributionManager() { String contributionPrompt = Language.text("update_check.updates_available.contributions"); @@ -195,6 +188,7 @@ public class UpdateCheck { return false; } + */ protected int readInt(String filename) throws IOException { diff --git a/app/src/processing/app/Util.java b/app/src/processing/app/Util.java index 8e2063337..c27cf9e96 100644 --- a/app/src/processing/app/Util.java +++ b/app/src/processing/app/Util.java @@ -25,6 +25,8 @@ package processing.app; import java.io.*; import java.nio.file.Files; import java.util.Enumeration; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.zip.*; import processing.core.PApplet; @@ -53,7 +55,7 @@ public class Util { static public byte[] loadBytesRaw(File file) throws IOException { int size = (int) file.length(); FileInputStream input = new FileInputStream(file); - byte buffer[] = new byte[size]; + byte[] buffer = new byte[size]; int offset = 0; int bytesRead; while ((bytesRead = input.read(buffer, offset, size-offset)) != -1) { @@ -61,7 +63,6 @@ public class Util { if (bytesRead == 0) break; } input.close(); // weren't properly being closed - input = null; return buffer; } @@ -71,13 +72,15 @@ public class Util { * that are separated by = and ignore comments with #. * Changed in 3.x to return null (rather than empty hash) if no file, * and changed return type to StringDict instead of Map or HashMap. + * Changed in 4.0 beta 9 to only use # for comments at beginning of + * line, otherwise it cannot parse hex color entries. */ static public StringDict readSettings(File inputFile) { if (!inputFile.exists()) { Messages.err(inputFile + " does not exist inside readSettings()"); return null; } - String lines[] = PApplet.loadStrings(inputFile); + String[] lines = PApplet.loadStrings(inputFile); if (lines == null) { System.err.println("Could not read " + inputFile); return null; @@ -89,7 +92,9 @@ public class Util { /** * Parse a String array that contains attribute/value pairs separated * by = (the equals sign). The # (hash) symbol is used to denote comments. - * Comments can be anywhere on a line. Blank lines are ignored. + * Changed in 4.0 beta 9 to only use # for comments at beginning of line, + * otherwise it cannot parse hex color entries. Blank lines are ignored. + *

* In 3.0a6, no longer taking a blank HashMap as param; no cases in the main * PDE code of adding to a (Hash)Map. Also returning the Map instead of void. * Both changes modify the method signature, but this was only used by the @@ -98,15 +103,10 @@ public class Util { static public StringDict readSettings(String filename, String[] lines) { StringDict settings = new StringDict(); for (String line : lines) { - // Remove comments - int commentMarker = line.indexOf('#'); - if (commentMarker != -1) { - line = line.substring(0, commentMarker); - } // Remove extra whitespace (including the x00A0 and xFEFF) line = PApplet.trim(line); - if (line.length() != 0) { + if (line.length() != 0 && line.charAt(0) != '#') { int equals = line.indexOf('='); if (equals == -1) { if (filename != null) { @@ -136,13 +136,13 @@ public class Util { to.write(buffer, 0, bytesRead); } from.close(); - from = null; to.flush(); to.close(); - to = null; + //noinspection ResultOfMethodCallIgnored targetFile.setLastModified(sourceFile.lastModified()); + //noinspection ResultOfMethodCallIgnored targetFile.setExecutable(sourceFile.canExecute()); } @@ -151,10 +151,14 @@ public class Util { * Grab the contents of a file as a string. Connects lines with \n, * even if the input file used \r\n. */ - static public String loadFile(File file) throws IOException { - String[] contents = PApplet.loadStrings(file); - if (contents == null) return null; - return PApplet.join(contents, "\n"); + static public String loadFile(File file) { + if (file != null && file.exists()) { + String[] contents = PApplet.loadStrings(file); + if (contents != null) { + return PApplet.join(contents, "\n"); + } + } + return null; } @@ -170,14 +174,13 @@ public class Util { File temp = File.createTempFile(file.getName(), null, file.getParentFile()); try { // fix from cjwant to prevent symlinks from being destroyed. - File canon = file.getCanonicalFile(); - // assign the var as second step since previous line may throw exception - file = canon; + file = file.getCanonicalFile(); + } catch (IOException e) { throw new IOException("Could not resolve canonical representation of " + file.getAbsolutePath()); } - // Could use saveStrings(), but the we wouldn't be able to checkError() + // Could use saveStrings(), but we wouldn't be able to checkError() PrintWriter writer = PApplet.createWriter(temp); for (String line : lines) { writer.println(line); @@ -208,7 +211,7 @@ public class Util { * Create a temporary folder by using the createTempFile() mechanism, * deleting the file it creates, and making a folder using the location * that was provided. - * + *

* Unlike createTempFile(), there is no minimum size for prefix. If * prefix is less than 3 characters, the remaining characters will be * filled with underscores @@ -216,18 +219,40 @@ public class Util { static public File createTempFolder(String prefix, String suffix, File directory) throws IOException { int fillChars = 3 - prefix.length(); - for (int i = 0; i < fillChars; i++) { - prefix += '_'; + if (fillChars > 0) { + prefix += "_".repeat(fillChars); + } + if (directory == null) { + directory = getProcessingTemp(); } File folder = File.createTempFile(prefix, suffix, directory); // Now delete that file and create a folder in its place - folder.delete(); - folder.mkdirs(); + if (!folder.delete()) { + throw new IOException("Could not remove " + folder + + " to create a temporary folder"); + } + if (!folder.mkdirs()) { + throw new IOException("Unable to create " + folder + + ", please check permissions for " + folder.getParentFile()); + } // And send the folder back to your friends return folder; } + static public File getProcessingTemp() throws IOException { + String tmpDir = System.getProperty("java.io.tmpdir"); + File directory = new File(tmpDir, "processing"); + if (!directory.exists()) { + if (!directory.mkdirs()) { + throw new IOException("Could not create temp directory. " + + "Check that you have permissions to write to " + tmpDir); + } + } + return directory; + } + + /** * Copy a folder from one place to another. This ignores all dot files and * folders found in the source directory, to avoid copying silly .DS_Store @@ -239,28 +264,35 @@ public class Util { final String urDum = "source and target directories are identical"; throw new IllegalArgumentException(urDum); } - targetDir.mkdirs(); - String files[] = sourceDir.list(); - for (int i = 0; i < files.length; i++) { - // Ignore dot files (.DS_Store), dot folders (.svn) while copying - if (files[i].charAt(0) == '.') continue; - //if (files[i].equals(".") || files[i].equals("..")) continue; - File source = new File(sourceDir, files[i]); - File target = new File(targetDir, files[i]); - if (source.isDirectory()) { - //target.mkdirs(); - copyDir(source, target); - target.setLastModified(source.lastModified()); - } else { - copyFile(source, target); + if (!targetDir.exists() && !targetDir.mkdirs()) { + throw new IOException("Could not create " + targetDir); + } + String[] filenames = sourceDir.list(); + if (filenames != null) { + for (String filename : filenames) { + // Ignore dot files (.DS_Store), dot folders (.svn) while copying + if (filename.charAt(0) != '.') { + File source = new File(sourceDir, filename); + File target = new File(targetDir, filename); + if (source.isDirectory()) { + //target.mkdirs(); + copyDir(source, target); + //noinspection ResultOfMethodCallIgnored + target.setLastModified(source.lastModified()); + } else { + copyFile(source, target); + } + } } + } else { + throw new IOException("Could not read " + sourceDir); } } static public void copyDirNative(File sourceDir, File targetDir) throws IOException { - Process process = null; + Process process; if (Platform.isMacOS() || Platform.isLinux()) { process = Runtime.getRuntime().exec(new String[] { "cp", "-a", sourceDir.getAbsolutePath(), targetDir.getAbsolutePath() @@ -280,37 +312,20 @@ public class Util { } -// /** -// * Delete a file or directory in a platform-specific manner. Removes a File -// * object (a file or directory) from the system by placing it in the Trash -// * or Recycle Bin (if available) or simply deleting it (if not). -// * -// * When the file/folder is on another file system, it may simply be removed -// * immediately, without additional warning. So only use this if you want to, -// * you know, "delete" the subject in question. -// * -// * NOTE: Not yet tested nor ready for prime-time. -// * -// * @param file the victim (a directory or individual file) -// * @return true if all ends well -// * @throws IOException what went wrong -// */ -// static public boolean platformDelete(File file) throws IOException { -// return Base.getPlatform().deleteFile(file); -// } - - /** * Remove all files in a directory and the directory itself. * Prints error messages with failed filenames. Does not follow symlinks. + * Use Platform.deleteFile() instead, which first attempts to use + * the Trash or Recycle Bin, out of an abundance of caution. */ static public boolean removeDir(File dir) { return removeDir(dir, true); } + /** * Remove all files in a directory and the directory itself. - * Optinally prints error messages with failed filenames. + * Optionally, prints error messages with failed filenames. * Does not follow symlinks. */ static public boolean removeDir(File dir, boolean printErrorMessages) { @@ -358,21 +373,25 @@ public class Util { * Note that the function calls itself recursively. */ static public long calcFolderSize(File folder) { - int size = 0; + long size = 0; - String files[] = folder.list(); + String[] filenames = folder.list(); // null if folder doesn't exist, happens when deleting sketch - if (files == null) return -1; + if (filenames == null) return -1; - for (int i = 0; i < files.length; i++) { - if (files[i].equals(".") || - files[i].equals("..") || - files[i].equals(".DS_Store")) continue; - File fella = new File(folder, files[i]); - if (fella.isDirectory()) { - size += calcFolderSize(fella); - } else { - size += (int) fella.length(); + for (String file : filenames) { + if (!file.equals(".") && !file.equals("..") && !file.equals(".DS_Store")) { + File fella = new File(folder, file); + if (fella.isDirectory()) { + long subfolderSize = calcFolderSize(fella); + if (subfolderSize == -1) { + return -1; + } else { + size += subfolderSize; + } + } else { + size += fella.length(); + } } } return size; @@ -410,7 +429,7 @@ public class Util { } return outgoing; } - return list.array(); + return list.toArray(); } @@ -440,13 +459,10 @@ public class Util { * @return a list of .jar and .zip files in that folder */ static public File[] listJarFiles(File folder) { - return folder.listFiles(new FilenameFilter() { - public boolean accept(File dir, String name) { - return (!name.startsWith(".") && - (name.toLowerCase().endsWith(".jar") || - name.toLowerCase().endsWith(".zip"))); - } - }); + return folder.listFiles((dir, name) -> + (!name.startsWith(".") && + (name.toLowerCase().endsWith(".jar") || + name.toLowerCase().endsWith(".zip")))); } @@ -456,12 +472,12 @@ public class Util { /** * Given a folder, return a list of absolute paths to all jar or zip files * inside that folder, separated by pathSeparatorChar. - * + *

* This will prepend a colon (or whatever the path separator is) * so that it can be directly appended to another path string. - * + *

* As of 0136, this will no longer add the root folder as well. - * + *

* This function doesn't bother checking to see if there are any .class * files in the folder or within a subfolder. */ @@ -480,17 +496,19 @@ public class Util { path += File.separator; } - String list[] = folder.list(); - for (int i = 0; i < list.length; i++) { - // Skip . and ._ files. Prior to 0125p3, .jar files that had - // OS X AppleDouble files associated would cause trouble. - if (list[i].startsWith(".")) continue; - - if (list[i].toLowerCase().endsWith(".jar") || - list[i].toLowerCase().endsWith(".zip")) { - sb.append(sep); - sb.append(path); - sb.append(list[i]); + String[] list = folder.list(); + if (list != null) { + for (String item : list) { + // Skip . and ._ files. Prior to 0125p3, .jar files that had + // OS X AppleDouble files associated would cause trouble. + if (!item.startsWith(".")) { + if (item.toLowerCase().endsWith(".jar") || + item.toLowerCase().endsWith(".zip")) { + sb.append(sep); + sb.append(path); + sb.append(item); + } + } } } } catch (IOException e) { @@ -511,25 +529,25 @@ public class Util { static public StringList packageListFromClassPath(String path) { // Map map = new HashMap(); StringList list = new StringList(); - String pieces[] = + String[] pieces = PApplet.split(path, File.pathSeparatorChar); - for (int i = 0; i < pieces.length; i++) { + for (String piece : pieces) { //System.out.println("checking piece '" + pieces[i] + "'"); - if (pieces[i].length() == 0) continue; + if (piece.length() != 0) { + if (piece.toLowerCase().endsWith(".jar") || + piece.toLowerCase().endsWith(".zip")) { + //System.out.println("checking " + pieces[i]); + packageListFromZip(piece, list); - if (pieces[i].toLowerCase().endsWith(".jar") || - pieces[i].toLowerCase().endsWith(".zip")) { - //System.out.println("checking " + pieces[i]); - packageListFromZip(pieces[i], list); - - } else { // it's another type of file or directory - File dir = new File(pieces[i]); - if (dir.exists() && dir.isDirectory()) { - packageListFromFolder(dir, null, list); - //importCount = magicImportsRecursive(dir, null, - // map); - //imports, importCount); + } else { // it's another type of file or directory + File dir = new File(piece); + if (dir.exists() && dir.isDirectory()) { + packageListFromFolder(dir, null, list); + //importCount = magicImportsRecursive(dir, null, + // map); + //imports, importCount); + } } } } @@ -586,30 +604,26 @@ public class Util { */ static private void packageListFromFolder(File dir, String sofar, StringList list) { -// Map map) { boolean foundClass = false; - String files[] = dir.list(); + String[] filenames = dir.list(); + if (filenames != null) { + for (String filename : filenames) { + if (filename.equals(".") || filename.equals("..")) continue; - for (int i = 0; i < files.length; i++) { - if (files[i].equals(".") || files[i].equals("..")) continue; - - File sub = new File(dir, files[i]); - if (sub.isDirectory()) { - String nowfar = - (sofar == null) ? files[i] : (sofar + "." + files[i]); - packageListFromFolder(sub, nowfar, list); - //System.out.println(nowfar); - //imports[importCount++] = nowfar; - //importCount = magicImportsRecursive(sub, nowfar, - // imports, importCount); - } else if (!foundClass) { // if no classes found in this folder yet - if (files[i].endsWith(".class")) { - //System.out.println("unique class: " + files[i] + " for " + sofar); -// map.put(sofar, new Object()); - list.appendUnique(sofar); - foundClass = true; + File sub = new File(dir, filename); + if (sub.isDirectory()) { + String nowfar = + (sofar == null) ? filename : (sofar + "." + filename); + packageListFromFolder(sub, nowfar, list); + } else if (!foundClass) { // if no classes found in this folder yet + if (filename.endsWith(".class")) { + list.appendUnique(sofar); + foundClass = true; + } } } + } else { + System.err.println("Could not read " + dir); } } @@ -623,7 +637,7 @@ public class Util { FileInputStream fis = new FileInputStream(zipFile); CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32()); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum)); - ZipEntry entry = null; + ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { final String name = entry.getName(); if (!name.startsWith(("__MACOSX"))) { @@ -650,7 +664,7 @@ public class Util { static protected void unzipEntry(ZipInputStream zin, File f) throws IOException { FileOutputStream out = new FileOutputStream(f); byte[] b = new byte[512]; - int len = 0; + int len; while ((len = zin.read(b)) != -1) { out.write(b, 0, len); } @@ -668,10 +682,73 @@ public class Util { } - static public final boolean containsNonASCII(String what) { + static public boolean containsNonASCII(String what) { for (char c : what.toCharArray()) { if (c < 32 || c > 127) return true; } return false; } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static public String sanitizeHtmlTags(String str) { + return str.replaceAll("<", "<") + .replaceAll(">", ">"); + } + + + /** + * This has a [link](http://example.com/) in [it](http://example.org/). + *

+ * Becomes... + *

+ * This has a link in it. + */ + static public String markDownLinksToHtml(String str) { + Pattern p = Pattern.compile("\\[(.*?)]\\((.*?)\\)"); + Matcher m = p.matcher(str); + + StringBuilder sb = new StringBuilder(); + + int start = 0; + while (m.find(start)) { + sb.append(str, start, m.start()); + + String text = m.group(1); + String url = m.group(2); + + sb.append(""); + sb.append(text); + sb.append(""); + + start = m.end(); + } + sb.append(str.substring(start)); + return sb.toString(); + } + + + static public String removeMarkDownLinks(String str) { + StringBuilder name = new StringBuilder(); + if (str != null) { + int parentheses = 0; + for (char c : str.toCharArray()) { + if (c == '[' || c == ']') { + // pass + } else if (c == '(') { + parentheses++; + } else if (c == ')') { + parentheses--; + } else if (parentheses == 0) { + name.append(c); + } + } + } + return name.toString(); + } } diff --git a/app/src/processing/app/contrib/AvailableContribution.java b/app/src/processing/app/contrib/AvailableContribution.java index 797676024..4c3e714f0 100644 --- a/app/src/processing/app/contrib/AvailableContribution.java +++ b/app/src/processing/app/contrib/AvailableContribution.java @@ -96,16 +96,34 @@ public class AvailableContribution extends Contribution { AvailableContribution ac = null; ZipFile zf = new ZipFile(contribArchive); - Enumeration entries = zf.entries(); + Enumeration entries = zf.entries(); while (entries.hasMoreElements()) { - ZipEntry entry = (ZipEntry) entries.nextElement(); + ZipEntry entry = entries.nextElement(); String name = entry.getName(); if (name.endsWith(".properties")) { ContributionType type = matchContribType(name); if (type != null) { - StringDict params = new StringDict(PApplet.createReader(zf.getInputStream(entry))); - ac = new AvailableContribution(type, params); - break; + StringDict params = new StringDict(); + String[] lines = PApplet.loadStrings(zf.getInputStream(entry)); + if (lines != null) { + for (String line : lines) { + if (!line.startsWith("#")) { + int equals = line.indexOf('='); + if (equals != -1) { + String key = line.substring(0, equals).trim(); + String value = line.substring(equals + 1).trim(); + params.set(key, value); + } + } + } + ac = new AvailableContribution(type, params); + break; // found, let's get outta here + + } else { + System.err.println("Could parse properties from " + name); + } + } else { + System.err.println("Could not find a matching .properties file"); } } } @@ -148,8 +166,11 @@ public class AvailableContribution extends Contribution { // Find the first legitimate folder in what we just unzipped File contribFolder = type.findCandidate(tempFolder); if (contribFolder == null) { + final String err = Language.interpolate("contrib.errors.no_contribution_found", type); if (status != null) { - status.setErrorMessage(Language.interpolate("contrib.errors.no_contribution_found", type)); + status.setErrorMessage(err); + } else { + System.err.println(err); } } else { File propFile = new File(contribFolder, type + ".properties"); @@ -158,55 +179,66 @@ public class AvailableContribution extends Contribution { propFile.getName() + ", please contact the author for a fix."); - } else if (rewritePropertiesFile(propFile)) { - // contribFolder now has a legit contribution, load it to get info. - LocalContribution newContrib = type.load(base, contribFolder); + } else { + if (rewritePropertiesFile(propFile)) { + // contribFolder now has a legit contribution, load it to get info. + LocalContribution newContrib = type.load(base, contribFolder); - // get info we need to delete the newContrib folder later - File newContribFolder = newContrib.getFolder(); + // get info we need to delete the newContrib folder later + File newContribFolder = newContrib.getFolder(); - // Check to make sure nothing has the same name already, - // backup old if needed, then move things into place and reload. - installedContrib = - newContrib.copyAndLoad(base, confirmReplace, status); + // Check to make sure nothing has the same name already, + // backup old if needed, then move things into place and reload. + installedContrib = + newContrib.copyAndLoad(base, confirmReplace, status); - // Unlock all the jars if it is a mode or tool - if (newContrib.getType() == ContributionType.MODE) { - ((ModeContribution) newContrib).clearClassLoader(base); + // Unlock all the jars if it is a mode or tool + if (newContrib.getType() == ContributionType.MODE) { + ((ModeContribution) newContrib).clearClassLoader(base); - } else if (newContrib.getType() == ContributionType.TOOL) { - ((ToolContribution) newContrib).clearClassLoader(); - } + } else if (newContrib.getType() == ContributionType.TOOL) { + ((ToolContribution) newContrib).clearClassLoader(); + } - // Delete the newContrib, do a garbage collection, hope and pray - // that Java will unlock the temp folder on Windows now - //noinspection UnusedAssignment - newContrib = null; - System.gc(); + // Delete the newContrib, do a garbage collection, hope and pray + // that Java will unlock the temp folder on Windows now + //noinspection UnusedAssignment + newContrib = null; + System.gc(); - if (Platform.isWindows()) { - // we'll even give it a second to finish up, - // because file ops are just that flaky on Windows. + if (Platform.isWindows()) { + // we'll even give it a second to finish up, + // because file ops are just that flaky on Windows. + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + // delete the contrib folder inside the libraryXXXXXXtmp folder + //Util.removeDir(newContribFolder, false); try { - Thread.sleep(1000); - } catch (InterruptedException e) { + Platform.deleteFile(newContribFolder); + } catch (IOException e) { e.printStackTrace(); } - } - - // delete the contrib folder inside the libraryXXXXXXtmp folder - Util.removeDir(newContribFolder, false); - - } else { - if (status != null) { - status.setErrorMessage(Language.text("contrib.errors.overwriting_properties")); + } else { + if (status != null) { + status.setErrorMessage(Language.text("contrib.errors.overwriting_properties")); + } } } } // Remove any remaining boogers if (tempFolder.exists()) { - Util.removeDir(tempFolder, false); + //Util.removeDir(tempFolder, false); + try { + Platform.deleteFile(tempFolder); + } catch (IOException e) { + e.printStackTrace(); + } } return installedContrib; } diff --git a/app/src/processing/app/contrib/ContribProgressBar.java b/app/src/processing/app/contrib/ContribProgress.java similarity index 58% rename from app/src/processing/app/contrib/ContribProgressBar.java rename to app/src/processing/app/contrib/ContribProgress.java index 8a0d41c3b..adb3e0396 100644 --- a/app/src/processing/app/contrib/ContribProgressBar.java +++ b/app/src/processing/app/contrib/ContribProgress.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2013-20 The Processing Foundation + Copyright (c) 2013-22 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -29,37 +29,50 @@ import javax.swing.JProgressBar; // I suspect this code can mostly be replaced with built-in Swing functions. // This code seems like it's adapted from old example code found on the web. -// https://github.com/processing/processing/issues/3176 +// (As of 220311 it's also been merged together from two classes (one called +// ProgressMonitor, the other ProgressBar, which wrapped the JProgressBar), +// so it looks less like that now. But the point is still relevant: most of +// what's here should be done with Swing housekeeping classes. [fry 220312] +// TODO https://github.com/processing/processing4/issues/351 -abstract class ContribProgressBar extends ContribProgressMonitor { - JProgressBar progressBar; +public class ContribProgress { + static private final int UNKNOWN = -1; - public ContribProgressBar(JProgressBar progressBar) { + final private JProgressBar progressBar; + + private boolean canceled = false; + private Exception exception; + + + public ContribProgress(JProgressBar progressBar) { this.progressBar = progressBar; } - public void startTask(String name, int maxValue) { - finished = false; - progressBar.setString(name); - progressBar.setIndeterminate(maxValue == UNKNOWN); - progressBar.setMaximum(maxValue); + + public void startTask(String name) { + startTask(name, UNKNOWN); } + + public void startTask(String name, int maxValue) { + if (progressBar != null) { + progressBar.setString(name); + progressBar.setIndeterminate(maxValue == UNKNOWN); + progressBar.setMaximum(maxValue); + } + } + + public void setProgress(int value) { - super.setProgress(value); - progressBar.setValue(value); + if (progressBar != null) { + progressBar.setValue(value); + } } - @Override + public final void finished() { - super.finished(); try { - EventQueue.invokeAndWait(new Runnable() { - @Override - public void run() { - finishedAction(); - } - }); + EventQueue.invokeAndWait(this::finishedAction); } catch (InterruptedException e) { e.printStackTrace(); } catch (InvocationTargetException e) { @@ -72,18 +85,14 @@ abstract class ContribProgressBar extends ContribProgressMonitor { } } - public abstract void finishedAction(); - @Override + public void finishedAction() { } + + public final void cancel() { - super.cancel(); + canceled = true; try { - EventQueue.invokeAndWait(new Runnable() { - @Override - public void run() { - cancelAction(); - } - }); + EventQueue.invokeAndWait(this::cancelAction); } catch (InterruptedException e) { e.printStackTrace(); } catch (InvocationTargetException e) { @@ -96,5 +105,26 @@ abstract class ContribProgressBar extends ContribProgressMonitor { } } + public void cancelAction() { } + + + public boolean isCanceled() { + return canceled; + } + + + public void setException(Exception e) { + exception = e; + } + + + public Exception getException() { + return exception; + } + + + public boolean isException() { + return exception != null; + } } diff --git a/app/src/processing/app/contrib/ContribProgressMonitor.java b/app/src/processing/app/contrib/ContribProgressMonitor.java deleted file mode 100644 index 4c897e22a..000000000 --- a/app/src/processing/app/contrib/ContribProgressMonitor.java +++ /dev/null @@ -1,78 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2013-15 The Processing Foundation - Copyright (c) 2011-12 Ben Fry and Casey Reas - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 - as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ -package processing.app.contrib; - - -// I suspect this code can mostly be replaced with built-in Swing functions. -// This code seems like it's adapted from old example code found on the web. -// https://github.com/processing/processing/issues/3176 - -public abstract class ContribProgressMonitor { - static final int UNKNOWN = -1; - boolean canceled = false; - boolean error = false; - boolean finished = false; - Exception exception; - int max; - int progress = 0; - - public void startTask(String name, int maxValue) { - } - - public void setProgress(int value) { - progress = value; - } - - public int getProgress() { - return progress; - } - - public boolean isCanceled() { - return canceled; - } - - public void cancel() { - canceled = true; - } - - public boolean isError() { - return error; - } - - public Exception getException() { - return exception; - } - - public void error(Exception e) { - error = true; - exception = e; - } - - public boolean isFinished() { - return finished; - } - - public void finished() { - finished = true; - } -} - diff --git a/app/src/processing/app/contrib/Contribution.java b/app/src/processing/app/contrib/Contribution.java index aa401375e..f5ba434d9 100644 --- a/app/src/processing/app/contrib/Contribution.java +++ b/app/src/processing/app/contrib/Contribution.java @@ -23,7 +23,8 @@ package processing.app.contrib; import java.io.File; import java.util.Arrays; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import processing.core.PApplet; import processing.data.StringDict; @@ -38,19 +39,20 @@ abstract public class Contribution { static final String MODES_PROPERTY = "modes"; static final String AUTHORS_PROPERTY = "authors"; - static final String SPECIAL_CATEGORY = "Starred"; static final String UNKNOWN_CATEGORY = "Unknown"; - static final List validCategories = - Arrays.asList("3D", "Animation", "Data", "Geometry", "GUI", "Hardware", - "I/O", "Math", "Simulation", "Sound", SPECIAL_CATEGORY, - "Typography", "Utilities", "Video & Vision", "Other"); + static final Set validCategories = + new HashSet<>(Arrays.asList( + "3D", "Animation", "Data", "Geometry", "GUI", "Hardware", + "I/O", "Math", "Renderer", "Simulation", "Sound", + "Typography", "Utilities", "Video & Vision", "Other" + )); static final String FOUNDATION_AUTHOR = "The Processing Foundation"; protected StringList categories; // "Sound", "Typography" protected String name; // "pdf" or "PDF Export" protected String authors; // [Ben Fry](http://benfry.com) - protected String url; // http://processing.org + protected String url; // https://processing.org protected String sentence; // Write graphics to PDF files. protected String paragraph; // protected int version; // 102 @@ -95,21 +97,8 @@ abstract public class Contribution { return imports; } -/* - protected String getImportStr() { - if (imports == null || imports.isEmpty()) { - return ""; - } - StringBuilder sb = new StringBuilder(); - for (String importName : imports) { - sb.append(importName); - sb.append(','); - } - sb.deleteCharAt(sb.length() - 1); // delete last comma - return sb.toString(); - } -*/ + /* protected boolean hasImport(String importName) { if (imports != null && importName != null) { for (String c : imports) { @@ -120,6 +109,7 @@ abstract public class Contribution { } return false; } + */ // "pdf" or "PDF Export" @@ -241,32 +231,18 @@ abstract public class Contribution { /** - * Returns true if the contribution is a starred/recommended contribution, - * or is by the Processing Foundation. + * Returns true if the contrib is from the Processing Foundation. */ - boolean isSpecial() { - if (authors != null && - authors.contains(FOUNDATION_AUTHOR)) { - return true; - } - - if (categories != null && - categories.hasValue(SPECIAL_CATEGORY)) { - return true; - } - - return false; - } - - public boolean isFoundation() { return FOUNDATION_AUTHOR.equals(authors); } + /* public StringDict loadProperties(File contribFolder) { return loadProperties(contribFolder, getType()); } + */ static public StringDict loadProperties(File contribFolder, @@ -278,13 +254,6 @@ abstract public class Contribution { return null; } - /** - * @return a single element list with "Unknown" as the category. - */ - static StringList unknownCategoryList() { - return new StringList(UNKNOWN_CATEGORY); - } - /** * @return the list of categories that this contribution is part of @@ -294,9 +263,6 @@ abstract public class Contribution { StringList outgoing = new StringList(); String categoryStr = properties.get(CATEGORIES_PROPERTY); - if (categoryStr == null) { - categoryStr = properties.get("category"); // try the old way - } if (categoryStr != null) { // Can't use splitTokens() because the names sometimes have spaces String[] listing = PApplet.trim(PApplet.split(categoryStr, ',')); @@ -308,7 +274,7 @@ abstract public class Contribution { } } if (outgoing.size() == 0) { - return unknownCategoryList(); + outgoing.append(UNKNOWN_CATEGORY); } return outgoing; } @@ -373,18 +339,25 @@ abstract public class Contribution { if (o instanceof Contribution) { Contribution that = (Contribution) o; - return name.toLowerCase().equals(that.name.toLowerCase()); + return name.equalsIgnoreCase(that.name); } return false; } + // TODO remove this because it hides actual differences in objects @Override public int hashCode() { return name.toLowerCase().hashCode(); } + @Override + public String toString() { + return getName() + " @" + Integer.toHexString(super.hashCode()); + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . diff --git a/app/src/processing/app/contrib/ContributionListing.java b/app/src/processing/app/contrib/ContributionListing.java index 8f2b272eb..173e3bd69 100644 --- a/app/src/processing/app/contrib/ContributionListing.java +++ b/app/src/processing/app/contrib/ContributionListing.java @@ -25,10 +25,8 @@ import java.awt.EventQueue; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.net.*; -import java.text.Normalizer; import java.util.*; import java.util.concurrent.locks.ReentrantLock; -import java.util.regex.Pattern; import processing.app.Base; import processing.app.Library; @@ -40,38 +38,37 @@ import processing.data.StringDict; public class ContributionListing { static volatile ContributionListing singleInstance; - /** Stable URL that will redirect to wherever the file is hosted */ - static final String LISTING_URL = "http://download.processing.org/contribs"; + /** + * Stable URL that will redirect to wherever the file is hosted. + * Changed to use https in 4.0 beta 8 (returns same data). + */ + static final String LISTING_URL = "https://download.processing.org/contribs"; static final String LOCAL_FILENAME = "contribs.txt"; /** Location of the listing file on disk, will be read and written. */ File listingFile; - List listeners; + Set listPanels; final List advertisedContributions; - Map> librariesByCategory; Map librariesByImportHeader; - // TODO: Every contribution is getting added twice - // and nothing is replaced ever. Set allContributions; boolean listDownloaded; - boolean listDownloadFailed; +// boolean listDownloadFailed; ReentrantLock downloadingListingLock; private ContributionListing() { - listeners = new ArrayList<>(); + listPanels = new HashSet<>(); advertisedContributions = new ArrayList<>(); - librariesByCategory = new HashMap<>(); librariesByImportHeader = new HashMap<>(); allContributions = new LinkedHashSet<>(); downloadingListingLock = new ReentrantLock(); - //listingFile = Base.getSettingsFile("contributions.txt"); listingFile = Base.getSettingsFile(LOCAL_FILENAME); - boolean writable = listingFile.setWritable(true, false); - if (writable && listingFile.exists()) { - setAdvertisedList(listingFile); + if (listingFile.exists()) { + // On the EDT already, but do this later on the EDT so that the + // constructor can finish more efficiently for getInstance(). + EventQueue.invokeLater(() -> setAdvertisedList(listingFile)); } } @@ -100,15 +97,17 @@ public class ContributionListing { /** - * Adds the installed libraries to the listing of libraries, replacing any - * pre-existing libraries by the same name as one in the list. + * Adds the installed libraries to the listing of libraries, replacing + * any pre-existing libraries by the same name as one in the list. */ protected void updateInstalledList(List installed) { for (Contribution contribution : installed) { Contribution existingContribution = getContribution(contribution); if (existingContribution != null) { - replaceContribution(existingContribution, contribution); - //} else if (contribution != null) { // 130925 why would this be necessary? + if (existingContribution != contribution) { + // don't replace contrib with itself + replaceContribution(existingContribution, contribution); + } } else { addContribution(contribution); } @@ -118,18 +117,6 @@ public class ContributionListing { protected void replaceContribution(Contribution oldLib, Contribution newLib) { if (oldLib != null && newLib != null) { - for (String category : oldLib.getCategories()) { - if (librariesByCategory.containsKey(category)) { - List list = librariesByCategory.get(category); - - for (int i = 0; i < list.size(); i++) { - if (list.get(i) == oldLib) { - list.set(i, newLib); - } - } - } - } - if (oldLib.getImports() != null) { for (String importName : oldLib.getImports()) { if (getLibrariesByImportHeader().containsKey(importName)) { @@ -137,11 +124,12 @@ public class ContributionListing { } } } - allContributions.remove(oldLib); allContributions.add(newLib); - notifyChange(oldLib, newLib); + for (ListPanel listener : listPanels) { + listener.contributionChanged(oldLib, newLib); + } } } @@ -152,36 +140,25 @@ public class ContributionListing { getLibrariesByImportHeader().put(importName, contribution); } } - for (String category : contribution.getCategories()) { - if (librariesByCategory.containsKey(category)) { - List list = librariesByCategory.get(category); - list.add(contribution); - list.sort(COMPARATOR); + allContributions.add(contribution); - } else { - ArrayList list = new ArrayList<>(); - list.add(contribution); - librariesByCategory.put(category, list); - } - allContributions.add(contribution); - notifyAdd(contribution); + for (ListPanel listener : listPanels) { + listener.contributionAdded(contribution); } } protected void removeContribution(Contribution contribution) { - for (String category : contribution.getCategories()) { - if (librariesByCategory.containsKey(category)) { - librariesByCategory.get(category).remove(contribution); - } - } if (contribution.getImports() != null) { for (String importName : contribution.getImports()) { getLibrariesByImportHeader().remove(importName); } } allContributions.remove(contribution); - notifyRemove(contribution); + + for (ListPanel listener : listPanels) { + listener.contributionRemoved(contribution); + } } @@ -209,129 +186,9 @@ public class ContributionListing { } - protected Set getCategories(Contribution.Filter filter) { - Set outgoing = new HashSet<>(); - - Set categorySet = librariesByCategory.keySet(); - for (String categoryName : categorySet) { - for (Contribution contrib : librariesByCategory.get(categoryName)) { - if (filter.matches(contrib)) { - // TODO still not sure why category would be coming back null [fry] - // http://code.google.com/p/processing/issues/detail?id=1387 - if (categoryName != null && !categoryName.trim().isEmpty()) { - outgoing.add(categoryName); - } - break; - } - } - } - return outgoing; - } - - - public boolean matches(Contribution contrib, String typed) { - int colon = typed.indexOf(":"); - if (colon != -1) { - String isText = typed.substring(0, colon); - String property = typed.substring(colon + 1); - - // Chances are the person is still typing the property, so rather than - // make the list flash empty (because nothing contains "is:" or "has:", - // just return true. - if (!isProperty(property)) { - return true; - } - - if ("is".equals(isText) || "has".equals(isText)) { - return hasProperty(contrib, typed.substring(colon + 1)); - } else if ("not".equals(isText)) { - return !hasProperty(contrib, typed.substring(colon + 1)); - } - } - - typed = ".*" + typed.toLowerCase() + ".*"; - - return (matchField(contrib.getName(), typed) || - matchField(contrib.getAuthorList(), typed) || - matchField(contrib.getSentence(), typed) || - matchField(contrib.getParagraph(), typed) || - contrib.hasCategory(typed)); - } - - - static private boolean matchField(String field, String typed) { - return (field != null) && - removeAccents(field.toLowerCase()).matches(typed); - } - - - // TODO is this removing characters with accents, not ascii normalizing them? [fry] - static private String removeAccents(String str) { - String nfdNormalizedString = Normalizer.normalize(str, Normalizer.Form.NFD); - Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); - return pattern.matcher(nfdNormalizedString).replaceAll(""); - } - - - static private boolean isProperty(String property) { - return property.startsWith("updat") || property.startsWith("upgrad") - || property.startsWith("instal") && !property.startsWith("installabl") - || property.equals("tool") || property.startsWith("lib") - || property.equals("mode") || property.equals("compilation"); - } - - - /** - * Returns true if the contribution fits the given property, false otherwise. - * If the property is invalid, returns false. - */ - private boolean hasProperty(Contribution contrib, String property) { - // update, updates, updatable, upgrade - if (property.startsWith("updat") || property.startsWith("upgrad")) { - return hasUpdates(contrib); - } - if (property.startsWith("instal") && !property.startsWith("installabl")) { - return contrib.isInstalled(); - } - if (property.equals("tool")) { - return contrib.getType() == ContributionType.TOOL; - } - if (property.startsWith("lib")) { - return contrib.getType() == ContributionType.LIBRARY; - } - if (property.equals("mode")) { - return contrib.getType() == ContributionType.MODE; - } - return false; - } - - - private void notifyRemove(Contribution contribution) { - for (ChangeListener listener : listeners) { - listener.contributionRemoved(contribution); - } - } - - - private void notifyAdd(Contribution contribution) { - for (ChangeListener listener : listeners) { - listener.contributionAdded(contribution); - } - } - - - private void notifyChange(Contribution oldLib, Contribution newLib) { - for (ChangeListener listener : listeners) { - listener.contributionChanged(oldLib, newLib); - } - } - - - protected void addListener(ChangeListener listener) { - for (Contribution contrib : allContributions) { - listener.contributionAdded(contrib); - } - listeners.add(listener); + // formerly addListener(), but the ListPanel was the only Listener + protected void addListPanel(ListPanel listener) { + listPanels.add(listener); } @@ -340,7 +197,7 @@ public class ContributionListing { * Only one instance will run at a time. */ public void downloadAvailableList(final Base base, - final ContribProgressMonitor progress) { + final ContribProgress progress) { // TODO: replace with SwingWorker [jv] new Thread(() -> { @@ -349,16 +206,20 @@ public class ContributionListing { try { URL url = new URL(LISTING_URL); File tempContribFile = Base.getSettingsFile("contribs.tmp"); - tempContribFile.setWritable(true, false); + if (tempContribFile.exists() && !tempContribFile.canWrite()) { + if (!tempContribFile.setWritable(true, false)) { + System.err.println("Could not set " + tempContribFile + " writable"); + } + } ContributionManager.download(url, base.getInstalledContribsInfo(), tempContribFile, progress); - if (!progress.isCanceled() && !progress.isError()) { + if (!progress.isCanceled() && !progress.isException()) { if (listingFile.exists()) { listingFile.delete(); // may silently fail, but below may still work } if (tempContribFile.renameTo(listingFile)) { listDownloaded = true; - listDownloadFailed = false; +// listDownloadFailed = false; try { // TODO: run this in SwingWorker done() [jv] EventQueue.invokeAndWait(() -> { @@ -375,13 +236,13 @@ public class ContributionListing { cause.printStackTrace(); } } - } else { - listDownloadFailed = true; +// } else { +// listDownloadFailed = true; } } } catch (MalformedURLException e) { - progress.error(e); + progress.setException(e); progress.finished(); } finally { downloadingListingLock.unlock(); @@ -390,21 +251,21 @@ public class ContributionListing { } - protected boolean hasUpdates(Contribution contribution) { - if (contribution.isInstalled()) { - Contribution advertised = getAvailableContribution(contribution); - if (advertised == null) { - return false; - } - return advertised.getVersion() > contribution.getVersion() - && advertised.isCompatible(Base.getRevision()); + protected boolean hasUpdates(Contribution contrib) { + if (!contrib.isInstalled()) { + return false; } - return false; + Contribution advertised = getAvailableContribution(contrib); + if (advertised == null) { + return false; + } + return (advertised.getVersion() > contrib.getVersion() && + advertised.isCompatible(Base.getRevision())); } - protected String getLatestPrettyVersion(Contribution contribution) { - Contribution newestContrib = getAvailableContribution(contribution); + protected String getLatestPrettyVersion(Contribution contrib) { + Contribution newestContrib = getAvailableContribution(contrib); if (newestContrib == null) { return null; } @@ -412,17 +273,12 @@ public class ContributionListing { } - protected boolean hasDownloadedLatestList() { + protected boolean isDownloaded() { return listDownloaded; } - protected boolean listDownloadSuccessful() { - return !listDownloadFailed; - } - - - private List parseContribList(File file) { + static private List parseContribList(File file) { List outgoing = new ArrayList<>(); if (file != null && file.exists()) { @@ -464,10 +320,10 @@ public class ContributionListing { /** - * TODO This needs to be called when the listing loads, and also whenever - * the contribs list has been updated (for whatever reason). In addition, - * the caller (presumably Base) should update all Editor windows with the - * correct information on the number of items available. + * TODO This needs to be called when the listing loads, and also + * the contribs list has been updated (for whatever reason). + * In addition, the caller (presumably Base) should update all + * Editor windows with the correct number of items available. * @return The number of contributions that have available updates. */ public int countUpdates(Base base) { @@ -494,7 +350,7 @@ public class ContributionListing { count++; } } - for (ExamplesContribution ec : base.getExampleContribs()) { + for (ExamplesContribution ec : base.getContribExamples()) { if (hasUpdates(ec)) { count++; } @@ -507,14 +363,4 @@ public class ContributionListing { public Map getLibrariesByImportHeader() { return librariesByImportHeader; } - - - static public Comparator COMPARATOR = Comparator.comparing(o -> o.getName().toLowerCase()); - - - public interface ChangeListener { - void contributionAdded(Contribution Contribution); - void contributionRemoved(Contribution Contribution); - void contributionChanged(Contribution oldLib, Contribution newLib); - } } diff --git a/app/src/processing/app/contrib/ContributionManager.java b/app/src/processing/app/contrib/ContributionManager.java index 66ced1939..b685f6367 100644 --- a/app/src/processing/app/contrib/ContributionManager.java +++ b/app/src/processing/app/contrib/ContributionManager.java @@ -1,9 +1,9 @@ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* - Part of the Processing project - http://processing.org + Part of the Processing project - https://processing.org - Copyright (c) 2013-20 The Processing Foundation + Copyright (c) 2013-22 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,11 +27,10 @@ import java.lang.reflect.InvocationTargetException; import java.net.*; import java.util.*; -import javax.swing.SwingWorker; - import processing.app.Base; import processing.app.Language; import processing.app.Messages; +import processing.app.Platform; import processing.app.Util; import processing.app.ui.Editor; import processing.core.PApplet; @@ -46,18 +45,19 @@ public class ContributionManager { * Blocks until the file is downloaded or an error occurs. * * @param source the URL of the file to download - * @param post Binary blob of POST data if a payload should be sent. + * @param post Binary blob of POST data if there is data to be sent. * Must already be URL-encoded and will be Gzipped for upload. - * @param dest The file on the local system where the file will be written. - * This must be a file (not a directory), and must already exist. + * If null, the connection will use GET instead of POST. + * @param dest The location on the local system to write the file. + * Its parent directory must already exist. * @param progress null if progress is irrelevant, such as when downloading - * for an install during startup, when the ProgressMonitor - * is useless since UI isn't setup yet. + * files for installation during startup, when the + * ProgressMonitor is useless because the UI is unavailable. * * @return true if the file was successfully downloaded, false otherwise. */ static boolean download(URL source, byte[] post, - File dest, ContribProgressMonitor progress) { + File dest, ContribProgress progress) { boolean success = false; try { HttpURLConnection conn = (HttpURLConnection) source.openConnection(); @@ -85,7 +85,6 @@ public class ContributionManager { if (progress != null) { // TODO this is often -1, may need to set progress to indeterminate int fileSize = conn.getContentLength(); - progress.max = fileSize; // System.out.println("file size is " + fileSize); progress.startTask(Language.text("contrib.progress.downloading"), fileSize); } @@ -123,7 +122,7 @@ public class ContributionManager { } } catch (IOException ioe) { if (progress != null) { - progress.error(ioe); + progress.setException(ioe); progress.cancel(); } } @@ -140,8 +139,8 @@ public class ContributionManager { static void downloadAndInstall(final Base base, final URL url, final AvailableContribution ad, - final ContribProgressBar downloadProgress, - final ContribProgressBar installProgress, + final ContribProgress downloadProgress, + final ContribProgress installProgress, final StatusPanel status) { // TODO: replace with SwingWorker [jv] new Thread(() -> { @@ -154,8 +153,8 @@ public class ContributionManager { try { download(url, null, contribZip, downloadProgress); - if (!downloadProgress.isCanceled() && !downloadProgress.isError()) { - installProgress.startTask(Language.text("contrib.progress.installing"), ContribProgressMonitor.UNKNOWN); + if (!downloadProgress.isCanceled() && !downloadProgress.isException()) { + installProgress.startTask(Language.text("contrib.progress.installing")); final LocalContribution contribution = ad.install(base, contribZip, false, status); @@ -164,14 +163,6 @@ public class ContributionManager { // TODO: run this in SwingWorker done() [jv] EventQueue.invokeAndWait(() -> { listing.replaceContribution(ad, contribution); - /* - if (contribution.getType() == ContributionType.MODE) { - List contribModes = editor.getBase().getModeContribs(); - if (!contribModes.contains(contribution)) { - contribModes.add((ModeContribution) contribution); - } - } - */ base.refreshContribs(contribution.getType()); base.setUpdatesAvailable(listing.countUpdates(base)); }); @@ -184,14 +175,16 @@ public class ContributionManager { installProgress.finished(); } else { - if (downloadProgress.exception instanceof SocketTimeoutException) { + Exception exception = downloadProgress.getException(); + if (exception instanceof SocketTimeoutException) { status.setErrorMessage(Language .interpolate("contrib.errors.contrib_download.timeout", ad.getName())); - } else { + } else if (exception != null) { status.setErrorMessage(Language .interpolate("contrib.errors.download_and_install", ad.getName())); + exception.printStackTrace(); } } contribZip.delete(); @@ -204,14 +197,13 @@ public class ContributionManager { cause instanceof NoSuchMethodError) { msg = "This item is not compatible with this version of Processing"; } else if (cause instanceof UnsupportedClassVersionError) { - msg = "This item needs to be recompiled for Java " + - PApplet.javaPlatform; + msg = "This needs to be recompiled for Java " + PApplet.javaPlatform; } } if (msg == null) { msg = Language.interpolate("contrib.errors.download_and_install", ad.getName()); - // Something unexpected, so print the trace + // Something unexpected, so print the trace for bug tracking e.printStackTrace(); } status.setErrorMessage(msg); @@ -227,7 +219,6 @@ public class ContributionManager { } - /** * Non-blocking call to download and install a contribution in a new thread. * Used when information about the progress of the download and install @@ -245,13 +236,10 @@ public class ContributionManager { filename = filename.substring(filename.lastIndexOf('/') + 1); try { File contribZip = File.createTempFile("download", filename); - contribZip.setWritable(true); // necessary? - try { download(url, null, contribZip, null); - - final LocalContribution contribution = ad.install(base, contribZip, - false, null); + final LocalContribution contribution = + ad.install(base, contribZip, false, null); if (contribution != null) { try { @@ -272,8 +260,9 @@ public class ContributionManager { } } } - - contribZip.delete(); + if (contribZip.exists() && !contribZip.delete()) { + System.err.println("Could not delete " + contribZip); + } handleUpdateFailedMarkers(ad); } catch (Exception e) { @@ -334,17 +323,16 @@ public class ContributionManager { */ static public void downloadAndInstallOnImport(final Base base, final List list) { - // To avoid the user from modifying stuff, since this function is only - // called during pre-processing + // Disable the Editor to avoid the user from modifying anything, + // because this function is only called during pre-processing. Editor editor = base.getActiveEditor(); editor.getTextArea().setEditable(false); -// base.getActiveEditor().getConsole().clear(); List installedLibList = new ArrayList<>(); // boolean variable to check if previous lib was installed successfully, // to give the user an idea about progress being made. - boolean isPrevDone = false; + boolean prevDone = false; for (final AvailableContribution contrib : list) { if (contrib.getType() != ContributionType.LIBRARY) { @@ -365,7 +353,7 @@ public class ContributionManager { // one install is completed and the next download has begun without // interfering with other status messages that may arise in the meanwhile String statusMsg = editor.getStatusMessage(); - if (isPrevDone) { + if (prevDone) { String status = statusMsg + " " + Language.interpolate("contrib.import.progress.download", contrib.name); editor.statusNotice(status); @@ -375,7 +363,7 @@ public class ContributionManager { editor.statusNotice(status); } - isPrevDone = false; + prevDone = false; download(url, null, contribZip, null); @@ -406,7 +394,7 @@ public class ContributionManager { contribZip.delete(); installedLibList.add(contrib.name); - isPrevDone = true; + prevDone = true; arg = "contrib.import.progress.done"; editor.statusNotice(Language.interpolate(arg,contrib.name)); @@ -433,18 +421,6 @@ public class ContributionManager { } - /* - static void refreshInstalled(Editor e) { - for (Editor ed : e.getBase().getEditors()) { - ed.getMode().rebuildImportMenu(); - ed.getMode().rebuildExamplesFrame(); - ed.rebuildToolMenu(); - ed.rebuildModeMenu(); - } - } - */ - - /** * Returns a file in the parent folder that does not exist yet. If * parent/fileName already exists, this will look for parent/fileName(2) @@ -513,25 +489,10 @@ public class ContributionManager { updateFlagged(base, Base.getSketchbookModesFolder()); updateFlagged(base, Base.getSketchbookToolsFolder()); - SwingWorker s = new SwingWorker<>() { - - @Override - protected Void doInBackground() throws Exception { - try { - // TODO: pls explain the sleep and why this runs on a worker thread, - // but a couple of lines above on EDT [jv] - Thread.sleep(1000); - installPreviouslyFailed(base, Base.getSketchbookToolsFolder()); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return null; - } - }; - s.execute(); - + /* clearRestartFlags(Base.getSketchbookModesFolder()); clearRestartFlags(Base.getSketchbookToolsFolder()); + */ } @@ -546,7 +507,12 @@ public class ContributionManager { if (possible != null) { for (File f : possible) { if (f.getName().matches(pattern)) { - Util.removeDir(f); + //Util.removeDir(f); + try { + Platform.deleteFile(f); + } catch (IOException e) { + e.printStackTrace(); + } } } } @@ -562,7 +528,12 @@ public class ContributionManager { ); if (markedForDeletion != null) { for (File folder : markedForDeletion) { - Util.removeDir(folder); + //Util.removeDir(folder); + try { + Platform.deleteFile(folder); + } catch (IOException e) { + e.printStackTrace(); + } } } } @@ -573,7 +544,7 @@ public class ContributionManager { * auto-update the previous time Processing was started up. */ static private void installPreviouslyFailed(Base base, File root) throws Exception { - File[] installList = root.listFiles(folder -> folder.isFile()); + File[] installList = root.listFiles(File::isFile); // https://github.com/processing/processing/issues/5823 if (installList != null) { @@ -614,22 +585,29 @@ public class ContributionManager { // Not sure the function here so I'm not fixing it at the moment, // but this whole function could use some cleaning. [fry 180105] - String type = root.getName().substring(root.getName().lastIndexOf('/') + 1); + // TODO getName() will (should?) never have a slash, wtf [fry 220312] + String contribType = root.getName().substring(root.getName().lastIndexOf('/') + 1); String propFileName = null; - if (type.equalsIgnoreCase("tools")) + if (contribType.equalsIgnoreCase("tools")) propFileName = "tool.properties"; - else if (type.equalsIgnoreCase("modes")) + else if (contribType.equalsIgnoreCase("modes")) propFileName = "mode.properties"; if (markedForUpdate != null) { for (File folder : markedForUpdate) { StringDict props = Util.readSettings(new File(folder, propFileName)); - String name = props.get("name"); - if (name != null) { // should not happen, but... - updateContribsNames.add(name); + if (props != null) { + String name = props.get("name", null); + if (name != null) { // should not happen, but... + updateContribsNames.add(name); + } + try { + Platform.deleteFile(folder); + } catch (IOException e) { + e.printStackTrace(); + } } - Util.removeDir(folder); } } @@ -663,27 +641,29 @@ public class ContributionManager { } + /* static private void clearRestartFlags(File root) { File[] folderList = root.listFiles(File::isDirectory); if (folderList != null) { for (File folder : folderList) { - LocalContribution.clearRestartFlags(folder); + LocalContribution.clearRestartFlag(folder); } } } + */ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - static ManagerFrame managerDialog; + static ManagerFrame managerFrame; static public void init(Base base) throws Exception { // long t1 = System.currentTimeMillis(); listing = ContributionListing.getInstance(); // Moved here to make sure it runs on EDT [jv 170121] // long t2 = System.currentTimeMillis(); - managerDialog = new ManagerFrame(base); + managerFrame = new ManagerFrame(base); // long t3 = System.currentTimeMillis(); cleanup(base); // long t4 = System.currentTimeMillis(); @@ -691,11 +671,18 @@ public class ContributionManager { } + static public void updateTheme() { + if (managerFrame != null) { + managerFrame.updateTheme(); + } + } + + /** * Show the Library installer window. */ static public void openLibraries() { - managerDialog.showFrame(ContributionType.LIBRARY); + managerFrame.showFrame(ContributionType.LIBRARY); } @@ -703,7 +690,7 @@ public class ContributionManager { * Show the Mode installer window. */ static public void openModes() { - managerDialog.showFrame(ContributionType.MODE); + managerFrame.showFrame(ContributionType.MODE); } @@ -711,7 +698,7 @@ public class ContributionManager { * Show the Tool installer window. */ static public void openTools() { - managerDialog.showFrame(ContributionType.TOOL); + managerFrame.showFrame(ContributionType.TOOL); } @@ -719,7 +706,7 @@ public class ContributionManager { * Show the Examples installer window. */ static public void openExamples() { - managerDialog.showFrame(ContributionType.EXAMPLES); + managerFrame.showFrame(ContributionType.EXAMPLES); } @@ -727,28 +714,6 @@ public class ContributionManager { * Open the updates panel. */ static public void openUpdates() { - managerDialog.showFrame(null); + managerFrame.showFrame(null); } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - /* - static int getTypeIndex(ContributionType contributionType) { - int index; - if (contributionType == ContributionType.LIBRARY) { - index = 0; - } else if (contributionType == ContributionType.MODE) { - index = 1; - } else if (contributionType == ContributionType.TOOL) { - index = 2; - } else if (contributionType == ContributionType.EXAMPLES) { - index = 3; - } else { - index = 4; - } - return index; - } - */ } diff --git a/app/src/processing/app/contrib/ContributionTab.java b/app/src/processing/app/contrib/ContributionTab.java index bd61eb439..6592824de 100644 --- a/app/src/processing/app/contrib/ContributionTab.java +++ b/app/src/processing/app/contrib/ContributionTab.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -22,35 +22,36 @@ package processing.app.contrib; -import java.awt.Color; -import java.awt.Cursor; -import java.awt.Dimension; -import java.awt.event.*; +import java.awt.*; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; import java.util.*; +import java.util.List; import javax.swing.*; -import javax.swing.event.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; -import processing.app.*; +import processing.app.Base; +import processing.app.Library; +import processing.app.laf.PdeComboBoxUI; import processing.app.ui.Editor; +import processing.app.ui.Theme; import processing.app.ui.Toolkit; public class ContributionTab extends JPanel { - static final String ANY_CATEGORY = Language.text("contrib.all"); - static final int FILTER_WIDTH = Toolkit.zoom(180); - + Base base; ContributionType contribType; - ManagerFrame contribDialog; + ManagerFrame managerFrame; Contribution.Filter filter; JComboBox categoryChooser; - ListPanel contributionListPanel; + ListPanel listPanel; StatusPanel statusPanel; FilterField filterField; - // TODO: remove or initialize restartButton - //JButton restartButton; - JLabel categoryLabel; + JLabel loaderLabel; JPanel errorPanel; @@ -58,77 +59,92 @@ public class ContributionTab extends JPanel { JButton tryAgainButton; JButton closeButton; - // the calling editor, so updates can be applied - Editor editor; String category; - ContributionListing contribListing; - JProgressBar progressBar; + //protected JProgressBar progressBar; // TODO this is not actually used? - public ContributionTab() { } + public ContributionTab(ManagerFrame dialog) { + this.managerFrame = dialog; + this.base = dialog.base; + buildErrorPanel(); - public ContributionTab(ManagerFrame dialog, ContributionType type) { - this.contribDialog = dialog; - this.contribType = type; - -// long t1 = System.currentTimeMillis(); - filter = contrib -> contrib.getType() == contribType; - -// long t2 = System.currentTimeMillis(); - contribListing = ContributionListing.getInstance(); -// long t3 = System.currentTimeMillis(); - statusPanel = new StatusPanel(this, 650); -// long t4 = System.currentTimeMillis(); - contributionListPanel = new ListPanel(this, filter, false); -// long t5 = System.currentTimeMillis(); - contribListing.addListener(contributionListPanel); // TODO optimize: this line is taking all of the time -// long t6 = System.currentTimeMillis(); -// System.out.println("ContributionTab. " + (t4-t1) + " " + (t5-t4) + " " + (t6-t5)); + loaderLabel = new JLabel(Toolkit.getLibIcon("manager/loader.gif")); + loaderLabel.setOpaque(false); } - public void showFrame(final Editor editor, boolean error, boolean loading) { - this.editor = editor; - setLayout(error, loading); - contributionListPanel.setVisible(!loading); + public ContributionTab(ManagerFrame frame, ContributionType type) { + this(frame); + this.contribType = type; + + filter = contrib -> contrib.getType() == contribType; + + listPanel = new ListPanel(this, filter, false); + // TODO init is after listPanel is created because it calls updateTheme() + // which needs it, but yuck, too messy [fry 220504] + statusPanel = new StatusPanel(this); + + ContributionListing.getInstance().addListPanel(listPanel); + } + + + protected void updateTheme() { + setBackground(Theme.getColor("manager.list.background.color")); + + if (filterField != null) { + filterField.updateTheme(); + } + + listPanel.updateTheme(); + statusPanel.updateTheme(); + + closeButton.setIcon(Toolkit.renderIcon("manager/close", Theme.get("manager.error.close.icon.color"), 16)); + } + + + public void rebuildLayout(boolean error, boolean loading) { + setLayout(); + + listPanel.setVisible(!loading); loaderLabel.setVisible(loading); errorPanel.setVisible(error); + listPanel.fireChange(); + validate(); repaint(); } - protected void setLayout(boolean activateErrorPanel, - boolean isLoading) { - if (progressBar == null) { - progressBar = new JProgressBar(); - progressBar.setVisible(false); - + protected void setLayout() { + if (categoryChooser == null) { + createComponents(); + } + /* + if (loaderLabel == null) { createComponents(); buildErrorPanel(); loaderLabel = new JLabel(Toolkit.getLibIcon("manager/loader.gif")); loaderLabel.setOpaque(false); - loaderLabel.setBackground(Color.WHITE); + //loaderLabel.setBackground(Color.WHITE); } + */ - int scrollBarWidth = contributionListPanel.scrollPane.getVerticalScrollBar().getPreferredSize().width; + final int scrollBarWidth = listPanel.getScrollBarWidth(); + final int filterWidth = Toolkit.zoom(180); GroupLayout layout = new GroupLayout(this); setLayout(layout); -// layout.setAutoCreateContainerGaps(true); -// layout.setAutoCreateGaps(true); layout.setHorizontalGroup(layout .createParallelGroup(GroupLayout.Alignment.CENTER) .addGroup(layout .createSequentialGroup() .addGap(ManagerFrame.STATUS_WIDTH) .addComponent(filterField, - FILTER_WIDTH, FILTER_WIDTH, FILTER_WIDTH) -// .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + filterWidth, filterWidth, filterWidth) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addComponent(categoryChooser, @@ -136,7 +152,7 @@ public class ContributionTab extends JPanel { ManagerFrame.AUTHOR_WIDTH, ManagerFrame.AUTHOR_WIDTH) .addGap(scrollBarWidth)).addComponent(loaderLabel) - .addComponent(contributionListPanel).addComponent(errorPanel) + .addComponent(listPanel).addComponent(errorPanel) .addComponent(statusPanel)); layout.setVerticalGroup(layout @@ -145,48 +161,43 @@ public class ContributionTab extends JPanel { .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(categoryChooser) .addComponent(filterField)) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + // https://github.com/processing/processing4/issues/520 + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(loaderLabel) - .addComponent(contributionListPanel)) + .addComponent(listPanel)) .addComponent(errorPanel) .addComponent(statusPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)); layout.linkSize(SwingConstants.VERTICAL, categoryChooser, filterField); // these will occupy space even if not visible - layout.setHonorsVisibility(contributionListPanel, false); + layout.setHonorsVisibility(listPanel, false); layout.setHonorsVisibility(categoryChooser, false); - setBackground(Color.WHITE); setBorder(null); } private void createComponents() { - categoryLabel = new JLabel(Language.text("contrib.category")); +// categoryLabel = new JLabel(Language.text("contrib.category")); - categoryChooser = new JComboBox(); + categoryChooser = new JComboBox<>(); categoryChooser.setMaximumRowCount(20); - categoryChooser.setFont(ManagerFrame.NORMAL_PLAIN); +// categoryChooser.setFont(ManagerFrame.NORMAL_PLAIN); updateCategoryChooser(); - categoryChooser.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - category = (String) categoryChooser.getSelectedItem(); - if (ManagerFrame.ANY_CATEGORY.equals(category)) { - category = null; - } - filterLibraries(category, filterField.filters); - contributionListPanel.updateColors(); + categoryChooser.addItemListener(e -> { + category = (String) categoryChooser.getSelectedItem(); + if (ManagerFrame.ANY_CATEGORY.equals(category)) { + category = null; } + //filterLibraries(category, filterField.filterWords); + filterLibraries(); }); filterField = new FilterField(); - - // TODO: initialize restartButton, whatever it is - // restartButton = ??? } @@ -199,36 +210,25 @@ public class ContributionTab extends JPanel { errorMessage = new JTextPane(); errorMessage.setEditable(false); errorMessage.setContentType("text/html"); - errorMessage.setText("

Could not connect to the Processing server.
" - + "Contributions cannot be installed or updated without an Internet connection.
" - + "Please verify your network connection again, then try connecting again.
"); - DetailPanel.setTextStyle(errorMessage, "1em"); + errorMessage.setText("
" + + "Could not connect to the Processing server.
" + + "Contributions cannot be installed or updated without an Internet connection.
" + + "Please verify your network connection again, then try connecting again." + + "
"); Dimension dim = new Dimension(550, 60); errorMessage.setMaximumSize(dim); errorMessage.setMinimumSize(dim); errorMessage.setOpaque(false); - /* - StyledDocument doc = errorMessage.getStyledDocument(); - SimpleAttributeSet center = new SimpleAttributeSet(); - StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER); - doc.setParagraphAttributes(0, doc.getLength(), center, false); - */ - - closeButton = Toolkit.createIconButton("manager/close"); +// closeButton = Toolkit.createIconButton("manager/close"); + closeButton = new JButton(); closeButton.setContentAreaFilled(false); - closeButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - contribDialog.makeAndShowTab(false, false); - } - }); + closeButton.addActionListener(e -> managerFrame.rebuildTabLayouts(false, false)); tryAgainButton = new JButton("Try Again"); - tryAgainButton.setFont(ManagerFrame.NORMAL_PLAIN); - tryAgainButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - contribDialog.makeAndShowTab(false, true); - contribDialog.downloadAndUpdateContributionListing(editor.getBase()); - } +// tryAgainButton.setFont(ManagerFrame.NORMAL_PLAIN); + tryAgainButton.addActionListener(e -> { + managerFrame.rebuildTabLayouts(false, true); + managerFrame.downloadAndUpdateContributionListing(); }); layout.setHorizontalGroup(layout.createSequentialGroup() .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, @@ -250,44 +250,67 @@ public class ContributionTab extends JPanel { } - protected void updateCategoryChooser() { - if (categoryChooser != null) { - ArrayList categories; - categoryChooser.removeAllItems(); - categories = new ArrayList(contribListing.getCategories(filter)); -// for (int i = 0; i < categories.size(); i++) { -// System.out.println(i + " category: " + categories.get(i)); -// } - Collections.sort(categories); -// categories.add(0, ContributionManagerDialog.ANY_CATEGORY); - boolean categoriesFound = false; - categoryChooser.addItem(ManagerFrame.ANY_CATEGORY); - for (String s : categories) { - categoryChooser.addItem(s); - if (!s.equals(Contribution.UNKNOWN_CATEGORY)) { - categoriesFound = true; + private Set listCategories() { + Set categories = new HashSet<>(); + + for (Contribution c : ContributionListing.getInstance().allContributions) { + if (filter.matches(c)) { + for (String category : c.getCategories()) { + categories.add(category); } } - categoryChooser.setVisible(categoriesFound); + } + return categories; + } + + + protected void updateCategoryChooser() { + if (categoryChooser != null) { + categoryChooser.removeAllItems(); + + Set categories = listCategories(); + if (categories.size() == 1 && + categories.contains(Contribution.UNKNOWN_CATEGORY)) { + // Add dummy item for sizing purposes + // https://github.com/processing/processing4/issues/520 + categoryChooser.addItem("NULL"); + // If no unique categories, hide the category chooser + categoryChooser.setVisible(false); + + } else { + // Build the category chooser dropdown from the list + categoryChooser.addItem(ManagerFrame.ANY_CATEGORY); + + String[] list = categories.toArray(String[]::new); + Arrays.sort(list); + for (String category : list) { + categoryChooser.addItem(category); + } + categoryChooser.setVisible(true); + } } } - protected void filterLibraries(String category, List filters) { - contributionListPanel.filterLibraries(category, filters); + //protected void filterLibraries(String category, List filters) { + protected void filterLibraries() { + listPanel.filterLibraries(category, filterField.filterWords); } + // TODO Why is this entire set of code only running when Editor + // is not null... And what's it doing anyway? Shouldn't it run + // on all editors? (The change to getActiveEditor() was made + // for 4.0b8 because the Editor may have been closed after the + // Contrib Manager was opened.) [fry 220311] protected void updateContributionListing() { + Editor editor = base.getActiveEditor(); if (editor != null) { - List contributions = new ArrayList(); - List libraries = - new ArrayList(editor.getMode().contribLibraries); + new ArrayList<>(editor.getMode().contribLibraries); - // Only add core libraries that are installed in the sketchbook + // Only add Foundation libraries that are installed in the sketchbook // https://github.com/processing/processing/issues/3688 - //libraries.addAll(editor.getMode().coreLibraries); final String sketchbookPath = Base.getSketchbookLibrariesFolder().getAbsolutePath(); for (Library lib : editor.getMode().coreLibraries) { @@ -296,164 +319,183 @@ public class ContributionTab extends JPanel { } } - contributions.addAll(libraries); + List contributions = new ArrayList<>(libraries); - Base base = editor.getBase(); +// List tools = base.getToolContribs(); +// contributions.addAll(tools); + contributions.addAll(base.getToolContribs()); - List tools = base.getToolContribs(); - contributions.addAll(tools); +// List modes = base.getModeContribs(); +// contributions.addAll(modes); + contributions.addAll(base.getModeContribs()); - List modes = base.getModeContribs(); - contributions.addAll(modes); +// List examples = base.getExampleContribs(); +// contributions.addAll(examples); + contributions.addAll(base.getContribExamples()); - List examples = base.getExampleContribs(); - contributions.addAll(examples); + ContributionListing.getInstance().updateInstalledList(contributions); -// ArrayList compilations = LibraryCompilation.list(libraries); -// -// // Remove libraries from the list that are part of a compilations -// for (LibraryCompilation compilation : compilations) { -// Iterator it = libraries.iterator(); -// while (it.hasNext()) { -// Library current = it.next(); -// if (compilation.getFolder().equals(current.getFolder().getParentFile())) { -// it.remove(); -// } -// } -// } - - contribListing.updateInstalledList(contributions); + //listPanel.filterLibraries(category, new ArrayList<>()); + //listPanel.filterDummy(category); } } - protected void setFilterText(String filter) { - if (filter == null || filter.isEmpty()) { - filterField.setText(""); - } else { - filterField.setText(filter); - } - filterField.applyFilter(); + public void updateStatusDetail(StatusDetail detail) { + statusPanel.updateDetail(detail); } + public boolean filterHasFocus() { + return filterField != null && filterField.hasFocus(); + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + class FilterField extends JTextField { - List filters; + List filterWords = new ArrayList<>(); - public FilterField () { - super(""); + JLabel placeholderLabel; + JButton resetButton; - JLabel filterLabel = new JLabel("Filter"); - filterLabel.setFont(ManagerFrame.NORMAL_PLAIN); - filterLabel.setOpaque(false); +// Color textColor; +// Color placeholderColor; +// Color backgroundColor; - setFont(ManagerFrame.NORMAL_PLAIN); - filterLabel.setIcon(Toolkit.getLibIconX("manager/search")); - JButton removeFilter = Toolkit.createIconButton("manager/remove"); - removeFilter.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2)); - removeFilter.setBorderPainted(false); - removeFilter.setContentAreaFilled(false); - removeFilter.setCursor(Cursor.getDefaultCursor()); - removeFilter.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setText(""); - filterField.requestFocusInWindow(); - } + FilterField () { +// super(""); // necessary? + + // a label that appears above the component when empty and not focused + placeholderLabel = new JLabel("Filter"); +// filterLabel.setFont(ManagerFrame.NORMAL_PLAIN); + placeholderLabel.setOpaque(false); +// filterLabel.setOpaque(true); +// setFont(ManagerFrame.NORMAL_PLAIN); +// placeholderLabel.setIcon(Toolkit.getLibIconX("manager/search")); + +// JButton removeFilter = Toolkit.createIconButton("manager/remove"); + resetButton = new JButton(); + resetButton.setBorder(new EmptyBorder(0, 0, 0, 2)); + resetButton.setBorderPainted(false); + resetButton.setContentAreaFilled(false); + resetButton.setCursor(Cursor.getDefaultCursor()); + resetButton.addActionListener(e -> { + setText(""); + FilterField.this.requestFocusInWindow(); }); - //searchIcon = new ImageIcon(java.awt.Toolkit.getDefaultToolkit().getImage("NSImage://NSComputerTemplate")); - setOpaque(false); + + //setOpaque(false); + setOpaque(true); + setBorder(new EmptyBorder(3, 7, 3, 7)); GroupLayout fl = new GroupLayout(this); setLayout(fl); fl.setHorizontalGroup(fl .createSequentialGroup() - .addComponent(filterLabel) + .addComponent(placeholderLabel) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) - .addComponent(removeFilter)); + .addComponent(resetButton)); fl.setVerticalGroup(fl.createSequentialGroup() .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addGroup(fl.createParallelGroup() - .addComponent(filterLabel) - .addComponent(removeFilter)) + .addComponent(placeholderLabel) + .addComponent(resetButton)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)); - removeFilter.setVisible(false); - - filters = new ArrayList(); + resetButton.setVisible(false); addFocusListener(new FocusListener() { public void focusLost(FocusEvent focusEvent) { if (getText().isEmpty()) { - filterLabel.setVisible(true); + placeholderLabel.setVisible(true); } } public void focusGained(FocusEvent focusEvent) { - filterLabel.setVisible(false); + placeholderLabel.setVisible(false); } }); getDocument().addDocumentListener(new DocumentListener() { public void removeUpdate(DocumentEvent e) { - removeFilter.setVisible(!getText().isEmpty()); + resetButton.setVisible(!getText().isEmpty()); applyFilter(); } public void insertUpdate(DocumentEvent e) { - removeFilter.setVisible(!getText().isEmpty()); + resetButton.setVisible(!getText().isEmpty()); applyFilter(); } public void changedUpdate(DocumentEvent e) { - removeFilter.setVisible(!getText().isEmpty()); + resetButton.setVisible(!getText().isEmpty()); applyFilter(); } }); + + updateTheme(); } - public void applyFilter() { - String filter = getText(); - filter = filter.toLowerCase(); + private void applyFilter() { + String filter = getText().toLowerCase(); // Replace anything but 0-9, a-z, or : with a space - filter = filter.replaceAll("[^\\x30-\\x39^\\x61-\\x7a^\\x3a]", " "); - filters = Arrays.asList(filter.split(" ")); - filterLibraries(category, filters); + //filter = filter.replaceAll("[^\\x30-\\x39^\\x61-\\x7a\\x3a]", " "); - contributionListPanel.updateColors(); + filterWords = Arrays.asList(filter.split(" ")); + //filterLibraries(category, filterWords); + filterLibraries(); } - } + protected void updateTheme() { + placeholderLabel.setForeground(Theme.getColor("manager.search.placeholder.color")); + placeholderLabel.setIcon(Toolkit.renderIcon("manager/search", Theme.get("manager.search.icon.color"), 16)); + resetButton.setIcon(Toolkit.renderIcon("manager/search-reset", Theme.get("manager.search.icon.color"), 16)); -// public boolean hasAlreadyBeenOpened() { -// return panel != null; -// } + /* + if (getUI() instanceof PdeTextFieldUI) { + ((PdeTextFieldUI) getUI()).updateTheme(); + } else { + setUI(new PdeTextFieldUI("manager.search")); + } + */ +// System.out.println(getBorder().getBorderInsets(FilterField.this)); + //setBorder(new EmptyBorder(0, 5, 0, 5)); + //setBorder(null); +// setBorder(new EmptyBorder(3, 7, 3, 7)); - public void updateStatusPanel(DetailPanel contributionPanel) { - statusPanel.update(contributionPanel); - } + setBackground(Theme.getColor("manager.search.background.color")); + setForeground(Theme.getColor("manager.search.text.color")); + // not yet in use, so leaving out for now + //filterField.setDisabledTextColor(Theme.getColor("manager.search.disabled.text.color")); - protected void updateAll() { - Collection collection = - contributionListPanel.panelByContribution.values(); - for (DetailPanel detailPanel : collection) { - detailPanel.update(); + setSelectionColor(Theme.getColor("manager.search.selection.background.color")); + setSelectedTextColor(Theme.getColor("manager.search.selection.text.color")); + + setCaretColor(Theme.getColor("manager.search.caret.color")); + + //SwingUtilities.updateComponentTreeUI(this); + + if (categoryChooser.getUI() instanceof PdeComboBoxUI) { + ((PdeComboBoxUI) categoryChooser.getUI()).updateTheme(); + } else { + categoryChooser.setUI(new PdeComboBoxUI("manager.categories")); + } + + /* + textColor = Theme.getColor("manager.list.search.text.color"); + placeholderColor = Theme.getColor("manager.list.search.placeholder.color"); + backgroundColor = Theme.getColor("manager.list.search.background.color"); + + setBackground(backgroundColor); + */ } - contributionListPanel.model.fireTableDataChanged(); - } - - - protected boolean hasUpdates() { - return contributionListPanel.getRowCount() > 0; - } - - public boolean filterHasFocus() { - return filterField != null && filterField.hasFocus(); } } diff --git a/app/src/processing/app/contrib/ContributionType.java b/app/src/processing/app/contrib/ContributionType.java index 6ff804424..6c6ff7eac 100644 --- a/app/src/processing/app/contrib/ContributionType.java +++ b/app/src/processing/app/contrib/ContributionType.java @@ -24,6 +24,7 @@ package processing.app.contrib; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import processing.app.Base; import processing.app.Library; @@ -31,6 +32,7 @@ import processing.app.Messages; import processing.app.Util; import processing.app.ui.Editor; + public enum ContributionType { LIBRARY, MODE, TOOL, EXAMPLES; @@ -61,41 +63,6 @@ public enum ContributionType { } - /* - public String getPluralTitle() { - switch (this) { - case LIBRARY: - return "Libraries"; - case MODE: - return "Modes"; - case TOOL: - return "Tools"; - case EXAMPLES: - return "Examples"; - } - return null; // should be unreachable - } - */ - - -// public String getFolderName() { -// return toString(); -// /* -// switch (this) { -// case LIBRARY: -// return "libraries"; -// case TOOL: -// return "tools"; -// case MODE: -// return "modes"; -// case EXAMPLES: -// return "examples"; -// } -// return null; // should be unreachable -// */ -// } - - /** Get the name of the properties file for this type of contribution. */ public String getPropertiesName() { return this + ".properties"; @@ -107,42 +74,11 @@ public enum ContributionType { } - /* - // removed for 4.0a6, doesn't appear to be in use - public File[] listTempFolders() throws IOException { - File base = getSketchbookFolder(); - return base.listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - String name = file.getName(); - return (file.isDirectory() && - name.startsWith(toString()) && name.endsWith("tmp")); - } - }); - } - */ - - public boolean isTempFolderName(String name) { return name.startsWith(toString()) && name.endsWith("tmp"); } -// public String getTempPrefix() { -// return toString(); -// } -// -// -// public String getTempSuffix() { -// return "tmp"; -// } - - -// public String getPropertiesName() { -// return toString() + ".properties"; -// } - - static public ContributionType fromName(String s) { if (s != null) { if ("library".equalsIgnoreCase(s)) { @@ -237,20 +173,22 @@ public enum ContributionType { } - ArrayList listContributions(Editor editor) { - ArrayList contribs = new ArrayList<>(); + List listContributions(Base base, Editor editor) { + List contribs = new ArrayList<>(); switch (this) { case LIBRARY: - contribs.addAll(editor.getMode().contribLibraries); + if (editor != null) { + contribs.addAll(editor.getMode().contribLibraries); + } break; case TOOL: - contribs.addAll(editor.getBase().getToolContribs()); + contribs.addAll(base.getToolContribs()); break; case MODE: - contribs.addAll(editor.getBase().getModeContribs()); + contribs.addAll(base.getModeContribs()); break; case EXAMPLES: - contribs.addAll(editor.getBase().getExampleContribs()); + contribs.addAll(base.getContribExamples()); break; } return contribs; @@ -264,11 +202,6 @@ public enum ContributionType { File createBackupFolder(StatusPanel status) { File backupFolder = getBackupFolder(); -// if (backupFolder.isDirectory()) { -// status.setErrorMessage("First remove the folder named \"old\" from the " + -// getFolderName() + " folder in the sketchbook."); -// return null; -// } if (!backupFolder.exists() && !backupFolder.mkdirs()) { status.setErrorMessage("Could not create a backup folder in the " + "sketchbook " + this + " folder."); @@ -276,29 +209,4 @@ public enum ContributionType { } return backupFolder; } - - -// /** -// * Create a filter for a specific contribution type. -// * @param type The type, or null for a generic update checker. -// */ -// Contribution.Filter createFilter2() { -// return new Contribution.Filter() { -// public boolean matches(Contribution contrib) { -// return contrib.getType() == ContributionType.this; -// } -// }; -// } - - -// static Contribution.Filter createUpdateFilter() { -// return new Contribution.Filter() { -// public boolean matches(Contribution contrib) { -// if (contrib instanceof LocalContribution) { -// return ContributionListing.getInstance().hasUpdates(contrib); -// } -// return false; -// } -// }; -// } } \ No newline at end of file diff --git a/app/src/processing/app/contrib/DetailPanel.java b/app/src/processing/app/contrib/DetailPanel.java deleted file mode 100644 index 7e2ed37cf..000000000 --- a/app/src/processing/app/contrib/DetailPanel.java +++ /dev/null @@ -1,915 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2013-16 The Processing Foundation - Copyright (c) 2011-12 Ben Fry and Casey Reas - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 - as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ -package processing.app.contrib; - -import java.awt.*; -import java.awt.event.*; -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Iterator; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.Date; -import java.text.DateFormat; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.event.HyperlinkEvent; -import javax.swing.text.Document; -import javax.swing.text.html.HTMLDocument; -import javax.swing.text.html.StyleSheet; - -import processing.app.Base; -import processing.app.Language; -import processing.app.Messages; -import processing.app.Platform; -import processing.app.ui.Editor; -import processing.app.ui.Toolkit; - - -/** - * Panel that expands and gives a brief overview of a library when clicked. - */ -class DetailPanel extends JPanel { - static public final String REMOVE_RESTART_MESSAGE = - String.format("%s", Language.text("contrib.messages.remove_restart")); - - static public final String INSTALL_RESTART_MESSAGE = - String.format("%s", Language.text("contrib.messages.install_restart")); - - static public final String UPDATE_RESTART_MESSAGE = - String.format("%s", Language.text("contrib.messages.update_restart")); - - static public final String PROGRESS_BAR_CONSTRAINT = "Install/Remove Progress Bar Panel"; - - static public final String BUTTON_CONSTRAINT = "Install/Remove Button Panel"; - - static public final String INCOMPATIBILITY_BLUR = "This contribution is not compatible with " - + "the current revision of Processing"; - - private final ListPanel listPanel; - private final ContributionListing contribListing = ContributionListing.getInstance(); - - static final int BUTTON_WIDTH = Toolkit.zoom(100); - static Icon foundationIcon; - - /** - * Should only be set through setContribution(), - * otherwise UI components will not be updated. - */ - private Contribution contrib; - - public Contribution getContrib() { - return contrib; - } - - private LocalContribution getLocalContrib() { - return (LocalContribution) contrib; - } - - private boolean alreadySelected; - private boolean enableHyperlinks; - private JTextPane descriptionPane; - private JLabel notificationLabel; - private JButton updateButton; - JProgressBar installProgressBar; - private JButton installRemoveButton; - final private JPopupMenu contextMenu; - final private JMenuItem openFolder; - - final private JPanel barButtonCardPane; - private CardLayout barButtonCardLayout; - - static private final String installText = Language.text("contrib.install"); - static private final String removeText = Language.text("contrib.remove"); - static private final String undoText = Language.text("contrib.undo"); - - boolean updateInProgress; - boolean installInProgress; - boolean removeInProgress; - - String description; - - - DetailPanel(ListPanel contributionListPanel) { - if (foundationIcon == null) { - foundationIcon = Toolkit.getLibIconX("icons/foundation", 32); - } - - listPanel = contributionListPanel; - barButtonCardPane = new JPanel(); - - contextMenu = new JPopupMenu(); - openFolder = new JMenuItem("Open Folder"); - openFolder.addActionListener(e -> { - if (contrib instanceof LocalContribution) { - File folder = ((LocalContribution) contrib).getFolder(); - Platform.openFolder(folder); - } - }); - - setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - - addPaneComponents(); - - setBackground(listPanel.getBackground()); - setOpaque(true); - setSelected(false); - - setExpandListener(this, new MouseAdapter() { - public void mousePressed(MouseEvent e) { - if (contrib.isCompatible(Base.getRevision())) { - listPanel.setSelectedPanel(DetailPanel.this); - } else { - setErrorMessage(contrib.getName() + - " cannot be used with this version of Processing"); - } - } - }); - } - - - /** - * Create the widgets for the header panel which is visible when the - * library panel is not clicked. - */ - private void addPaneComponents() { - setLayout(new BorderLayout()); - - descriptionPane = new JTextPane(); - descriptionPane.setInheritsPopupMenu(true); - descriptionPane.setEditable(false); // why would this ever be true? - Insets margin = descriptionPane.getMargin(); - margin.bottom = 0; - descriptionPane.setMargin(margin); - descriptionPane.setContentType("text/html"); - setTextStyle(descriptionPane, "0.95em"); - descriptionPane.setOpaque(false); - if (UIManager.getLookAndFeel().getID().equals("Nimbus")) { - descriptionPane.setBackground(new Color(0, 0, 0, 0)); - } - - descriptionPane.setBorder(new EmptyBorder(4, 7, 7, 7)); - descriptionPane.setHighlighter(null); - descriptionPane.addHyperlinkListener(e -> { - if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - // for 3.2.3, added the isSelected() prompt here, rather than - // adding/removing the listener repeatedly - if (isSelected()) { - if (enableHyperlinks && e.getURL() != null) { - Platform.openURL(e.getURL().toString()); - } - } - } - }); - - add(descriptionPane, BorderLayout.CENTER); - - JPanel updateBox = new JPanel(); - updateBox.setLayout(new BorderLayout()); - - notificationLabel = new JLabel(); - notificationLabel.setInheritsPopupMenu(true); - notificationLabel.setVisible(false); - notificationLabel.setOpaque(false); - notificationLabel.setFont(ManagerFrame.SMALL_PLAIN); - - { - updateButton = new JButton("Update"); - updateButton.setInheritsPopupMenu(true); - Dimension dim = - new Dimension(BUTTON_WIDTH, updateButton.getPreferredSize().height); - updateButton.setMinimumSize(dim); - updateButton.setPreferredSize(dim); - updateButton.setOpaque(false); - updateButton.setVisible(false); - - updateButton.addActionListener(e -> update()); - } - - updateBox.add(updateButton, BorderLayout.EAST); - updateBox.add(notificationLabel, BorderLayout.WEST); - updateBox.setBorder(new EmptyBorder(4, 7, 7, 7)); - updateBox.setOpaque(false); - add(updateBox, BorderLayout.SOUTH); - - JPanel rightPane = new JPanel(); - rightPane.setInheritsPopupMenu(true); - rightPane.setOpaque(false); - rightPane.setLayout(new BoxLayout(rightPane, BoxLayout.Y_AXIS)); - rightPane.setMinimumSize(new Dimension(BUTTON_WIDTH, 1)); - add(rightPane, BorderLayout.EAST); - - barButtonCardLayout = new CardLayout(); - barButtonCardPane.setLayout(barButtonCardLayout); - barButtonCardPane.setInheritsPopupMenu(true); - barButtonCardPane.setOpaque(false); - barButtonCardPane.setMinimumSize(new Dimension(BUTTON_WIDTH, 1)); - - { - installProgressBar = new JProgressBar(); - installProgressBar.setInheritsPopupMenu(true); - installProgressBar.setStringPainted(true); - resetInstallProgressBarState(); - Dimension dim = - new Dimension(BUTTON_WIDTH, - installProgressBar.getPreferredSize().height); - installProgressBar.setPreferredSize(dim); - installProgressBar.setMaximumSize(dim); - installProgressBar.setMinimumSize(dim); - installProgressBar.setOpaque(false); - installProgressBar.setAlignmentX(CENTER_ALIGNMENT); - } - - installRemoveButton = new JButton(" "); - installRemoveButton.setInheritsPopupMenu(true); - installRemoveButton.addActionListener(e -> { - String mode = installRemoveButton.getText(); - if (mode.equals(installText)) { - install(); - } else if (mode.equals(removeText)) { - remove(); - } else if (mode.equals(undoText)) { - undo(); - } - }); - - Dimension installButtonDimensions = installRemoveButton.getPreferredSize(); - installButtonDimensions.width = BUTTON_WIDTH; - installRemoveButton.setPreferredSize(installButtonDimensions); - installRemoveButton.setMaximumSize(installButtonDimensions); - installRemoveButton.setMinimumSize(installButtonDimensions); - installRemoveButton.setOpaque(false); - installRemoveButton.setAlignmentX(CENTER_ALIGNMENT); - - JPanel barPane = new JPanel(); - barPane.setOpaque(false); - - JPanel buttonPane = new JPanel(); - buttonPane.setOpaque(false); - buttonPane.add(installRemoveButton); - - barButtonCardPane.add(buttonPane, BUTTON_CONSTRAINT); - barButtonCardPane.add(barPane, PROGRESS_BAR_CONSTRAINT); - barButtonCardLayout.show(barButtonCardPane, BUTTON_CONSTRAINT); - - rightPane.add(barButtonCardPane); - - // Set the minimum size of this pane to be the sum of the height of the - // progress bar and install button - Dimension dim = - new Dimension(BUTTON_WIDTH, - installRemoveButton.getPreferredSize().height); - rightPane.setMinimumSize(dim); - rightPane.setPreferredSize(dim); - } - - - private void reorganizePaneComponents() { - BorderLayout layout = (BorderLayout) this.getLayout(); - remove(layout.getLayoutComponent(BorderLayout.SOUTH)); - remove(layout.getLayoutComponent(BorderLayout.EAST)); - - JPanel updateBox = new JPanel(); - updateBox.setLayout(new BorderLayout()); - updateBox.setInheritsPopupMenu(true); - updateBox.add(notificationLabel, BorderLayout.WEST); - updateBox.setBorder(new EmptyBorder(4, 7, 7, 7)); - updateBox.setOpaque(false); - add(updateBox, BorderLayout.SOUTH); - - JPanel rightPane = new JPanel(); - rightPane.setInheritsPopupMenu(true); - rightPane.setOpaque(false); - rightPane.setLayout(new BoxLayout(rightPane, BoxLayout.Y_AXIS)); - rightPane.setMinimumSize(new Dimension(BUTTON_WIDTH, 1)); - add(rightPane, BorderLayout.EAST); - - if (updateButton.isVisible() && !removeInProgress && !contrib.isDeletionFlagged()) { - JPanel updateRemovePanel = new JPanel(); - updateRemovePanel.setLayout(new FlowLayout()); - updateRemovePanel.setOpaque(false); - updateRemovePanel.add(updateButton); - updateRemovePanel.setInheritsPopupMenu(true); - updateRemovePanel.add(installRemoveButton); - updateBox.add(updateRemovePanel, BorderLayout.EAST); - - JPanel barPane = new JPanel(); - barPane.setOpaque(false); - barPane.setInheritsPopupMenu(true); - rightPane.add(barPane); - - if (updateInProgress) { - barButtonCardLayout.show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT); - } - } else { - updateBox.add(updateButton, BorderLayout.EAST); - barButtonCardPane.removeAll(); - - JPanel barPane = new JPanel(); - barPane.setOpaque(false); - barPane.setInheritsPopupMenu(true); - - JPanel buttonPane = new JPanel(); - buttonPane.setOpaque(false); - buttonPane.setInheritsPopupMenu(true); - buttonPane.add(installRemoveButton); - - barButtonCardPane.add(buttonPane, BUTTON_CONSTRAINT); - barButtonCardPane.add(barPane, PROGRESS_BAR_CONSTRAINT); - if (installInProgress || removeInProgress || updateInProgress) { - barButtonCardLayout.show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT); - } else { - barButtonCardLayout.show(barButtonCardPane, BUTTON_CONSTRAINT); - } - rightPane.add(barButtonCardPane); - } - - Dimension progressDim = installProgressBar.getPreferredSize(); - Dimension installDim = installRemoveButton.getPreferredSize(); - progressDim.width = BUTTON_WIDTH; - progressDim.height = Math.max(progressDim.height, installDim.height); - rightPane.setMinimumSize(progressDim); - rightPane.setPreferredSize(progressDim); - } - - - private void setExpandListener(Component component, - MouseListener expandListener) { - // If it's a JButton, adding the listener will make this stick on OS X - // https://github.com/processing/processing/issues/3172 - if (!(component instanceof JButton)) { - component.addMouseListener(expandListener); - if (component instanceof Container) { - for (Component child : ((Container) component).getComponents()) { - setExpandListener(child, expandListener); - } - } - } - } - - - private void blurContributionPanel(Component component) { - component.setFocusable(false); - component.setEnabled(false); - if (component instanceof JComponent) { - ((JComponent) component).setToolTipText(INCOMPATIBILITY_BLUR); - } - if (component instanceof Container) { - for (Component child : ((Container) component).getComponents()) { - blurContributionPanel(child); - } - } - } - - - public void setContribution(Contribution contrib) { - this.contrib = contrib; - - if (contrib.isSpecial()) { - JLabel iconLabel = new JLabel(foundationIcon); - iconLabel.setBorder(new EmptyBorder(4, 7, 7, 7)); - iconLabel.setVerticalAlignment(SwingConstants.TOP); - add(iconLabel, BorderLayout.WEST); - } - - // Avoid ugly synthesized bold - Font boldFont = ManagerFrame.SMALL_BOLD; - String fontFace = ""; - - StringBuilder desc = new StringBuilder(); - desc.append(""); - desc.append(fontFace); - if (contrib.getUrl() == null) { - desc.append(contrib.getName()); - } else { - desc.append(""); - desc.append(contrib.getName()); - desc.append(""); - } - desc.append(" "); - - String prettyVersion = contrib.getPrettyVersion(); - if (prettyVersion != null) { - desc.append(prettyVersion); - } - desc.append("
"); - - String authorList = contrib.getAuthorList(); - if (authorList != null && !authorList.isEmpty()) { - desc.append(toHtmlLinks(contrib.getAuthorList())); - } - desc.append("

"); - - if (contrib.isDeletionFlagged()) { - desc.append(REMOVE_RESTART_MESSAGE); - } else if (contrib.isRestartFlagged()) { - desc.append(INSTALL_RESTART_MESSAGE); - } else if (contrib.isUpdateFlagged()) { - desc.append(UPDATE_RESTART_MESSAGE); - } else { - String sentence = contrib.getSentence(); - if (sentence == null || sentence.isEmpty()) { - sentence = String.format("%s", Language.text("contrib.errors.description_unavailable")); - } else { - sentence = sanitizeHtmlTags(sentence); - sentence = toHtmlLinks(sentence); - } - desc.append(sentence); - } - - long lastUpdatedUTC = contrib.getLastUpdated(); - if (lastUpdatedUTC != 0) { - DateFormat dateFormatter = DateFormat.getDateInstance(DateFormat.MEDIUM); - Date lastUpdatedDate = new Date(lastUpdatedUTC); - if (prettyVersion != null) { - desc.append(", "); - } - desc.append("Last Updated on "); - desc.append(dateFormatter.format(lastUpdatedDate)); - } - - desc.append(""); - description = desc.toString(); - descriptionPane.setText(description); - - if (contribListing.hasUpdates(contrib) && contrib.isCompatible(Base.getRevision())) { - StringBuilder versionText = new StringBuilder(); - versionText.append(""); - //noinspection StatementWithEmptyBody - if (contrib.isUpdateFlagged() || contrib.isDeletionFlagged()) { - // Already marked for deletion, see requiresRestart() notes below. - // versionText.append("To finish an update, reinstall this contribution after restarting."); - - } else { - String latestVersion = contribListing.getLatestPrettyVersion(contrib); - if (latestVersion != null) { - versionText.append("New version (" + latestVersion + ") available."); - } else { - versionText.append("New version available."); - } - } - versionText.append(""); - notificationLabel.setText(versionText.toString()); - notificationLabel.setVisible(true); - } else { - notificationLabel.setText(""); - notificationLabel.setVisible(false); - } - - updateButton.setEnabled(true); - updateButton.setVisible((contribListing.hasUpdates(contrib) && !contrib.isUpdateFlagged() && !contrib.isDeletionFlagged()) || updateInProgress); - - if (contrib.isDeletionFlagged()) { - installRemoveButton.setText(undoText); - - } else if (contrib.isInstalled()) { - installRemoveButton.setText(removeText); - installRemoveButton.setVisible(true); - installRemoveButton.setEnabled(!contrib.isUpdateFlagged()); - reorganizePaneComponents(); - } else { - installRemoveButton.setText(installText); - } - - contextMenu.removeAll(); - if (contrib.isInstalled()) { - contextMenu.add(openFolder); - setComponentPopupMenu(contextMenu); - } else { - setComponentPopupMenu(null); - } - - if (!contrib.isCompatible(Base.getRevision())) { - blurContributionPanel(this); - } - } - - - private void installContribution(AvailableContribution info) { - if (info.link == null) { - setErrorMessage(Language.interpolate("contrib.unsupported_operating_system", info.getType())); - } else { - installContribution(info, info.link); - } - } - - - private void finishInstall(boolean error) { - resetInstallProgressBarState(); - installRemoveButton.setEnabled(!contrib.isUpdateFlagged()); - - if (error) { - setErrorMessage(Language.text("contrib.download_error")); - } - barButtonCardLayout.show(barButtonCardPane, BUTTON_CONSTRAINT); - installInProgress = false; - if (updateInProgress) { - updateInProgress = false; - } - updateButton.setVisible(contribListing.hasUpdates(contrib) && !contrib.isUpdateFlagged()); - setSelected(true); - } - - - private void installContribution(AvailableContribution ad, String url) { - installRemoveButton.setEnabled(false); - - try { - URL downloadUrl = new URL(url); - installProgressBar.setVisible(true); - - ContribProgressBar downloadProgress = new ContribProgressBar(installProgressBar) { - public void finishedAction() { - // nothing? - } - - public void cancelAction() { - finishInstall(false); - } - }; - - ContribProgressBar installProgress = new ContribProgressBar(installProgressBar) { - public void finishedAction() { - finishInstall(isError()); - } - - public void cancelAction() { - finishedAction(); - } - }; - - ContributionManager.downloadAndInstall(getBase(), downloadUrl, ad, - downloadProgress, installProgress, - getStatusPanel()); - - } catch (MalformedURLException e) { - Messages.showWarning(Language.text("contrib.errors.install_failed"), - Language.text("contrib.errors.malformed_url"), e); - // not sure why we'd re-enable the button if it had an error... - //installRemoveButton.setEnabled(true); - } - } - - - protected void resetInstallProgressBarState() { - installProgressBar.setString(Language.text("contrib.progress.starting")); - installProgressBar.setIndeterminate(false); - installProgressBar.setValue(0); - installProgressBar.setVisible(false); - } - - - /* - static final HyperlinkListener NULL_HYPERLINK_LISTENER = new HyperlinkListener() { - public void hyperlinkUpdate(HyperlinkEvent e) { } - }; - */ - - - /** - * Should be called whenever this component is selected (clicked on) - * or unselected, even if it is already selected. - */ - public void setSelected(boolean isSelected) { - // Only enable hyperlinks if this component is already selected. - // Why? Because otherwise if the user happened to click on what is - // now a hyperlink, it will be opened as the mouse is released. - enableHyperlinks = alreadySelected; - - if (contrib != null) { - updateButton.setVisible((contribListing.hasUpdates(contrib) && !contrib.isUpdateFlagged() && !contrib.isDeletionFlagged()) || updateInProgress); - updateButton.setEnabled(contribListing.listDownloadSuccessful()); - } - installRemoveButton.setVisible(isSelected() || installRemoveButton.getText().equals(Language.text("contrib.remove")) || updateInProgress); - installRemoveButton.setEnabled(installRemoveButton.getText().equals(Language.text("contrib.remove")) || contribListing.listDownloadSuccessful()); - reorganizePaneComponents(); - - /* - descriptionPane.removeHyperlinkListener(NULL_HYPERLINK_LISTENER); - descriptionPane.removeHyperlinkListener(conditionalHyperlinkOpener); - if (isSelected()) { - descriptionPane.addHyperlinkListener(conditionalHyperlinkOpener); -// descriptionPane.setEditable(false); - } else { - descriptionPane.addHyperlinkListener(NULL_HYPERLINK_LISTENER); -// descriptionPane.setEditable(true); - } - */ - - // Update style of hyperlinks - setSelectionStyle(descriptionPane, isSelected()); - - alreadySelected = isSelected(); - } - - - public boolean isSelected() { - return listPanel.getSelectedPanel() == this; - } - - - public void setForeground(Color fg) { - super.setForeground(fg); - - if (contrib != null) { - boolean installed = contrib.isInstalled(); - setForegroundStyle(descriptionPane, installed, isSelected()); - } - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - static String sanitizeHtmlTags(String stringIn) { - stringIn = stringIn.replaceAll("<", "<"); - stringIn = stringIn.replaceAll(">", ">"); - return stringIn; - } - - - /** - * This has a [link](http://example.com/) in [it](http://example.org/). - * - * Becomes... - * - * This has a link in it. - */ - static String toHtmlLinks(String stringIn) { - Pattern p = Pattern.compile("\\[(.*?)]\\((.*?)\\)"); - Matcher m = p.matcher(stringIn); - - StringBuilder sb = new StringBuilder(); - - int start = 0; - while (m.find(start)) { - sb.append(stringIn, start, m.start()); - - String text = m.group(1); - String url = m.group(2); - - sb.append(""); - sb.append(text); - sb.append(""); - - start = m.end(); - } - sb.append(stringIn.substring(start)); - return sb.toString(); - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - /** - * Sets coloring based on whether installed or not; - * also makes ugly blue HTML links into the specified color (black). - */ - static void setForegroundStyle(JTextPane textPane, - boolean installed, boolean selected) { - Document doc = textPane.getDocument(); - if (doc instanceof HTMLDocument) { - HTMLDocument html = (HTMLDocument) doc; - StyleSheet stylesheet = html.getStyleSheet(); - - // slightly grayed when installed - String c = (installed && !selected) ? "#555555" : "#000000"; - stylesheet.addRule("body { color:" + c + "; }"); - stylesheet.addRule("a { color:" + c + "; }"); - } - } - - - static void setTextStyle(JTextPane textPane, String fontSize) { - Document doc = textPane.getDocument(); - if (doc instanceof HTMLDocument) { - HTMLDocument html = (HTMLDocument) doc; - StyleSheet stylesheet = html.getStyleSheet(); - stylesheet.addRule("body { " + - " margin: 0; padding: 0;" + - " font-family: " + Toolkit.getSansFontName() + ", Arial, Helvetica, sans-serif;" + - " font-size: 100%;" + "font-size: " + fontSize + "; " + - "}"); - } - } - - - static void setSelectionStyle(JTextPane textPane, boolean selected) { - Document doc = textPane.getDocument(); - if (doc instanceof HTMLDocument) { - HTMLDocument html = (HTMLDocument) doc; - StyleSheet styleSheet = html.getStyleSheet(); - if (selected) { - styleSheet.addRule("a { text-decoration:underline } "); - } else { - styleSheet.addRule("a { text-decoration:none }"); - } - } - } - - - public void install() { - clearStatusMessage(); - installInProgress = true; - barButtonCardLayout.show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT); - if (contrib instanceof AvailableContribution) { - installContribution((AvailableContribution) contrib); - contribListing.replaceContribution(contrib, contrib); - } - } - - - public void update() { - clearStatusMessage(); - updateInProgress = true; - if (contrib.getType().requiresRestart()) { - installRemoveButton.setEnabled(false); - installProgressBar.setVisible(true); - installProgressBar.setIndeterminate(true); - - ContribProgressBar progress = new UpdateProgressBar(installProgressBar); - getLocalContrib().removeContribution(getBase(), progress, getStatusPanel()); - } else { - updateButton.setEnabled(false); - installRemoveButton.setEnabled(false); - AvailableContribution ad = - contribListing.getAvailableContribution(contrib); - installContribution(ad, ad.link); - } - } - - - class UpdateProgressBar extends ContribProgressBar { - public UpdateProgressBar(JProgressBar progressBar) { - super(progressBar); - } - - public void finishedAction() { - resetInstallProgressBarState(); - updateButton.setEnabled(false); - AvailableContribution ad = - contribListing.getAvailableContribution(contrib); - String url = ad.link; - installContribution(ad, url); - } - - @Override - public void cancelAction() { - resetInstallProgressBarState(); - //listPanel.contributionTab.statusPanel.setMessage(""); // same as clear? - clearStatusMessage(); - updateInProgress = false; - installRemoveButton.setEnabled(true); - if (contrib.isDeletionFlagged()) { - getLocalContrib().setUpdateFlag(true); - getLocalContrib().setDeletionFlag(false); - contribListing.replaceContribution(contrib, contrib); - } - - if (isModeActive(contrib)) { - updateButton.setEnabled(true); - } else { - // TODO: remove or uncomment if the button was added - //listPanel.contributionTab.restartButton.setVisible(true); - } - } - } - - - public void remove() { - clearStatusMessage(); - if (contrib.isInstalled() && contrib instanceof LocalContribution) { - removeInProgress = true; - barButtonCardLayout.show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT); - updateButton.setEnabled(false); - installRemoveButton.setEnabled(false); - installProgressBar.setVisible(true); - installProgressBar.setIndeterminate(true); - - ContribProgressBar monitor = new RemoveProgressBar(installProgressBar); - getLocalContrib().removeContribution(getBase(), monitor, getStatusPanel()); - } - } - - - class RemoveProgressBar extends ContribProgressBar { - public RemoveProgressBar(JProgressBar progressBar) { - super(progressBar); - } - - private void preAction() { - resetInstallProgressBarState(); - removeInProgress = false; - installRemoveButton.setEnabled(true); - reorganizePaneComponents(); - setSelected(true); // Needed for smooth working. Dunno why, though... - } - - public void finishedAction() { - // Finished uninstalling the library - preAction(); - } - - public void cancelAction() { - preAction(); - - if (isModeActive(contrib)) { - updateButton.setEnabled(true); - } else { - // TODO: remove or uncomment if the button was added - //contributionTab.restartButton.setVisible(true); - } - } - } - - - private void undo() { - clearStatusMessage(); - if (contrib instanceof LocalContribution) { - LocalContribution installed = getLocalContrib(); - installed.setDeletionFlag(false); - contribListing.replaceContribution(contrib, contrib); // ?? - Iterator contribsListIter = contribListing.allContributions.iterator(); - boolean toBeRestarted = false; - while (contribsListIter.hasNext()) { - Contribution contribElement = contribsListIter.next(); - if (contrib.getType().equals(contribElement.getType())) { - if (contribElement.isDeletionFlagged() || - contribElement.isUpdateFlagged()) { - toBeRestarted = !toBeRestarted; - break; - } - } - } - // TODO: remove or uncomment if the button was added - //listPanel.contributionTab.restartButton.setVisible(toBeRestarted); - } - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - // Can't be called from the constructor because the path isn't set all the - // way down. However, it's not Base changes over time. More importantly, - // though, is that the functions being called in Base are somewhat suspect - // since they're contribution-related, and should perhaps live closer. - private Base getBase() { - return listPanel.contributionTab.editor.getBase(); - } - - - private boolean isModeActive(Contribution contrib) { - if (contrib.getType() == ContributionType.MODE) { - ModeContribution m = (ModeContribution) contrib; - for (Editor e : getBase().getEditors()) { - if (e.getMode().equals(m.getMode())) { - return true; - } - } - } - return false; - } - - - private StatusPanel getStatusPanel() { - return listPanel.contributionTab.statusPanel; // TODO this is gross [fry] - } - - - private void clearStatusMessage() { - getStatusPanel().clearMessage(); - } - - - private void setErrorMessage(String message) { - getStatusPanel().setErrorMessage(message); - } -} diff --git a/app/src/processing/app/contrib/ExamplesContribution.java b/app/src/processing/app/contrib/ExamplesContribution.java index 2e091c721..e264d7da0 100644 --- a/app/src/processing/app/contrib/ExamplesContribution.java +++ b/app/src/processing/app/contrib/ExamplesContribution.java @@ -36,8 +36,7 @@ public class ExamplesContribution extends LocalContribution { /** - * Function to determine whether or not the example present in the - * exampleLocation directory is compatible with the current mode. + * Determine whether the example is compatible with the current Mode. * @return true if compatible with the Mode of the currently active editor */ static public boolean isCompatible(Mode mode, StringDict props) { @@ -68,9 +67,9 @@ public class ExamplesContribution extends LocalContribution { static public void loadMissing(Base base) { File examplesFolder = Base.getSketchbookExamplesFolder(); - List contribExamples = base.getExampleContribs(); + List contribExamples = base.getContribExamples(); - Map existing = new HashMap(); + Map existing = new HashMap<>(); for (ExamplesContribution contrib : contribExamples) { existing.put(contrib.getFolder(), contrib); } diff --git a/app/src/processing/app/contrib/ListPanel.java b/app/src/processing/app/contrib/ListPanel.java index 8e4be3b21..8a397f1c1 100644 --- a/app/src/processing/app/contrib/ListPanel.java +++ b/app/src/processing/app/contrib/ListPanel.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2013-15 The Processing Foundation + Copyright (c) 2013-22 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -24,75 +24,90 @@ package processing.app.contrib; import java.awt.*; import java.util.List; import java.util.*; -import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; import javax.swing.*; import javax.swing.RowSorter.SortKey; -import javax.swing.border.Border; import javax.swing.table.*; import processing.app.Base; -import processing.app.Platform; +import processing.app.Util; +import processing.app.ui.Theme; +import processing.app.laf.PdeScrollBarUI; import processing.app.ui.Toolkit; // The "Scrollable" implementation and its methods here take care of preventing // the scrolling area from running exceptionally slowly. Not sure why they're -// necessary in the first place, however; seems like odd behavior. +// necessary in the first place, however. Is that hiding a bigger problem? // It also allows the description text in the panels to wrap properly. -public class ListPanel extends JPanel -implements Scrollable, ContributionListing.ChangeListener { +public class ListPanel extends JPanel implements Scrollable { ContributionTab contributionTab; - TreeMap panelByContribution = new TreeMap<>(ContributionListing.COMPARATOR); +// static public Comparator COMPARATOR = +// Comparator.comparing(o -> o.getName().toLowerCase()); +// TreeMap detailForContrib = +// new TreeMap<>(ContributionListing.COMPARATOR); +// TreeMap detailForContrib = +// new TreeMap<>(Comparator.comparing(o -> o.getName().toLowerCase())); + Map detailForContrib = + new ConcurrentHashMap<>(); - private DetailPanel selectedPanel; - protected ContributionRowFilter filter; + private final Contribution.Filter filter; + + private StatusDetail selectedDetail; + protected ContributionRowFilter rowFilter; protected JTable table; protected TableRowSorter sorter; - ContributionTableModel model; + protected ContributionTableModel model; + + // state icons appearing to the left side of the list + Icon upToDateIcon; + Icon updateAvailableIcon; + Icon incompatibleIcon; + Icon downloadingIcon; + + // used in the list next to the creator name + Icon foundationIcon; + + Color headerFgColor; + Color headerBgColor; + Color sectionColor; + Color rowColor; + + Color textColor; + Color selectionColor; + Color textColorIncompatible; + Color selectionColorIncompatible; + JScrollPane scrollPane; - static Icon upToDateIcon; - static Icon updateAvailableIcon; - static Icon incompatibleIcon; - static Icon foundationIcon; - static Icon downloadingIcon; - - // Should this be in theme.txt? Of course! Is it? No. - static final Color HEADER_BGCOLOR = new Color(0xffEBEBEB); - static final Color SECTION_COLOR = new Color(0xFFf8f8f8); - static final Color SELECTION_COLOR = new Color(0xffe0fffd); - static final SectionHeaderContribution[] sections = { - new SectionHeaderContribution(ContributionType.LIBRARY), - new SectionHeaderContribution(ContributionType.MODE), - new SectionHeaderContribution(ContributionType.TOOL), - new SectionHeaderContribution(ContributionType.EXAMPLES) + new SectionHeaderContribution(ContributionType.LIBRARY), + new SectionHeaderContribution(ContributionType.MODE), + new SectionHeaderContribution(ContributionType.TOOL), + new SectionHeaderContribution(ContributionType.EXAMPLES) }; - public ListPanel() { - if (upToDateIcon == null) { - upToDateIcon = Toolkit.getLibIconX("manager/up-to-date"); - updateAvailableIcon = Toolkit.getLibIconX("manager/update-available"); - incompatibleIcon = Toolkit.getLibIconX("manager/incompatible"); - foundationIcon = Toolkit.getLibIconX("icons/foundation", 16); - downloadingIcon = Toolkit.getLibIconX("manager/downloading"); - } - } - public ListPanel(final ContributionTab contributionTab, final Contribution.Filter filter, final boolean enableSections, final ContributionColumn... columns) { - this(); this.contributionTab = contributionTab; - this.filter = new ContributionRowFilter(filter); + this.filter = filter; + + this.rowFilter = new ContributionRowFilter(filter); + +// if (upToDateIcon == null) { +// upToDateIcon = Toolkit.getLibIconX("manager/up-to-date"); +// updateAvailableIcon = Toolkit.getLibIconX("manager/update-available"); +// incompatibleIcon = Toolkit.getLibIconX("manager/incompatible"); +// foundationIcon = Toolkit.getLibIconX("icons/foundation", 16); +// downloadingIcon = Toolkit.getLibIconX("manager/downloading"); +// } - setLayout(new GridBagLayout()); setOpaque(true); - setBackground(Color.WHITE); model = new ContributionTableModel(columns); model.enableSections(enableSections); table = new JTable(model) { @@ -101,36 +116,43 @@ implements Scrollable, ContributionListing.ChangeListener { Component c = super.prepareRenderer(renderer, row, column); Object rowValue = getValueAt(row, column); if (rowValue instanceof SectionHeaderContribution) { - c.setBackground(SECTION_COLOR); + c.setBackground(sectionColor); } else if (isRowSelected(row)) { - c.setBackground(SELECTION_COLOR); + if (((Contribution) rowValue).isCompatible(Base.getRevision())) { + c.setBackground(selectionColor); + } else { + c.setBackground(selectionColorIncompatible); + } } else { - c.setBackground(Color.white); + c.setBackground(rowColor); } return c; } @Override public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) { + // disallow selection of the header lines if (!(getValueAt(rowIndex, columnIndex) instanceof SectionHeaderContribution)) { super.changeSelection(rowIndex, columnIndex, toggle, extend); } } }; - // There is a space before Status scrollPane = new JScrollPane(table); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.getVerticalScrollBar().setUI(new PdeScrollBarUI("manager.scrollbar")); scrollPane.setBorder(BorderFactory.createEmptyBorder()); table.setFillsViewportHeight(true); table.setDefaultRenderer(Contribution.class, new ContribStatusRenderer()); - table.setFont(ManagerFrame.NORMAL_PLAIN); table.setRowHeight(Toolkit.zoom(28)); table.setRowMargin(Toolkit.zoom(6)); - table.getColumnModel().setColumnMargin(0); - table.getColumnModel().getColumn(0).setMaxWidth(ManagerFrame.STATUS_WIDTH); - table.getColumnModel().getColumn(2).setMinWidth(ManagerFrame.AUTHOR_WIDTH); - table.getColumnModel().getColumn(2).setMaxWidth(ManagerFrame.AUTHOR_WIDTH); + + TableColumnModel tcm = table.getColumnModel(); + tcm.setColumnMargin(0); + tcm.getColumn(0).setMaxWidth(ManagerFrame.STATUS_WIDTH); + tcm.getColumn(2).setMinWidth(ManagerFrame.AUTHOR_WIDTH); + tcm.getColumn(2).setMaxWidth(ManagerFrame.AUTHOR_WIDTH); + table.setShowGrid(false); table.setColumnSelectionAllowed(false); table.setCellSelectionEnabled(false); @@ -139,12 +161,17 @@ implements Scrollable, ContributionListing.ChangeListener { table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.getSelectionModel().addListSelectionListener(event -> { - // TODO this executes 2 times when clicked and 1 time when traversed - // using arrow keys. Ideally this should always be true but while - // clearing the table something fishy is going on. [Akarshit 150704] - if (table.getSelectedRow() != -1) { - setSelectedPanel(panelByContribution.get(table.getValueAt(table - .getSelectedRow(), 0))); + // This is called twice for each mouse click, once on mouse press with + // event.getValueIsAdjusting()) set true, and again when the mouse is + // released where adjusting will be set false. But instead of only + // responding to one or the other, need to fire on both so that the + // selection updates while the user drags mouse across the list (and + // not just when released). Using the arrow keys will only fire once + // because adjusting will be false (no ongoing drag with keys). + int row = table.getSelectedRow(); + if (row != -1) { + Contribution contrib = (Contribution) table.getValueAt(row, 0); + setSelectedDetail(detailForContrib.get(contrib)); // Preventing the focus to move out of filterField after typing every character if (!contributionTab.filterHasFocus()) { table.requestFocusInWindow(); @@ -154,31 +181,67 @@ implements Scrollable, ContributionListing.ChangeListener { sorter = new TableRowSorter<>(model); table.setRowSorter(sorter); - sorter.setRowFilter(this.filter); - for (int i=0; i < model.getColumnCount(); i++) { + sorter.setRowFilter(this.rowFilter); + for (int i = 0; i < model.getColumnCount(); i++) { if (model.columns[i] == ContributionColumn.NAME) { sorter.setSortKeys(Collections.singletonList(new SortKey(i, SortOrder.ASCENDING))); } sorter.setComparator(i, model.columns[i].getComparator()); } table.getTableHeader().setDefaultRenderer(new ContribHeaderRenderer()); - - GroupLayout layout = new GroupLayout(this); - layout.setHorizontalGroup(layout.createParallelGroup().addComponent(scrollPane)); - layout.setVerticalGroup(layout.createSequentialGroup().addComponent(scrollPane)); - - this.setLayout(layout); + table.getTableHeader().setReorderingAllowed(false); + table.getTableHeader().setResizingAllowed(true); table.setVisible(true); + + setLayout(new BorderLayout()); + add(scrollPane, BorderLayout.CENTER); } + + protected void updateTheme() { + headerFgColor = Theme.getColor("manager.list.header.fgcolor"); + headerBgColor = Theme.getColor("manager.list.header.bgcolor"); + sectionColor = Theme.getColor("manager.list.section.color"); + + textColor = Theme.getColor("manager.list.text.color"); + selectionColor = Theme.getColor("manager.list.selection.color"); + + textColorIncompatible = Theme.getColor("manager.list.incompatible.text.color"); + selectionColorIncompatible = Theme.getColor("manager.list.incompatible.selection.color"); + + rowColor = Theme.getColor("manager.list.background.color"); + table.setBackground(rowColor); + + foundationIcon = Toolkit.renderIcon("manager/foundation", Theme.get("manager.list.foundation.color"), 16); + + upToDateIcon = Toolkit.renderIcon("manager/list-up-to-date", Theme.get("manager.list.icon.color"), 16); + updateAvailableIcon = Toolkit.renderIcon("manager/list-update-available", Theme.get("manager.list.icon.color"), 16); + incompatibleIcon = Toolkit.renderIcon("manager/list-incompatible", Theme.get("manager.list.icon.color"), 16); + downloadingIcon = Toolkit.renderIcon("manager/list-downloading", Theme.get("manager.list.icon.color"), 16); + + ((PdeScrollBarUI) scrollPane.getVerticalScrollBar().getUI()).updateTheme(); + } + + + // TODO remove this, yuck [fry 220313] + protected int getScrollBarWidth() { + return scrollPane.getVerticalScrollBar().getPreferredSize().width; + } + + private static int getContributionStatusRank(Contribution c) { + // Uninstalled items are at the bottom of the sort order int pos = 4; + if (c.isInstalled()) { pos = 1; if (ContributionListing.getInstance().hasUpdates(c)) { pos = 2; } if (!c.isCompatible(Base.getRevision())) { + // This is weird because it means some grayed-out items will + // show up before non-gray items. We probably need another + // state icon for 'installed but incompatible' [fry 220116] pos = 3; } } @@ -186,7 +249,10 @@ implements Scrollable, ContributionListing.ChangeListener { } - static class ContribHeaderRenderer extends DefaultTableCellRenderer { + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + class ContribHeaderRenderer extends DefaultTableCellRenderer { public ContribHeaderRenderer() { setHorizontalTextPosition(LEFT); @@ -217,60 +283,77 @@ implements Scrollable, ContributionListing.ChangeListener { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - JTableHeader tableHeader = table.getTableHeader(); - if (tableHeader != null) { - setForeground(tableHeader.getForeground()); - } - setFont(ManagerFrame.SMALL_PLAIN); - setIcon(getSortIcon(table, column)); - setBackground(HEADER_BGCOLOR); -// if (column % 2 == 0) { -// setBackground(new Color(0xdfdfdf)); -// } else { -// setBackground(new Color(0xebebeb)); +// JTableHeader tableHeader = table.getTableHeader(); +// if (tableHeader != null) { +// setForeground(tableHeader.getForeground()); // } + setForeground(headerFgColor); + //setText(getText() + "\u2191\u2193"); + setText(getText() + getSortText(table, column)); + putClientProperty("FlatLaf.styleClass", "small"); +// setFont(ManagerFrame.SMALL_PLAIN); + //setIcon(getSortIcon(table, column)); + setBackground(headerBgColor); setBorder(null); return this; } - /** - * Overloaded to return an icon suitable to the primary sorted column, or null if - * the column is not the primary sort key. - * - * @param table the JTable. - * @param column the column index. - * @return the sort icon, or null if the column is unsorted. - */ - protected Icon getSortIcon(JTable table, int column) { - SortKey sortKey = getSortKey(table, column); + +// /** +// * Return an icon suitable to the primary sorted column, +// * or null if the column is not the primary sort key. +// * +// * @param table the JTable. +// * @param column the column index. +// * @return the sort icon, or null if the column is unsorted. +// */ +// private Icon getSortIcon(JTable table, int column) { +// SortKey sortKey = getSortKey(table); +// if (sortKey != null && table.convertColumnIndexToView(sortKey.getColumn()) == column) { +// switch (sortKey.getSortOrder()) { +// case ASCENDING: +// return UIManager.getIcon("Table.ascendingSortIcon"); +// case DESCENDING: +// return UIManager.getIcon("Table.descendingSortIcon"); +// } +// } +// return null; +// } + + + private String getSortText(JTable table, int column) { + SortKey sortKey = getSortKey(table); if (sortKey != null && table.convertColumnIndexToView(sortKey.getColumn()) == column) { switch (sortKey.getSortOrder()) { case ASCENDING: - return UIManager.getIcon("Table.ascendingSortIcon"); + return " \u2193"; case DESCENDING: - return UIManager.getIcon("Table.descendingSortIcon"); + return " \u2191"; } } - return null; + // if not sorting on this column + return ""; } + /** * Returns the current sort key, or null if the column is unsorted. * * @param table the table - * @param column the column index * @return the SortKey, or null if the column is unsorted */ - protected SortKey getSortKey(JTable table, int column) { + protected SortKey getSortKey(JTable table) { return Optional.ofNullable(table.getRowSorter()) - .map(RowSorter::getSortKeys) - .map(columns -> columns.isEmpty() ? null : columns.get(0)) - .orElse(null); - + .map(RowSorter::getSortKeys) + .map(columns -> columns.isEmpty() ? null : columns.get(0)) + .orElse(null); } } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + private class ContribStatusRenderer extends DefaultTableCellRenderer { @Override @@ -317,49 +400,51 @@ implements Scrollable, ContributionListing.ChangeListener { break; } - if(!contribution.isCompatible(Base.getRevision())){ - label.setForeground(Color.LIGHT_GRAY); + if (contribution.isCompatible(Base.getRevision())) { + label.setForeground(textColor); + } else { + label.setForeground(textColorIncompatible); } return label; } private void configureStatusColumnLabel(JLabel label, Contribution contribution) { Icon icon = null; - label.setFont(ManagerFrame.NORMAL_PLAIN); - if ((panelByContribution.get(contribution)).updateInProgress || - (panelByContribution.get(contribution)).installInProgress) { - // Display "Loading icon" if download/install in progress + StatusDetail detail = detailForContrib.get(contribution); + + if (detail != null && (detail.updateInProgress || detail.installInProgress)) { + // Display "loading" icon if download/install in progress icon = downloadingIcon; } else if (contribution.isInstalled()) { if (!contribution.isCompatible(Base.getRevision())) { icon = incompatibleIcon; } else if (ContributionListing.getInstance().hasUpdates(contribution)) { icon = updateAvailableIcon; - } else if (panelByContribution.get(contribution).installInProgress - || panelByContribution.get(contribution).updateInProgress) { + } else if (detail != null && (detail.installInProgress || detail.updateInProgress)) { icon = downloadingIcon; } else { icon = upToDateIcon; } } - label.setIcon(icon); label.setHorizontalAlignment(SwingConstants.CENTER); } private void configureNameColumnLabel(JTable table, JLabel label, Contribution contribution) { // Generating ellipses based on fontMetrics - final Font boldFont = ManagerFrame.NORMAL_BOLD; - FontMetrics fontMetrics = table.getFontMetrics(boldFont); //table.getFont()); +// final Font boldFont = ManagerFrame.NORMAL_BOLD; + final Font boldFont = Theme.getFont("manager.list.heavy.font"); + FontMetrics fontMetrics = table.getFontMetrics(boldFont); int colSize = table.getColumnModel().getColumn(1).getWidth(); int currentWidth = fontMetrics.stringWidth(contribution.getName() + " | ..."); - String sentence = contribution.getSentence(); - StringBuilder text = new StringBuilder("") - .append(contribution.getName()); + String sentence = Util.removeMarkDownLinks(contribution.getSentence()); + StringBuilder text = + new StringBuilder("") + .append(contribution.getName()); - if (sentence == null) { + if (sentence.length() == 0) { text.append(""); } else { int index; @@ -370,26 +455,27 @@ implements Scrollable, ContributionListing.ChangeListener { } } text.append(" | ").append(sentence, 0, index); - // Adding ellipses only if text doesn't fits into the column + // Adding ellipses only if text doesn't fit into the column if (index != sentence.length()) { text.append("..."); } } text.append(""); label.setText(text.toString()); - label.setFont(ManagerFrame.NORMAL_PLAIN); +// label.setFont(ManagerFrame.NORMAL_PLAIN); } private void configureAuthorsColumnLabel(JLabel label, Contribution contribution) { - if (contribution.isSpecial()) { + if (contribution.isFoundation()) { label.setIcon(foundationIcon); } String authorList = contribution.getAuthorList(); - String name = getAuthorNameWithoutMarkup(authorList); + String name = Util.removeMarkDownLinks(authorList); label.setText(name); label.setHorizontalAlignment(SwingConstants.LEFT); label.setForeground(Color.BLACK); - label.setFont(ManagerFrame.NORMAL_BOLD); + //label.setFont(ManagerFrame.NORMAL_BOLD); + label.setFont(Theme.getFont("manager.list.heavy.font")); } } @@ -409,23 +495,28 @@ implements Scrollable, ContributionListing.ChangeListener { Comparator getComparator() { Comparator comparator = Comparator.comparing(Contribution::getType) - .thenComparingInt(contribution -> contribution instanceof SectionHeaderContribution ? 0 : 1); - switch (this) { - case STATUS: - case STATUS_NO_HEADER: - return comparator.thenComparingInt(ListPanel::getContributionStatusRank); - case AUTHOR: - return comparator.thenComparing(contribution -> getAuthorNameWithoutMarkup(contribution.getAuthorList())); - case NAME: - default: - return comparator.thenComparing(Contribution::getName, String.CASE_INSENSITIVE_ORDER); + .thenComparingInt(contribution -> contribution instanceof SectionHeaderContribution ? 0 : 1); + + if (this == STATUS || this == STATUS_NO_HEADER) { + return comparator.thenComparingInt(ListPanel::getContributionStatusRank); + } else if (this == AUTHOR) { + return comparator.thenComparing(contribution -> Util.removeMarkDownLinks(contribution.getAuthorList())); + } else { // default case, or this == NAME + return comparator.thenComparing(Contribution::getName, String.CASE_INSENSITIVE_ORDER); } } } - static class ContributionTableModel extends AbstractTableModel { - ContributionColumn[] columns = { ContributionColumn.STATUS, ContributionColumn.NAME, ContributionColumn.AUTHOR }; + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static class ContributionTableModel extends AbstractTableModel { + ContributionColumn[] columns = { + ContributionColumn.STATUS, + ContributionColumn.NAME, + ContributionColumn.AUTHOR + }; boolean sectionsEnabled; ContributionTableModel(ContributionColumn... columns) { @@ -449,7 +540,6 @@ implements Scrollable, ContributionListing.ChangeListener { if (column < 0 || column > columns.length) { return ""; } - return columns[column].name; } @@ -460,27 +550,41 @@ implements Scrollable, ContributionListing.ChangeListener { @Override public Object getValueAt(int rowIndex, int columnIndex) { - if (rowIndex >= ContributionListing.getInstance().allContributions.size()) { - return sections[rowIndex - ContributionListing.getInstance().allContributions.size()]; + final Set allContribs = + ContributionListing.getInstance().allContributions; + if (rowIndex >= allContribs.size()) { + return sections[rowIndex - allContribs.size()]; } - - return ContributionListing.getInstance().allContributions.stream().skip(rowIndex).findFirst().orElse(null); + return allContribs.stream().skip(rowIndex).findFirst().orElse(null); } - /* - public void setColumns(ContributionColumn[] columns) { - this.columns = columns; - } - */ - public void enableSections(boolean enable) { this.sectionsEnabled = enable; } } - protected class ContributionRowFilter extends RowFilter { + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static protected boolean matches(Contribution contrib, String typed) { + String search = ".*" + typed.toLowerCase() + ".*"; + + return (matchField(contrib.getName(), search) || + matchField(contrib.getSentence(), search) || + matchField(contrib.getAuthorList(), search) || + matchField(contrib.getParagraph(), search)); + } + + + static private boolean matchField(String field, String regex) { + return (field != null) && field.toLowerCase().matches(regex); + } + + + static class ContributionRowFilter extends RowFilter { Contribution.Filter contributionFilter; - Optional categoryFilter = Optional.empty(); + String categoryFilter; List stringFilters = Collections.emptyList(); ContributionRowFilter(Contribution.Filter contributionFilter) { @@ -488,7 +592,7 @@ implements Scrollable, ContributionListing.ChangeListener { } public void setCategoryFilter(String categoryFilter) { - this.categoryFilter = Optional.ofNullable(categoryFilter); + this.categoryFilter = categoryFilter; } public void setStringFilters(List filters) { @@ -505,24 +609,37 @@ implements Scrollable, ContributionListing.ChangeListener { } private boolean includeContribution(Contribution contribution) { - return contributionFilter.matches(contribution) - && categoryFilter.map(contribution::hasCategory).orElse(true) - && stringFilters.stream().allMatch(pattern -> ContributionListing.getInstance().matches(contribution, pattern)); + return contributionFilter.matches(contribution) && + Optional.ofNullable(categoryFilter).map(contribution::hasCategory).orElse(true) && + stringFilters.stream().allMatch(pattern -> matches(contribution, pattern)); } private boolean includeSection(SectionHeaderContribution section) { return ContributionListing.getInstance().allContributions.stream() - .filter(contribution -> contribution.getType() == section.getType()) - .anyMatch(this::includeContribution); + .filter(contribution -> contribution.getType() == section.getType()) + .anyMatch(this::includeContribution); } } - protected static class SectionHeaderContribution extends Contribution { + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + /** + * Fake contribution that's just a section header for the updates panel. + */ + static class SectionHeaderContribution extends Contribution { ContributionType type; SectionHeaderContribution(ContributionType type) { this.type = type; - this.name = getTypeName(); + + switch (type) { + case LIBRARY: this.name = "Libraries"; break; + case MODE: this.name = "Modes"; break; + case TOOL: this.name = "Tools"; break; + case EXAMPLES: this.name = "Examples"; break; + } } @Override @@ -536,145 +653,109 @@ implements Scrollable, ContributionListing.ChangeListener { } } - static String getAuthorNameWithoutMarkup(String authorList) { - StringBuilder name = new StringBuilder(); - if (authorList != null) { - int parentheses = 0; - for (int i = 0; i < authorList.length(); i++) { - if (authorList.charAt(i) == '[' || authorList.charAt(i) == ']') { - continue; - } - if (authorList.charAt(i) == '(') { - parentheses++; - } else if (authorList.charAt(i) == ')') { - parentheses--; - } else if (parentheses == 0) { - name.append(authorList.charAt(i)); - } - } - } - return name.toString(); - } - - // Thread: EDT - public void contributionAdded(final Contribution contribution) { - if (!panelByContribution.containsKey(contribution)) { -// long t1 = System.currentTimeMillis(); - DetailPanel newPanel = new DetailPanel(this); - panelByContribution.put(contribution, newPanel); - newPanel.setContribution(contribution); - add(newPanel); - model.fireTableDataChanged(); -// long t2 = System.currentTimeMillis(); - updateColors(); // XXX this is the place -// long t3 = System.currentTimeMillis(); -// System.out.println("ListPanel.contributionAdded() " + (t2-t1) + " " + (t3-t2) + " " + contribution.getTypeName() + " " + contribution.getName()); - } - } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . // Thread: EDT - public void contributionRemoved(final Contribution contribution) { - DetailPanel panel = panelByContribution.get(contribution); - if (panel != null) { - remove(panel); - panelByContribution.remove(contribution); - } - model.fireTableDataChanged(); - updateColors(); - updateUI(); - } + protected void contributionAdded(final Contribution contribution) { +// if (true || filter.matches(contribution)) { + if (filter.matches(contribution)) { +// System.out.println(contributionTab.contribType + " tab: " + +// "added " + contribution.name); + //new Exception().printStackTrace(System.out); - - // Thread: EDT - public void contributionChanged(final Contribution oldContrib, - final Contribution newContrib) { - DetailPanel panel = panelByContribution.get(oldContrib); - if (panel == null) { - contributionAdded(newContrib); - } else { - panelByContribution.remove(oldContrib); - panel.setContribution(newContrib); - panelByContribution.put(newContrib, panel); + if (!detailForContrib.containsKey(contribution)) { +// System.out.println(contributionTab.contribType + " tab: " + +// "actually adding " + contribution.name); +// new Exception().printStackTrace(System.out); +// long t1 = System.currentTimeMillis(); + //StatusPanelDetail newPanel = new StatusPanelDetail(this); + StatusDetail newPanel = + new StatusDetail(contributionTab.base, contributionTab.statusPanel); + detailForContrib.put(contribution, newPanel); + newPanel.setContrib(contribution); +// add(newPanel); model.fireTableDataChanged(); +// long t2 = System.currentTimeMillis(); +// System.out.println("ListPanel.contributionAdded() " + (t2 - t1) + " " + contribution.getTypeName() + " " + contribution.getName()); } +// } else { +// System.out.println("ignoring contrib " + contribution.getName()); + } } // Thread: EDT - public void filterLibraries(String category, List filters) { - filter.setCategoryFilter(category); - filter.setStringFilters(filters); + protected void contributionRemoved(final Contribution contribution) { + if (filter.matches(contribution)) { +// System.out.println(contributionTab.contribType + " tab: " + +// "removed " + contribution.name); +// if (true || filter.matches(contribution)) { + StatusDetail panel = detailForContrib.get(contribution); + if (panel != null) { + detailForContrib.remove(contribution); + } + model.fireTableDataChanged(); + updateUI(); + } + } + + + // Thread: EDT + protected void contributionChanged(final Contribution oldContrib, + final Contribution newContrib) { + if (filter.matches(oldContrib)) { +// if (true || filter.matches(oldContrib)) { +// System.out.println(contributionTab.contribType + " tab: " + +// "changed " + oldContrib + " -> " + newContrib); +// new Exception().printStackTrace(System.out); + StatusDetail detail = detailForContrib.get(oldContrib); +// if (panel == null) { +//// System.out.println("panel null for " + newContrib); +// contributionAdded(newContrib); +// } else { + detailForContrib.remove(oldContrib); + detail.setContrib(newContrib); + detailForContrib.put(newContrib, detail); + model.fireTableDataChanged(); +// } + } + } + + + // Thread: EDT + protected void filterLibraries(String category, List filters) { + rowFilter.setCategoryFilter(category); + rowFilter.setStringFilters(filters); model.fireTableDataChanged(); } + protected void fireChange() { + model.fireTableDataChanged(); + } +// protected void filterDummy(String category) { +// System.out.println("LAST CHANCE DUMMY"); +//// rowFilter.setCategoryFilter(category); +//// rowFilter.setStringFilters(new ArrayList<>()); +// model.fireTableDataChanged(); +// } + + // Thread: EDT - protected void setSelectedPanel(DetailPanel contributionPanel) { - contributionTab.updateStatusPanel(contributionPanel); + private void setSelectedDetail(StatusDetail contribDetail) { + contributionTab.updateStatusDetail(contribDetail); - if (selectedPanel == contributionPanel) { - selectedPanel.setSelected(true); - - } else { - DetailPanel lastSelected = selectedPanel; - selectedPanel = contributionPanel; - - if (lastSelected != null) { - lastSelected.setSelected(false); - } - contributionPanel.setSelected(true); - - updateColors(); + if (selectedDetail != contribDetail) { + selectedDetail = contribDetail; requestFocusInWindow(); } } - protected DetailPanel getSelectedPanel() { - return selectedPanel; - } - - - // Thread: EDT - /** - * Updates the colors of all library panels that are visible. - */ - protected void updateColors() { - int count = 0; - for (Entry entry : panelByContribution.entrySet()) { - DetailPanel panel = entry.getValue(); - Border border = BorderFactory.createEmptyBorder(1, 1, 1, 1); - - if (panel.isVisible()) { - boolean oddRow = count % 2 == 1; - Color bgColor = null; - Color fgColor = UIManager.getColor("List.foreground"); - - if (panel.isSelected()) { - bgColor = UIManager.getColor("List.selectionBackground"); - fgColor = UIManager.getColor("List.selectionForeground"); - border = UIManager.getBorder("List.focusCellHighlightBorder"); - } else if (Platform.isMacOS()) { - border = oddRow - ? UIManager.getBorder("List.oddRowBackgroundPainter") - : UIManager.getBorder("List.evenRowBackgroundPainter"); - } else { - bgColor = oddRow - ? new Color(219, 224, 229) - : new Color(241, 241, 241); - } - - panel.setForeground(fgColor); - if (bgColor != null) { - panel.setBackground(bgColor); - } - count++; - } - - panel.setBorder(border); - } + protected StatusDetail getSelectedDetail() { + return selectedDetail; } @@ -719,9 +800,6 @@ implements Scrollable, ContributionListing.ChangeListener { int bottomOfScrollArea = visibleRect.y + visibleRect.height; for (Component c : getComponents()) { - if (!(c.isVisible() && c instanceof DetailPanel)) { - continue; - } Dimension d = c.getPreferredSize(); int nextHeight = height + d.height; @@ -742,7 +820,6 @@ implements Scrollable, ContributionListing.ChangeListener { lastHeight = height; height = nextHeight; } - return 0; } diff --git a/app/src/processing/app/contrib/LocalContribution.java b/app/src/processing/app/contrib/LocalContribution.java index 619b9cf8a..7d7f41702 100644 --- a/app/src/processing/app/contrib/LocalContribution.java +++ b/app/src/processing/app/contrib/LocalContribution.java @@ -1,9 +1,9 @@ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* - Part of the Processing project - http://processing.org + Part of the Processing project - https://processing.org - Copyright (c) 2013-20 The Processing Foundation + Copyright (c) 2013-22 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -40,16 +40,15 @@ import processing.data.StringList; /** - * A contribution that has been downloaded to the disk, and may or may not - * be installed. + * A contribution that has been downloaded to the disk, + * and may or may not be installed. */ public abstract class LocalContribution extends Contribution { static public final String DELETION_FLAG = "marked_for_deletion"; static public final String UPDATE_FLAGGED = "marked_for_update"; - static public final String RESTART_FLAG = "requires_restart"; +// static public final String RESTART_FLAG = "requires_restart"; - protected String id; // 1 (unique id for this library) - protected int latestVersion; // 103 + protected String id; // 1 (unique id for this library) protected File folder; protected StringDict properties; protected ClassLoader loader; @@ -63,69 +62,54 @@ public abstract class LocalContribution extends Contribution { if (propertiesFile.exists()) { properties = Util.readSettings(propertiesFile); - name = properties.get("name"); - id = properties.get("id"); - categories = parseCategories(properties); - imports = parseImports(properties); - if (name == null) { - name = folder.getName(); + if (properties != null) { + name = properties.get("name"); + id = properties.get("id"); + categories = parseCategories(properties); + imports = parseImports(properties); + if (name == null) { + name = folder.getName(); + } + // changed 'authorList' to 'authors' in 3.0a11 + authors = properties.get(AUTHORS_PROPERTY); + url = properties.get("url"); + sentence = properties.get("sentence"); + paragraph = properties.get("paragraph"); + + try { + version = Integer.parseInt(properties.get("version")); + } catch (NumberFormatException e) { + System.err.println("The version number for the “" + name + "” library is not a number."); + System.err.println("Please contact the library author to fix it according to the guidelines."); + } + + setPrettyVersion(properties.get("prettyVersion")); + + try { + lastUpdated = Long.parseLong(properties.get("lastUpdated")); + } catch (NumberFormatException e) { + lastUpdated = 0; + } + + String minRev = properties.get("minRevision"); + if (minRev != null) { + minRevision = PApplet.parseInt(minRev, 0); + } + + String maxRev = properties.get("maxRevision"); + if (maxRev != null) { + maxRevision = PApplet.parseInt(maxRev, 0); + } + } else { + Messages.log("Could not read " + propertiesFile.getAbsolutePath()); } - // changed 'authorList' to 'authors' in 3.0a11 - authors = properties.get(AUTHORS_PROPERTY); - url = properties.get("url"); - sentence = properties.get("sentence"); - paragraph = properties.get("paragraph"); - - try { - version = Integer.parseInt(properties.get("version")); - } catch (NumberFormatException e) { - System.err.println("The version number for the “" + name + "” library is not a number."); - System.err.println("Please contact the library author to fix it according to the guidelines."); - } - - setPrettyVersion(properties.get("prettyVersion")); - - try { - lastUpdated = Long.parseLong(properties.get("lastUpdated")); - } catch (NumberFormatException e) { - lastUpdated = 0; - - // Better comment these out till all contribs have a lastUpdated -// System.err.println("The last updated timestamp for the “" + name + "” library is not set properly."); -// System.err.println("Please contact the library author to fix it according to the guidelines."); - } - - String minRev = properties.get("minRevision"); - if (minRev != null) { - minRevision = PApplet.parseInt(minRev, 0); - } - - String maxRev = properties.get("maxRevision"); - if (maxRev != null) { - maxRevision = PApplet.parseInt(maxRev, 0); - } - } else { Messages.log("No properties file at " + propertiesFile.getAbsolutePath()); + } + if (name == null) { // fall-through case // We'll need this to be set at a minimum. name = folder.getName(); - categories = unknownCategoryList(); - } - - if (categories.hasValue(SPECIAL_CATEGORY)) { - validateSpecial(); - } - } - - - private void validateSpecial() { - for (AvailableContribution available : ContributionListing.getInstance().advertisedContributions) { - if (available.getName().equals(name)) { - if (!available.isSpecial()) { - categories.removeValue(SPECIAL_CATEGORY); - } - } - break; + categories = new StringList(UNKNOWN_CATEGORY); } } @@ -175,62 +159,6 @@ public abstract class LocalContribution extends Contribution { } - /* - // doesn't work with URLClassLoader, but works with the system CL - static void listClasses(ClassLoader loader) { -// loader = Thread.currentThread().getContextClassLoader(); - try { - Field f = ClassLoader.class.getDeclaredField("classes"); - f.setAccessible(true); - Vector classes = (Vector) f.get(loader); - for (Class c : classes) { - System.out.println(c.getName()); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - */ - - -// static protected boolean isCandidate(File potential, final ContributionType type) { -// return (potential.isDirectory() && -// new File(potential, type.getFolderName()).exists()); -// } -// -// -// /** -// * Return a list of directories that have the necessary subfolder for this -// * contribution type. For instance, a list of folders that have a 'mode' -// * subfolder if this is a ModeContribution. -// */ -// static protected File[] listCandidates(File folder, final ContributionType type) { -// return folder.listFiles(new FileFilter() { -// public boolean accept(File potential) { -// return isCandidate(potential, type); -// } -// }); -// } -// -// -// /** -// * Return the first directory that has the necessary subfolder for this -// * contribution type. For instance, the first folder that has a 'mode' -// * subfolder if this is a ModeContribution. -// */ -// static protected File findCandidate(File folder, final ContributionType type) { -// File[] folders = listCandidates(folder, type); -// -// if (folders.length == 0) { -// return null; -// -// } else if (folders.length > 1) { -// Base.log("More than one " + type.toString() + " found inside " + folder.getAbsolutePath()); -// } -// return folders[0]; -// } - - LocalContribution copyAndLoad(Base base, boolean confirmReplace, StatusPanel status) { @@ -242,12 +170,12 @@ public abstract class LocalContribution extends Contribution { File contribTypeFolder = getType().getSketchbookFolder(); File contribFolder = new File(contribTypeFolder, contribFolderName); - if (status != null) { // when status != null, install is not occurring on startup - + // when status is null, that means we're starting up the PDE + if (status != null) { Editor editor = base.getActiveEditor(); - ArrayList oldContribs = - getType().listContributions(editor); + List oldContribs = + getType().listContributions(base, editor); // In case an update marker exists, and the user wants to install, delete the update marker if (contribFolder.exists() && !contribFolder.isDirectory()) { @@ -263,14 +191,6 @@ public abstract class LocalContribution extends Contribution { if (!oldContrib.backup(false, status)) { return null; } - /* - try { - Platform.deleteFile(oldContrib.getFolder()); - } catch (IOException e) { - status.setErrorMessage(e.getMessage()); - return null; - } - */ } else { int result; boolean doBackup = Preferences.getBoolean("contribution.backup.on_install"); @@ -308,14 +228,24 @@ public abstract class LocalContribution extends Contribution { // At this point it should be safe to replace this fella if (contribFolder.exists()) { - Util.removeDir(contribFolder); + //Util.removeDir(contribFolder); + try { + Platform.deleteFile(contribFolder); + } catch (IOException e) { + e.printStackTrace(); + } } } else { // This if should ideally never happen, since this function // is to be called only when restarting on update if (contribFolder.exists() && contribFolder.isDirectory()) { - Util.removeDir(contribFolder); + //Util.removeDir(contribFolder); + try { + Platform.deleteFile(contribFolder); + } catch (IOException e) { + e.printStackTrace(); + } } else if (contribFolder.exists()) { contribFolder.delete(); @@ -324,7 +254,6 @@ public abstract class LocalContribution extends Contribution { } File oldFolder = getFolder(); - try { Util.copyDir(oldFolder, contribFolder); } catch (IOException e) { @@ -334,15 +263,6 @@ public abstract class LocalContribution extends Contribution { return null; } - - /* - if (!getFolder().renameTo(contribFolder)) { - status.setErrorMessage("Could not move " + getTypeName() + - " \"" + getName() + "\" to the sketchbook."); - return null; - } - */ - return getType().load(base, contribFolder); } @@ -383,93 +303,106 @@ public abstract class LocalContribution extends Contribution { /** * Non-blocking call to remove a contribution in a new thread. */ - void removeContribution(final Base base, - final ContribProgressMonitor pm, - final StatusPanel status) { + protected void removeContribution(Base base, + ContribProgress pm, + StatusPanel status, + boolean updating) { // TODO: replace with SwingWorker [jv] - new Thread(() -> remove(base, - pm, - status, - ContributionListing.getInstance()), "Contribution Uninstaller").start(); + new Thread(() -> remove(base, pm, status, updating), "Contribution Uninstaller").start(); } - void remove(final Base base, - final ContribProgressMonitor pm, - final StatusPanel status, - final ContributionListing contribListing) { - pm.startTask("Removing", ContribProgressMonitor.UNKNOWN); + private void remove(Base base, ContribProgress pm, StatusPanel status, boolean updating) { + pm.startTask("Removing"); boolean doBackup = Preferences.getBoolean("contribution.backup.on_remove"); -// if (getType().requiresRestart()) { -// if (!doBackup || (doBackup && backup(editor, false, status))) { -// if (setDeletionFlag(true)) { -// contribListing.replaceContribution(this, this); -// } -// } -// } else { - boolean success; if (getType() == ContributionType.MODE) { - boolean isModeActive = false; + //Set sketches = new HashSet<>(); + List editors = new ArrayList<>(); // might be nice to be in order ModeContribution m = (ModeContribution) this; - for (Editor e : base.getEditors()) { - if (e.getMode().equals(m.getMode())) { - isModeActive = true; - break; + for (Editor editor : base.getEditors()) { + if (editor.getMode().equals(m.getMode())) { + Sketch sketch = editor.getSketch(); + if (sketch.isModified()) { + pm.cancel(); + editor.toFront(); + Messages.showMessage("Save Sketch", + "Please first save “" + sketch.getName() + "”."); + return; + } else { + // Keep track of open Editor windows using this Mode + //sketchMainList.add(sketch.getMainPath()); + //sketches.add(sketch); + editors.add(editor); + } } } - if (!isModeActive) { - m.clearClassLoader(base); - } else { + // Close any open Editor windows that were using this Mode, + // and if updating, build up a list of paths for the sketches + // so that we can dispose of the Editor objects. + //StringList sketchPathList = new StringList(); + for (Editor editor : editors) { + //sketchPathList.append(editor.getSketch().getMainPath()); + StatusDetail.storeSketchPath(editor.getSketch().getMainPath()); + base.handleClose(editor, true); + } + editors.clear(); + m.clearClassLoader(base); + //StatusPanelDetail.storeSketches(sketchPathList); + + /* pm.cancel(); Messages.showMessage("Mode Manager", "Please save your Sketch and change the Mode of all Editor\n" + "windows that have " + name + " as the active Mode."); return; + */ + + if (!updating) { + // Notify the Base in case this is the current Mode + base.modeRemoved(m.getMode()); + // If that was the last Editor window, and we deleted its Mode, + // open a fresh window using the default Mode. + if (base.getEditors().size() == 0) { + base.handleNew(); + } } } if (getType() == ContributionType.TOOL) { - /* - ToolContribution t = (ToolContribution) this; - Iterator iter = editor.getBase().getEditors().iterator(); - while (iter.hasNext()) { - Editor ed = iter.next(); - ed.clearToolMenu(); - } - t.clearClassLoader(editor.getBase()); - */ // menu will be rebuilt below with the refreshContribs() call base.clearToolMenus(); ((ToolContribution) this).clearClassLoader(); } + boolean success; if (doBackup) { success = backup(true, status); } else { - success = Util.removeDir(getFolder(), false); + try { + success = Platform.deleteFile(getFolder()); + } catch (IOException e) { + e.printStackTrace(); + success = false; + } } if (success) { - // this was just rebuilding the tool menu in one editor, which happens - // yet again down below with the call to refreshInstalled() [fry 150828] -// if (getType() == ContributionType.TOOL) { -// editor.removeTool(); -// } - try { // TODO: run this in SwingWorker done() [jv] EventQueue.invokeAndWait(() -> { + ContributionListing cl = ContributionListing.getInstance(); + Contribution advertisedVersion = - contribListing.getAvailableContribution(LocalContribution.this); + cl.getAvailableContribution(LocalContribution.this); if (advertisedVersion == null) { - contribListing.removeContribution(LocalContribution.this); + cl.removeContribution(LocalContribution.this); } else { - contribListing.replaceContribution(LocalContribution.this, advertisedVersion); + cl.replaceContribution(LocalContribution.this, advertisedVersion); } base.refreshContribs(LocalContribution.this.getType()); - base.setUpdatesAvailable(contribListing.countUpdates(base)); + base.setUpdatesAvailable(cl.countUpdates(base)); }); } catch (InterruptedException e) { e.printStackTrace(); @@ -484,15 +417,16 @@ public abstract class LocalContribution extends Contribution { } else { // There was a failure backing up the folder - if (!doBackup || (doBackup && backup(false, status))) { + if (!doBackup || backup(false, status)) { if (setDeletionFlag(true)) { try { // TODO: run this in SwingWorker done() [jv] EventQueue.invokeAndWait(() -> { - contribListing.replaceContribution(LocalContribution.this, + ContributionListing cl = ContributionListing.getInstance(); + cl.replaceContribution(LocalContribution.this, LocalContribution.this); base.refreshContribs(LocalContribution.this.getType()); - base.setUpdatesAvailable(contribListing.countUpdates(base)); + base.setUpdatesAvailable(cl.countUpdates(base)); }); } catch (InterruptedException e) { e.printStackTrace(); @@ -527,74 +461,11 @@ public abstract class LocalContribution extends Contribution { } -// public String getCategory() { -// return category; -// } -// -// -// public String getName() { -// return name; -// } - - public String getId() { return id; } -// public String getAuthorList() { -// return authorList; -// } -// -// -// public String getUrl() { -// return url; -// } -// -// -// public String getSentence() { -// return sentence; -// } -// -// -// public String getParagraph() { -// return paragraph; -// } -// -// -// public int getVersion() { -// return version; -// } - - - public int getLatestVersion() { - return latestVersion; - } - - -// public String getPrettyVersion() { -// return prettyVersion; -// } -// -// -// public String getTypeName() { -// return getType().toString(); -// } - - - /* - static protected String findClassInZipFileList(String base, File[] fileList) { - for (File file : fileList) { - String found = findClassInZipFile(base, file); - if (found != null) { - return found; - } - } - return null; - } - */ - - /** * Returns the imports (package-names) for a library, as specified in its library.properties * (e.g., imports=libname.*,libname.support.*) @@ -602,37 +473,10 @@ public abstract class LocalContribution extends Contribution { * @return String[] packageNames (without wildcards) or null if none are specified */ public StringList getImports() { - //return imports != null ? imports.toArray(new String[0]) : null; return imports; } - // this duplicates code found in Contribution (though that version doesn't check for .* at the end) -// /** -// * @return the list of Java imports to be added to the sketch when the library is imported -// * or null if none are specified -// */ -// static StringList parseImports(String importsStr) { -// StringList outgoing = new StringList(); -// -// if (importsStr != null) { -// String[] listing = PApplet.trim(PApplet.split(importsStr, ',')); -// for (String imp : listing) { -// -// // In case the wildcard is specified, strip it, as it gets added later) -// if (imp.endsWith(".*")) { -// -// imp = imp.substring(0, imp.length() - 2); -// } -// -// outgoing.add(imp); -// } -// } -//// return (outgoing.size() > 0) ? outgoing : null; -// return outgoing; -// } - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -654,8 +498,8 @@ public abstract class LocalContribution extends Contribution { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - boolean setUpdateFlag(boolean flag) { - return setFlag(UPDATE_FLAGGED, flag); + boolean setUpdateFlag() { + return setFlag(UPDATE_FLAGGED, true); } @@ -672,6 +516,7 @@ public abstract class LocalContribution extends Contribution { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + /* boolean setRestartFlag() { //System.out.println("setting restart flag for " + folder); return setFlag(RESTART_FLAG, true); @@ -685,12 +530,13 @@ public abstract class LocalContribution extends Contribution { } - static void clearRestartFlags(File folder) { + static void clearRestartFlag(File folder) { File restartFlag = new File(folder, RESTART_FLAG); if (restartFlag.exists()) { restartFlag.delete(); } } + */ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . diff --git a/app/src/processing/app/contrib/ManagerFrame.java b/app/src/processing/app/contrib/ManagerFrame.java index fd92b9b94..2bcedc888 100644 --- a/app/src/processing/app/contrib/ManagerFrame.java +++ b/app/src/processing/app/contrib/ManagerFrame.java @@ -1,9 +1,9 @@ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* - Part of the Processing project - http://processing.org + Part of the Processing project - https://processing.org - Copyright (c) 2013-15 The Processing Foundation + Copyright (c) 2013-22 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -26,8 +26,8 @@ import java.awt.event.*; import javax.swing.*; -import processing.app.*; -import processing.app.ui.Editor; +import processing.app.Base; +import processing.app.Language; import processing.app.ui.Theme; import processing.app.ui.Toolkit; @@ -41,6 +41,7 @@ public class ManagerFrame { static final int AUTHOR_WIDTH = Toolkit.zoom(240); static final int STATUS_WIDTH = Toolkit.zoom(66); + static final int VERSION_WIDTH = Toolkit.zoom(66); static final String title = "Contribution Manager"; @@ -54,38 +55,33 @@ public class ManagerFrame { ContributionTab examplesTab; UpdateContributionTab updatesTab; - static Font SMALL_PLAIN; - static Font SMALL_BOLD; - static Font NORMAL_PLAIN; - static Font NORMAL_BOLD; + ContributionTab[] tabList; public ManagerFrame(Base base) { this.base = base; -// long t1 = System.currentTimeMillis(); - final int smallSize = Toolkit.zoom(12); - final int normalSize = Toolkit.zoom(14); - SMALL_PLAIN = Toolkit.getSansFont(smallSize, Font.PLAIN); - SMALL_BOLD = Toolkit.getSansFont(smallSize, Font.BOLD); - NORMAL_PLAIN = Toolkit.getSansFont(normalSize, Font.PLAIN); - NORMAL_BOLD = Toolkit.getSansFont(normalSize, Font.BOLD); - // TODO Optimize these inits... unfortunately it needs to run on the EDT, // and Swing is a piece of s*t, so it's gonna be slow with lots of contribs. - // All the time is being used up between t2 and t3. // In particular, load everything and then fire the update events. // Also, don't pull all the colors over and over again. -// long t2 = System.currentTimeMillis(); +// long t1 = System.currentTimeMillis(); librariesTab = new ContributionTab(this, ContributionType.LIBRARY); +// long t2 = System.currentTimeMillis(); modesTab = new ContributionTab(this, ContributionType.MODE); - toolsTab = new ContributionTab(this, ContributionType.TOOL); - examplesTab = new ContributionTab(this, ContributionType.EXAMPLES); // long t3 = System.currentTimeMillis(); - updatesTab = new UpdateContributionTab(this); - + toolsTab = new ContributionTab(this, ContributionType.TOOL); // long t4 = System.currentTimeMillis(); -// System.out.println("ManagerFrame. " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3)); + examplesTab = new ContributionTab(this, ContributionType.EXAMPLES); +// long t5 = System.currentTimeMillis(); + updatesTab = new UpdateContributionTab(this); +// long t6 = System.currentTimeMillis(); + + tabList = new ContributionTab[] { + librariesTab, modesTab, toolsTab, examplesTab, updatesTab + }; + +// System.out.println("ManagerFrame. " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3) + " " + (t5-t4) + " " + (t6-t5)); } @@ -96,7 +92,8 @@ public class ManagerFrame { // done before as downloadAndUpdateContributionListing() // requires the current selected tab tabs.setPanel(showTab); - downloadAndUpdateContributionListing(base); + //downloadAndUpdateContributionListing(base); + downloadAndUpdateContributionListing(); } else { tabs.setPanel(showTab); } @@ -109,9 +106,9 @@ public class ManagerFrame { private void makeFrame() { frame = new JFrame(title); frame.setMinimumSize(Toolkit.zoom(750, 500)); - tabs = new ManagerTabs(base); + tabs = new ManagerTabs(); - makeAndShowTab(false, true); + rebuildTabLayouts(false, true); tabs.addPanel(librariesTab, "Libraries"); tabs.addPanel(modesTab, "Modes"); @@ -121,9 +118,6 @@ public class ManagerFrame { frame.setResizable(true); -// Container c = frame.getContentPane(); -// c.add(tabs); -// c.setBackground(Theme.getColor("manager.tab.background")); frame.getContentPane().add(tabs); updateTheme(); @@ -139,7 +133,18 @@ public class ManagerFrame { protected void updateTheme() { - frame.getContentPane().setBackground(Theme.getColor("manager.tab.background")); + // don't update if the Frame doesn't actually exist yet + // https://github.com/processing/processing4/issues/476 + if (frame != null) { + Color bgColor = Theme.getColor("manager.tab.background"); + frame.getContentPane().setBackground(bgColor); + + tabs.updateTheme(); + + for (ContributionTab tab : tabList) { + tab.updateTheme(); + } + } } @@ -177,53 +182,56 @@ public class ManagerFrame { // TODO move this to ContributionTab (this is handled weirdly, period) [fry] - void downloadAndUpdateContributionListing(Base base) { + //void downloadAndUpdateContributionListing(Base base) { + void downloadAndUpdateContributionListing() { //activeTab is required now but should be removed - //as there is only one instance of contribListing and it should be present in this class + //as there is only one instance of contribListing, and it should be present in this class final ContributionTab activeTab = getActiveTab(); - ContribProgressMonitor progress = - new ContribProgressBar(activeTab.progressBar) { - + /* + final JProgressBar bar = activeTab.progressBar; + ContribProgress progress = new ContribProgress(bar) { @Override public void startTask(String name, int maxValue) { super.startTask(name, maxValue); - progressBar.setVisible(true); - progressBar.setString(null); + bar.setVisible(true); + bar.setString(null); } @Override public void setProgress(int value) { super.setProgress(value); -// int percent = 100 * value / this.max; - progressBar.setValue(value); + bar.setValue(value); } @Override public void finishedAction() { - progressBar.setVisible(false); + bar.setVisible(false); + */ activeTab.updateContributionListing(); activeTab.updateCategoryChooser(); - if (error) { + /* + Exception exception = getException(); + if (exception != null) { exception.printStackTrace(); makeAndShowTab(true, false); } else { - makeAndShowTab(false, false); + */ + rebuildTabLayouts(false, false); + /* } } }; - activeTab.contribListing.downloadAvailableList(base, progress); + ContributionListing.getInstance().downloadAvailableList(base, progress); + */ } - void makeAndShowTab(boolean error, boolean loading) { - Editor editor = base.getActiveEditor(); - librariesTab.showFrame(editor, error, loading); - modesTab.showFrame(editor, error, loading); - toolsTab.showFrame(editor, error, loading); - examplesTab.showFrame(editor, error, loading); - updatesTab.showFrame(editor, error, loading); + protected void rebuildTabLayouts(boolean error, boolean loading) { + for (ContributionTab tab : tabList) { + tab.rebuildLayout(error, loading); + } } diff --git a/app/src/processing/app/contrib/ManagerTabs.java b/app/src/processing/app/contrib/ManagerTabs.java index 571a059c7..3d8577e4b 100644 --- a/app/src/processing/app/contrib/ManagerTabs.java +++ b/app/src/processing/app/contrib/ManagerTabs.java @@ -1,9 +1,9 @@ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* - Part of the Processing project - http://processing.org + Part of the Processing project - https://processing.org - Copyright (c) 2015 The Processing Foundation + Copyright (c) 2015-22 The Processing Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,7 +19,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - package processing.app.contrib; import java.awt.CardLayout; @@ -35,38 +34,34 @@ import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; -import javax.swing.*; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JComponent; +import javax.swing.JPanel; import javax.swing.border.EmptyBorder; -import processing.app.Base; -import processing.app.Mode; import processing.app.ui.Theme; import processing.app.ui.Toolkit; /** - * Console/error/whatever tabs at the bottom of the editor window. + * Tab button bar for Libraries, Modes, Tools, and Updates, across the + * top of the Contribution Manager. Tab content is in ContributionTab. + * Most of the sizing dimensions are identical to EditorHeader. */ public class ManagerTabs extends Box { // height of this tab bar - static final int HIGH = Toolkit.zoom(34); + static final int HIGH = Toolkit.zoom(31); // amount of space around the entire window static final int BORDER = Toolkit.zoom(8); static final int CURVE_RADIUS = Toolkit.zoom(6); - static final int TAB_TOP = Toolkit.zoom(0); - static final int TAB_BOTTOM = HIGH - Toolkit.zoom(2); // amount of extra space between individual tabs static final int TAB_BETWEEN = Toolkit.zoom(2); // amount of margin on the left/right for the text on the tab - static final int MARGIN = Toolkit.zoom(14); - - static final int ICON_WIDTH = Toolkit.zoom(16); - static final int ICON_HEIGHT = Toolkit.zoom(16); - static final int ICON_TOP = Toolkit.zoom(7); - static final int ICON_MARGIN = Toolkit.zoom(7); + static final int MARGIN = Toolkit.zoom(13); static final int UNSELECTED = 0; static final int SELECTED = 1; @@ -76,15 +71,9 @@ public class ManagerTabs extends Box { List tabList = new ArrayList<>(); - Mode mode; - Font font; int fontAscent; - Image offscreen; - int sizeW, sizeH; - int imageW, imageH; - Image gradient; JPanel cardPanel; @@ -94,21 +83,10 @@ public class ManagerTabs extends Box { Component currentPanel; - public ManagerTabs(Base base) { + public ManagerTabs() { super(BoxLayout.Y_AXIS); - // A mode shouldn't actually override these, they're coming from theme.txt. - // But use the default (Java) mode settings just in case. - mode = base.getDefaultMode(); - - textColor[SELECTED] = Theme.getColor("manager.tab.text.selected.color"); - textColor[UNSELECTED] = Theme.getColor("manager.tab.text.unselected.color"); - font = Theme.getFont("manager.tab.text.font"); - - tabColor[SELECTED] = Theme.getColor("manager.tab.selected.color"); - tabColor[UNSELECTED] = Theme.getColor("manager.tab.unselected.color"); - - gradient = Theme.makeGradient("manager.tab", Toolkit.zoom(400), HIGH); + updateTheme(); setBorder(new EmptyBorder(BORDER, BORDER, BORDER, BORDER)); @@ -121,32 +99,34 @@ public class ManagerTabs extends Box { } - /** Add a panel with no icon. */ - public void addPanel(Component comp, String name) { - addPanel(comp, name, null); + protected void updateTheme() { + textColor[SELECTED] = Theme.getColor("manager.tab.text.selected.color"); + textColor[UNSELECTED] = Theme.getColor("manager.tab.text.unselected.color"); + font = Theme.getFont("manager.tab.text.font"); + + tabColor[SELECTED] = Theme.getColor("manager.tab.selected.color"); + tabColor[UNSELECTED] = Theme.getColor("manager.tab.unselected.color"); + + gradient = Theme.makeGradient("manager.tab", Toolkit.zoom(400), HIGH); + + repaint(); } /** - * Add a panel with a name and icon. + * Add a panel with a name. * @param comp Component that will be shown when this tab is selected * @param name Title to appear on the tab itself - * @param icon Prefix of the file name for the icon */ - public void addPanel(Component comp, String name, String icon) { + public void addPanel(Component comp, String name) { if (tabList.isEmpty()) { currentPanel = comp; } - tabList.add(new Tab(comp, name, icon)); + tabList.add(new Tab(comp, name)); cardPanel.add(name, comp); } -// public void setPanel(int index) { -// cardLayout.show(cardPanel, tabs.get(index).name); -// } - - public void setPanel(Component comp) { for (Tab tab : tabList) { if (tab.comp == comp) { @@ -163,16 +143,6 @@ public class ManagerTabs extends Box { } - public void setNotification(Component comp, boolean note) { - for (Tab tab : tabList) { - if (tab.comp == comp) { - tab.notification = note; - repaint(); - } - } - } - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -193,33 +163,10 @@ public class ManagerTabs extends Box { }); } - public void paintComponent(Graphics screen) { - if (screen == null) return; - Dimension size = getSize(); - if ((size.width != sizeW) || (size.height != sizeH)) { - // component has been resized + public void paintComponent(Graphics g) { + if (g == null) return; - if ((size.width > imageW) || (size.height > imageH)) { - // nix the image and recreate, it's too small - offscreen = null; - - } else { - // if the image is larger than necessary, no need to change - sizeW = size.width; - sizeH = size.height; - } - } - - if (offscreen == null) { - sizeW = size.width; - sizeH = size.height; - imageW = sizeW; - imageH = sizeH; - offscreen = Toolkit.offscreenGraphics(this, imageW, imageH); - } - - Graphics g = offscreen.getGraphics(); g.setFont(font); // need to set this each time through if (fontAscent == 0) { fontAscent = (int) Toolkit.getAscent(g); @@ -227,47 +174,26 @@ public class ManagerTabs extends Box { Graphics2D g2 = Toolkit.prepareGraphics(g); - g.drawImage(gradient, 0, 0, imageW, imageH, this); - - g.setColor(tabColor[SELECTED]); - // draw the two pixel line that extends left/right below the tabs - // can't be done with lines, b/c retina leaves tiny hairlines - g.fillRect(0, TAB_BOTTOM, imageW, Toolkit.zoom(2)); + g.drawImage(gradient, 0, 0, getWidth(), getHeight(), this); // reset all tab positions for (Tab tab : tabList) { tab.textWidth = (int) font.getStringBounds(tab.name, g2.getFontRenderContext()).getWidth(); } - - placeTabs(0); + placeTabs(); // now actually draw the tabs drawTabs(g2); - - screen.drawImage(offscreen, 0, 0, imageW, imageH, null); } - /** - * @param left starting position from the left - * @param g graphics context, or null if we're not drawing - */ - private void placeTabs(int left) { //, Graphics2D g) { - int x = left; - + private void placeTabs() { + int x = 0; for (Tab tab : tabList) { tab.left = x; x += MARGIN; - if (tab.hasIcon()) { - x += ICON_WIDTH + MARGIN; - } x += tab.textWidth + MARGIN; tab.right = x; - -// // if drawing and not just placing -// if (g != null) { -// tab.draw(g); -// } x += TAB_BETWEEN; } // Align the final tab (the "updates") to the right-hand side @@ -309,24 +235,13 @@ public class ManagerTabs extends Box { Component comp; boolean notification; - Image enabledIcon; - Image selectedIcon; - int left; int right; int textWidth; - Tab(Component comp, String name, String icon) { + Tab(Component comp, String name) { this.comp = comp; this.name = name; - - if (icon != null) { - enabledIcon = mode.loadImageX(icon + "-enabled"); - selectedIcon = mode.loadImageX(icon + "-selected"); - if (selectedIcon == null) { - selectedIcon = enabledIcon; // use this as the default - } - } } boolean contains(int x) { @@ -349,15 +264,7 @@ public class ManagerTabs extends Box { } int getTextLeft() { - int links = left; - if (enabledIcon != null) { - links += ICON_WIDTH + ICON_MARGIN; - } - return links + ((right - links) - textWidth) / 2; - } - - boolean hasIcon() { - return enabledIcon != null; + return left + ((right - left) - textWidth) / 2; } void draw(Graphics g) { @@ -365,28 +272,19 @@ public class ManagerTabs extends Box { g.setColor(tabColor[state]); Graphics2D g2 = (Graphics2D) g; -// g2.fill(Toolkit.createRoundRect(left, TAB_TOP, right, TAB_BOTTOM, 0, 0, -// isLast() ? CURVE_RADIUS : 0, -// hastLeftCurve() ? CURVE_RADIUS : 0)); - g2.fill(Toolkit.createRoundRect(left, TAB_TOP, - right, TAB_BOTTOM, + g2.fill(Toolkit.createRoundRect(left, 0, + right, HIGH, hasLeftNotch() ? CURVE_RADIUS : 0, hasRightNotch() ? CURVE_RADIUS : 0, 0, 0)); - if (hasIcon()) { - Image icon = (isCurrent() || notification) ? selectedIcon : enabledIcon; - g.drawImage(icon, left + MARGIN, ICON_TOP, ICON_WIDTH, ICON_HEIGHT, null); - } - int textLeft = getTextLeft(); if (notification && state == UNSELECTED) { g.setColor(textColor[SELECTED]); } else { g.setColor(textColor[state]); } - int tabHeight = TAB_BOTTOM - TAB_TOP; - int baseline = TAB_TOP + (tabHeight + fontAscent) / 2; + int baseline = (HIGH + fontAscent) / 2; g.drawString(name, textLeft, baseline); } } diff --git a/app/src/processing/app/contrib/ModeContribution.java b/app/src/processing/app/contrib/ModeContribution.java index 28dbd7782..4d7f4a154 100644 --- a/app/src/processing/app/contrib/ModeContribution.java +++ b/app/src/processing/app/contrib/ModeContribution.java @@ -1,9 +1,9 @@ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* - Part of the Processing project - http://processing.org + Part of the Processing project - https://processing.org - Copyright (c) 2013-15 The Processing Foundation + Copyright (c) 2013-22 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ import java.net.URLClassLoader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import processing.app.Base; import processing.app.Messages; @@ -54,12 +55,10 @@ public class ModeContribution extends LocalContribution { Messages.log(ig.getMessage()); } catch (Throwable err) { - System.out.println("err is " + err); - // Throwable to catch Exceptions or UnsupportedClassVersionError et al + // Throwable to catch Exceptions or UnsupportedClassVersionError et al. if (searchName == null) { - //err.printStackTrace(System.out); - // for 3.0b1, pass this through to the Contribution Manager so that - // we can provide better error messages + // for 3.0b1, pass this through to the Contribution Manager + // so that we can provide better error messages throw new RuntimeException(err); } else { @@ -74,7 +73,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 @@ -89,24 +87,18 @@ public class ModeContribution extends LocalContribution { Constructor con = modeClass.getConstructor(Base.class, File.class); mode = (Mode) con.newInstance(base, folder); mode.setClassLoader(loader); -// if (base != null) { -// mode.setupGUI(); -// } } } /** - * Method to close the ClassLoader so that the archives are no longer "locked" - * and a mode can be removed without restart. + * Method to close the ClassLoader so that the archives are no longer + * "locked" and a Mode can be removed without restart. */ public void clearClassLoader(Base base) { List contribModes = base.getModeContribs(); - int botherToRemove = contribModes.indexOf(this); - // The poor thing isn't even loaded, and we're trying to remove it... - if (botherToRemove != -1) { - contribModes.remove(botherToRemove); - + if (contribModes.contains(this)) { + contribModes.remove(this); try { // This cast should be safe, since the only case when loader is not a // URLClassLoader is when no archives were found in the first place. @@ -149,12 +141,12 @@ public class ModeContribution extends LocalContribution { } } - ArrayList extraUrls = new ArrayList<>(); + List extraUrls = new ArrayList<>(); if (imports != null && imports.size() > 0) { // if the mode has any dependencies (defined as imports in // mode.properties), add the dependencies to the classloader - HashMap installedModes = new HashMap<>(); + Map installedModes = new HashMap<>(); for(Mode m: base.getModeList()){ // Base.log("Mode contrib: " + m.getClass().getName() + " : "+ m.getFolder()); installedModes.put(m.getClass().getName(), m); diff --git a/app/src/processing/app/contrib/StatusDetail.java b/app/src/processing/app/contrib/StatusDetail.java new file mode 100644 index 000000000..1aa9cd364 --- /dev/null +++ b/app/src/processing/app/contrib/StatusDetail.java @@ -0,0 +1,262 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - https://processing.org + + Copyright (c) 2013-22 The Processing Foundation + Copyright (c) 2011-12 Ben Fry and Casey Reas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +package processing.app.contrib; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.AbstractQueue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import javax.swing.JProgressBar; + +import processing.app.*; +import processing.app.laf.PdeProgressBarUI; + + +/** + * An unfortunate mix of state information about the installation + * status of a Contribution, *as well as* the methods to handle + * installation and update of that Contribution. + */ +class StatusDetail { + private final Base base; + private final StatusPanel statusPanel; + + private Contribution contrib; + private JProgressBar progressBar; + + boolean updateInProgress; + boolean installInProgress; + boolean removeInProgress; + + + StatusDetail(Base base, StatusPanel statusPanel) { + this.base = base; + this.statusPanel = statusPanel; + +// initProgressBar(); +// updateTheme(); + } + + + protected Contribution getContrib() { + return contrib; + } + + + private LocalContribution getLocalContrib() { + return (LocalContribution) contrib; + } + + + protected void setContrib(Contribution contrib) { + this.contrib = contrib; + } + + + protected void setProgressBar(JProgressBar progressBar) { + this.progressBar = progressBar; + } + + + private void installContribution(AvailableContribution info) { + if (info.link == null) { + statusPanel.setErrorMessage(Language.interpolate("contrib.unsupported_operating_system", info.getType())); + } else { + installContribution(info, info.link); + } + } + + + private void finishInstall(boolean error) { + statusPanel.resetProgressBar(); + + if (error) { + statusPanel.setErrorMessage(Language.text("contrib.download_error")); + } + installInProgress = false; + if (updateInProgress) { + updateInProgress = false; + } + // change the status icon from downloading to installed + statusPanel.contributionTab.repaint(); + } + + + private void installContribution(AvailableContribution ad, String url) { + try { + URL downloadUrl = new URL(url); + progressBar.setVisible(true); + + ContribProgress downloadProgress = new ContribProgress(progressBar) { + public void finishedAction() { } + + public void cancelAction() { + finishInstall(false); + } + }; + + ContribProgress installProgress = new ContribProgress(progressBar) { + public void finishedAction() { + finishInstall(isException()); + + // if it was a Mode, restore any sketches + //if (contrib.getType() == ContributionType.MODE) { // no need + restoreSketches(); + } + + public void cancelAction() { + finishedAction(); + } + }; + + ContributionManager.downloadAndInstall(base, downloadUrl, ad, + downloadProgress, installProgress, + statusPanel); + + } catch (MalformedURLException e) { + Messages.showWarning(Language.text("contrib.errors.install_failed"), + Language.text("contrib.errors.malformed_url"), e); + } + } + + + protected void install() { + //clearStatusMessage(); + statusPanel.clearMessage(); + installInProgress = true; + if (contrib instanceof AvailableContribution) { + installContribution((AvailableContribution) contrib); + ContributionListing.getInstance().replaceContribution(contrib, contrib); + } + } + + + // TODO Update works by first calling a remove, and then ContribProgress, + // of all things, calls install() in its finishedAction() method. + // FFS this is gross. [fry 220311] + protected void update() { + //clearStatusMessage(); + statusPanel.clearMessage(); + updateInProgress = true; + + ContributionListing contribListing = ContributionListing.getInstance(); + + // TODO not really a 'restart' anymore, just requires care [fry 220312] + if (contrib.getType().requiresRestart()) { + // For the special "Updates" tab in the manager, there are no progress + // bars, so if that's what we're doing, this will create a dummy bar. + // TODO Not a good workaround [fry 220312] +// if (progressBar == null) { +// initProgressBar(); +// } + progressBar.setVisible(true); + progressBar.setIndeterminate(true); + + ContribProgress progress = new ContribProgress(progressBar) { + @Override + public void finishedAction() { + statusPanel.resetProgressBar(); + AvailableContribution ad = + contribListing.getAvailableContribution(contrib); + // install the new version of the Mode (or Tool) + installContribution(ad, ad.link); + } + + @Override + public void cancelAction() { + statusPanel.resetProgressBar(); + //clearStatusMessage(); + statusPanel.clearMessage(); + updateInProgress = false; + if (contrib.isDeletionFlagged()) { + getLocalContrib().setUpdateFlag(); + getLocalContrib().setDeletionFlag(false); + contribListing.replaceContribution(contrib, contrib); + } + } + }; + getLocalContrib().removeContribution(base, progress, statusPanel, true); + + } else { + AvailableContribution ad = + contribListing.getAvailableContribution(contrib); + installContribution(ad, ad.link); + } + } + + + protected void remove() { + //clearStatusMessage(); + statusPanel.clearMessage(); + if (contrib.isInstalled() && contrib instanceof LocalContribution) { + removeInProgress = true; + progressBar.setVisible(true); + progressBar.setIndeterminate(true); + + ContribProgress progress = new ContribProgress(progressBar) { + @Override + public void finishedAction() { + statusPanel.resetProgressBar(); + removeInProgress = false; + } + + @Override + public void cancelAction() { + statusPanel.resetProgressBar(); + removeInProgress = false; + } + }; + getLocalContrib().removeContribution(base, progress, statusPanel, false); + } + } + + + protected void updateTheme() { + if (progressBar != null) { + if (progressBar.getUI() instanceof PdeProgressBarUI) { + ((PdeProgressBarUI) progressBar.getUI()).updateTheme(); + } else { + progressBar.setUI(new PdeProgressBarUI("manager.progress")); + } + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static AbstractQueue restoreQueue = new ConcurrentLinkedQueue<>(); + + + static protected void storeSketchPath(String path) { + restoreQueue.add(path); + } + + + protected void restoreSketches() { + while (!restoreQueue.isEmpty()) { + String path = restoreQueue.remove(); + base.handleOpen(path); + } + } +} diff --git a/app/src/processing/app/contrib/StatusPanel.java b/app/src/processing/app/contrib/StatusPanel.java index 4c25c5ec1..bfc07ad2d 100644 --- a/app/src/processing/app/contrib/StatusPanel.java +++ b/app/src/processing/app/contrib/StatusPanel.java @@ -1,9 +1,9 @@ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* - Part of the Processing project - http://processing.org + Part of the Processing project - https://processing.org - Copyright (c) 2013-16 The Processing Foundation + Copyright (c) 2013-22 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -21,68 +21,75 @@ */ package processing.app.contrib; -import java.awt.BorderLayout; -import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; import java.awt.Font; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.text.DateFormat; +import java.util.Date; import javax.swing.GroupLayout; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JProgressBar; import javax.swing.JTextPane; import javax.swing.LayoutStyle; import javax.swing.SwingConstants; import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; import javax.swing.text.html.HTMLDocument; +import processing.app.Language; +import processing.app.Util; +import processing.app.laf.PdeButtonUI; +import processing.app.laf.PdeProgressBarUI; +import processing.app.ui.Theme; import processing.app.ui.Toolkit; import processing.app.Base; import processing.app.Platform; +/** + * The panel that explains a particular Contribution in the Manager. + * One for each tab (Libraries, Modes, etc) in the Manager. + */ class StatusPanel extends JPanel { + static final int LABEL_WIDTH = Toolkit.zoom(480); static final int BUTTON_WIDTH = Toolkit.zoom(150); + Icon foundationIcon; + /* static Icon foundationIcon; static Icon installIcon; static Icon updateIcon; static Icon removeIcon; - static Font buttonFont; + */ + + String detailStyle; JTextPane label; JButton installButton; - JPanel progressPanel; + JProgressBar progressBar; JLabel updateLabel; JButton updateButton; JButton removeButton; GroupLayout layout; - JLabel iconLabel; - ContributionListing contributionListing = ContributionListing.getInstance(); + JLabel iconLabel; // Foundation Icon to the left of the detail ContributionTab contributionTab; - private String bodyRule; - - /** Needed by ContributionListPanel */ - public StatusPanel() { } - - - public StatusPanel(final ContributionTab contributionTab, int width) { + public StatusPanel(final ContributionTab contributionTab) { this.contributionTab = contributionTab; - if (foundationIcon == null) { - foundationIcon = Toolkit.getLibIconX("icons/foundation", 32); - installIcon = Toolkit.getLibIconX("manager/install"); - updateIcon = Toolkit.getLibIconX("manager/update"); - removeIcon = Toolkit.getLibIconX("manager/remove"); - buttonFont = ManagerFrame.NORMAL_PLAIN; - } +// if (foundationIcon == null) { +// foundationIcon = Toolkit.getLibIconX("icons/foundation", 32); +// installIcon = Toolkit.getLibIconX("manager/install"); +// updateIcon = Toolkit.getLibIconX("manager/update"); +// removeIcon = Toolkit.getLibIconX("manager/remove"); +// buttonFont = ManagerFrame.NORMAL_PLAIN; +// } - setBackground(new Color(0xebebeb)); + //setBackground(new Color(0xebebeb)); iconLabel = new JLabel(); iconLabel.setHorizontalAlignment(SwingConstants.CENTER); @@ -91,69 +98,76 @@ class StatusPanel extends JPanel { label.setEditable(false); label.setOpaque(false); label.setContentType("text/html"); - bodyRule = "a, body { font-family: " + buttonFont.getFamily() + "; " + - "font-size: " + buttonFont.getSize() + "pt; color: black; text-decoration: none;}"; - label.addHyperlinkListener(new HyperlinkListener() { - - @Override - public void hyperlinkUpdate(HyperlinkEvent e) { - if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - if (e.getURL() != null) { - Platform.openURL(e.getURL().toString()); - } + label.addHyperlinkListener(e -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + if (e.getURL() != null) { + Platform.openURL(e.getURL().toString()); } } }); - installButton = Toolkit.createIconButton("Install", installIcon); + //installButton = Toolkit.createIconButton("Install", installIcon); + installButton = new JButton("Install"); //installButton.setDisabledIcon(installIcon); - installButton.setFont(buttonFont); +// installButton.setFont(buttonFont); installButton.setHorizontalAlignment(SwingConstants.LEFT); - installButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - installButton.setEnabled(false); - DetailPanel currentPanel = - contributionTab.contributionListPanel.getSelectedPanel(); - currentPanel.install(); - StatusPanel.this.update(currentPanel); - } + installButton.addActionListener(e -> { + installButton.setEnabled(false); + StatusDetail currentDetail = + contributionTab.listPanel.getSelectedDetail(); + currentDetail.install(); + updateDetail(currentDetail); }); - progressPanel = new JPanel(); - progressPanel.setLayout(new BorderLayout()); - progressPanel.setOpaque(false); + + progressBar = new JProgressBar(); + /* + progressBar = new JProgressBar() { + @Override + public void setBackground(Color c) { + new Exception("setting bg to " + c).printStackTrace(System.out); + super.setBackground(c); + } + }; + */ + progressBar.setStringPainted(true); + progressBar.setAlignmentX(Component.CENTER_ALIGNMENT); + //progressBar.setOpaque(true); + + resetProgressBar(); + + final int high = progressBar.getPreferredSize().height; + Dimension dim = new Dimension(BUTTON_WIDTH, high); + progressBar.setPreferredSize(dim); + progressBar.setMaximumSize(dim); + progressBar.setMinimumSize(dim); updateLabel = new JLabel(" "); - updateLabel.setFont(buttonFont); +// updateLabel.setFont(buttonFont); updateLabel.setHorizontalAlignment(SwingConstants.CENTER); - updateButton = Toolkit.createIconButton("Update", updateIcon); - updateButton.setFont(buttonFont); + //updateButton = Toolkit.createIconButton("Update", updateIcon); + updateButton = new JButton("Update"); +// updateButton.setFont(buttonFont); updateButton.setHorizontalAlignment(SwingConstants.LEFT); - updateButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - updateButton.setEnabled(false); - DetailPanel currentPanel = - contributionTab.contributionListPanel.getSelectedPanel(); - currentPanel.update(); - StatusPanel.this.update(currentPanel); - } + updateButton.addActionListener(e -> { + updateButton.setEnabled(false); + StatusDetail currentDetail = + contributionTab.listPanel.getSelectedDetail(); + currentDetail.update(); + updateDetail(currentDetail); }); - removeButton = Toolkit.createIconButton("Remove", removeIcon); - removeButton.setFont(buttonFont); + //removeButton = Toolkit.createIconButton("Remove", removeIcon); + removeButton = new JButton("Remove"); +// removeButton.setFont(buttonFont); removeButton.setHorizontalAlignment(SwingConstants.LEFT); - removeButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - removeButton.setEnabled(false); - DetailPanel currentPanel = - contributionTab.contributionListPanel.getSelectedPanel(); - currentPanel.remove(); - StatusPanel.this.update(currentPanel); - } + removeButton.addActionListener(e -> { + removeButton.setEnabled(false); + StatusDetail currentPanel = + contributionTab.listPanel.getSelectedDetail(); + currentPanel.remove(); + updateDetail(currentPanel); }); - int labelWidth = (width != 0) ? - (3 * width / 4) : GroupLayout.PREFERRED_SIZE; layout = new GroupLayout(this); this.setLayout(layout); @@ -168,13 +182,13 @@ class StatusPanel extends JPanel { ManagerFrame.STATUS_WIDTH, ManagerFrame.STATUS_WIDTH) .addGap(0) - .addComponent(label, labelWidth, labelWidth, labelWidth) + .addComponent(label, LABEL_WIDTH, LABEL_WIDTH, LABEL_WIDTH) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(installButton, BUTTON_WIDTH, BUTTON_WIDTH, BUTTON_WIDTH) - .addComponent(progressPanel) + .addComponent(progressBar) .addComponent(updateLabel, BUTTON_WIDTH, BUTTON_WIDTH, BUTTON_WIDTH) .addComponent(updateButton) @@ -188,15 +202,14 @@ class StatusPanel extends JPanel { .addGroup(layout.createSequentialGroup() .addComponent(installButton) .addGroup(layout.createParallelGroup() - .addComponent(progressPanel) + .addComponent(progressBar) .addComponent(updateLabel)) .addComponent(updateButton).addComponent(removeButton))); layout.linkSize(SwingConstants.HORIZONTAL, - installButton, progressPanel, updateButton, removeButton); + installButton, progressBar, updateButton, removeButton); - progressPanel.setVisible(false); - updateLabel.setVisible(false); + progressBar.setVisible(false); installButton.setEnabled(false); updateButton.setEnabled(false); @@ -210,11 +223,98 @@ class StatusPanel extends JPanel { } - void setMessage(String message) { - if (label != null) { - label.setText(message); - label.repaint(); + protected void resetProgressBar() { + // TODO is this overkill for a reset? is this really only being used + // when we mean to call setVisible(false)? [fry 220311] + progressBar.setString(Language.text("contrib.progress.starting")); + progressBar.setIndeterminate(false); + progressBar.setValue(0); + progressBar.setVisible(false); + } + + + protected void updateTheme() { + setBackground(Theme.getColor("manager.panel.background.color")); + + Font detailFont = Theme.getFont("manager.panel.font"); + detailStyle = + "body { " + + " margin: 0; " + + " padding: 0;" + + " font-family: " + detailFont.getName() + ", sans-serif;" + + " font-size: " + detailFont.getSize() + "px;" + + " color: " + Theme.get("manager.panel.text.color") + ";" + + "}" + + "a { " + + " color: " + Theme.get("manager.panel.link.color") + ";" + + " text-decoration: none;" + + "}"; + + updateLabel.setForeground(Theme.getColor("manager.panel.text.color")); + + // I'm not circuitous, *you're* circuitous. + StatusDetail detail = contributionTab.listPanel.getSelectedDetail(); + if (detail != null) { + updateDetail(detail); } + + foundationIcon = Toolkit.renderIcon("manager/foundation", Theme.get("manager.panel.foundation.color"), 32); + + updateButtonTheme(installButton, "install"); + updateButtonTheme(updateButton, "update"); + updateButtonTheme(removeButton, "remove"); + + StatusDetail currentDetail = + contributionTab.listPanel.getSelectedDetail(); + if (currentDetail != null) { + currentDetail.updateTheme(); + } + + if (progressBar.getUI() instanceof PdeProgressBarUI) { + ((PdeProgressBarUI) progressBar.getUI()).updateTheme(); + } else { + progressBar.setUI(new PdeProgressBarUI("manager.progress")); + } + + /* + if (installButton.getUI() instanceof PdeButtonUI) { + ((PdeButtonUI) installButton.getUI()).updateTheme(); + } else { + installButton.setUI(new PdeButtonUI("manager.button")); + } + */ + + /* + installButton.setForeground(Theme.getColor("manager.button.text.color")); + installButton.setBackground(Theme.getColor("manager.button.background.color")); + //installButton.setBorder(new EmptyBorder(2, 14, 2, 14)); + //installButton.setBorder(new LineBorder(Color.ORANGE, 1, true)); + + // draws correctly, but specifying rounded doesn't help + // still doesn't work for disabled state, so what's the point + installButton.setBorder(new CompoundBorder( + new LineBorder(Color.ORANGE, 1), //, true), + new EmptyBorder(2, 14, 2, 14) + )); + */ + +// installButton.setBorder(new EmptyBorder(0, 0, 0, 0)); +// installButton.setMargin(new Insets(2, 14, 2, 14)); +// installButton.putClientProperty(FlatClientProperties.OUTLINE, Color.GREEN); +// installButton.putClientProperty("Component.borderWidth", "10"); // does not work + } + + + static private void updateButtonTheme(JButton button, String name) { + if (button.getUI() instanceof PdeButtonUI) { + ((PdeButtonUI) button.getUI()).updateTheme(); + } else { + button.setUI(new PdeButtonUI("manager.button")); + } + + button.setIcon(Toolkit.renderIcon("manager/panel-" + name, Theme.get("manager.button.enabled.icon.color"), 16)); + button.setPressedIcon(Toolkit.renderIcon("manager/panel-" + name, Theme.get("manager.button.pressed.icon.color"), 16)); + button.setDisabledIcon(Toolkit.renderIcon("manager/panel-" + name, Theme.get("manager.button.disabled.icon.color"), 16)); } @@ -234,42 +334,116 @@ class StatusPanel extends JPanel { } - public void update(DetailPanel panel) { - progressPanel.removeAll(); + static private final String REMOVE_RESTART_MESSAGE = + String.format("%s", Language.text("contrib.messages.remove_restart")); - iconLabel.setIcon(panel.getContrib().isSpecial() ? foundationIcon : null); - label.setText(panel.description); - ((HTMLDocument)label.getDocument()).getStyleSheet().addRule(bodyRule); + static private final String INSTALL_RESTART_MESSAGE = + String.format("%s", Language.text("contrib.messages.install_restart")); - updateButton.setEnabled(contributionListing.hasDownloadedLatestList() && - (contributionListing.hasUpdates(panel.getContrib()) && - !panel.getContrib().isUpdateFlagged()) && - !panel.updateInProgress); + static private final String UPDATE_RESTART_MESSAGE = + String.format("%s", Language.text("contrib.messages.update_restart")); - String latestVersion = - contributionListing.getLatestPrettyVersion(panel.getContrib()); - String currentVersion = panel.getContrib().getPrettyVersion(); + static String updateDescription(Contribution contrib) { + String fontFace = ""; - installButton.setEnabled(!panel.getContrib().isInstalled() && - contributionListing.hasDownloadedLatestList() && - panel.getContrib().isCompatible(Base.getRevision()) && - !panel.installInProgress); + StringBuilder desc = new StringBuilder(); + desc.append(""); + desc.append(fontFace); + if (contrib.getUrl() == null) { + desc.append(contrib.getName()); + } else { + desc.append(""); + desc.append(contrib.getName()); + desc.append(""); + } + desc.append(" "); - if (panel.getContrib().isCompatible(Base.getRevision())) { + String prettyVersion = contrib.getPrettyVersion(); + if (prettyVersion != null) { + desc.append(prettyVersion); + } + desc.append("
"); + + String authorList = contrib.getAuthorList(); + if (authorList != null && !authorList.isEmpty()) { + desc.append(Util.markDownLinksToHtml(contrib.getAuthorList())); + } + desc.append("

"); + + if (contrib.isDeletionFlagged()) { + desc.append(REMOVE_RESTART_MESSAGE); + } else if (contrib.isRestartFlagged()) { + desc.append(INSTALL_RESTART_MESSAGE); + } else if (contrib.isUpdateFlagged()) { + desc.append(UPDATE_RESTART_MESSAGE); + } else { + String sentence = contrib.getSentence(); + if (sentence == null || sentence.isEmpty()) { + sentence = "" + Language.text("contrib.errors.description_unavailable") + ""; + } else { + sentence = Util.sanitizeHtmlTags(sentence); + sentence = Util.markDownLinksToHtml(sentence); + } + desc.append(sentence); + } + + long lastUpdatedUTC = contrib.getLastUpdated(); + if (lastUpdatedUTC != 0) { + DateFormat dateFormatter = DateFormat.getDateInstance(DateFormat.MEDIUM); + Date lastUpdatedDate = new Date(lastUpdatedUTC); + if (prettyVersion != null) { + desc.append(", "); + } + desc.append("Last Updated on "); + desc.append(dateFormatter.format(lastUpdatedDate)); + } + + desc.append(""); + return desc.toString(); + } + + + void updateDetail(StatusDetail detail) { +// System.out.println("rebuilding status detail for " + detail.getContrib().name); +// new Exception("rebuilding status detail for " + detail.getContrib().name).printStackTrace(System.out); + Contribution contrib = detail.getContrib(); + + iconLabel.setIcon(contrib.isFoundation() ? foundationIcon : null); + label.setText(updateDescription(contrib)); + ((HTMLDocument) label.getDocument()).getStyleSheet().addRule(detailStyle); + + ContributionListing listing = ContributionListing.getInstance(); + + updateButton.setEnabled(listing.isDownloaded() && + (listing.hasUpdates(contrib) && + !contrib.isUpdateFlagged()) && + !detail.updateInProgress); + + String latestVersion = listing.getLatestPrettyVersion(contrib); + String currentVersion = contrib.getPrettyVersion(); + + installButton.setEnabled(!contrib.isInstalled() && + listing.isDownloaded() && + contrib.isCompatible(Base.getRevision()) && + !detail.installInProgress); + + if (contrib.isCompatible(Base.getRevision())) { if (installButton.isEnabled()) { if (latestVersion != null) { updateLabel.setText(latestVersion + " available"); } else { updateLabel.setText("Available"); } - } else { + } else { // install disabled if (currentVersion != null) { updateLabel.setText(currentVersion + " installed"); } else { updateLabel.setText("Installed"); } } - } else { + } else { // contrib not compatible if (currentVersion != null) { updateLabel.setText(currentVersion + " not compatible"); } else { @@ -277,26 +451,27 @@ class StatusPanel extends JPanel { } } - if (latestVersion != null) { - latestVersion = "Update to " + latestVersion; - } else { - latestVersion = "Update"; - } - - if (updateButton.isEnabled()) { - updateButton.setText(latestVersion); + if (updateButton.isEnabled() && latestVersion != null) { + updateButton.setText("Update to " + latestVersion); } else { updateButton.setText("Update"); } - removeButton.setEnabled(panel.getContrib().isInstalled() && !panel.removeInProgress); - progressPanel.add(panel.installProgressBar); - progressPanel.setVisible(false); - updateLabel.setVisible(true); - if (panel.updateInProgress || panel.installInProgress || panel.removeInProgress) { - progressPanel.setVisible(true); + removeButton.setEnabled(contrib.isInstalled() && !detail.removeInProgress); + + if (detail.updateInProgress || detail.installInProgress || detail.removeInProgress) { +// progressBar.setUI(new PdeProgressBarUI("manager.progress")); +// System.out.println(progressBar.getUI()); +// ((PdeProgressBarUI) progressBar.getUI()).updateTheme(); + progressBar.setVisible(true); +// System.out.println(progressBar.getUI()); +// System.out.println("progress bar bg: " + progressBar.getBackground()); updateLabel.setVisible(false); - progressPanel.repaint(); + } else { + progressBar.setVisible(false); + updateLabel.setVisible(true); } + detail.setProgressBar(progressBar); +// progressPanel.repaint(); // needed? [fry 220504] } } diff --git a/app/src/processing/app/contrib/ToolContribution.java b/app/src/processing/app/contrib/ToolContribution.java index a9afc7f29..955220cc2 100644 --- a/app/src/processing/app/contrib/ToolContribution.java +++ b/app/src/processing/app/contrib/ToolContribution.java @@ -1,9 +1,9 @@ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* - Part of the Processing project - http://processing.org + Part of the Processing project - https://processing.org - Copyright (c) 2013-15 The Processing Foundation + Copyright (c) 2013-22 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - package processing.app.contrib; import java.io.*; @@ -33,8 +32,8 @@ import processing.app.tools.Tool; public class ToolContribution extends LocalContribution implements Tool, Comparable { private Tool tool; + final private File referenceFile; // shortname/reference/index.html - private File referenceFile; // shortname/reference/index.html static public ToolContribution load(File folder) { try { @@ -83,16 +82,6 @@ public class ToolContribution extends LocalContribution implements Tool, Compara } -// static protected List discover(File folder) { -// File[] folders = listCandidates(folder, "tool"); -// if (folders == null) { -// return new ArrayList(); -// } else { -// return Arrays.asList(folders); -// } -// } - - static public List loadAll(File toolsFolder) { File[] list = ContributionType.TOOL.listCandidates(toolsFolder); ArrayList outgoing = new ArrayList<>(); @@ -137,9 +126,7 @@ public class ToolContribution extends LocalContribution implements Tool, Compara /** * Returns the object stored in the referenceFile field, which contains an - * instance of the file object representing the index file of the reference - * - * @return referenceFile + * instance of the file object representing the index file of the reference. */ public File getReferenceIndexFile() { return referenceFile; @@ -148,9 +135,7 @@ public class ToolContribution extends LocalContribution implements Tool, Compara /** * Tests whether the reference's index file indicated by referenceFile exists. - * - * @return true if and only if the file denoted by referenceFile exists; false - * otherwise. + * @return true if the referenceFile exists; false otherwise. */ public boolean hasReference() { return referenceFile.exists(); diff --git a/app/src/processing/app/contrib/UpdateContributionTab.java b/app/src/processing/app/contrib/UpdateContributionTab.java index a3de07f38..f104cf4fe 100644 --- a/app/src/processing/app/contrib/UpdateContributionTab.java +++ b/app/src/processing/app/contrib/UpdateContributionTab.java @@ -1,19 +1,32 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - https://processing.org + + Copyright (c) 2015-22 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ package processing.app.contrib; -import java.awt.Color; - import javax.swing.GroupLayout; -import javax.swing.JLabel; -import javax.swing.JProgressBar; - -import processing.app.ui.Toolkit; public class UpdateContributionTab extends ContributionTab { public UpdateContributionTab(ManagerFrame dialog) { - super(); - this.contribDialog = dialog; + super(dialog); filter = contrib -> { if (contrib instanceof ListPanel.SectionHeaderContribution) { @@ -24,34 +37,34 @@ public class UpdateContributionTab extends ContributionTab { } return false; }; - contributionListPanel = new UpdateListPanel(this, filter); -// contributionListPanel.setBorder(new EmptyBorder(8, 8, 8, 8)); + listPanel = new UpdateListPanel(this, filter); - statusPanel = new UpdateStatusPanel(this, 650); - contribListing = ContributionListing.getInstance(); - contribListing.addListener(contributionListPanel); + statusPanel = new UpdateStatusPanel(this); + ContributionListing.getInstance().addListPanel(listPanel); } @Override - protected void setLayout(boolean error, boolean loading) { - if (progressBar == null) { - progressBar = new JProgressBar(); - progressBar.setVisible(false); + protected void setLayout() { + /* + if (loaderLabel == null) { +// if (progressBar == null) { +// progressBar = new JProgressBar(); +// progressBar.setVisible(false); buildErrorPanel(); loaderLabel = new JLabel(Toolkit.getLibIcon("manager/loader.gif")); loaderLabel.setOpaque(false); -// loaderLabel.setBackground(Color.WHITE); } + */ GroupLayout layout = new GroupLayout(this); setLayout(layout); layout.setHorizontalGroup(layout .createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(loaderLabel) - .addComponent(contributionListPanel) + .addComponent(listPanel) .addComponent(errorPanel) .addComponent(statusPanel)); @@ -59,17 +72,18 @@ public class UpdateContributionTab extends ContributionTab { .createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(loaderLabel) - .addComponent(contributionListPanel)) + .addComponent(listPanel)) .addComponent(errorPanel) .addComponent(statusPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)); - layout.setHonorsVisibility(contributionListPanel, false); + layout.setHonorsVisibility(listPanel, false); - setBackground(Color.WHITE); + //setBackground(Color.WHITE); } + @Override - public void updateStatusPanel(DetailPanel contributionPanel) { + public void updateStatusDetail(StatusDetail detail) { // Do nothing } } diff --git a/app/src/processing/app/contrib/UpdateListPanel.java b/app/src/processing/app/contrib/UpdateListPanel.java index 6ade2e439..b374ab1ba 100644 --- a/app/src/processing/app/contrib/UpdateListPanel.java +++ b/app/src/processing/app/contrib/UpdateListPanel.java @@ -1,9 +1,32 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - https://processing.org + + Copyright (c) 2015-22 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ package processing.app.contrib; -public class UpdateListPanel extends ListPanel { +import javax.swing.table.TableColumnModel; + +public class UpdateListPanel extends ListPanel { Contribution.Filter contribFilter; + public UpdateListPanel(ContributionTab contributionTab, Contribution.Filter contribFilter) { super(contributionTab, contribFilter, true, @@ -13,38 +36,48 @@ public class UpdateListPanel extends ListPanel { ContributionColumn.INSTALLED_VERSION, ContributionColumn.AVAILABLE_VERSION); + TableColumnModel tcm = table.getColumnModel(); + tcm.getColumn(3).setMaxWidth(ManagerFrame.VERSION_WIDTH); + tcm.getColumn(4).setMaxWidth(ManagerFrame.VERSION_WIDTH); + this.contribFilter = contribFilter; + // This is apparently a hack to prevent rows from being sorted by + // clicking on the column headers, which makes a mess because this + // list has sub-headers for the categories mixed into the list. + // However, unfortunately it also breaks column resizing. [fry 220726] table.getTableHeader().setEnabled(false); } + // Thread: EDT @Override public void contributionAdded(final Contribution contribution) { // Ensures contributionAdded in ListPanel is only run on LocalContributions if (contribFilter.matches(contribution)) { super.contributionAdded(contribution); - ((UpdateStatusPanel) contributionTab.statusPanel).update(); // Enables update button + ((UpdateStatusPanel) contributionTab.statusPanel).updateEnabled(getRowCount() > 0); // Enables update button } } + // Thread: EDT @Override public void contributionRemoved(final Contribution contribution) { super.contributionRemoved(contribution); - ((UpdateStatusPanel) contributionTab.statusPanel).update(); // Disables update button on last contribution + ((UpdateStatusPanel) contributionTab.statusPanel).updateEnabled(getRowCount() > 0); // Disables update button on last contribution } + // Thread: EDT @Override public void contributionChanged(final Contribution oldContrib, final Contribution newContrib) { - DetailPanel panel = panelByContribution.get(oldContrib); - if (panel == null) { + StatusDetail detail = detailForContrib.get(oldContrib); + if (detail == null) { contributionAdded(newContrib); } else if (newContrib.isInstalled()) { - panelByContribution.remove(oldContrib); + detailForContrib.remove(oldContrib); } model.fireTableDataChanged(); } - } diff --git a/app/src/processing/app/contrib/UpdateStatusPanel.java b/app/src/processing/app/contrib/UpdateStatusPanel.java index f91db9fbd..0fff20b45 100644 --- a/app/src/processing/app/contrib/UpdateStatusPanel.java +++ b/app/src/processing/app/contrib/UpdateStatusPanel.java @@ -1,9 +1,9 @@ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* - Part of the Processing project - http://processing.org + Part of the Processing project - https://processing.org - Copyright (c) 2013-16 The Processing Foundation + Copyright (c) 2013-22 The Processing Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 @@ -20,36 +20,23 @@ */ package processing.app.contrib; -import java.awt.Color; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - import javax.swing.GroupLayout; +import javax.swing.JButton; import javax.swing.LayoutStyle; import javax.swing.SwingConstants; -import processing.app.ui.Toolkit; - public class UpdateStatusPanel extends StatusPanel { - public UpdateStatusPanel(UpdateContributionTab tab, int width) { - super(); - this.contributionTab = tab; + public UpdateStatusPanel(UpdateContributionTab tab) { + super(tab); - updateButton = Toolkit.createIconButton("Update All", "manager/update"); - updateButton.setFont(ManagerFrame.NORMAL_PLAIN); + updateButton = new JButton("Update All"); updateButton.setHorizontalAlignment(SwingConstants.LEFT); updateButton.setVisible(true); updateButton.setEnabled(false); - updateButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - contributionTab.updateAll(); - } - }); - setBackground(new Color(0xebebeb)); + updateButton.addActionListener(e -> updateAll()); layout = new GroupLayout(this); setLayout(layout); @@ -68,7 +55,15 @@ public class UpdateStatusPanel extends StatusPanel { } - public void update() { - updateButton.setEnabled(contributionTab.hasUpdates()); + private void updateAll() { + for (StatusDetail detail : contributionTab.listPanel.detailForContrib.values()) { + detail.update(); + } + contributionTab.listPanel.model.fireTableDataChanged(); + } + + + protected void updateEnabled(boolean updateEnabled) { + updateButton.setEnabled(updateEnabled); } } \ No newline at end of file diff --git a/app/src/processing/app/laf/FlatLaf.properties b/app/src/processing/app/laf/FlatLaf.properties new file mode 100644 index 000000000..d21d94540 --- /dev/null +++ b/app/src/processing/app/laf/FlatLaf.properties @@ -0,0 +1,21 @@ +# The default is 8, which creates tiny nubby scroll bars +ScrollBar.width = 16 + + +# Better matched for macOS dark mode (but using everywhere) +# https://github.com/JFormDesigner/FlatLaf/issues/497 + +[dark]@background = #1e1e1e +[dark]@foreground = #e0e0e0 +[dark]@accentColor = #107aff +[dark]@accentFocusColor = #176896 + +[dark]Component.arrowType = chevron + +[dark]CheckBox.icon.style = filled +[dark]CheckBox.icon[filled].selectedBorderColor = @accentColor +[dark]CheckBox.icon[filled].selectedBackground = @accentColor +[dark]CheckBox.icon[filled].checkmarkColor = @foreground + +[dark]RadioButton.icon.style = filled +[dark]RadioButton.icon[filled].centerDiameter = 6 diff --git a/app/src/processing/app/laf/PdeButtonUI.java b/app/src/processing/app/laf/PdeButtonUI.java new file mode 100644 index 000000000..305b39547 --- /dev/null +++ b/app/src/processing/app/laf/PdeButtonUI.java @@ -0,0 +1,139 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PdeScrollBarUI - Custom scroll bar for the editor + Part of the Processing project - https://processing.org + + Copyright (c) 2022 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +package processing.app.laf; + +import processing.app.ui.Theme; +import processing.app.ui.Toolkit; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.plaf.basic.BasicButtonUI; +import java.awt.*; + + +// https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/master/jdk/src/share/classes/javax/swing/plaf/basic/BasicButtonUI.java +public class PdeButtonUI extends BasicButtonUI { + final String prefix; + + Color enabledFgColor; + Color enabledBgColor; + Color pressedFgColor; + Color pressedBgColor; + Color disabledFgColor; + Color disabledBgColor; + + + public PdeButtonUI(String prefix) { + this.prefix = prefix; + updateTheme(); + } + + + protected void installDefaults(AbstractButton b) { + super.installDefaults(b); + //b.setBorder(null); + b.setBorder(new EmptyBorder(2, 14, 2, 14)); +// b.setBorder(new EmptyBorder(2, 2, 2, 2)); + b.setOpaque(false); // so that rounded rect works properly + } + + + @Override + public void update(Graphics g, JComponent c) { + ButtonModel model = ((AbstractButton) c).getModel(); + +// if (c.isOpaque()) { + //g.setColor(c.getBackground()); + //g.fillRect(0, 0, c.getWidth(),c.getHeight()); + if (model.isPressed()) { + g.setColor(pressedBgColor); + } else if (model.isEnabled()) { + g.setColor(enabledBgColor); + } else { + g.setColor(disabledBgColor); + } + //g.setColor(c.isEnabled() ? bgColor : disabledBgColor); + Toolkit.prepareGraphics(g); + g.fillRoundRect(0, 0, c.getWidth(), c.getHeight(), 8, 8); +// } + paint(g, c); + } + + + /* + @Override + public void paint(Graphics g, JComponent c) { + //c.setBorder(null); + //c.setBorder(new EmptyBorder(2, 14, 2, 14)); + c.setBackground(c.isEnabled() ? bgColor : disabledBgColor); +// c.setBackground(null); + + // this didn't seem to draw anything? +// g.setColor(c.isEnabled() ? bgColor : disabledBgColor); +// Rectangle r = c.getBounds(); +// System.out.println("bounds = " + r); +// ((Graphics2D) g).fill(c.getBounds()); +// g.fillRoundRect(r.x, r.y, r.width, r.height, 4, 4); + + super.paint(g, c); + } + */ + + + protected void paintText(Graphics g, JComponent c, Rectangle textRect, String text) { + AbstractButton b = (AbstractButton) c; + ButtonModel model = b.getModel(); + FontMetrics fm = SwingUtilities2.getFontMetrics(c, g); +// FontMetrics fm = c.getFontMetrics(g.getFont()); + int mnemonicIndex = b.getDisplayedMnemonicIndex(); + + if (model.isPressed()) { + g.setColor(pressedFgColor); + } else if (model.isEnabled()) { + g.setColor(enabledFgColor); + } else { + g.setColor(disabledFgColor); + } + SwingUtilities2.drawStringUnderlineCharAt(c, g,text, mnemonicIndex, + textRect.x + getTextShiftOffset(), + textRect.y + fm.getAscent() + getTextShiftOffset()); + +// } else { +// //g.setColor(b.getBackground().brighter()); +// g.setColor(disabledFgColor); +// SwingUtilities2.drawStringUnderlineCharAt(c, g, text, mnemonicIndex, +// textRect.x, textRect.y + fm.getAscent()); +// } + } + + + public void updateTheme() { + enabledFgColor = Theme.getColor(prefix + ".enabled.text.color"); + enabledBgColor = Theme.getColor(prefix + ".enabled.background.color"); + pressedFgColor = Theme.getColor(prefix + ".pressed.text.color"); + pressedBgColor = Theme.getColor(prefix + ".pressed.background.color"); + disabledFgColor = Theme.getColor(prefix + ".disabled.text.color"); + disabledBgColor = Theme.getColor(prefix + ".disabled.background.color"); + } +} \ No newline at end of file diff --git a/app/src/processing/app/laf/PdeComboBoxUI.java b/app/src/processing/app/laf/PdeComboBoxUI.java new file mode 100644 index 000000000..84bc6c5c7 --- /dev/null +++ b/app/src/processing/app/laf/PdeComboBoxUI.java @@ -0,0 +1,189 @@ +package processing.app.laf; + +import processing.app.ui.Theme; +import processing.app.ui.Toolkit; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.plaf.basic.BasicComboBoxUI; +import java.awt.*; + + +// https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/master/jdk/src/share/classes/javax/swing/plaf/basic/BasicComboBoxUI.java +public class PdeComboBoxUI extends BasicComboBoxUI { + final String prefix; + + Color enabledFgColor; + Color enabledBgColor; + Color disabledFgColor; + Color disabledBgColor; + Color selectedFgColor; + Color selectedBgColor; + + + public PdeComboBoxUI(String prefix) { + this.prefix = prefix; + } + + + @Override + protected void installDefaults() { + super.installDefaults(); + comboBox.setBorder(null); + } + + + @Override + protected JButton createArrowButton() { + /* + JButton button = new BasicArrowButton(BasicArrowButton.SOUTH, + UIManager.getColor("ComboBox.buttonBackground"), + UIManager.getColor("ComboBox.buttonShadow"), + UIManager.getColor("ComboBox.buttonDarkShadow"), + UIManager.getColor("ComboBox.buttonHighlight")); + button.setName("ComboBox.arrowButton"); + return button; + */ + JButton button = new JButton(); + //JButton button = new JButton("\u2193"); // arrow down +// JButton button = new JButton("\u25BC"); // filled triangle +// JButton button = new JButton("\u25BD"); // outlined triangle + //JButton button = new JButton("\u02C5 . \u02EC . \u142f"); +// JButton button = new JButton("\u02EC . \u142f"); +// button.setFont(new Font("Dialog", Font.PLAIN, 8)); +// button.putClientProperty("FlatLaf.styleClass", "mini"); + //button.putClientProperty(); + button.setBorder(null); + return button; + } + + + @Override + protected ListCellRenderer createRenderer() { + return new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + // TODO There must be a less convoluted way to do this [fry 220505] + Container parent = getParent(); + if (parent instanceof CellRendererPane) { + Container crp = parent.getParent(); + // anonymous class javax.swing.plaf.basic.BasicComboPopup$1 + if (crp instanceof JComponent) { + Container viewport = crp.getParent(); + if (viewport instanceof JViewport) { + Container scrollPane = viewport.getParent(); + if (scrollPane instanceof JScrollPane) { + Container popup = scrollPane.getParent(); + if (popup instanceof JComponent) { + // com.formdev.flatlaf.ui.FlatComboBoxUI$FlatComboPopup + JComponent c = (JComponent) popup; + if (!(c.getBorder() instanceof EmptyBorder)) { // just once + // remove the black outline from the popup + c.setBorder(new EmptyBorder(0, 0, 0, 0)); + } + } + } + } + } + } + + // Can't use instanceof because FlatLaf Border is EmptyBorder subclass. + // If this is the currently selected item (index == -1), do not add + // extra left indent, so the list items left-align with the selection. + setBorder(new EmptyBorder(2, index == -1 ? 0 : 6, 2, 2)); + + if (isSelected) { + setForeground(selectedFgColor); + setBackground(selectedBgColor); + } else { + setForeground(enabledFgColor); + setBackground(enabledBgColor); + } + setText(String.valueOf(value)); + return this; + } + }; + } + + + @Override + public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus) { + ListCellRenderer renderer = comboBox.getRenderer(); + Component c; + + if (hasFocus && !isPopupVisible(comboBox) ) { + c = renderer.getListCellRendererComponent(listBox, comboBox.getSelectedItem(), -1, true, false ); + } else { + c = renderer.getListCellRendererComponent(listBox, comboBox.getSelectedItem(), -1, false, false ); + //c.setBackground(UIManager.getColor("ComboBox.background")); + c.setBackground(enabledBgColor); + } + c.setFont(comboBox.getFont()); +// if (hasFocus && !isPopupVisible(comboBox)) { +// //c.setForeground(listBox.getSelectionForeground()); +// //c.setBackground(listBox.getSelectionBackground()); +// c.setForeground(selectedFgColor); +// c.setBackground(selectedBgColor); +// +// } else { + if (comboBox.isEnabled()) { +// c.setForeground(comboBox.getForeground()); +// c.setBackground(comboBox.getBackground()); + c.setForeground(enabledFgColor); + c.setBackground(enabledBgColor); + } else { + c.setForeground(disabledFgColor); + c.setBackground(disabledBgColor); + } +// } + + // Fix for 4238829: should lay out the JPanel. + boolean shouldValidate = c instanceof JPanel; + + int x = bounds.x, y = bounds.y, w = bounds.width, h = bounds.height; + if (padding != null) { + x = bounds.x + padding.left; + y = bounds.y + padding.top; + w = bounds.width - (padding.left + padding.right); + h = bounds.height - (padding.top + padding.bottom); + } + + currentValuePane.paintComponent(g, c, comboBox, x, y, w, h, shouldValidate); + } + + + @Override + public void paintCurrentValueBackground(Graphics g, Rectangle bounds, boolean hasFocus) { + Color t = g.getColor(); + if (comboBox.isEnabled()) { + //g.setColor(DefaultLookup.getColor(comboBox, this, "ComboBox.background", null)); + g.setColor(enabledBgColor); + } else { + //g.setColor(DefaultLookup.getColor(comboBox, this, "ComboBox.disabledBackground", null)); + g.setColor(disabledBgColor); + } + g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); + g.setColor(t); + } + + + public void updateTheme() { + enabledFgColor = Theme.getColor(prefix + ".enabled.fgcolor"); + enabledBgColor = Theme.getColor(prefix + ".enabled.bgcolor"); + disabledFgColor = Theme.getColor(prefix + ".disabled.fgcolor"); + disabledBgColor = Theme.getColor(prefix + ".disabled.bgcolor"); + selectedFgColor = Theme.getColor(prefix + ".selected.fgcolor"); + selectedBgColor = Theme.getColor(prefix + ".selected.bgcolor"); + + if (arrowButton.isEnabled()) { + arrowButton.setBackground(enabledBgColor); + arrowButton.setForeground(enabledFgColor); + arrowButton.setIcon(Toolkit.renderIcon("manager/chevron", Theme.get(prefix + ".enabled.fgcolor"), 16)); + } else { + arrowButton.setBackground(disabledBgColor); + arrowButton.setForeground(disabledFgColor); + arrowButton.setIcon(Toolkit.renderIcon("manager/chevron", Theme.get(prefix + ".disabled.fgcolor"), 16)); + } + } +} \ No newline at end of file diff --git a/app/src/processing/app/laf/PdeLabel.java b/app/src/processing/app/laf/PdeLabel.java new file mode 100644 index 000000000..a7cf23740 --- /dev/null +++ b/app/src/processing/app/laf/PdeLabel.java @@ -0,0 +1,76 @@ +package processing.app.laf; + +import javax.swing.JTextPane; +import javax.swing.text.html.HTMLDocument; + + +// not in use; this all works with JLabel, but it's confusing as heck +public class PdeLabel extends JTextPane { + String message; + String css; + + + public PdeLabel() { + setEditable(false); + setOpaque(false); + setContentType("text/html"); + + /* + pane.addHyperlinkListener(e -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + if (e.getURL() != null) { + Platform.openURL(e.getURL().toString()); + } + } + }); + */ + } + + + @Override + public void setText(String message) { + this.message = message; + rebuild(); + } + + + public void setText(String message, String css) { + this.message = message; + this.css = css; + rebuild(); + } + + + private void rebuild() { + StringBuffer buffer = new StringBuffer(""); +// if (css != null) { +// buffer.append(""); +// } + buffer.append(""); + buffer.append(message); +// System.out.println(buffer); + super.setText(buffer.toString()); + if (css != null) { + ((HTMLDocument) getDocument()).getStyleSheet().addRule(css); + } + /* + if (css != null) { // reapply the styles + setStyle(css); + } + */ + } + + + public void setStyle(String css) { + //((HTMLDocument) getDocument()).getStyleSheet().addRule(css); + this.css = css; + rebuild(); + } + + + /* + public void setURL(String url) { + this.url = url; + } + */ +} \ No newline at end of file diff --git a/app/src/processing/app/laf/PdeLookAndFeel.java b/app/src/processing/app/laf/PdeLookAndFeel.java new file mode 100644 index 000000000..a29d5de77 --- /dev/null +++ b/app/src/processing/app/laf/PdeLookAndFeel.java @@ -0,0 +1,36 @@ +package processing.app.laf; + +import javax.swing.plaf.basic.BasicLookAndFeel; + + +/** + * Custom Look and Feel class. Not currently in use: + * for now, only individual component UIs are being overridden. + * https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/master/jdk/src/share/classes/javax/swing/plaf/basic/BasicLookAndFeel.java + */ +public class PdeLookAndFeel extends BasicLookAndFeel { + + public String getDescription( ) { + return "The Processing Look and Feel"; + } + + + public String getID( ) { + return "Processing"; + } + + + public String getName( ) { + return "Processing"; + } + + + public boolean isNativeLookAndFeel( ) { + return false; + } + + + public boolean isSupportedLookAndFeel( ) { + return true; + } +} \ No newline at end of file diff --git a/app/src/processing/app/laf/PdeMenuItemUI.java b/app/src/processing/app/laf/PdeMenuItemUI.java new file mode 100644 index 000000000..5bc97628d --- /dev/null +++ b/app/src/processing/app/laf/PdeMenuItemUI.java @@ -0,0 +1,47 @@ +package processing.app.laf; + +import processing.app.ui.Theme; + +import javax.swing.plaf.basic.BasicMenuItemUI; +import java.awt.Color; + + +// https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/master/jdk/src/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java +public class PdeMenuItemUI extends BasicMenuItemUI { + final String prefix; + + Color enabledFgColor; + Color enabledBgColor; + Color disabledFgColor; + Color disabledBgColor; + Color selectedFgColor; + Color selectedBgColor; + + + public PdeMenuItemUI(String prefix) { + this.prefix = prefix; + } + + + public void updateTheme() { + enabledFgColor = Theme.getColor(prefix + ".enabled.fgcolor"); + enabledBgColor = Theme.getColor(prefix + ".enabled.bgcolor"); + disabledFgColor = Theme.getColor(prefix + ".disabled.fgcolor"); + disabledBgColor = Theme.getColor(prefix + ".disabled.bgcolor"); + selectedFgColor = Theme.getColor(prefix + ".selected.fgcolor"); + selectedBgColor = Theme.getColor(prefix + ".selected.bgcolor"); + + // when drawing, this will be overridden when disabled or selected + menuItem.setForeground(enabledFgColor); + + // set bg color of the parent item instead of setting everything opaque +// menuItem.setOpaque(true); +// menuItem.setBackground(enabledBgColor); + + acceleratorForeground = enabledFgColor; + acceleratorSelectionForeground = selectedFgColor; + selectionBackground = selectedBgColor; + selectionForeground = selectedFgColor; + disabledForeground = disabledFgColor; + } +} \ No newline at end of file diff --git a/app/src/processing/app/laf/PdeProgressBarUI.java b/app/src/processing/app/laf/PdeProgressBarUI.java new file mode 100644 index 000000000..9ecc89da9 --- /dev/null +++ b/app/src/processing/app/laf/PdeProgressBarUI.java @@ -0,0 +1,62 @@ +package processing.app.laf; + +import processing.app.ui.Theme; + +import javax.swing.plaf.basic.BasicProgressBarUI; +import java.awt.Color; + + +// https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/master/jdk/src/share/classes/javax/swing/plaf/basic/BasicProgressBarUI.java +public class PdeProgressBarUI extends BasicProgressBarUI { + final String prefix; + + Color incompleteFgColor; + Color incompleteBgColor; + Color completeFgColor; + Color completeBgColor; + + + public PdeProgressBarUI(String prefix) { + this.prefix = prefix; + } + + + @Override + protected void installDefaults() { + super.installDefaults(); + updateTheme(); + } + + + /** + * The "selectionForeground" is the color of the text when it is painted + * over a filled area of the progress bar. + */ + @Override + protected Color getSelectionForeground() { + return completeFgColor; + } + + + /** + * The "selectionBackground" is the color of the text when it is painted + * over an unfilled area of the progress bar. + * + * @return the color of the selected background + */ + @Override + protected Color getSelectionBackground() { + return incompleteFgColor; + } + + + public void updateTheme() { + incompleteFgColor = Theme.getColor(prefix + ".incomplete.fgcolor"); // green + incompleteBgColor = Theme.getColor(prefix + ".incomplete.bgcolor"); // blue + completeFgColor = Theme.getColor(prefix + ".complete.fgcolor"); // black + completeBgColor = Theme.getColor(prefix + ".complete.bgcolor"); // red + + progressBar.setForeground(completeBgColor); + progressBar.setBackground(incompleteBgColor); + } +} \ No newline at end of file diff --git a/app/src/processing/app/laf/PdeScrollBarUI.java b/app/src/processing/app/laf/PdeScrollBarUI.java new file mode 100644 index 000000000..63e3a72c7 --- /dev/null +++ b/app/src/processing/app/laf/PdeScrollBarUI.java @@ -0,0 +1,157 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PdeScrollBarUI - Custom scroll bar for the editor + Part of the Processing project - https://processing.org + + Copyright (c) 2022 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +package processing.app.laf; + +import processing.app.ui.Theme; + +import javax.swing.*; +import javax.swing.plaf.basic.BasicScrollBarUI; +import java.awt.*; + + +/** + * Custom scroll bar style for the editor. + * Originally based on this post + * and extends BasicScrollBarUI. + */ +public class PdeScrollBarUI extends BasicScrollBarUI { + private final Dimension NONE = new Dimension(); + + final private String backgroundAttr; + final private String pressedAttr; + final private String rolloverAttr; + final private String enabledAttr; + + private Color backgroundColor; + private Color pressedColor; + private Color rolloverColor; + private Color enabledColor; + + + public PdeScrollBarUI(String prefix) { + /* + this(prefix + ".scrollbar.color", + prefix + ".scrollbar.thumb.pressed.color", + prefix + ".scrollbar.thumb.rollover.color", + prefix + ".scrollbar.thumb.enabled.color"); + + } + + public PdeScrollBarUI(String backgroundAttr, String pressedAttr, + String rolloverAttr, String enabledAttr) { + this.backgroundAttr = backgroundAttr; + this.pressedAttr = pressedAttr; + this.rolloverAttr = rolloverAttr; + this.enabledAttr = enabledAttr; + */ + + this.backgroundAttr = prefix + ".color"; + this.pressedAttr = prefix + ".thumb.pressed.color"; + this.rolloverAttr = prefix + ".thumb.rollover.color"; + this.enabledAttr = prefix + ".thumb.enabled.color"; + } + + + public void updateTheme() { + backgroundColor = Theme.getColor(backgroundAttr); + pressedColor = Theme.getColor(pressedAttr); + rolloverColor = Theme.getColor(rolloverAttr); + enabledColor = Theme.getColor(enabledAttr); + } + + + @Override + protected JButton createDecreaseButton(int orientation) { + return new JButton() { + + @Override + public Dimension getPreferredSize() { + return NONE; + } + }; + } + + + @Override + protected JButton createIncreaseButton(int orientation) { + return new JButton() { + + @Override + public Dimension getPreferredSize() { + return NONE; + } + }; + } + + + /* + @Override + public void paint(Graphics g, JComponent c) { + // this takes care of the track as well as the corner notch + // where the horizontal and vertical scrollbars meet (edit: nope) + g.setColor(Theme.getColor("editor.scrollbar.color")); + g.fillRect(0, 0, c.getWidth(), c.getHeight()); + super.paint(g, c); + } + */ + + + @Override + protected void paintTrack(Graphics g, JComponent c, Rectangle r) { + g.setColor(backgroundColor); + g.fillRect(0, 0, c.getWidth(), c.getHeight()); + } + + + @Override + protected void paintThumb(Graphics g, JComponent c, Rectangle r) { + Graphics2D g2 = (Graphics2D) g.create(); + // this can't really be necessary, can it? + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + Color color; + JScrollBar sb = (JScrollBar) c; + if (sb.isEnabled()) { + if (isDragging) { + color = pressedColor; + } else if (isThumbRollover()) { + color = rolloverColor; + } else { + color = enabledColor; + } + g2.setPaint(color); + int inset = 3; + int arc = Math.min(c.getWidth(), c.getHeight()) - inset*2; + g2.fillRoundRect(r.x + inset, r.y + inset, r.width - inset*2, r.height - inset*2, arc, arc); + g2.dispose(); + } + } + + + @Override + protected void setThumbBounds(int x, int y, int width, int height) { + super.setThumbBounds(x, y, width, height); + scrollbar.repaint(); + } +} \ No newline at end of file diff --git a/app/src/processing/app/laf/PdeTextFieldUI.java b/app/src/processing/app/laf/PdeTextFieldUI.java new file mode 100644 index 000000000..acc06a69e --- /dev/null +++ b/app/src/processing/app/laf/PdeTextFieldUI.java @@ -0,0 +1,125 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PdeScrollBarUI - Custom scroll bar for the editor + Part of the Processing project - https://processing.org + + Copyright (c) 2022 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +package processing.app.laf; + +import processing.app.ui.Theme; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.plaf.UIResource; +import javax.swing.plaf.basic.BasicScrollBarUI; +import javax.swing.plaf.basic.BasicTextFieldUI; +import javax.swing.plaf.basic.BasicTextUI; +import javax.swing.text.JTextComponent; +import java.awt.*; + + +// Not currently in use / not yet finished +// https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/master/jdk/src/share/classes/javax/swing/plaf/basic/BasicTextFieldUI.java +public class PdeTextFieldUI extends BasicTextFieldUI { + final String prefix; + + JTextComponent jtc; + + + public PdeTextFieldUI(String prefix) { + this.prefix = prefix; + } + + + public void installUI(JComponent c) { + super.installUI(c); + + jtc = (JTextComponent) c; + +// manager.list.search.text.color = #000000 +// manager.list.search.placeholder.color = #cccccc +// manager.list.search.background.color = #ffffff + +// jtc.setBorder(null); // makes text too flush to the sides + jtc.setBorder(new EmptyBorder(0, 5, 0, 5)); + + jtc.setBackground(Theme.getColor(prefix + ".background.color")); + jtc.setForeground(Theme.getColor(prefix + ".text.color")); + + // not yet in use, so leaving out for now + //jtc.setDisabledTextColor(Theme.getColor(prefix + ".disabled.text.color")); + + jtc.setSelectionColor(Theme.getColor(prefix + ".selection.background.color")); + jtc.setSelectedTextColor(Theme.getColor(prefix + ".selection.text.color")); + + jtc.setCaretColor(Theme.getColor(prefix + ".caret.color")); + + //JComponent editor = ((BasicTextUI) c).getComponent(); +// JComponent editor = ((BasicTextUI) c.getUI()).getComponent(); +// editor.setBackground(UIManager.getColor(prefix + ".background")); + + /* + Color fg = editor.getForeground(); + if ((fg == null) || (fg instanceof UIResource)) { + editor.setForeground(UIManager.getColor(prefix + ".foreground")); + } + + Color color = editor.getCaretColor(); + if ((color == null) || (color instanceof UIResource)) { + editor.setCaretColor(UIManager.getColor(prefix + ".caretForeground")); + } + + Color s = editor.getSelectionColor(); + if ((s == null) || (s instanceof UIResource)) { + editor.setSelectionColor(UIManager.getColor(prefix + ".selectionBackground")); + } + + Color sfg = editor.getSelectedTextColor(); + if ((sfg == null) || (sfg instanceof UIResource)) { + editor.setSelectedTextColor(UIManager.getColor(prefix + ".selectionForeground")); + } + + Color dfg = editor.getDisabledTextColor(); + if ((dfg == null) || (dfg instanceof UIResource)) { + editor.setDisabledTextColor(UIManager.getColor(prefix + ".inactiveForeground")); + } + + Border b = editor.getBorder(); + if ((b == null) || (b instanceof UIResource)) { + editor.setBorder(UIManager.getBorder(prefix + ".border")); + } + + Insets margin = editor.getMargin(); + if (margin == null || margin instanceof UIResource) { + editor.setMargin(UIManager.getInsets(prefix + ".margin")); + } + */ + } + + + public void updateTheme() { + //System.out.println("updating theme inside PdeTextFieldUI"); + if (jtc != null) { + //System.out.println("reinstalling ui"); + installUI(jtc); + } + } +} \ No newline at end of file diff --git a/app/src/processing/app/laf/SwingUtilities2.java b/app/src/processing/app/laf/SwingUtilities2.java new file mode 100644 index 000000000..c95aa7ab8 --- /dev/null +++ b/app/src/processing/app/laf/SwingUtilities2.java @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2002, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package processing.app.laf; + +import javax.swing.JComponent; +import java.awt.Component; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Toolkit; +import java.awt.font.FontRenderContext; +import java.awt.font.TextAttribute; +import java.awt.font.TextHitInfo; +import java.awt.font.TextLayout; +import java.awt.geom.Rectangle2D; +import java.util.HashMap; +import java.util.Map; + +import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING; +import static java.awt.RenderingHints.KEY_TEXT_LCD_CONTRAST; + + +/** + * Hacked up from the source of sun.swing.SwingUtilities2. + */ +class SwingUtilities2 { + private static final int CHAR_BUFFER_SIZE = 100; + private static final Object charsBufferLock = new Object(); + private static char[] charsBuffer = new char[CHAR_BUFFER_SIZE]; + + public static final FontRenderContext DEFAULT_FRC = + new FontRenderContext(null, false, false); + + /** + * Fill the character buffer cache. Return the buffer length. + */ + private static int syncCharsBuffer(String s) { + int length = s.length(); + if ((charsBuffer == null) || (charsBuffer.length < length)) { + charsBuffer = s.toCharArray(); + } else { + s.getChars(0, length, charsBuffer, 0); + } + return length; + } + + /** + * checks whether TextLayout is required to handle characters. + * + * @param text characters to be tested + * @param start start + * @param limit limit + * @return {@code true} if TextLayout is required + * {@code false} if TextLayout is not required + */ + public static final boolean isComplexLayout(char[] text, int start, int limit) { + //return FontUtilities.isComplexText(text, start, limit); + return false; + } + + /** + * Returns the FontMetrics for the current Font of the passed + * in Graphics. This method is used when a Graphics + * is available, typically when painting. If a Graphics is not + * available the JComponent method of the same name should be used. + *

+ * Callers should pass in a non-null JComponent, the exception + * to this is if a JComponent is not readily available at the time of + * painting. + *

+ * This does not necessarily return the FontMetrics from the + * Graphics. + * + * @param c JComponent requesting FontMetrics, may be null + * @param g Graphics Graphics + */ + public static FontMetrics getFontMetrics(JComponent c, Graphics g) { + return getFontMetrics(c, g, g.getFont()); + } + + + /** + * Returns the FontMetrics for the specified Font. + * This method is used when a Graphics is available, typically when + * painting. If a Graphics is not available the JComponent method of + * the same name should be used. + *

+ * Callers should pass in a non-null JComonent, the exception + * to this is if a JComponent is not readily available at the time of + * painting. + *

+ * This does not necessarily return the FontMetrics from the + * Graphics. + * + * @param c JComponent requesting FontMetrics, may be null + * @param c Graphics Graphics + * @param font Font to get FontMetrics for + */ + @SuppressWarnings("deprecation") + public static FontMetrics getFontMetrics(JComponent c, Graphics g, + Font font) { + if (c != null) { + // Note: We assume that we're using the FontMetrics + // from the widget to layout out text, otherwise we can get + // mismatches when printing. + return c.getFontMetrics(font); + } + return Toolkit.getDefaultToolkit().getFontMetrics(font); + } + + + /** + * Returns the width of the passed in String. + * If the passed String is {@code null}, returns zero. + * + * @param c JComponent that will display the string, may be null + * @param fm FontMetrics used to measure the String width + * @param string String to get the width of + */ + public static int stringWidth(JComponent c, FontMetrics fm, String string) { + return (int) stringWidth(c, fm, string, false); + } + + /** + * Returns the width of the passed in String. + * If the passed String is {@code null}, returns zero. + * + * @param c JComponent that will display the string, may be null + * @param fm FontMetrics used to measure the String width + * @param string String to get the width of + * @param useFPAPI use floating point API + */ + public static float stringWidth(JComponent c, FontMetrics fm, String string, + boolean useFPAPI){ + if (string == null || string.isEmpty()) { + return 0; + } + boolean needsTextLayout = ((c != null) && + (c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null)); + if (needsTextLayout) { + synchronized(charsBufferLock) { + int length = syncCharsBuffer(string); + needsTextLayout = isComplexLayout(charsBuffer, 0, length); + } + } + if (needsTextLayout) { + TextLayout layout = createTextLayout(c, string, + fm.getFont(), fm.getFontRenderContext()); + return layout.getAdvance(); + } else { + return getFontStringWidth(string, fm, useFPAPI); + } + } + + public static float getFontStringWidth(String data, FontMetrics fm, + boolean useFPAPI) + { + if (useFPAPI) { + Rectangle2D bounds = fm.getFont() + .getStringBounds(data, fm.getFontRenderContext()); + return (float) bounds.getWidth(); + } else { + return fm.stringWidth(data); + } + } + + private static TextLayout createTextLayout(JComponent c, String s, + Font f, FontRenderContext frc) { + Object shaper = (c == null ? + null : c.getClientProperty(TextAttribute.NUMERIC_SHAPING)); + if (shaper == null) { + return new TextLayout(s, f, frc); + } else { + Map a = new HashMap(); + a.put(TextAttribute.FONT, f); + a.put(TextAttribute.NUMERIC_SHAPING, shaper); + return new TextLayout(s, a, frc); + } + } + + + /** + * Draws the string at the specified location. + * + * @param c JComponent that will display the string, may be null + * @param g Graphics to draw the text to + * @param text String to display + * @param x X coordinate to draw the text at + * @param y Y coordinate to draw the text at + * @param useFPAPI use floating point API + */ + public static void drawString(JComponent c, Graphics g, String text, + float x, float y, boolean useFPAPI) { + // c may be null + + // All non-editable widgets that draw strings call into this + // methods. By non-editable that means widgets like JLabel, JButton + // but NOT JTextComponents. + if ( text == null || text.length() <= 0 ) { //no need to paint empty strings + return; + } + /* + if (isPrinting(g)) { + Graphics2D g2d = getGraphics2D(g); + if (g2d != null) { + String trimmedText = trimTrailingSpaces(text); + if (!trimmedText.isEmpty()) { + float screenWidth = (float) g2d.getFont().getStringBounds + (trimmedText, getFontRenderContext(c)).getWidth(); + TextLayout layout = createTextLayout(c, text, g2d.getFont(), + g2d.getFontRenderContext()); + + // If text fits the screenWidth, then do not need to justify + if (sun.swing.SwingUtilities2.stringWidth(c, g2d.getFontMetrics(), + trimmedText) > screenWidth) { + layout = layout.getJustifiedLayout(screenWidth); + } + // Use alternate print color if specified + Color col = g2d.getColor(); + if (col instanceof PrintColorUIResource) { + g2d.setColor(((PrintColorUIResource)col).getPrintColor()); + } + + layout.draw(g2d, x, y); + + g2d.setColor(col); + } + + return; + } + } + */ + + // If we get here we're not printing + if (g instanceof Graphics2D) { + Graphics2D g2 = (Graphics2D)g; + + boolean needsTextLayout = ((c != null) && + (c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null)); + + if (needsTextLayout) { + synchronized(charsBufferLock) { + int length = syncCharsBuffer(text); + needsTextLayout = isComplexLayout(charsBuffer, 0, length); + } + } + + Object aaHint = (c == null) + ? null + : c.getClientProperty(KEY_TEXT_ANTIALIASING); + if (aaHint != null) { + Object oldContrast = null; + Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING); + if (aaHint != oldAAValue) { + g2.setRenderingHint(KEY_TEXT_ANTIALIASING, aaHint); + } else { + oldAAValue = null; + } + + Object lcdContrastHint = c.getClientProperty( + KEY_TEXT_LCD_CONTRAST); + if (lcdContrastHint != null) { + oldContrast = g2.getRenderingHint(KEY_TEXT_LCD_CONTRAST); + if (lcdContrastHint.equals(oldContrast)) { + oldContrast = null; + } else { + g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, + lcdContrastHint); + } + } + + if (needsTextLayout) { + TextLayout layout = createTextLayout(c, text, g2.getFont(), + g2.getFontRenderContext()); + layout.draw(g2, x, y); + } else { + g2.drawString(text, x, y); + } + + if (oldAAValue != null) { + g2.setRenderingHint(KEY_TEXT_ANTIALIASING, oldAAValue); + } + if (oldContrast != null) { + g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, oldContrast); + } + + return; + } + + if (needsTextLayout){ + TextLayout layout = createTextLayout(c, text, g2.getFont(), + g2.getFontRenderContext()); + layout.draw(g2, x, y); + return; + } + } + + g.drawString(text, (int) x, (int) y); + } + + + /** + * Draws the string at the specified location underlining the specified + * character. + * + * @param c JComponent that will display the string, may be null + * @param g Graphics to draw the text to + * @param text String to display + * @param underlinedIndex Index of a character in the string to underline + * @param x X coordinate to draw the text at + * @param y Y coordinate to draw the text at + */ + + public static void drawStringUnderlineCharAt(JComponent c,Graphics g, + String text, int underlinedIndex, int x, int y) { + drawStringUnderlineCharAt(c, g, text, underlinedIndex, x, y, false); + } + + + /** + * Draws the string at the specified location underlining the specified + * character. + * + * @param c JComponent that will display the string, may be null + * @param g Graphics to draw the text to + * @param text String to display + * @param underlinedIndex Index of a character in the string to underline + * @param x X coordinate to draw the text at + * @param y Y coordinate to draw the text at + * @param useFPAPI use floating point API + */ + public static void drawStringUnderlineCharAt(JComponent c, Graphics g, + String text, int underlinedIndex, + float x, float y, + boolean useFPAPI) { + if (text == null || text.length() <= 0) { + return; + } + drawString(c, g, text, x, y, useFPAPI); + int textLength = text.length(); + if (underlinedIndex >= 0 && underlinedIndex < textLength ) { + float underlineRectY = y; + int underlineRectHeight = 1; + float underlineRectX = 0; + int underlineRectWidth = 0; +// boolean isPrinting = isPrinting(g); +// boolean needsTextLayout = isPrinting; + boolean needsTextLayout = false; + if (!needsTextLayout) { + synchronized (charsBufferLock) { + syncCharsBuffer(text); + needsTextLayout = + isComplexLayout(charsBuffer, 0, textLength); + } + } + if (!needsTextLayout) { + FontMetrics fm = g.getFontMetrics(); + underlineRectX = x + + stringWidth(c,fm, + text.substring(0,underlinedIndex)); + underlineRectWidth = fm.charWidth(text. + charAt(underlinedIndex)); + } else { + Graphics2D g2d = getGraphics2D(g); + if (g2d != null) { + TextLayout layout = + createTextLayout(c, text, g2d.getFont(), + g2d.getFontRenderContext()); +// if (isPrinting) { +// float screenWidth = (float)g2d.getFont(). +// getStringBounds(text, getFontRenderContext(c)).getWidth(); +// // If text fits the screenWidth, then do not need to justify +// if (stringWidth(c, g2d.getFontMetrics(), +// text) > screenWidth) { +// layout = layout.getJustifiedLayout(screenWidth); +// } +// } + TextHitInfo leading = + TextHitInfo.leading(underlinedIndex); + TextHitInfo trailing = + TextHitInfo.trailing(underlinedIndex); + Shape shape = + layout.getVisualHighlightShape(leading, trailing); + Rectangle rect = shape.getBounds(); + underlineRectX = x + rect.x; + underlineRectWidth = rect.width; + } + } + g.fillRect((int) underlineRectX, (int) underlineRectY + 1, + underlineRectWidth, underlineRectHeight); + } + } + + + /* + * Tries it best to get Graphics2D out of the given Graphics + * returns null if can not derive it. + */ + public static Graphics2D getGraphics2D(Graphics g) { + if (g instanceof Graphics2D) { + return (Graphics2D) g; +// } else if (g instanceof ProxyPrintGraphics) { +// return (Graphics2D)(((ProxyPrintGraphics)g).getGraphics()); + } else { + return null; + } + } + + + /* + * Returns FontRenderContext associated with Component. + * FontRenderContext from Component.getFontMetrics is associated + * with the component. + * + * Uses Component.getFontMetrics to get the FontRenderContext from. + * see JComponent.getFontMetrics and TextLayoutStrategy.java + */ + public static FontRenderContext getFontRenderContext(Component c) { + assert c != null; + if (c == null) { + return DEFAULT_FRC; + } else { + return c.getFontMetrics(c.getFont()).getFontRenderContext(); + } + } +} \ No newline at end of file diff --git a/app/src/processing/app/platform/DefaultPlatform.java b/app/src/processing/app/platform/DefaultPlatform.java index e2977237d..96d4c6892 100644 --- a/app/src/processing/app/platform/DefaultPlatform.java +++ b/app/src/processing/app/platform/DefaultPlatform.java @@ -26,17 +26,21 @@ package processing.app.platform; import java.awt.Desktop; import java.awt.Font; import java.io.File; -import java.net.URI; import javax.swing.UIManager; import javax.swing.plaf.FontUIResource; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.FlatLightLaf; import com.sun.jna.Library; import com.sun.jna.Native; import processing.app.Base; +import processing.app.Language; import processing.app.Preferences; import processing.app.ui.Toolkit; +import processing.awt.ShimAWT; +import processing.core.PApplet; /** @@ -61,6 +65,7 @@ public class DefaultPlatform { "CheckBox", "CheckBoxMenuItem", "ComboBox", + "Label", "List", "Menu", "MenuBar", @@ -76,6 +81,7 @@ public class DefaultPlatform { "Table", "TableHeader", "TextArea", + "TextField", "TextPane", "TitledBorder", "ToggleButton", @@ -105,26 +111,58 @@ public class DefaultPlatform { * @throws Exception Just like I said. */ public void setLookAndFeel() throws Exception { - String laf = Preferences.get("editor.laf"); - if (laf == null || laf.length() == 0) { // normal situation - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } else { - UIManager.setLookAndFeel(laf); - } + // In 4.0 beta 9, getting rid of the editor.laf preference, + // because we're using FlatLaf everywhere, and mixing others + // (i.e. Nimbus on Linux) with our custom components is badness. + // dummy font call so that it's registered for FlatLaf + Font defaultFont = Toolkit.getSansFont(14, Font.PLAIN); + UIManager.put("defaultFont", defaultFont); + + // pull in FlatLaf.properties from the processing.app.laf folder + FlatLaf.registerCustomDefaultsSource("processing.app.laf"); + + // start with Light, but updateTheme() will be called soon + UIManager.setLookAndFeel(new FlatLightLaf()); + + /* + javax.swing.UIDefaults defaults = UIManager.getDefaults(); + for (java.util.Map.Entry entry : defaults.entrySet()) { + System.out.println(entry.getKey() + " = " + entry.getValue()); + } + */ + + /* // 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"); +// fontName = "Processing Sans Pro"; +// fontSize = 13; if (!"Dialog".equals(fontName) || fontSize != 12) { setUIFont(new FontUIResource(fontName, Font.PLAIN, fontSize)); +// setUIFont(new FontUIResource(createFallingFont(fontName, Font.PLAIN, fontSize))); +// setUIFont((FontUIResource) StyleContext.getDefaultStyleContext().getFont(fontName, Font.PLAIN, fontSize)); + // Map attributes = new HashMap<>(); // attributes.put(TextAttribute.KERNING, TextAttribute.KERNING_ON); // Font font = new Font(fontName, Font.PLAIN, fontSize).deriveFont(attributes); // setUIFont(new FontUIResource(font)); } + */ } +// // Adapted from https://stackoverflow.com/a/64667581/18247494 +// static Font createFallingFont(final String family, final int style, final int size) { +// return new NonUIResourceFont(StyleContext.getDefaultStyleContext().getFont(family, style, size)); +// } +// +// static class NonUIResourceFont extends Font { +// public NonUIResourceFont(final Font font) { +// super(font); +// } +// } + /* // Rewritten from https://stackoverflow.com/a/7434935 static private void setUIFont(FontUIResource f) { for (Object key : UIManager.getLookAndFeelDefaults().keySet()) { @@ -134,6 +172,7 @@ public class DefaultPlatform { } } } + */ public void setInterfaceZoom() throws Exception { @@ -143,11 +182,14 @@ public class DefaultPlatform { scaleDefaultFont(widgetName); } - 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); +// Font defaultFont = Toolkit.getSansFont(14, Font.PLAIN); +// UIManager.put("defaultFont", defaultFont); + +// 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); } } @@ -166,7 +208,13 @@ public class DefaultPlatform { * Do not return null. */ public File getSettingsFolder() throws Exception { - // otherwise make a .processing directory int the user's home dir + File override = Base.getSettingsOverride(); + if (override != null) { + return override; + } + + // If no subclass has a behavior, default to making a + // ".processing" directory in the user's home directory. File home = new File(System.getProperty("user.home")); return new File(home, ".processing"); } @@ -181,17 +229,24 @@ public class DefaultPlatform { } + // TODO this should be openLink(), as in PApplet, but need to look + // into what else it might break by changing it [fry 220202] public void openURL(String url) throws Exception { - Desktop.getDesktop().browse(new URI(url)); + if (!ShimAWT.openLink(url)) { + PApplet.launch(url); + } } public boolean openFolderAvailable() { - return Desktop.isDesktopSupported(); + return Desktop.isDesktopSupported() && + Desktop.getDesktop().isSupported(Desktop.Action.OPEN); } public void openFolder(File file) throws Exception { + // TODO Looks like this should instead be Action.BROWSE_FILE_DIR, + // which was added in Java 9. (Also update available method.) Desktop.getDesktop().open(file); } diff --git a/app/src/processing/app/platform/LinuxPlatform.java b/app/src/processing/app/platform/LinuxPlatform.java index f90777721..15a8e3d5a 100644 --- a/app/src/processing/app/platform/LinuxPlatform.java +++ b/app/src/processing/app/platform/LinuxPlatform.java @@ -39,9 +39,11 @@ public class LinuxPlatform extends DefaultPlatform { public void initBase(Base base) { super.initBase(base); - // Set x11 WM_CLASS property which is used as the application - // name by Gnome3 and other window managers. + // Set X11 WM_CLASS property which is used as the application + // name by Gnome 3 and other window managers. // https://github.com/processing/processing/issues/2534 + // For Java 17, this hack requires an addition to the command line: + // --add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED try { Toolkit xToolkit = Toolkit.getDefaultToolkit(); java.lang.reflect.Field awtAppClassNameField = @@ -85,6 +87,11 @@ public class LinuxPlatform extends DefaultPlatform { @Override public File getSettingsFolder() throws Exception { + File override = Base.getSettingsOverride(); + if (override != null) { + return override; + } + // https://github.com/processing/processing4/issues/203 // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html diff --git a/app/src/processing/app/platform/MacPlatform.java b/app/src/processing/app/platform/MacPlatform.java index 31da72c5f..e9644cdad 100644 --- a/app/src/processing/app/platform/MacPlatform.java +++ b/app/src/processing/app/platform/MacPlatform.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-2013 The Processing Foundation + Copyright (c) 2012-2022 The Processing Foundation Copyright (c) 2008-2012 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or @@ -92,34 +92,11 @@ public class MacPlatform extends DefaultPlatform { } - /* - @Override - public void setLookAndFeel() throws Exception { - super.setLookAndFeel(); - - 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(); - UIManager.put("Tree.closedIcon", leaf); - UIManager.put("Tree.openIcon", leaf); - UIManager.put("Tree.collapsedIcon", open); - UIManager.put("Tree.expandedIcon", collapse); - UIManager.put("Tree.leafIcon", leaf); - } - } - */ - - public File getSettingsFolder() throws Exception { + File override = Base.getSettingsOverride(); + if (override != null) { + return override; + } return new File(getLibraryFolder(), "Processing"); } @@ -149,6 +126,9 @@ 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] + // However, anecdotally it seems that just using the name works, + // and the localization is handled transparently. [fry 220116] + // https://github.com/processing/processing4/issues/9 protected String getLibraryFolder() throws FileNotFoundException { File folder = new File(System.getProperty("user.home"), "Library"); if (!folder.exists()) { @@ -158,7 +138,7 @@ public class MacPlatform extends DefaultPlatform { } - // TODO see note on getLibraryFolder() + // TODO See above, and https://github.com/processing/processing4/issues/9 protected String getDocumentsFolder() throws FileNotFoundException { File folder = new File(System.getProperty("user.home"), "Documents"); if (!folder.exists()) { @@ -204,80 +184,4 @@ public class MacPlatform extends DefaultPlatform { return FileManager.findFolder(kUserDomain, kDocumentsFolderType); } */ - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - // VAQUA WORKAROUNDS FROM SAM - - -// /** -// * Spacer icon for macOS when using Vaqua. -// -// * Due to potential rendering issues, this small spacer is used -// * to ensure that rendering is stable while using Vaqua with non-standard -// * Swing components. Without this, some sizing calculations non-standard -// * components may fail or become unreliable. -// */ -// static class VAquaEmptyIcon implements Icon { -// private final int SIZE = 1; -// -// @Override -// public int getIconWidth() { -// return SIZE; -// } -// -// @Override -// public int getIconHeight() { -// return SIZE; -// } -// -// @Override -// public void paintIcon(Component c, Graphics g, int x, int y) { } -// } -// -// -// /** -// * Replacement tree icon for macOS when using Vaqua. -// * -// * Due to potential rendering issues with the regular tree icon set, -// * this replacement tree icon for macOS ensures stable rendering when using -// * Vaqua with non-standard swing components. Without this, some sizing -// * calculations within non-standard components may fail or become unreliable. -// */ -// static private class VAquaTreeIcon implements Icon { -// private final int SIZE = 12; -// private final boolean isOpen; -// -// /** -// * Create a new tree icon. -// * -// * @param newIsOpen Flag indicating if the icon should be in the open or closed state at -// * construction. True if open false otherwise. -// */ -// public VAquaTreeIcon(boolean newIsOpen) { -// isOpen = newIsOpen; -// } -// -// @Override -// public int getIconWidth() { -// return SIZE; -// } -// -// @Override -// public int getIconHeight() { -// return SIZE; -// } -// -// @Override -// public void paintIcon(Component c, Graphics g, int x, int y) { -// g.setColor(Color.GRAY); -// -// g.drawLine(x + SIZE / 2 - 3, y + SIZE / 2, x + SIZE / 2 + 3, y + SIZE / 2); -// -// if (!isOpen) { -// g.drawLine(x + SIZE / 2, y + SIZE / 2 - 3, x + SIZE / 2, y + SIZE / 2 + 3); -// } -// } -// } } diff --git a/app/src/processing/app/platform/WindowsPlatform.java b/app/src/processing/app/platform/WindowsPlatform.java index 61a21aaf4..7b994808a 100644 --- a/app/src/processing/app/platform/WindowsPlatform.java +++ b/app/src/processing/app/platform/WindowsPlatform.java @@ -292,6 +292,11 @@ public class WindowsPlatform extends DefaultPlatform { // looking for Documents and Settings/blah/Application Data/Processing public File getSettingsFolder() throws Exception { + File override = Base.getSettingsOverride(); + if (override != null) { + return override; + } + try { String appDataRoaming = getAppDataPath(); if (appDataRoaming != null) { diff --git a/app/src/processing/app/syntax/DefaultInputHandler.java b/app/src/processing/app/syntax/DefaultInputHandler.java index 10bc9e73f..aadc87a5e 100644 --- a/app/src/processing/app/syntax/DefaultInputHandler.java +++ b/app/src/processing/app/syntax/DefaultInputHandler.java @@ -131,6 +131,12 @@ public class DefaultInputHandler extends InputHandler { int keyCode = evt.getKeyCode(); int modifiers = evt.getModifiersEx(); + // Remove mouse button down masks that get mixed in with KeyEvent. + // https://github.com/processing/processing4/issues/403 + modifiers &= ~(InputEvent.BUTTON1_DOWN_MASK | + InputEvent.BUTTON2_DOWN_MASK | + InputEvent.BUTTON3_DOWN_MASK); + // moved this earlier so it doesn't get random meta clicks if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_SHIFT || @@ -182,7 +188,7 @@ public class DefaultInputHandler extends InputHandler { if ((modifiers & InputEvent.META_DOWN_MASK) != 0) return; // Prevent CTRL-/ from going through as a typed '/' character - // http://code.google.com/p/processing/issues/detail?id=596 + // https://github.com/processing/processing/issues/635 if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0 && c == '/') return; if (c != KeyEvent.CHAR_UNDEFINED) { diff --git a/app/src/processing/app/syntax/JEditTextArea.java b/app/src/processing/app/syntax/JEditTextArea.java index a42a3037a..566119e3d 100644 --- a/app/src/processing/app/syntax/JEditTextArea.java +++ b/app/src/processing/app/syntax/JEditTextArea.java @@ -17,9 +17,8 @@ import java.awt.*; import java.awt.datatransfer.*; import java.awt.event.*; import java.util.Arrays; -import java.util.Enumeration; -import java.util.Vector; +import javax.swing.border.EmptyBorder; import javax.swing.event.*; import javax.swing.text.*; import javax.swing.undo.*; @@ -30,6 +29,7 @@ import java.awt.im.InputMethodRequests; import processing.app.syntax.im.InputMethodSupport; import processing.core.PApplet; + /** * The text area component from the JEdit Syntax (syntax.jedit.org) project. * This is a very early version of what later was completely rewritten and @@ -123,10 +123,31 @@ public class JEditTextArea extends JComponent partialPixelWidth = 0; // Initialize the GUI + /* setLayout(new ScrollLayout()); add(CENTER, painter); add(RIGHT, vertical = new JScrollBar(Adjustable.VERTICAL)); add(BOTTOM, horizontal = new JScrollBar(Adjustable.HORIZONTAL)); + */ + + setLayout(new BorderLayout()); + add(painter, BorderLayout.CENTER); + add(vertical = new JScrollBar(Adjustable.VERTICAL), BorderLayout.EAST); + add(horizontal = new JScrollBar(Adjustable.HORIZONTAL), BorderLayout.SOUTH); + // what a dreadful hack to get the scrollbar to align + horizontal.setBorder(new EmptyBorder(0, 0, 0, vertical.getPreferredSize().width)); + + /* + // this fixes the glitch at the lower-right of the scrollbars, + // but results in the scrolling area behaving very oddly, + // presumably due to quirks in this very old JEditSyntax code. + JScrollPane pane = new JScrollPane(painter); + pane.setBorder(BorderFactory.createEmptyBorder()); + horizontal = pane.getHorizontalScrollBar(); + vertical = pane.getVerticalScrollBar(); + setLayout(new BorderLayout()); + add(pane, BorderLayout.CENTER); + */ // Add some event listeners vertical.addAdjustmentListener(new AdjustHandler()); @@ -136,7 +157,7 @@ public class JEditTextArea extends JComponent painter.addMouseMotionListener(new DragHandler()); addFocusListener(new FocusHandler()); // send tab keys through to the text area - // http://dev.processing.org/bugs/show_bug.cgi?id=1267 + // https://download.processing.org/bugzilla/1267.html setFocusTraversalKeysEnabled(false); // Load the defaults @@ -400,8 +421,8 @@ public class JEditTextArea extends JComponent int painterWidth = painter.getScrollWidth(); // Update to how horizontal scrolling is handled - // http://code.google.com/p/processing/issues/detail?id=280 - // http://code.google.com/p/processing/issues/detail?id=316 + // https://github.com/processing/processing/issues/319 + // https://github.com/processing/processing/issues/355 //setValues(int newValue, int newExtent, int newMin, int newMax) if (horizontalOffset < 0) { horizontal.setValues(-horizontalOffset, painterWidth, -leftHandGutter, width); @@ -633,7 +654,6 @@ public class JEditTextArea extends JComponent public int _offsetToX(int line, int offset) { TokenMarkerState tokenMarker = getTokenMarker(); - // Use painter's cached info for speed FontMetrics fm = painter.getFontMetrics(); getLineText(line, lineSegment); @@ -1218,6 +1238,19 @@ public class JEditTextArea extends JComponent select(0,getDocumentLength()); } + /** + * Selects all text in the given line. + * @param line The line number to select all text in it. + */ + private void selectLine(final int line) { + selectLine = true; + final int lineStart = getLineStartOffset(line); + final int lineEnd = getLineSelectionStopOffset(line); + select(lineStart, lineEnd); + selectionAncorStart = selectionStart; + selectionAncorEnd = selectionEnd; + } + /** * Moves the mark to the caret position. */ @@ -1326,66 +1359,125 @@ public class JEditTextArea extends JComponent return CharacterKinds.Other; } - /** - * Get the width in pixels of a segment of text within the IDE. - * - *

- * Fractional-font aware implementation of Utilities.getTabbedTextWidth that determines if there - * are fractional character widths present in a font in order to return a more accurate pixel - * width for an input segment. - *

- * - * @param s The segment of text for which a pixel width should be returned. - * @param metrics The metrics for the font in which the given segment will be drawn. - * @param x The x origin. - * @param expander The strategy for converting tabs into characters. - * @param startOffset The offset to apply before the text will be drawn. - * @return The width of the input segment in pixels with fractional character widths considered. - */ - private int getTabbedTextWidth(Segment s, FontMetrics metrics, float x, - TabExpander expander, int startOffset) { - float additionalOffset = - getPartialPixelWidth(metrics, x, expander, startOffset) * s.length(); - return Math.round( - Utilities.getTabbedTextWidth(s, metrics, x, expander, startOffset) + additionalOffset - ); + /* + static float getFontCharWidth(char c, FontMetrics fm) { + return getFontCharsWidth(new char[] { c }, 0, 1, fm); } - /** - * Get any partial widths applied within a font. - * - *

- * Get any partial widths applied within a font, caching results for the latest requested font - * (as identified via a FontMetrics object). Note that this is calculated for a sample character - * and is only valid for extrapolation in a monospaced font (that one might want to use in an - * IDE). - *

- * - * @param candidateMetrics The FontMetrics for which partial character pixel widths should be - * returned. - * @param x The x origin. - * @param expander The strategy for converting tabs into characters. - * @param startOffset The offset to apply before the text will be drawn. - * @return The partial width of a sample character within a font. - */ - private float getPartialPixelWidth(FontMetrics candidateMetrics, float x, TabExpander expander, - int startOffset) { - // See https://github.com/sampottinger/processing/issues/103 - // Requires reference not object equality check - if (candidateMetrics != cachedPartialPixelWidthFont) { - float withFractional = - Utilities.getTabbedTextWidth(TEST_SEGMENT, candidateMetrics, - x, expander, startOffset); - int withoutFractional = (int) withFractional; + static final char[] spaceChar = new char[] { ' ' }; - partialPixelWidth = withFractional - withoutFractional; - cachedPartialPixelWidthFont = candidateMetrics; + static float getFontCharsWidth(char[] data, int offset, int len, + FontMetrics fm) { + if (len == 0) { + return 0; + } + // doesn't seem to do anything fractional + float wi = fm.charsWidth(data, offset, len); + if (wi != ((int) wi)) { + System.out.println("extra: " + wi); } - return partialPixelWidth; + int spaceWidth = fm.charsWidth(spaceChar, 0, 1); + //return fm.charsWidth(data, offset, len); + return len * spaceWidth; } + */ + + + /** + * Hacked up version of the function with the same name from + * javax.swing.text.Utilities. + * + * In spite of being a fixed width font, Source Code Pro (the default + * font starting in Processing 3) returns slightly different widths + * depending on the number of characters shown. Using the getBounds() + * method on text won't even give us these metrics for individual + * characters, which returns a float but never with any fractional. + * + * This function forces the width of each character to stay the same, + * just as we're doing by drawing individual characters in the + * TextAreaPainter class. + * + * #447, + * #226, + * #194, + * and Sam's 103 + */ + + static int getTabbedTextWidth(Segment s, + FontMetrics metrics, int x, + TabExpander e, int startOffset) { + int nextX = x; + char[] txt = s.array; + int txtOffset = s.offset; + int n = s.offset + s.count; + + for (int i = txtOffset; i < n; i++) { + if (txt[i] == '\t' && e != null) { + nextX = (int) e.nextTabStop(nextX, startOffset + i - txtOffset); + continue; + } + nextX += metrics.charWidth(txt[i]); + } + return nextX - x; + } +/* + static int getTabbedTextWidth(Segment s, + FontMetrics metrics, int x, + TabExpander e, int startOffset) { + int nextX = x; + char[] txt = s.array; + int txtOffset = s.offset; + int n = s.offset + s.count; + int charCount = 0; +// int spaceAddon = 0; + + int spaceWidth = metrics.charWidth(' '); + + for (int i = txtOffset; i < n; i++) { + if (txt[i] == '\t') { + //nextX += metrics.charsWidth(txt, i-charCount, charCount); + nextX += charCount * spaceWidth; + charCount = 0; + if (txt[i] == '\t') { + if (e != null) { + nextX = (int) e.nextTabStop(nextX, startOffset + i - txtOffset); + } else { + // if no tab expander, just return the size of a space + //nextX += getFontCharWidth(' ', metrics); + nextX += spaceWidth; + } + } else if (txt[i] == ' ') { + //float spaceWidth = getFontCharWidth(' ', metrics); + //nextX += spaceWidth + spaceAddon; + nextX += spaceWidth; + } + } else if (txt[i] == '\n') { + // Ignore newlines, they take up space, and shouldn't be counted. + //nextX += getFontCharsWidth(txt, i - charCount, charCount, metrics); + nextX += charCount * spaceWidth; + // But this doesn't make any sense: why are we adding horizontally, + // shouldn't nextX be *reset* here? Guessing that segments never + // include a new line, so we never run into this. [fry 220129] + charCount = 0; + } else { + charCount++; + } + } + //nextX += getFontCharsWidth(txt, n - charCount, charCount, metrics); + nextX += charCount * spaceWidth; + +// int amt = (int) (nextX - x); +// float spc = getFontCharWidth(' ', metrics); +// System.out.println(amt + " % " + spc + " = " + (amt % spc)); + +// return (int) (nextX - x); // nextX was a float, this was returning a float [fry 220128] + return nextX - x; + } +*/ + protected void setNewSelectionWord( int line, int offset ) { @@ -1641,6 +1733,7 @@ public class JEditTextArea extends JComponent /** * Deletes the selected text from the text area and places it * into the clipboard. + * If no selection is made, the whole line with caret will be selectd. */ public void cut() { if (editable) { @@ -1652,16 +1745,21 @@ public class JEditTextArea extends JComponent /** * Places the selected text into the clipboard. + * If no selection is made, the whole line with caret will be selectd. */ public void copy() { - if (selectionStart != selectionEnd) { - Clipboard clipboard = getToolkit().getSystemClipboard(); - - String selection = getSelectedText(); - if (selection != null) { - int repeatCount = inputHandler.getRepeatCount(); - clipboard.setContents(new StringSelection(selection.repeat(Math.max(0, repeatCount))), null); + if (selectionStart == selectionEnd) { + selectLine(getCaretLine()); + } + Clipboard clipboard = getToolkit().getSystemClipboard(); + 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); } } @@ -1853,7 +1951,7 @@ public class JEditTextArea extends JComponent } // Remove tabs and replace with spaces - // http://code.google.com/p/processing/issues/detail?id=69 + // https://github.com/processing/processing/issues/108 if (selection.contains("\t")) { int tabSize = Preferences.getInteger("editor.tabs.size"); char[] c = new char[tabSize]; @@ -2096,6 +2194,7 @@ public class JEditTextArea extends JComponent } } + /* class ScrollLayout implements LayoutManager { //final int LEFT_EXTRA = 5; @@ -2210,6 +2309,7 @@ public class JEditTextArea extends JComponent private Component bottom; private final Vector leftOfScrollBar = new Vector<>(); } + */ class MutableCaretEvent extends CaretEvent { @@ -2323,8 +2423,8 @@ public class JEditTextArea extends JComponent } - class DragHandler implements MouseMotionListener - { + class DragHandler implements MouseMotionListener { + public void mouseDragged(MouseEvent evt) { if (popup != null && popup.isVisible()) return; @@ -2515,10 +2615,7 @@ public class JEditTextArea extends JComponent private void doTripleClick(MouseEvent evt, int line, int offset, int dot) { - selectLine = true; - select(getLineStartOffset(line),getLineSelectionStopOffset(line)); - selectionAncorStart = selectionStart; - selectionAncorEnd = selectionEnd; + selectLine(line); } } diff --git a/app/src/processing/app/syntax/PdeInputHandler.java b/app/src/processing/app/syntax/PdeInputHandler.java index 3417bb19d..cc763bd3d 100644 --- a/app/src/processing/app/syntax/PdeInputHandler.java +++ b/app/src/processing/app/syntax/PdeInputHandler.java @@ -130,7 +130,7 @@ public class PdeInputHandler extends DefaultInputHandler { if (Platform.isMacOS()) { // Additional OS X key bindings added for 0215. // Also note that two more are added above and marked 0215. - // http://code.google.com/p/processing/issues/detail?id=1354 + // https://github.com/processing/processing/issues/1392 // "Mac keyboard shortcuts" document from Apple: // https://support.apple.com/en-us/HT201236 diff --git a/app/src/processing/app/syntax/PdeTextArea.java b/app/src/processing/app/syntax/PdeTextArea.java index 38efbc6e5..a76bb75be 100644 --- a/app/src/processing/app/syntax/PdeTextArea.java +++ b/app/src/processing/app/syntax/PdeTextArea.java @@ -20,8 +20,7 @@ along with this program; if not, write to the Free Software Foundation, Inc. package processing.app.syntax; -import java.awt.Cursor; -import java.awt.Image; +import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.util.HashMap; @@ -29,6 +28,7 @@ import java.util.Map; import processing.app.ui.Editor; import processing.app.ui.Theme; +import processing.app.laf.PdeScrollBarUI; /** @@ -55,13 +55,16 @@ public class PdeTextArea extends JEditTextArea { super(defaults, inputHandler); this.editor = editor; +// vertical.setUI(new ThemeScrollBarUI("editor")); +// horizontal.setUI(new ThemeScrollBarUI("editor")); + // change cursor to pointer in the gutter area painter.addMouseMotionListener(gutterCursorMouseAdapter); - add(CENTER, painter); + // already added by call to super(), removing [fry 220112] + //add(CENTER, painter); - // load settings from theme.txt - gutterGradient = Theme.makeGradient("editor", Editor.LEFT_GUTTER, 500); + updateTheme(); } @@ -76,15 +79,22 @@ public class PdeTextArea extends JEditTextArea { } - /* - public void setMode(Mode mode) { - ((PdeTextAreaPainter) painter).setMode(mode); - } - */ - @Override public void updateTheme() { - ((PdeTextAreaPainter) painter).updateTheme(); + painter.updateTheme(); + + gutterGradient = Theme.makeGradient("editor", Editor.LEFT_GUTTER, 500); + + if (vertical.getUI() instanceof PdeScrollBarUI) { +// System.out.println("PdeTextArea.updateTheme() just updating"); + ((PdeScrollBarUI) vertical.getUI()).updateTheme(); + ((PdeScrollBarUI) horizontal.getUI()).updateTheme(); + } else { +// System.out.println("PdeTextArea.updateTheme() setting ui"); + vertical.setUI(new PdeScrollBarUI("editor.scrollbar")); + horizontal.setUI(new PdeScrollBarUI("editor.scrollbar")); + } + repaint(); } @@ -154,7 +164,7 @@ public class PdeTextArea extends JEditTextArea { * take gutter width into account. * @param line the 0-based line number * @param x the horizontal pixel position - * @return he character offset (0 is the first character on a line) + * @return the character offset (0 is the first character on a line) */ @Override public int xToOffset(int line, int x) { diff --git a/app/src/processing/app/syntax/PdeTextAreaDefaults.java b/app/src/processing/app/syntax/PdeTextAreaDefaults.java index 74e9590f1..bdffa8216 100644 --- a/app/src/processing/app/syntax/PdeTextAreaDefaults.java +++ b/app/src/processing/app/syntax/PdeTextAreaDefaults.java @@ -30,16 +30,16 @@ import processing.app.ui.Theme; /** - * Defaults that are PDE (but not Mode) specific. PDE specific in this case - * means that it's using other PDE classes like Preferences. + * Defaults that are PDE (but not Mode) specific. PDE specific in this + * case means that it's using other PDE classes like Preferences. */ public class PdeTextAreaDefaults extends TextAreaDefaults { - public PdeTextAreaDefaults(Mode mode) { + public PdeTextAreaDefaults() { document = new SyntaxDocument(); // Set to 0 for revision 0215 because it causes strange jumps - // http://code.google.com/p/processing/issues/detail?id=1055 + // https://github.com/processing/processing/issues/1093 electricScroll = 0; caretVisible = true; @@ -47,7 +47,7 @@ public class PdeTextAreaDefaults extends TextAreaDefaults { blockCaret = Preferences.getBoolean("editor.caret.block"); cols = 80; // Set the number of rows lower to avoid layout badness with large fonts - // http://code.google.com/p/processing/issues/detail?id=1275 + // https://github.com/processing/processing/issues/1313 rows = 5; styles = new SyntaxStyle[Token.ID_COUNT]; @@ -55,6 +55,16 @@ public class PdeTextAreaDefaults extends TextAreaDefaults { } + /** + * Deprecated since 4.0 beta 5, because the Mode is no longer used; + * simply use the default constructor instead. + */ + @Deprecated + public PdeTextAreaDefaults(Mode ignoredMode) { + this(); + } + + protected void updateTheme() { fgcolor = Theme.getColor("editor.fgcolor"); bgcolor = Theme.getColor("editor.bgcolor"); @@ -85,11 +95,11 @@ public class PdeTextAreaDefaults extends TextAreaDefaults { caretColor = Theme.getColor("editor.caret.color"); selectionColor = Theme.getColor("editor.selection.color"); - lineHighlight = Theme.getBoolean("editor.linehighlight"); - lineHighlightColor = Theme.getColor("editor.linehighlight.color"); - bracketHighlight = Theme.getBoolean("editor.brackethighlight"); - bracketHighlightColor = Theme.getColor("editor.brackethighlight.color"); - eolMarkers = Theme.getBoolean("editor.eolmarkers"); - eolMarkerColor = Theme.getColor("editor.eolmarkers.color"); + lineHighlight = Theme.getBoolean("editor.line.highlight"); + lineHighlightColor = Theme.getColor("editor.line.highlight.color"); + bracketHighlight = Theme.getBoolean("editor.bracket.highlight"); + bracketHighlightColor = Theme.getColor("editor.bracket.highlight.color"); + eolMarkers = Theme.getBoolean("editor.eol_markers"); + eolMarkerColor = Theme.getColor("editor.eol_markers.color"); } } diff --git a/app/src/processing/app/syntax/PdeTextAreaPainter.java b/app/src/processing/app/syntax/PdeTextAreaPainter.java index ad5ea3bab..ef517a9d2 100644 --- a/app/src/processing/app/syntax/PdeTextAreaPainter.java +++ b/app/src/processing/app/syntax/PdeTextAreaPainter.java @@ -20,18 +20,13 @@ along with this program; if not, write to the Free Software Foundation, Inc. package processing.app.syntax; -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; +import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.GeneralPath; import java.util.List; import javax.swing.text.BadLocationException; -import javax.swing.text.Segment; -import javax.swing.text.Utilities; import processing.app.Problem; import processing.app.ui.Editor; @@ -40,21 +35,25 @@ import processing.app.ui.Theme; /** * Adds support to TextAreaPainter for background colors, - * and the left hand gutter area with background color and text. + * and the left-hand gutter area with background color and text. */ public class PdeTextAreaPainter extends TextAreaPainter { public Color errorUnderlineColor; public Color warningUnderlineColor; protected Font gutterTextFont; - protected Color gutterTextColor; - protected Color gutterPastColor; - protected Color gutterLineHighlightColor; + protected Color gutterTextActiveColor; + protected Color gutterTextInactiveColor; + protected Color gutterHighlightColor; public PdeTextAreaPainter(JEditTextArea textArea, TextAreaDefaults defaults) { super(textArea, defaults); + // Was looking a little flickery on Windows, but not 100% sure + // that adding this is actually doing anything. [fry 220129] + setDoubleBuffered(true); + // Handle mouse clicks to toggle breakpoints addMouseListener(new MouseAdapter() { long lastTime; // OS X seems to be firing multiple mouse events @@ -93,12 +92,17 @@ public class PdeTextAreaPainter extends TextAreaPainter { warningUnderlineColor = Theme.getColor("editor.warning.underline.color"); gutterTextFont = Theme.getFont("editor.gutter.text.font"); - gutterTextColor = Theme.getColor("editor.gutter.text.color"); - gutterPastColor = new Color(gutterTextColor.getRed(), - gutterTextColor.getGreen(), - gutterTextColor.getBlue(), - 96); - gutterLineHighlightColor = Theme.getColor("editor.gutter.linehighlight.color"); + + Color textColor = Theme.getColor("editor.gutter.text.color"); + int textRGB = textColor.getRGB() & 0xFFFFFF; + + int activeAlpha = 255 * Theme.getInteger("editor.gutter.text.active.alpha") / 100; + gutterTextActiveColor = new Color(activeAlpha << 24 | textRGB, true); + + int inactiveAlpha = 255 * Theme.getInteger("editor.gutter.text.inactive.alpha") / 100; + gutterTextInactiveColor = new Color(inactiveAlpha << 24 | textRGB, true); + + gutterHighlightColor = Theme.getColor("editor.gutter.highlight.color"); // pull in changes for syntax style, as well as foreground and background color if (defaults instanceof PdeTextAreaDefaults) { @@ -151,7 +155,7 @@ public class PdeTextAreaPainter extends TextAreaPainter { int wiggleStart = Math.max(startOffset, lineOffset); int wiggleStop = Math.min(stopOffset, textArea.getLineStopOffset(line)); - int y = textArea.lineToY(line) + fm.getLeading() + fm.getMaxDescent(); + int y = textArea.lineToY(line) + getLineDisplacement(); try { String badCode; @@ -182,8 +186,8 @@ public class PdeTextAreaPainter extends TextAreaPainter { int x1 = textArea.offsetToX(line, goodCode.length() + leftTrimLength); int x2 = textArea.offsetToX(line, goodCode.length() + rightTrimmedLength); - if (x1 == x2) x2 += fm.stringWidth(" "); - int y1 = y + fm.getHeight() - 2; + if (x1 == x2) x2 += fontMetrics.stringWidth(" "); + int y1 = y + fontMetrics.getHeight() - 2; if (line != problem.getLineNumber()) { x1 = Editor.LEFT_GUTTER; // on the following lines, wiggle extends to the left border @@ -209,15 +213,15 @@ public class PdeTextAreaPainter extends TextAreaPainter { * @param x horizontal position */ protected void paintLeftGutter(Graphics gfx, int line, int x) { - int y = textArea.lineToY(line) + fm.getLeading() + fm.getMaxDescent(); + int y = textArea.lineToY(line) + getLineDisplacement(); if (line == textArea.getSelectionStopLine()) { - gfx.setColor(gutterLineHighlightColor); - gfx.fillRect(0, y, Editor.LEFT_GUTTER, fm.getHeight()); + gfx.setColor(gutterHighlightColor); + gfx.fillRect(0, y, Editor.LEFT_GUTTER, fontMetrics.getHeight()); } else { - //gfx.setColor(getJavaTextArea().gutterBgColor); - gfx.setClip(0, y, Editor.LEFT_GUTTER, fm.getHeight()); + Rectangle clip = gfx.getClipBounds(); + gfx.setClip(0, y, Editor.LEFT_GUTTER, fontMetrics.getHeight()); gfx.drawImage(((PdeTextArea) textArea).getGutterGradient(), 0, 0, getWidth(), getHeight(), this); - gfx.setClip(null); // reset + gfx.setClip(clip); // reset } String text = null; @@ -225,12 +229,12 @@ public class PdeTextAreaPainter extends TextAreaPainter { text = getPdeTextArea().getGutterText(line); } - gfx.setColor(line < textArea.getLineCount() ? gutterTextColor : gutterPastColor); + gfx.setColor(line < textArea.getLineCount() ? gutterTextActiveColor : gutterTextInactiveColor); // if (line >= textArea.getLineCount()) { // //gfx.setColor(new Color(gutterTextColor.getRGB(), ); // } int textRight = Editor.LEFT_GUTTER - Editor.GUTTER_MARGIN; - int textBaseline = textArea.lineToY(line) + fm.getHeight(); + int textBaseline = textArea.lineToY(line) + fontMetrics.getHeight(); if (text != null) { if (text.equals(PdeTextArea.BREAK_MARKER)) { @@ -251,15 +255,19 @@ public class PdeTextAreaPainter extends TextAreaPainter { // Right-align the text char[] txt = text.toCharArray(); int tx = textRight - gfx.getFontMetrics().charsWidth(txt, 0, txt.length); + /* // Using 'fm' here because it's relative to the editor text size, // not the numbers in the gutter Utilities.drawTabbedText(new Segment(txt, 0, text.length()), (float) tx, (float) textBaseline, (Graphics2D) gfx, this, 0); + */ + gfx.drawString(text, tx, textBaseline); } } + @SuppressWarnings("SameParameterValue") static private void drawDiamond(Graphics g, float x, float y, float w, float h) { Graphics2D g2 = (Graphics2D) g; @@ -273,6 +281,7 @@ public class PdeTextAreaPainter extends TextAreaPainter { } + @SuppressWarnings("SameParameterValue") static private void drawRightArrow(Graphics g, float x, float y, float w, float h) { Graphics2D g2 = (Graphics2D) g; @@ -311,7 +320,8 @@ public class PdeTextAreaPainter extends TextAreaPainter { @Override public String getToolTipText(MouseEvent event) { - int line = event.getY() / getFontMetrics().getHeight() + textArea.getFirstLine(); + fontMetrics = getFontMetrics(); + int line = event.getY() / fontMetrics.getHeight() + textArea.getFirstLine(); if (line >= 0 || line < textArea.getLineCount()) { List problems = getEditor().findProblems(line); for (Problem problem : problems) { @@ -343,7 +353,7 @@ public class PdeTextAreaPainter extends TextAreaPainter { @Override public int getScrollWidth() { - // TODO https://github.com/processing/processing/issues/3591 + // https://github.com/processing/processing/issues/3591 return super.getWidth() - Editor.LEFT_GUTTER; } diff --git a/app/src/processing/app/syntax/README.md b/app/src/processing/app/syntax/README.md new file mode 100644 index 000000000..04e7bdc32 --- /dev/null +++ b/app/src/processing/app/syntax/README.md @@ -0,0 +1,72 @@ +# 🐉 Fixing this code: here be dragons. 🐉 + +Every few years, we've looked at replacing this package with [RSyntaxArea](https://github.com/bobbylight/RSyntaxTextArea), most recently with two attempts during the course of developing [Processing 4](https://github.com/processing/processing4/wiki/Processing-4), but probably dating back to the mid-2000s. + +The bottom line is that the time is better spent [pursuing a different approach](https://github.com/processing/processing4/blob/master/CONTRIBUTING.md#editor)—a Language Server implementation and probably a lightweight (HTML/JS) GUI on top of it. But so that I don't attempt this again, some reminders as to why it's not worth the effort: + +* At a minimum, replacing this text component would break all Modes, because of how they're invoked. That places significant burden on those authors, so making the switch means there must be major, demonstrable improvements. (The code being “cleaner” or “better” does not qualify, though other improvements might.) +* The token coloring uses a completely different system, which would also need to be expanded across Modes. +* Everything that lives in `PdeTextAreaPainter`, the error squiggles, popups, etc would need to be rewritten. +* Similarly, line numbering is supported by default in `RSyntaxArea` so we'd need to carefully remove all our “left gutter” hacking. +* Most methods throw `BadLocationException`, which we'd either need to include, and break more existing code, or hide, and have undefined behavior. Not a good investment. +* The current `Editor` object evolved from a time when we didn't even support individual tabs. As a result, there's a lot of “current tab” state that still lives inside `Editor`, and other state that lives in `SketchCode`. +* Most of those `Editor` methods should instead be talking to `SketchCode` objects, however that kind of change is likely to introduce small regressions with *major* effects. Again, just not worth it. +* More ways to introduce regressions when fixing: `SketchCode` currently syncs between `program`, `savedProgram`, and `Document` objects. This can obviously be collapsed to just one object (maaaybe two), but this is Regression City. The stuff of nightmares. +* The text area object needs to be moved into the individual tabs (and out of `SketchCode`. This would be fantastic for cleaning things up (the Arduino folks moved ahead with this change a while back). But it's another breaking change for Modes, even though it would be necessary to cleanly separate all Undo behavior. +* So many small quirks, hard-learned lessons from over the years that may no longer be necessary, but the amount of testing necessary is too significant. For instance, inside File → Print, all the Document objects for the tabs are synched up. This might no longer be necessary if we do this properly—it's a gross hack—but we don't have time to find out. There are dozens of situations like this, creating a “refactored but not renewed” sort of situation that would likely take longer than the LS implementation. + +I don't enjoy having the code in this state, but it's there and working, and has allowed a single primary maintainer to support millions of users over more than 20 years. A [larger-scale replacement](https://github.com/processing/processing4/blob/master/CONTRIBUTING.md#editor) is a better use of time. + +— Ben Fry, 20 January 2022 + + +# License + +``` +OLDSYNTAX PACKAGE README + +I am placing the jEdit 2.2.1 syntax highlighting package in the public +domain. This means it can be integrated into commercial programs, etc. + +This package requires at least Java 1.1 and Swing 1.1. Syntax +highlighting for the following file types is supported: + +- C++, C +- CORBA IDL +- Eiffel +- HTML +- Java +- Java properties +- JavaScript +- MS-DOS INI +- MS-DOS batch files +- Makefile +- PHP +- Perl +- Python +- TeX +- Transact-SQL +- Unix patch/diff +- Unix shell script +- XML + +This package is undocumented; read the source (start by taking a look at +JEditTextArea.java) to find out how to use it; it's really simple. Feel +free to e-mail questions, queries, etc. to me, but keep in mind that +this code is very old and I no longer maintain it. So if you find a bug, +don't bother me about it; fix it yourself. + +* Copyright + +The jEdit 2.2.1 syntax highlighting package contains code that is +Copyright 1998-1999 Slava Pestov, Artur Biesiadowski, Clancy Malcolm, +Jonathan Revusky, Juha Lindfors and Mike Dillon. + +You may use and modify this package for any purpose. Redistribution is +permitted, in both source and binary form, provided that this notice +remains intact in all source distributions of this package. + +-- Slava Pestov +25 September 2000 + +``` diff --git a/app/src/processing/app/syntax/TextAreaPainter.java b/app/src/processing/app/syntax/TextAreaPainter.java index 02fdd431e..5824ef6b2 100644 --- a/app/src/processing/app/syntax/TextAreaPainter.java +++ b/app/src/processing/app/syntax/TextAreaPainter.java @@ -19,6 +19,7 @@ import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.MouseEvent; +import java.awt.geom.Line2D; import javax.swing.ToolTipManager; import javax.swing.text.*; @@ -46,7 +47,8 @@ public class TextAreaPainter extends JComponent implements TabExpander { private boolean antialias; protected int tabSize; - protected FontMetrics fm; +// protected FontMetrics fm; + protected FontMetrics fontMetrics; protected Highlight highlights; @@ -91,8 +93,8 @@ public class TextAreaPainter extends JComponent implements TabExpander { antialias = Preferences.getBoolean("editor.smooth"); // moved from setFont() override (never quite comfortable w/ that override) - fm = super.getFontMetrics(plainFont); - tabSize = fm.charWidth(' ') * Preferences.getInteger("editor.tabs.size"); + fontMetrics = super.getFontMetrics(plainFont); + tabSize = fontMetrics.charWidth(' ') * Preferences.getInteger("editor.tabs.size"); textArea.recalculateVisibleLines(); } @@ -177,9 +179,9 @@ public class TextAreaPainter extends JComponent implements TabExpander { } - /** Returns the font metrics used by this component. */ + /** Updates and returns the font metrics used by this component. */ public FontMetrics getFontMetrics() { - return fm; + return fontMetrics = getFontMetrics(plainFont); } @@ -190,7 +192,13 @@ public class TextAreaPainter extends JComponent implements TabExpander { // fry [160806 for 3.2] public int getLineHeight() { - return fm.getHeight() + fm.getDescent(); + return fontMetrics.getHeight() + fontMetrics.getDescent(); + } + + + // how much space a line might take up [fry 220119] + protected int getLineDisplacement() { + return fontMetrics.getLeading() + fontMetrics.getMaxDescent(); } @@ -199,6 +207,9 @@ public class TextAreaPainter extends JComponent implements TabExpander { * @param gfx The graphics context */ public void paint(Graphics gfx) { + // Good time to update the metrics; about to draw + fontMetrics = getFontMetrics(plainFont); + Graphics2D g2 = (Graphics2D) gfx; g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, antialias ? @@ -216,7 +227,7 @@ public class TextAreaPainter extends JComponent implements TabExpander { // We don't use yToLine() here because that method doesn't // return lines past the end of the document - int height = fm.getHeight(); + int height = fontMetrics.getHeight(); int firstLine = textArea.getFirstLine(); int firstInvalid = firstLine + clipRect.y / height; // Because the clipRect height is usually an even multiple @@ -249,8 +260,8 @@ public class TextAreaPainter extends JComponent implements TabExpander { * @param line The line to invalidate */ final public void invalidateLine(int line) { - repaint(0, textArea.lineToY(line) + fm.getMaxDescent() + fm.getLeading(), - getWidth(), fm.getHeight()); + repaint(0, textArea.lineToY(line) + getLineDisplacement(), + getWidth(), fontMetrics.getHeight()); } @@ -260,9 +271,8 @@ public class TextAreaPainter extends JComponent implements TabExpander { * @param lastLine The last line to invalidate */ final void invalidateLineRange(int firstLine, int lastLine) { - repaint(0,textArea.lineToY(firstLine) + - fm.getMaxDescent() + fm.getLeading(), - getWidth(),(lastLine - firstLine + 1) * fm.getHeight()); + repaint(0,textArea.lineToY(firstLine) + getLineDisplacement(), + getWidth(),(lastLine - firstLine + 1) * fontMetrics.getHeight()); } @@ -283,8 +293,9 @@ public class TextAreaPainter extends JComponent implements TabExpander { public Dimension getPreferredSize() { - return new Dimension(fm.charWidth('w') * defaults.cols, - fm.getHeight() * defaults.rows); + fontMetrics = getFontMetrics(plainFont); + return new Dimension(fontMetrics.charWidth('w') * defaults.cols, + fontMetrics.getHeight() * defaults.rows); } @@ -362,15 +373,14 @@ public class TextAreaPainter extends JComponent implements TabExpander { gfx.setColor(defaults.fgcolor); gfx.setFont(plainFont); - y += fm.getHeight(); - // doesn't respect fixed width like it should -// x = Utilities.drawTabbedText(currentLine, x, y, gfx, this, 0); -// int w = fm.charWidth(' '); + y += fontMetrics.getHeight(); for (int i = 0; i < currentLine.count; i++) { - gfx.drawChars(currentLine.array, currentLine.offset+i, 1, x, y); - x = currentLine.array[currentLine.offset + i] == '\t' ? - x0 + (int)nextTabStop(x - x0, i) : - x + fm.charWidth(currentLine.array[currentLine.offset+i]); + gfx.drawChars(currentLine.array, currentLine.offset + i, 1, x, y); + if (currentLine.array[currentLine.offset + i] == '\t') { + x = x0 + (int) nextTabStop(x - x0, i); + } else { + x += fontMetrics.charWidth(currentLine.array[currentLine.offset + i]); // TODO why this char? + } //textArea.offsetToX(line, currentLine.offset + i); } @@ -397,7 +407,7 @@ public class TextAreaPainter extends JComponent implements TabExpander { // gfx.setFont(defaultFont); // gfx.setColor(defaultColor); - y += fm.getHeight(); + y += fontMetrics.getHeight(); // x = paintSyntaxLine(currentLine, // currentLineTokens, // defaults.styles, this, gfx, x, y); @@ -458,20 +468,15 @@ public class TextAreaPainter extends JComponent implements TabExpander { gfx.setFont(ss.isBold() ? boldFont : plainFont); } line.count = length; // huh? suspicious - // doesn't respect mono metrics, insists on spacing w/ fractional or something -// x = Utilities.drawTabbedText(line, x, y, gfx, this, 0); -// gfx.drawChars(line.array, line.offset, line.count, x, y); -// int w = fm.charWidth(' '); for (int i = 0; i < line.count; i++) { - gfx.drawChars(line.array, line.offset+i, 1, x, y); - x = line.array[line.offset + i] == '\t' ? - x0 + (int)nextTabStop(x - x0, i) : - x + fm.charWidth(line.array[line.offset+i]); + gfx.drawChars(line.array, line.offset + i, 1, x, y); + if (line.array[line.offset + i] == '\t') { + x = x0 + (int) nextTabStop(x - x0, i); + } else { + x += fontMetrics.charWidth(line.array[line.offset + i]); + } } - //x += fm.charsWidth(line.array, line.offset, line.count); - //x += fm.charWidth(' ') * line.count; line.offset += length; - tokens = tokens.next; } @@ -500,8 +505,8 @@ public class TextAreaPainter extends JComponent implements TabExpander { protected void paintLineHighlight(Graphics gfx, int line, int y) { - int height = fm.getHeight(); - y += fm.getLeading() + fm.getMaxDescent(); + int height = fontMetrics.getHeight(); + y += getLineDisplacement(); int selectionStart = textArea.getSelectionStart(); int selectionEnd = textArea.getSelectionStop(); @@ -522,10 +527,12 @@ public class TextAreaPainter extends JComponent implements TabExpander { if (selectionStartLine == selectionEndLine) { x1 = textArea._offsetToX(line, selectionStart - lineStart); x2 = textArea._offsetToX(line, selectionEnd - lineStart); - } else if(line == selectionStartLine) { + + } else if (line == selectionStartLine) { x1 = textArea._offsetToX(line, selectionStart - lineStart); x2 = getWidth(); - } else if(line == selectionEndLine) { + + } else if (line == selectionEndLine) { //x1 = 0; // hack from Stendahl to avoid doing weird side selection thing x1 = textArea._offsetToX(line, 0); @@ -549,31 +556,28 @@ public class TextAreaPainter extends JComponent implements TabExpander { protected void paintBracketHighlight(Graphics gfx, int line, int y) { int position = textArea.getBracketPosition(); if (position != -1) { - y += fm.getLeading() + fm.getMaxDescent(); + y += getLineDisplacement(); int x = textArea._offsetToX(line, position); gfx.setColor(defaults.bracketHighlightColor); // Hack!!! Since there is no fast way to get the character // from the bracket matching routine, we use ( since all // brackets probably have the same width anyway - gfx.drawRect(x,y,fm.charWidth('(') - 1, fm.getHeight() - 1); + gfx.drawRect(x, y,fontMetrics.charWidth('(') - 1, fontMetrics.getHeight() - 1); } } protected void paintCaret(Graphics gfx, int line, int y) { - //System.out.println("painting caret " + line + " " + y); if (textArea.isCaretVisible()) { - //System.out.println("caret is visible"); int offset = textArea.getCaretPosition() - textArea.getLineStartOffset(line); int caretX = textArea._offsetToX(line, offset); - int caretWidth = ((defaults.blockCaret || - textArea.isOverwriteEnabled()) ? - fm.charWidth('w') : 1); - y += fm.getLeading() + fm.getMaxDescent(); - int height = fm.getHeight(); - - //System.out.println("caretX, width = " + caretX + " " + caretWidth); + int caretWidth = 1; + if (defaults.blockCaret || textArea.isOverwriteEnabled()) { + caretWidth = fontMetrics.charWidth('w'); + } + y += getLineDisplacement(); + int height = fontMetrics.getHeight(); gfx.setColor(defaults.caretColor); @@ -581,13 +585,15 @@ public class TextAreaPainter extends JComponent implements TabExpander { gfx.fillRect(caretX, y + height - 1, caretWidth,1); } else { - // some machines don't like the drawRect for the single - // pixel caret.. this caused a lot of hell because on that + // Some machines don't like the drawRect when the caret is a + // single pixel wide. This caused a lot of hell because on that // minority of machines, the caret wouldn't show up past - // the first column. the fix is to use drawLine() in - // those cases, as a workaround. + // the first column. The fix is to use drawLine() instead. if (caretWidth == 1) { - gfx.drawLine(caretX, y, caretX, y + height - 1); + //gfx.drawLine(caretX, y, caretX, y + height - 1); + // workaround for single pixel dots showing up when caret + // is rendered a single pixel too tall [fry 220129] + ((Graphics2D) gfx).draw(new Line2D.Float(caretX, y + 0.5f, caretX, y + height - 0.5f)); } else { gfx.drawRect(caretX, y, caretWidth - 1, height - 1); } diff --git a/app/src/processing/app/syntax/readme.txt b/app/src/processing/app/syntax/readme.txt deleted file mode 100644 index 07a825cd7..000000000 --- a/app/src/processing/app/syntax/readme.txt +++ /dev/null @@ -1,46 +0,0 @@ -OLDSYNTAX PACKAGE README - -I am placing the jEdit 2.2.1 syntax highlighting package in the public -domain. This means it can be integrated into commercial programs, etc. - -This package requires at least Java 1.1 and Swing 1.1. Syntax -highlighting for the following file types is supported: - -- C++, C -- CORBA IDL -- Eiffel -- HTML -- Java -- Java properties -- JavaScript -- MS-DOS INI -- MS-DOS batch files -- Makefile -- PHP -- Perl -- Python -- TeX -- Transact-SQL -- Unix patch/diff -- Unix shell script -- XML - -This package is undocumented; read the source (start by taking a look at -JEditTextArea.java) to find out how to use it; it's really simple. Feel -free to e-mail questions, queries, etc. to me, but keep in mind that -this code is very old and I no longer maintain it. So if you find a bug, -don't bother me about it; fix it yourself. - -* Copyright - -The jEdit 2.2.1 syntax highlighting package contains code that is -Copyright 1998-1999 Slava Pestov, Artur Biesiadowski, Clancy Malcolm, -Jonathan Revusky, Juha Lindfors and Mike Dillon. - -You may use and modify this package for any purpose. Redistribution is -permitted, in both source and binary form, provided that this notice -remains intact in all source distributions of this package. - --- Slava Pestov -25 September 2000 - diff --git a/app/src/processing/app/tools/ColorSelector.java b/app/src/processing/app/tools/ColorSelector.java index 0a59744c7..e414d02bc 100644 --- a/app/src/processing/app/tools/ColorSelector.java +++ b/app/src/processing/app/tools/ColorSelector.java @@ -28,7 +28,6 @@ import processing.app.ui.Toolkit; import java.awt.Color; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; -import java.awt.event.*; /** @@ -37,8 +36,9 @@ import java.awt.event.*; * Using the keyboard shortcuts, you can copy/paste the values for the * colors and paste them into your program. We didn't do any sort of * auto-insert of colorMode() or fill() or stroke() code cuz we couldn't - * decide on a good way to do this.. your contributions welcome). + * decide on a good way to do this... your contributions welcome). */ +@SuppressWarnings("unused") public class ColorSelector implements Tool { /** @@ -67,14 +67,10 @@ public class ColorSelector implements Tool { selector = new ColorChooser(base.getActiveEditor(), false, Color.WHITE, Language.text("menu.edit.copy"), - new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { + e -> { Clipboard c = Toolkit.getSystemClipboard(); c.setContents(new StringSelection(selector.getHexColor()), null); - } - }); + }); } } } diff --git a/app/src/processing/app/tools/CreateFont.java b/app/src/processing/app/tools/CreateFont.java index 1ef55c2c0..886fda6e5 100644 --- a/app/src/processing/app/tools/CreateFont.java +++ b/app/src/processing/app/tools/CreateFont.java @@ -62,13 +62,12 @@ public class CreateFont extends JFrame implements Tool { JButton okButton; JTextField filenameField; - Map table; - boolean smooth = true; + String[] fontNames; + Map nameToFont; Font font; - - String[] list; int selection = -1; + boolean smooth = true; CharacterSelector charSelector; @@ -105,8 +104,6 @@ public class CreateFont extends JFrame implements Tool { textarea.setFont(new Font("Dialog", Font.PLAIN, 12)); pain.add(textarea); - // don't care about families starting with . or # - // also ignore Dialog, DialogInput, Monospaced, Serif, SansSerif // getFontList is deprecated in 1.4, so this has to be used //long t = System.currentTimeMillis(); @@ -115,25 +112,24 @@ public class CreateFont extends JFrame implements Tool { Font[] fonts = ge.getAllFonts(); //System.out.println("font startup took " + (System.currentTimeMillis() - t) + " ms"); - String[] fontList = new String[fonts.length]; - table = new HashMap<>(); + nameToFont = new HashMap<>(); - int index = 0; - for (Font value : fonts) { + for (Font font : fonts) { try { - fontList[index++] = value.getPSName(); - table.put(value.getPSName(), value); + if (!skipFontFamily(font.getFamily())) { + // Use the PostScript name since it has a little more consistency + nameToFont.put(font.getPSName(), font); + } } catch (Exception e) { - // Sometimes fonts cause lots of trouble. - // http://code.google.com/p/processing/issues/detail?id=442 + // Fonts can cause all kinds of weird trouble; just ignore and move on. + // https://github.com/processing/processing/issues/481 e.printStackTrace(); } } - list = new String[index]; - System.arraycopy(fontList, 0, list, 0, index); - - fontSelector = new JList<>(list); + fontNames = nameToFont.keySet().toArray(new String[0]); + Arrays.sort(fontNames, String.CASE_INSENSITIVE_ORDER); + fontSelector = new JList<>(fontNames); fontSelector.addListSelectionListener(e -> { if (!e.getValueIsAdjusting()) { selection = fontSelector.getSelectedIndex(); @@ -153,7 +149,7 @@ public class CreateFont extends JFrame implements Tool { sample = new SampleComponent(this); // Seems that in some instances, no default font is set - // http://dev.processing.org/bugs/show_bug.cgi?id=777 + // https://download.processing.org/bugzilla/777.html sample.setFont(new Font("Dialog", Font.PLAIN, 12)); pain.add(sample); @@ -213,7 +209,7 @@ public class CreateFont extends JFrame implements Tool { //System.out.println(getPreferredSize()); // do this after pack so it doesn't affect layout - sample.setFont(new Font(list[0], Font.PLAIN, 48)); + sample.setFont(new Font(fontNames[0], Font.PLAIN, 48)); fontSelector.setSelectedIndex(0); @@ -224,6 +220,20 @@ public class CreateFont extends JFrame implements Tool { } + /** + * Skip the synthetic fonts (Dialog, DialogInput, Serif, SansSerif, + * and Monospaced). Also skipping those starting . or # because they're + * not intended for use, and creating files starting with a dot is + * likely to confuse (newer) users when they're invisible. + */ + static private boolean skipFontFamily(String family) { + return (family.charAt(0) == '.' || family.charAt(0) == '#' || + Font.DIALOG.equals(family) || Font.DIALOG_INPUT.equals(family) || + Font.SERIF.equals(family) || Font.SANS_SERIF.equals(family) || + Font.MONOSPACED.equals(family)); + } + + public void run() { setVisible(true); } @@ -239,13 +249,13 @@ public class CreateFont extends JFrame implements Tool { // if a deselect occurred, selection will be -1 if ((fontSize > 0) && (fontSize < 256) && (selection != -1)) { //font = new Font(list[selection], Font.PLAIN, fontSize); - Font instance = table.get(list[selection]); + Font instance = nameToFont.get(fontNames[selection]); // 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(' ', '_'); + String filenameSuggestion = fontNames[selection].replace(' ', '_'); filenameSuggestion += "-" + fontSize; filenameField.setText(filenameSuggestion); } @@ -275,9 +285,8 @@ public class CreateFont extends JFrame implements Tool { } try { - Font instance = table.get(list[selection]); + Font instance = nameToFont.get(fontNames[selection]); font = instance.deriveFont(Font.PLAIN, fontSize); - //PFont f = new PFont(font, smooth, all ? null : PFont.CHARSET); PFont f = new PFont(font, smooth, charSelector.getCharacters()); // the editor may have changed while the window was open @@ -339,8 +348,8 @@ class SampleComponent extends JComponent { } public void paintComponent(Graphics g) { -// System.out.println("smoothing set to " + smooth); Graphics2D g2 = (Graphics2D) g; + g2.setColor(Color.WHITE); Dimension dim = getSize(); g2.fillRect(0, 0, dim.width, dim.height); @@ -355,10 +364,17 @@ class SampleComponent extends JComponent { parent.smooth ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + parent.smooth ? + RenderingHints.VALUE_INTERPOLATION_BICUBIC : + RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + + // don't do this, it will reset the drawing settings //super.paintComponent(g2); + Font font = getFont(); int ascent = g2.getFontMetrics().getAscent(); -// System.out.println(f.getName()); + g2.setFont(font); g2.drawString(text, 5, dim.height - (dim.height - ascent) / 2); } diff --git a/app/src/processing/app/tools/ThemeSelector.java b/app/src/processing/app/tools/ThemeSelector.java new file mode 100644 index 000000000..c51ee843e --- /dev/null +++ b/app/src/processing/app/tools/ThemeSelector.java @@ -0,0 +1,542 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + ThemeSelector - easy selection of alternate color systems + Part of the Processing project - https://processing.org + + Copyright (c) 2022 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package processing.app.tools; + +import processing.app.*; +import processing.app.laf.PdeComboBoxUI; +import processing.app.ui.Editor; +import processing.app.ui.Theme; +import processing.app.ui.Toolkit; +import processing.core.PApplet; +import processing.data.StringDict; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class ThemeSelector extends JFrame implements Tool { + static final String HOWTO_URL = + "https://github.com/processing/processing4/wiki/Themes"; + static final String ORDER_FILENAME = "order.txt"; + + String miniSvgXml; + + List sets; + + String defaultTheme; + + File sketchbookFile; + ThemeSet currentSet; + int currentIndex; + + JComboBox setSelector; + ColorfulPanel selector; + + JLabel howtoLabel; + JLabel reloadTheme; + + Base base; + + + public String getMenuTitle() { + return "Theme Selector"; + } + + + public void init(Base base) { + this.base = base; + + try { + File themeFolder = Theme.getThemeFolder(); + File[] setFolders = themeFolder.listFiles(file -> { + if (file.isDirectory()) { + File orderFile = new File(file, ORDER_FILENAME); + return orderFile.exists(); + } + return false; + }); + if (setFolders == null) { + Messages.showWarning("Could not load themes", + "The themes directory could not be read.\n" + + "Please reinstall Processing."); + return; + } + + File miniFile = new File(themeFolder, "mini.svg"); + miniSvgXml = Util.loadFile(miniFile); + + sets = new ArrayList<>(); + for (File folder : setFolders) { + sets.add(new ThemeSet(folder)); + } + currentSet = sets.get(0); + defaultTheme = getDefaultTheme(); + + } catch (IOException e) { + e.printStackTrace(); + } + + Container pane = getContentPane(); + + Box axis = Box.createVerticalBox(); + + String[] setNames = new String[sets.size()]; + for (int i = 0; i < sets.size(); i++) { + setNames[i] = sets.get(i).name; + } + setSelector = new JComboBox<>(setNames); + setSelector.addItemListener(e -> { + currentSet = sets.get(setSelector.getSelectedIndex()); + updateCurrentIndex(); + repaint(); + }); + addRow(axis, setSelector); + + axis.add(Box.createVerticalStrut(13)); + + axis.add(selector = new ColorfulPanel()); // flush with sides + + axis.add(Box.createVerticalStrut(13)); + + addRow(axis, howtoLabel = new JLabel()); + howtoLabel.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + Platform.openURL(HOWTO_URL); + } + + public void mouseEntered(MouseEvent e) { + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + //clickable.setForeground(Theme.getColor("laf.accent.color")); + } + + // Set the text back to black when the mouse is outside + public void mouseExited(MouseEvent e) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + // steal the color from a component that doesn't change + // (so that it works after updateTheme() has been called) + //clickable.setForeground(sketchbookLocationLabel.getForeground()); + } + }); + + axis.add(Box.createVerticalStrut(6)); + + addRow(axis, reloadTheme = new JLabel()); + reloadTheme.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + if (!sketchbookFile.exists()) { + // When first called, just create the theme.txt file + Theme.save(); + updateTheme(); // changes the JLabel for this fella + + } else { + reloadTheme(); + + // May be too subtle, but popping up a dialog box is too much. + // Feels like something needed because theme.txt edits may be subtle. + Editor activeEditor = base.getActiveEditor(); + if (activeEditor != null) { + activeEditor.statusNotice("Finished updating theme."); + } + } + } + + public void mouseEntered(MouseEvent e) { + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + + public void mouseExited(MouseEvent e) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }); + + axis.setBorder(new EmptyBorder(20, 20, 20, 20)); + pane.add(axis); + + Toolkit.registerWindowCloseKeys(getRootPane(), e -> setVisible(false)); + setTitle(getMenuTitle()); + setResizable(false); + updateTheme(); // important before pack() + pack(); + setLocationRelativeTo(null); + } + + static final int ROW_H_GAP = 7; + static final int ROW_V_GAP = 0; + + static private void addRow(Container axis, Component... components) { + JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT, ROW_H_GAP, ROW_V_GAP)); + row.setOpaque(false); + for (Component comp : components) { + row.add(comp); + } + axis.add(row); + } + + + public void run() { + // location for theme.txt in the sketchbook folder + // (doing this in run() in case the sketchbook location has changed) + sketchbookFile = Theme.getSketchbookFile(); + + updateTheme(); + + // figure out if the current theme in sketchbook is a known one + //currentIndex = getCurrentIndex(); + updateCurrentIndex(); + +// invalidate(); +// pack(); + setVisible(true); + } + + + private void updateTheme() { + getContentPane().setBackground(Theme.getColor("theme_selector.window.color")); + + if (setSelector.getUI() instanceof PdeComboBoxUI) { + ((PdeComboBoxUI) setSelector.getUI()).updateTheme(); + } else { + setSelector.setUI(new PdeComboBoxUI("theme_selector.combo_box")); + } + +// String textColor = Theme.get("theme_selector.text.color"); +// String linkColor = Theme.get("theme_selector.link.color"); + + String labelStyle = + "body { " + +// " margin: 0; " + +// " padding: 0;" + +// " font-family: " + detailFont.getName() + ", sans-serif;" + +// " font-size: " + detailFont.getSize() + "px;" + +// " font-size: 18px; " + + " color: " + Theme.get("theme_selector.text.color") + ";" + + "} " + + "a { " + + " color: " + Theme.get("theme_selector.link.color") + ";" + + " text-decoration: none;" + + "}"; + + String prefix = ""; +// System.out.println(prefix); + +// howtoLabel.setText("Read about how to create your own themes."); +// howtoLabel.setText(prefix + "→ Read about how to create your own themes"); +// howtoLabel.setText("→ Read about how to create your own themes", labelStyle); + howtoLabel.setText(prefix + "→ Read about how to create your own themes"); + + if (Theme.getSketchbookFile().exists()) { +// reloadTheme.setText("→ Reload theme.txt to update the current theme."); +// reloadTheme.setText(prefix + "→ Reload theme.txt to update the current theme"); +// reloadTheme.setText("→ Reload theme.txt to update the current theme", labelStyle); + reloadTheme.setText(prefix + "→ Reload theme.txt to update the current theme"); + + } else { +// reloadTheme.setText("Save theme.txt to sketchbook for editing."); +// reloadTheme.setText(prefix + "→ Save theme.txt to sketchbook for editing."); + reloadTheme.setText(prefix + "→ Save theme.txt to sketchbook for editing"); + } + //((HTMLDocument) howtoLabel.getDocument()).getStyleSheet().addRule(detailStyle); + } + + + private String getCurrentTheme() { + if (sketchbookFile.exists()) { + return Util.loadFile(sketchbookFile); + } + return defaultTheme; + } + + + private String getDefaultTheme() { + // should be entry 0 of set 0, but let's not make that assumption + try { + return Util.loadFile(Base.getLibFile("theme.txt")); + } catch (IOException e) { + e.printStackTrace(); + } + // do this as a fallback + return sets.get(0).themes[0]; + } + + + /** + * @return true if sketchbook/theme.txt does not match a built-in theme. + */ + private boolean userModifiedTheme() { + if (!sketchbookFile.exists()) { + return false; + } + String currentTheme = getCurrentTheme(); + for (ThemeSet set : sets) { + if (set.getIndex(currentTheme) != -1) { + return false; // this is a built-in theme + } + } + return true; + } + + + private void setCurrentIndex(int index) { + currentIndex = index; + + // If there is a theme.txt file in the sketchbook folder, + // archive it and move out of the way. + if (userModifiedTheme()) { + boolean success = Theme.archiveCurrent(); + if (!success) { + Messages.showWarning("Could not back up theme", + "Could not save a backup of theme.txt in your sketchbook folder.\n" + + "Rename it manually and try setting the theme again."); + return; + } + } + + // required to update the color of the dropdown menu + setSelector.repaint(); + + // No longer saving a new theme.txt when making a selection, just setting a + // preference so that subsequent Processing updates load new theme changes. + //Util.saveFile(currentSet.get(index), sketchbookFile); + Preferences.set("theme", currentSet.getPath(index)); + reloadTheme(); + } + + + /** + * Called when user clicks the 'reload' button, or when the theme + * is changed by clicking on a built-in selection. + */ + private void reloadTheme() { + Theme.reload(); + base.updateTheme(); + updateTheme(); + } + + + private void updateCurrentIndex() { + String currentTheme = getCurrentTheme(); + currentIndex = currentSet.getIndex(currentTheme); + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + class ThemeSet { + final String name; + private int count; + private String[] paths; + private String[] themes; + private Image[] images; + private Map indices; + + ThemeSet(File dir) { + name = dir.getName(); + + File orderFile = new File(dir, ORDER_FILENAME); + String[] lines = PApplet.loadStrings(orderFile); + if (lines != null) { + count = Math.min(16, lines.length); + if (count < lines.length) { + System.err.println("Only using the first 16 themes inside " + orderFile); + } + paths = new String[count]; + themes = new String[count]; + images = new Image[count]; + indices = new HashMap<>(count); + + // don't load more than 16 entries + for (int i = 0; i < count; i++) { + String filename = lines[i] + ".txt"; + File file = new File(dir, filename); + String theme = Util.loadFile(file); + indices.put(theme, i); + paths[i] = name + "/" + filename; + themes[i] = theme; + images[i] = renderImage(file.getName(), theme); + } + } + } + + /** + * Render the Mini SVG using the theme colors. + * @param filename only used for debug messages + * @param theme all the lines of the theme file, joined + * @return mini image of the PDE with theme colors applied + */ + private Image renderImage(String filename, String theme) { + // parse the txt file to get entries for swapping + StringDict entries = + Util.readSettings(filename, PApplet.split(theme, '\n')); + //entries.print(); + + StringDict replacements = new StringDict(new String[][] { + { "#000000", entries.get("console.color") }, + { "#111111", entries.get("editor.gutter.highlight.color") }, + { "#222222", entries.get("footer.gradient.top") }, + { "#444444", entries.get("mode.background.color") }, + { "#555555", entries.get("toolbar.button.enabled.glyph") }, + { "#666666", entries.get("editor.gradient.top") }, + { "#777777", entries.get("editor.gradient.bottom") }, + { "#888888", entries.get("toolbar.button.selected.field") }, + { "#CCCCCC", entries.get("toolbar.button.enabled.field") }, + { "#DDDDDD", entries.get("editor.line.highlight.color") }, + { "#EEEEEE", entries.get("toolbar.button.selected.glyph") }, + { "#FFFFFF", entries.get("editor.bgcolor") } + }); + //replacements.print(); + //System.out.println(); + + return Toolkit.svgToImageMult(miniSvgXml, ColorfulPanel.DIM, ColorfulPanel.DIM, replacements); + } + + String getPath(int index) { + return paths[index]; + } + +// String getTheme(int index) { +// return themes[index]; +// } + + /** + * Return the index for a given theme in this set, + * or -1 if not part of this set. + */ + int getIndex(String theme) { + return indices.getOrDefault(theme, -1); + } + } + + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + class ColorfulPanel extends JPanel { + static final int OUTSET = 5; + static final int OUTLINE = 3; + + static final int DIM = 80; + static final int BETWEEN = 25; + static final int EACH = DIM + BETWEEN; + static final int MARGIN = OUTSET + (OUTLINE + 1) / 2; + static final int SIZE = MARGIN*2 + DIM*4 + BETWEEN*3; + + ColorfulPanel() { + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + handleMouse(e); + } + }); + + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + handleMouse(e); + } + }); + + setOpaque(false); + } + + private void handleMouse(MouseEvent e) { + int col = constrain((e.getX() - MARGIN) / EACH); + int colEx = constrain((e.getX() - MARGIN) % EACH); + int row = constrain((e.getY() - MARGIN) / EACH); + int rowEx = constrain((e.getY() - MARGIN) % EACH); + + if (colEx < DIM && rowEx < DIM) { + int index = row * 4 + col; + if (index < currentSet.count && index != currentIndex) { + setCurrentIndex(index); + repaint(); + } + } + } + + private int constrain(int value) { + return Math.max(0, Math.min(value, 3)); + } + + @Override + public void paintComponent(Graphics g) { + for (int i = 0; i < currentSet.count; i++) { + int col = i % 4; + int row = i / 4; + int x = MARGIN + col*EACH; + int y = MARGIN + row*EACH; + g.drawImage(currentSet.images[i], x, y, DIM, DIM, null); + } + + Graphics2D g2 = (Graphics2D) g; + g2.setStroke(new BasicStroke(OUTLINE)); + g2.setColor(Color.GRAY); + + if (currentIndex != -1) { + int col = currentIndex % 4; + int row = currentIndex / 4; + g2.drawRect(MARGIN + EACH * col - OUTSET, + MARGIN + EACH * row - OUTSET, + DIM + OUTSET * 2, + DIM + OUTSET * 2); + } + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(SIZE, SIZE); + } + + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + @Override + public Dimension getMaximumSize() { + return getPreferredSize(); + } + } +} \ No newline at end of file diff --git a/app/src/processing/app/ui/About.java b/app/src/processing/app/ui/About.java index 526bc31de..b75599254 100644 --- a/app/src/processing/app/ui/About.java +++ b/app/src/processing/app/ui/About.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-19 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or @@ -52,18 +52,6 @@ public class About extends Window { width = icon.getIconWidth(); height = icon.getIconHeight(); - /* - if (Toolkit.highResDisplay()) { - image = Toolkit.getLibImage("about-2x.jpg"); //$NON-NLS-1$ - width = image.getWidth(null) / 2; - height = image.getHeight(null) / 2; - } else { - image = Toolkit.getLibImage("about.jpg"); //$NON-NLS-1$ - width = image.getWidth(null); - height = image.getHeight(null); - } - */ - addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { dispose(); @@ -79,10 +67,7 @@ public class About extends Window { } }); -// Dimension screen = Toolkit.getScreenSize(); -// setBounds((screen.width-width)/2, (screen.height-height)/2, width, height); setSize(width, height); -// setLocationRelativeTo(null); setLocationRelativeTo(frame); setVisible(true); requestFocus(); @@ -90,22 +75,18 @@ public class About extends Window { public void paint(Graphics g) { -// Graphics2D g2 = Toolkit.prepareGraphics(g); -// g2.scale(0.5, 0.5); - Graphics2D g2 = (Graphics2D) g; - // OS X looks better doing its own thing, Windows and Linux need AA + + // macOS looks better doing its own thing, Windows and Linux need AA g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, Platform.isMacOS() ? RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT : RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g.drawImage(icon.getImage(), 0, 0, width, height, null); -// g.setColor(Color.ORANGE); -// g.fillRect(0, 0, width, height); g.setFont(Toolkit.getSansFont(12, Font.PLAIN)); - g.setColor(Color.WHITE); - g.drawString(Base.getVersionName(), 26, 29); + g.setColor(Color.DARK_GRAY); + g.drawString(Base.getVersionName(), width - 40, height - 15); } } \ No newline at end of file diff --git a/app/src/processing/app/ui/ChangeDetector.java b/app/src/processing/app/ui/ChangeDetector.java index 1861f30bc..ce5be8bc4 100644 --- a/app/src/processing/app/ui/ChangeDetector.java +++ b/app/src/processing/app/ui/ChangeDetector.java @@ -45,12 +45,12 @@ public class ChangeDetector implements WindowFocusListener { private final Sketch sketch; private final Editor editor; - private List ignoredRemovals = new ArrayList<>(); - private List ignoredModifications = new ArrayList<>(); + final private List ignoredRemovals = new ArrayList<>(); + final private List ignoredModifications = new ArrayList<>(); // Windows and others seem to have a few hundred ms difference in reported // times, so we're arbitrarily setting a gap in time here. - // Mac OS X has an (exactly) one second difference. Not sure if it's a Java + // Mac OS X has an (exactly) one-second difference. Not sure if it's a Java // bug or something else about how OS X is writing files. static private final int MODIFICATION_WINDOW_MILLIS = Preferences.getInteger("editor.watcher.window"); @@ -116,7 +116,7 @@ public class ChangeDetector implements WindowFocusListener { // REMOVED FILES - // Get codes which don't have file + // Get codes that do not have a file List removedCodes = Optional.ofNullable(existsMap.get(Boolean.FALSE)) .orElse(Collections.emptyList()); List removedCodesFinal = removedCodes.stream() @@ -129,9 +129,9 @@ public class ChangeDetector implements WindowFocusListener { /// MODIFIED FILES - // Get codes which have file with different modification time - List modifiedCodes = existsMap.containsKey(Boolean.TRUE) ? - existsMap.get(Boolean.TRUE) : Collections.emptyList(); + // Get codes that have a file with different modification time + List modifiedCodes = + existsMap.getOrDefault(Boolean.TRUE, Collections.emptyList()); List modifiedCodesFinal = new ArrayList<>(); for (SketchCode code : modifiedCodes) { if (ignoredModifications.contains(code)) continue; @@ -210,7 +210,7 @@ public class ChangeDetector implements WindowFocusListener { scKeep.setLastModified(); scKeep.setModified(true); }, - scDelete -> sketch.removeCode(scDelete), + sketch::removeCode, scResave -> { try { scResave.save(); diff --git a/app/src/processing/app/ui/ColorChooser.java b/app/src/processing/app/ui/ColorChooser.java index 5693db6c6..867fa1792 100644 --- a/app/src/processing/app/ui/ColorChooser.java +++ b/app/src/processing/app/ui/ColorChooser.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-19 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2006-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -23,8 +23,7 @@ package processing.app.ui; import processing.app.Language; -import processing.app.Platform; -import processing.core.*; +import processing.core.PApplet; import java.awt.BorderLayout; import java.awt.Color; @@ -65,11 +64,6 @@ public class ColorChooser { //extends JFrame implements DocumentListener { JDialog window; -// public String getMenuTitle() { -// return "Color Selector"; -// } - - public ColorChooser(Frame owner, boolean modal, Color initialColor, String buttonName, ActionListener buttonListener) { //super("Color Selector"); @@ -82,7 +76,7 @@ public class ColorChooser { //extends JFrame implements DocumentListener { range = new ColorRange(); Box rangeBox = new Box(BoxLayout.Y_AXIS); rangeBox.setAlignmentY(0); - rangeBox.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); + //rangeBox.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); rangeBox.add(range); box.add(rangeBox); box.add(Box.createHorizontalStrut(10)); @@ -90,30 +84,20 @@ public class ColorChooser { //extends JFrame implements DocumentListener { slider = new ColorSlider(); Box sliderBox = new Box(BoxLayout.Y_AXIS); sliderBox.setAlignmentY(0); - sliderBox.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); + //sliderBox.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); sliderBox.add(slider); box.add(sliderBox); box.add(Box.createHorizontalStrut(10)); box.add(createColorFields(buttonName, buttonListener)); -// System.out.println("1: " + hexField.getInsets()); box.add(Box.createHorizontalStrut(10)); -// System.out.println("2: " + hexField.getInsets()); - window.getContentPane().add(box, BorderLayout.CENTER); -// System.out.println(hexField); -// System.out.println("3: " + hexField.getInsets()); -// colorPanel.setInsets(hexField.getInsets()); window.pack(); window.setResizable(false); -// Dimension size = getSize(); -// Dimension screen = Toolkit.getScreenSize(); -// setLocation((screen.width - size.width) / 2, -// (screen.height - size.height) / 2); window.setLocationRelativeTo(null); window.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); @@ -122,12 +106,7 @@ public class ColorChooser { //extends JFrame implements DocumentListener { hide(); } }); - Toolkit.registerWindowCloseKeys(window.getRootPane(), new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - hide(); - } - }); - + Toolkit.registerWindowCloseKeys(window.getRootPane(), actionEvent -> hide()); Toolkit.setIcon(window); colorListener = new ColorListener(); @@ -140,13 +119,9 @@ public class ColorChooser { //extends JFrame implements DocumentListener { hexField.getDocument().addDocumentListener(colorListener); setColor(initialColor); -// System.out.println("4: " + hexField.getInsets()); } - //hexField.setText("#FFFFFF"); - - public void show() { window.setVisible(true); } @@ -273,7 +248,7 @@ public class ColorChooser { //extends JFrame implements DocumentListener { * Set the HSB values based on the current RGB values. */ protected void updateHSB() { - float hsb[] = new float[3]; + float[] hsb = new float[3]; Color.RGBtoHSB(red, green, blue, hsb); hue = (int) (hsb[0] * 359.0f); @@ -305,11 +280,7 @@ public class ColorChooser { //extends JFrame implements DocumentListener { try { int value = Integer.parseInt(text); if (value > max) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - field.setText(String.valueOf(max)); - } - }); + SwingUtilities.invokeLater(() -> field.setText(String.valueOf(max))); return max; } return value; @@ -324,43 +295,33 @@ public class ColorChooser { //extends JFrame implements DocumentListener { Box box = Box.createVerticalBox(); box.setAlignmentY(0); - final int GAP = Platform.isWindows() ? 5 : 0; - final int BETWEEN = Platform.isWindows() ? 8 : 6; //10; + final int GAP = 3; + final int BETWEEN = 8; Box row; row = Box.createHorizontalBox(); - if (Platform.isMacOS()) { - row.add(Box.createHorizontalStrut(17)); - } else { - row.add(createFixedLabel("")); - } + row.add(createFixedSpace()); + // Can't just set the bg color of the panel because it also tints the bevel // (on OS X), which looks odd. So instead we override paintComponent(). colorPanel = new JPanel() { - public void paintComponent(Graphics g) { - g.setColor(new Color(red, green, blue)); - Dimension size = getSize(); - g.fillRect(0, 0, size.width, size.height); - } - }; - colorPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); - Dimension dim = new Dimension(70, 25); - colorPanel.setMinimumSize(dim); - colorPanel.setMaximumSize(dim); - colorPanel.setPreferredSize(dim); + public void paintComponent(Graphics g) { + g.setColor(new Color(red, green, blue)); + Dimension size = getSize(); + g.fillRect(0, 0, size.width, size.height); + } + }; row.add(colorPanel); row.add(Box.createHorizontalGlue()); box.add(row); box.add(Box.createVerticalStrut(BETWEEN)); -// if (Base.isMacOS()) { // need a little extra -// box.add(Box.createVerticalStrut(BETWEEN)); -// } + // row = Box.createHorizontalBox(); row.add(createFixedLabel("H")); - row.add(hueField = new NumberField(4, false)); + row.add(hueField = new NumberField(3, false)); row.add(new JLabel(" \u00B0")); // degree symbol row.add(Box.createHorizontalGlue()); box.add(row); @@ -368,7 +329,7 @@ public class ColorChooser { //extends JFrame implements DocumentListener { row = Box.createHorizontalBox(); row.add(createFixedLabel("S")); - row.add(saturationField = new NumberField(4, false)); + row.add(saturationField = new NumberField(3, false)); row.add(new JLabel(" %")); row.add(Box.createHorizontalGlue()); box.add(row); @@ -376,7 +337,7 @@ public class ColorChooser { //extends JFrame implements DocumentListener { row = Box.createHorizontalBox(); row.add(createFixedLabel("B")); - row.add(brightnessField = new NumberField(4, false)); + row.add(brightnessField = new NumberField(3, false)); row.add(new JLabel(" %")); row.add(Box.createHorizontalGlue()); box.add(row); @@ -386,21 +347,21 @@ public class ColorChooser { //extends JFrame implements DocumentListener { row = Box.createHorizontalBox(); row.add(createFixedLabel("R")); - row.add(redField = new NumberField(4, false)); + row.add(redField = new NumberField(3, false)); row.add(Box.createHorizontalGlue()); box.add(row); box.add(Box.createVerticalStrut(GAP)); row = Box.createHorizontalBox(); row.add(createFixedLabel("G")); - row.add(greenField = new NumberField(4, false)); + row.add(greenField = new NumberField(3, false)); row.add(Box.createHorizontalGlue()); box.add(row); box.add(Box.createVerticalStrut(GAP)); row = Box.createHorizontalBox(); row.add(createFixedLabel("B")); - row.add(blueField = new NumberField(4, false)); + row.add(blueField = new NumberField(3, false)); row.add(Box.createHorizontalGlue()); box.add(row); box.add(Box.createVerticalStrut(BETWEEN)); @@ -408,33 +369,17 @@ public class ColorChooser { //extends JFrame implements DocumentListener { // row = Box.createHorizontalBox(); - row.add(createFixedLabel("")); - // Windows needs extra space, OS X and Linux do not - // Mac OS X needs 6 because #CCCCCC is quite wide - final int hexCount = Platform.isWindows() ? 7 : 6; - row.add(hexField = new NumberField(hexCount, true)); + row.add(createFixedSpace()); + row.add(hexField = new NumberField(0, true)); row.add(Box.createHorizontalGlue()); box.add(row); box.add(Box.createVerticalStrut(GAP)); // -// // Not great, because the insets make things weird anyway -// //Dimension dim = new Dimension(hexField.getPreferredSize()); -// Dimension dim = new Dimension(70, 20); -// colorPanel.setMinimumSize(dim); -// colorPanel.setMaximumSize(dim); -// colorPanel.setPreferredSize(dim); -//// colorPanel.setBorder(new EmptyBorder(hexField.getInsets())); - - // - row = Box.createHorizontalBox(); - if (Platform.isMacOS()) { - row.add(Box.createHorizontalStrut(11)); - } else { - row.add(createFixedLabel("")); - } + row.add(createFixedLabel("")); + JButton button = new JButton(buttonName); button.addActionListener(buttonListener); //System.out.println("button: " + button.getInsets()); @@ -442,12 +387,16 @@ public class ColorChooser { //extends JFrame implements DocumentListener { row.add(Box.createHorizontalGlue()); box.add(row); + // Removing the "Cancel" button in 4.0 beta 9. When using the + // Color Selector, it's not a "Cancel" operation, and overkill + // to have multiple states when one can just close the window. + /* row = Box.createHorizontalBox(); - if (Platform.isMacOS()) { - row.add(Box.createHorizontalStrut(11)); - } else { +// if (Platform.isMacOS()) { +// row.add(Box.createHorizontalStrut(11)); +// } else { row.add(createFixedLabel("")); - } +// } button = new JButton(Language.text("prompt.cancel")); button.addActionListener(new ActionListener() { @@ -459,6 +408,20 @@ public class ColorChooser { //extends JFrame implements DocumentListener { row.add(button); row.add(Box.createHorizontalGlue()); box.add(row); + */ + + // + + Dimension dim = button.getPreferredSize(); + + colorPanel.setMinimumSize(dim); + colorPanel.setMaximumSize(dim); + colorPanel.setPreferredSize(dim); + + hexField.setMinimumSize(dim); + hexField.setMaximumSize(dim); + hexField.setPreferredSize(dim); + // box.add(Box.createVerticalGlue()); @@ -468,6 +431,11 @@ public class ColorChooser { //extends JFrame implements DocumentListener { int labelH; + protected JLabel createFixedSpace() { + return createFixedLabel(""); + } + + /** * return a label of a fixed width */ @@ -526,10 +494,10 @@ public class ColorChooser { //extends JFrame implements DocumentListener { if ((mouseX >= 0) && (mouseX < WIDE) && (mouseY >= 0) && (mouseY < HIGH)) { - int nsaturation = (int) (100 * (mouseX / 255.0f)); - int nbrightness = 100 - ((int) (100 * (mouseY / 255.0f))); - saturationField.setText(String.valueOf(nsaturation)); - brightnessField.setText(String.valueOf(nbrightness)); + int newSaturation = (int) (100 * (mouseX / 255.0f)); + int newBrightness = 100 - ((int) (100 * (mouseY / 255.0f))); + saturationField.setText(String.valueOf(newSaturation)); + brightnessField.setText(String.valueOf(newBrightness)); lastX = mouseX; lastY = mouseY; @@ -608,8 +576,8 @@ public class ColorChooser { //extends JFrame implements DocumentListener { if ((mouseX >= 0) && (mouseX < WIDE) && (mouseY >= 0) && (mouseY < HIGH)) { - int nhue = 359 - (int) (359 * (mouseY / 255.0f)); - hueField.setText(String.valueOf(nhue)); + int newHue = 359 - (int) (359 * (mouseY / 255.0f)); + hueField.setText(String.valueOf(newHue)); } } @@ -657,13 +625,6 @@ public class ColorChooser { //extends JFrame implements DocumentListener { return new NumberDocument(this); } - public Dimension getPreferredSize() { - if (!allowHex) { - return new Dimension(45, super.getPreferredSize().height); - } - return super.getPreferredSize(); - } - public Dimension getMinimumSize() { return getPreferredSize(); } @@ -691,7 +652,7 @@ public class ColorChooser { //extends JFrame implements DocumentListener { if (str == null) return; - char chars[] = str.toCharArray(); + char[] chars = str.toCharArray(); int charCount = 0; // remove any non-digit chars for (int i = 0; i < chars.length; i++) { @@ -713,11 +674,4 @@ public class ColorChooser { //extends JFrame implements DocumentListener { // seems to have something to do with how Document objects are set up } } - - -// static public void main(String[] args) { -// ColorSelector cs = new ColorSelector(); -// cs.init(null); -// EventQueue.invokeLater(cs); -// } } diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 6ebd283fe..2fe89cb86 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -23,23 +23,6 @@ package processing.app.ui; -import processing.app.Base; -import processing.app.Formatter; -import processing.app.Language; -import processing.app.Messages; -import processing.app.Mode; -import processing.app.Platform; -import processing.app.Preferences; -import processing.app.Problem; -import processing.app.RunnerListener; -import processing.app.Sketch; -import processing.app.SketchCode; -import processing.app.SketchException; -import processing.app.Util; -import processing.app.contrib.ContributionManager; -import processing.app.syntax.*; -import processing.core.*; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; @@ -65,12 +48,30 @@ import java.util.TimerTask; import java.util.stream.Collectors; import javax.swing.*; +import javax.swing.border.EmptyBorder; import javax.swing.event.*; import javax.swing.plaf.basic.*; import javax.swing.text.*; import javax.swing.text.html.*; import javax.swing.undo.*; +import processing.app.Base; +import processing.app.Formatter; +import processing.app.Language; +import processing.app.Messages; +import processing.app.Mode; +import processing.app.Platform; +import processing.app.Preferences; +import processing.app.Problem; +import processing.app.RunnerListener; +import processing.app.Sketch; +import processing.app.SketchCode; +import processing.app.SketchException; +import processing.app.contrib.ContributionManager; +import processing.app.laf.PdeMenuItemUI; +import processing.app.syntax.*; +import processing.core.*; + /** * Main editor panel for the Processing Development Environment. @@ -80,12 +81,13 @@ public abstract class Editor extends JFrame implements RunnerListener { protected EditorState state; protected Mode mode; - // There may be certain gutter sizes which cause text bounds inside the console to be calculated incorrectly. 45 - // seems to work but change with caution. + // There may be certain gutter sizes that cause text bounds + // inside the console to be calculated incorrectly. + // 45 seems to work but change with caution. [sampottinger 191107] static public final int LEFT_GUTTER = Toolkit.zoom(45); static public final int RIGHT_GUTTER = Toolkit.zoom(12); - static public final int GUTTER_MARGIN = Toolkit.zoom(3); + static public final int GUTTER_MARGIN = Toolkit.zoom(5); protected MarkerColumn errorColumn; @@ -171,7 +173,7 @@ public abstract class Editor extends JFrame implements RunnerListener { }); // don't close the window when clicked, the app will take care // of that via the handleQuitInternal() methods - // http://dev.processing.org/bugs/show_bug.cgi?id=440 + // https://download.processing.org/bugzilla/440.html setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); // When bringing a window to front, let the Base know @@ -347,7 +349,7 @@ public abstract class Editor extends JFrame implements RunnerListener { protected JEditTextArea createTextArea() { - return new JEditTextArea(new PdeTextAreaDefaults(mode), + return new JEditTextArea(new PdeTextAreaDefaults(), new PdeInputHandler(this)); } @@ -465,20 +467,6 @@ public abstract class Editor extends JFrame implements RunnerListener { for (final Mode m : base.getModeList()) { JRadioButtonMenuItem item = new JRadioButtonMenuItem(m.getTitle()); item.addActionListener(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 { - 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 @@ -494,9 +482,9 @@ public abstract class Editor extends JFrame implements RunnerListener { } modePopup.addSeparator(); - JMenuItem addLib = new JMenuItem(Language.text("toolbar.add_mode")); - addLib.addActionListener(e -> ContributionManager.openModes()); - modePopup.add(addLib); + JMenuItem manageModes = new JMenuItem(Language.text("toolbar.manage_modes")); + manageModes.addActionListener(e -> ContributionManager.openModes()); + modePopup.add(manageModes); Toolkit.setMenuMnemsInside(modePopup); } @@ -535,6 +523,7 @@ public abstract class Editor extends JFrame implements RunnerListener { abstract public EditorToolbar createToolbar(); + @SuppressWarnings("unused") public EditorToolbar getToolbar() { return toolbar; } @@ -571,27 +560,14 @@ public abstract class Editor extends JFrame implements RunnerListener { * with things in the Preferences window. */ public void applyPreferences() { - // Even though this is only updating the theme (colors, icons), subclasses - // use this to apply other preferences (i.e. error checking changes in Java Mode). + // Even though this is only updating the theme (colors, icons), + // subclasses use this to apply other preferences. + // For instance, Java Mode applies changes to error checking. updateTheme(); - -// // Update fonts and other items controllable from the prefs -//// textarea.getPainter().updateAppearance(); -//// textarea.repaint(); -// textarea.updateTheme(); -// console.updateTheme(); } public void updateTheme() { - /* - PdeTextArea pta = getPdeTextArea(); - // will be null if a subclass has overridden createTextArea() - // to return something besides a PdeTextArea - if (pta != null) { - pta.updateAppearance(); - } - */ header.updateTheme(); toolbar.updateTheme(); textarea.updateTheme(); @@ -611,6 +587,29 @@ public abstract class Editor extends JFrame implements RunnerListener { toolTipTextColor = Theme.getColor("errors.selection.fgcolor"); toolTipWarningColor = Theme.getColor("errors.selection.warning.bgcolor"); toolTipErrorColor = Theme.getColor("errors.selection.error.bgcolor"); + + JPopupMenu popup = modePopup.getPopupMenu(); + // Cannot use instanceof because com.formdev.flatlaf.ui.FlatPopupMenuBorder + // is a subclass of EmptyBorder, so just override each time. Cannot set + // null because that will reset the border to the default, not remove it. + // The top/bottom in FlatLaf is 6px, but feels too large. + popup.setBorder(new EmptyBorder(3, 0, 3, 0)); + popup.setBackground(Theme.getColor("mode.popup.enabled.bgcolor")); + + for (Component comp : modePopup.getMenuComponents()) { + if (comp instanceof JMenuItem) { + JMenuItem item = (JMenuItem) comp; + if (item.getUI() instanceof PdeMenuItemUI) { + ((PdeMenuItemUI) item.getUI()).updateTheme(); + } else { + item.setUI(new PdeMenuItemUI("mode.popup")); + } + } else if (comp instanceof JPopupMenu.Separator) { + comp.setForeground(Theme.getColor("mode.popup.disabled.fgcolor")); + } + } + + repaint(); // for good measure } @@ -643,15 +642,6 @@ public abstract class Editor extends JFrame implements RunnerListener { abstract public JMenu buildFileMenu(); -// public JMenu buildFileMenu(Editor editor) { -// return buildFileMenu(editor, null); -// } -// -// -// // most of these items are per-mode -// protected JMenu buildFileMenu(Editor editor, JMenuItem[] exportItems) { - - protected JMenu buildFileMenu(JMenuItem[] exportItems) { JMenuItem item; JMenu fileMenu = new JMenu(Language.text("menu.file")); @@ -664,10 +654,8 @@ public abstract class Editor extends JFrame implements RunnerListener { item.addActionListener(e -> base.handleOpenPrompt()); fileMenu.add(item); -// fileMenu.add(base.getSketchbookMenu()); - item = Toolkit.newJMenuItemShift(Language.text("menu.file.sketchbook"), 'K'); - item.addActionListener(e -> mode.showSketchbookFrame()); + item.addActionListener(e -> base.showSketchbookFrame()); fileMenu.add(item); item = Toolkit.newJMenuItemShift(Language.text("menu.file.examples"), 'O'); @@ -680,12 +668,10 @@ public abstract class Editor extends JFrame implements RunnerListener { item = Toolkit.newJMenuItem(Language.text("menu.file.save"), 'S'); item.addActionListener(e -> handleSave(false)); -// saveMenuItem = item; fileMenu.add(item); item = Toolkit.newJMenuItemShift(Language.text("menu.file.save_as"), 'S'); item.addActionListener(e -> handleSaveAs()); -// saveAsMenuItem = item; fileMenu.add(item); if (exportItems != null) { @@ -722,16 +708,6 @@ public abstract class Editor extends JFrame implements RunnerListener { } -// public void setSaveItem(JMenuItem item) { -// saveMenuItem = item; -// } - - -// public void setSaveAsItem(JMenuItem item) { -// saveAsMenuItem = item; -// } - - protected JMenu buildEditMenu() { JMenu menu = new JMenu(Language.text("menu.edit")); JMenuItem item; @@ -765,36 +741,6 @@ public abstract class Editor extends JFrame implements RunnerListener { item.addActionListener(e -> textarea.selectAll()); menu.add(item); - /* - menu.addSeparator(); - - item = Toolkit.newJMenuItem("Delete Selected Lines", 'D'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleDeleteLines(); - } - }); - menu.add(item); - - item = new JMenuItem("Move Selected Lines Up"); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_UP, Event.ALT_MASK)); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleMoveLines(true); - } - }); - menu.add(item); - - item = new JMenuItem("Move Selected Lines Down"); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, Event.ALT_MASK)); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleMoveLines(false); - } - }); - menu.add(item); - */ - menu.addSeparator(); item = Toolkit.newJMenuItem(Language.text("menu.edit.auto_format"), 'T'); @@ -914,6 +860,7 @@ public abstract class Editor extends JFrame implements RunnerListener { }); sketchMenu.add(item); + //noinspection ConstantConditions if (runItems != null && runItems.length != 0) { sketchMenu.addSeparator(); } @@ -1005,20 +952,6 @@ public abstract class Editor extends JFrame implements RunnerListener { } - /* - public JMenu getToolMenu() { - if (toolsMenu == null) { - rebuildToolMenu(); - } - return toolsMenu; - } - - public void removeTool() { - rebuildToolMenu(); - } - */ - - /** * Clears the Tool menu and runs the gc so that contributions can be updated * without classes still being in use. @@ -1074,9 +1007,9 @@ public abstract class Editor extends JFrame implements RunnerListener { static public void showChanges() { - // http://code.google.com/p/processing/issues/detail?id=1520 + // https://github.com/processing/processing/issues/1558 if (!Base.isCommandLine()) { - Platform.openURL("https://github.com/processing/processing/wiki/Changes"); + Platform.openURL("https://github.com/processing/processing4/wiki/Changes-in-4.0"); } } @@ -1426,6 +1359,7 @@ public abstract class Editor extends JFrame implements RunnerListener { } + @SuppressWarnings("unused") public void insertText(String what) { startCompoundEdit(); int caret = getCaretOffset(); @@ -1440,6 +1374,7 @@ public abstract class Editor extends JFrame implements RunnerListener { } + @SuppressWarnings("unused") public void setSelectedText(String what) { textarea.setSelectedText(what); } @@ -1505,6 +1440,7 @@ public abstract class Editor extends JFrame implements RunnerListener { /** * Replace the text on a specified line. */ + @SuppressWarnings("unused") public void setLineText(int line, String what) { startCompoundEdit(); textarea.select(getLineStartOffset(line), getLineStopOffset(line)); @@ -1881,7 +1817,7 @@ public abstract class Editor extends JFrame implements RunnerListener { // Put the scrollbar position back, otherwise it jumps on each format. // Since we're not doing a good job of maintaining position anyway, // a more complicated workaround here is fairly pointless. - // http://code.google.com/p/processing/issues/detail?id=1533 + // https://github.com/processing/processing/issues/1571 if (scrollPos != textarea.getVerticalScrollPosition()) { textarea.setVerticalScrollPosition(scrollPos); } @@ -2166,11 +2102,12 @@ public abstract class Editor extends JFrame implements RunnerListener { * Check if the sketch is modified and ask user to save changes. * @return false if canceling the close/quit operation */ + @SuppressWarnings({"BooleanMethodIsAlwaysInverted", "RedundantIfStatement"}) public boolean checkModified() { if (!sketch.isModified()) return true; // As of Processing 1.0.10, this always happens immediately. - // http://dev.processing.org/bugs/show_bug.cgi?id=1456 + // https://download.processing.org/bugzilla/1456.html // With Java 7u40 on OS X, need to bring the window forward. toFront(); @@ -2199,25 +2136,10 @@ public abstract class Editor extends JFrame implements RunnerListener { } } else { - // This code is disabled unless Java 1.5 is being used on Mac OS X - // because of a Java bug that prevents the initial value of the - // dialog from being set properly (at least on my MacBook Pro). - // The bug causes the "Don't Save" option to be the highlighted, - // blinking, default. This sucks. But I'll tell you what doesn't - // suck--workarounds for the Mac and Apple's snobby attitude about it! - // I think it's nifty that they treat their developers like dirt. - - // Pane formatting adapted from the quaqua guide - // http://www.randelshofer.ch/quaqua/guide/joptionpane.html + String tier1 = Language.interpolate("save.title", sketch.getName()); + String tier2 = Language.text("save.hint"); JOptionPane pane = - new JOptionPane(" " + - " " + - "" + Language.interpolate("save.title", sketch.getName()) + "" + - "

" + Language.text("save.hint") + "

", - JOptionPane.QUESTION_MESSAGE); + new JOptionPane(Toolkit.formatMessage(tier1, tier2), JOptionPane.QUESTION_MESSAGE); String[] options = new String[] { Language.text("save.btn.save"), @@ -2257,6 +2179,11 @@ public abstract class Editor extends JFrame implements RunnerListener { * shouldn't rely on any of its variables being initialized already. */ protected void handleOpenInternal(String path) throws EditorException { + // All this logic should be happening back in Base, not here. + // Presumably it lived here so that other Modes could override the + // behavior, but that changes with 4.0 beta 6. [fry 220206] + + /* // check to make sure that this .pde file is // in a folder of the same name final File file = new File(path); @@ -2330,6 +2257,7 @@ public abstract class Editor extends JFrame implements RunnerListener { throw new EditorException(); } } + */ try { sketch = new Sketch(path, this); @@ -2339,13 +2267,6 @@ public abstract class Editor extends JFrame implements RunnerListener { header.rebuild(); updateTitle(); - // Disable untitled setting from previous document, if any -// untitled = false; - - // Store information on who's open and running - // (in case there's a crash or something that can't be recovered) - // TODO this probably need not be here because of the Recent menu, right? - Preferences.save(); } @@ -2357,15 +2278,15 @@ public abstract class Editor extends JFrame implements RunnerListener { setTitle(sketch.getName() + " | Processing " + Base.getVersionName()); if (!sketch.isUntitled()) { - // set current file for OS X so that cmd-click in title bar works - File sketchFile = sketch.getMainFile(); - getRootPane().putClientProperty("Window.documentFile", sketchFile); + // Set current file for macOS so that cmd-click in title bar works. + // For 4.0 beta 6 changing this to the sketch folder, rather than the + // .pde for the main tab. (Otherwise, we should have it update when + // the tab changes, which seems like overkill for how this is used.) + getRootPane().putClientProperty("Window.documentFile", sketch.getFolder()); } else { // per other applications, don't set this until the file has been saved getRootPane().putClientProperty("Window.documentFile", null); } - -// toolbar.setText(sketch.getName()); } @@ -2376,10 +2297,12 @@ public abstract class Editor extends JFrame implements RunnerListener { * save is happening. If 'immediately' is true, then it will happen * immediately. This is used during a quit, because invokeLater() * won't run properly while a quit is happening. This fixes - * Bug 276. + * Bug 276. */ public boolean handleSave(boolean immediately) { -// handleStop(); // 0136 + // This was a mistake (rectified in 0136) that would cause long-running + // sketches to be interrupted, causing much sadness. + //handleStop(); if (sketch.isUntitled()) { return handleSaveAs(); diff --git a/app/src/processing/app/ui/EditorButton.java b/app/src/processing/app/ui/EditorButton.java index 97080b571..6a7fda0ec 100644 --- a/app/src/processing/app/ui/EditorButton.java +++ b/app/src/processing/app/ui/EditorButton.java @@ -27,11 +27,20 @@ import java.awt.event.*; import javax.swing.*; import processing.app.Mode; +import processing.data.StringDict; abstract public class EditorButton extends JComponent implements MouseListener, MouseMotionListener, ActionListener { - static public final int DIM = Toolkit.zoom(30); + static public final int DIM = Toolkit.zoom(36); + + /** + * The lowercase short name/path used to load its SVG/PNG image data. + * This will usually be something like /lib/toolbar/run (to pull in + * an icon from the Processing /lib folder) or if it's a relative path, + * it will be sourced from the mode folder. + */ + protected String name; /** Button's description. */ protected String title; @@ -69,18 +78,43 @@ implements MouseListener, MouseMotionListener, ActionListener { public EditorButton(EditorToolbar parent, String name, String title, String titleShift, String titleAlt) { + this.name = name; this.toolbar = parent; this.title = title; this.titleShift = titleShift; this.titleAlt = titleAlt; - Mode mode = toolbar.mode; + updateTheme(); - disabledImage = mode.loadImageX(name + "-disabled"); - enabledImage = mode.loadImageX(name + "-enabled"); - selectedImage = mode.loadImageX(name + "-selected"); - pressedImage = mode.loadImageX(name + "-pressed"); - rolloverImage = mode.loadImageX(name + "-rollover"); + addMouseListener(this); + addMouseMotionListener(this); + } + + + protected Image renderImage(String state) { + Mode mode = toolbar.mode; + String xmlOrig = mode.loadString(name + ".svg"); + + // If no SVG available, load image data from PNG files + if (xmlOrig == null) { + return mode.loadImageX(name + "-" + state); + } + + StringDict replacements = new StringDict(new String[][] { + { "#fff", Theme.get("toolbar.button." + state + ".field") }, + { "#ff5757", Theme.get("toolbar.button." + state + ".glyph") }, + { "silver", Theme.get("toolbar.button." + state + ".stroke") } + }); + return Toolkit.svgToImageMult(xmlOrig, DIM, DIM, replacements); + } + + + public void updateTheme() { + disabledImage = renderImage("disabled"); + enabledImage = renderImage("enabled"); + selectedImage = renderImage("selected"); + pressedImage = renderImage("pressed"); + rolloverImage = renderImage("rollover"); if (disabledImage == null) { disabledImage = enabledImage; @@ -94,8 +128,6 @@ implements MouseListener, MouseMotionListener, ActionListener { if (rolloverImage == null) { rolloverImage = enabledImage; // could be pressed image } - addMouseListener(this); - addMouseMotionListener(this); } @@ -191,12 +223,16 @@ implements MouseListener, MouseMotionListener, ActionListener { @Override public void mouseEntered(MouseEvent e) { toolbar.setRollover(this, e); + rollover = true; + repaint(); } @Override public void mouseExited(MouseEvent e) { toolbar.setRollover(null, e); + rollover = false; + repaint(); } diff --git a/app/src/processing/app/ui/EditorConsole.java b/app/src/processing/app/ui/EditorConsole.java index 66a211e19..14be32d68 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-21 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -38,16 +38,16 @@ import javax.swing.text.*; import processing.app.Console; import processing.app.Preferences; +import processing.app.laf.PdeScrollBarUI; /** * Message console that sits below the editing area. */ public class EditorConsole extends JScrollPane { + static final Color TRANSPARENT = new Color(0, 0, 0, 0); + Editor editor; - - Timer flushTimer; - JTextPane consoleTextPane; BufferedStyledDocument consoleDoc; @@ -62,6 +62,8 @@ public class EditorConsole extends JScrollPane { static EditorConsole current; + Timer flushTimer; + public EditorConsole(Editor editor) { this.editor = editor; @@ -86,9 +88,7 @@ public class EditorConsole extends JScrollPane { protected void flush() { // only if new text has been added - if (consoleDoc.hasAppendage()) { - // insert the text that's been added in the meantime - consoleDoc.insertAll(); + if (consoleDoc.insertQueued()) { // always move to the end of the text as it's added consoleTextPane.setCaretPosition(consoleDoc.getLength()); } @@ -130,6 +130,20 @@ public class EditorConsole extends JScrollPane { protected void updateTheme() { + JScrollBar vertical = getVerticalScrollBar(); + if (vertical.getUI() instanceof PdeScrollBarUI) { + ((PdeScrollBarUI) vertical.getUI()).updateTheme(); + } else { + vertical.setUI(new PdeScrollBarUI("console.scrollbar")); + } + + JScrollBar horizontal = getHorizontalScrollBar(); + if (horizontal.getUI() instanceof PdeScrollBarUI) { + ((PdeScrollBarUI) horizontal.getUI()).updateTheme(); + } else { + horizontal.setUI(new PdeScrollBarUI("console.scrollbar")); + } + // necessary? MutableAttributeSet standard = new SimpleAttributeSet(); StyleConstants.setAlignment(standard, StyleConstants.ALIGN_LEFT); @@ -148,7 +162,10 @@ public class EditorConsole extends JScrollPane { stdStyle = new SimpleAttributeSet(); StyleConstants.setForeground(stdStyle, fgColorOut); - StyleConstants.setBackground(stdStyle, bgColor); + // Changed to TRANSPARENT because it causes trouble when changing + // the theme color. But it looks like it's not necessary to set it + // anyway, so removing the setBackground() call entirely. + //StyleConstants.setBackground(stdStyle, TRANSPARENT); //bgColor); StyleConstants.setFontSize(stdStyle, font.getSize()); StyleConstants.setFontFamily(stdStyle, font.getFamily()); StyleConstants.setBold(stdStyle, font.isBold()); @@ -156,17 +173,17 @@ public class EditorConsole extends JScrollPane { errStyle = new SimpleAttributeSet(); StyleConstants.setForeground(errStyle, fgColorErr); - StyleConstants.setBackground(errStyle, bgColor); + //StyleConstants.setBackground(stdStyle, TRANSPARENT); //bgColor); StyleConstants.setFontSize(errStyle, font.getSize()); StyleConstants.setFontFamily(errStyle, font.getFamily()); StyleConstants.setBold(errStyle, font.isBold()); StyleConstants.setItalic(errStyle, font.isItalic()); String lookAndFeel = UIManager.getLookAndFeel().getID(); - if (lookAndFeel.equals("Nimbus") || lookAndFeel.equals("VAqua")) { + if (lookAndFeel.equals("Nimbus")) { getViewport().setBackground(bgColor); consoleTextPane.setOpaque(false); - consoleTextPane.setBackground(new Color(0, 0, 0, 0)); + consoleTextPane.setBackground(TRANSPARENT); } else { consoleTextPane.setBackground(bgColor); } @@ -232,6 +249,11 @@ public class EditorConsole extends JScrollPane { } else if (what.contains("XInitThreads() called for concurrent")) { // "Info: XInitThreads() called for concurrent Thread support" message on Linux return true; + + } else if (what.contains("accessibilityHitTest")) { + // "java.lang.NoSuchMethodError: accessibilityHitTest" + // https://github.com/processing/processing4/issues/368 + return true; } } else { // !err if (what.contains("Listening for transport dt_socket at address")) { @@ -302,13 +324,10 @@ public class EditorConsole extends JScrollPane { * swing event thread, so they need to be synchronized */ class BufferedStyledDocument extends DefaultStyledDocument { - //List elements = new ArrayList<>(); LinkedBlockingQueue elements; -// AtomicInteger queuedLineCount = new AtomicInteger(); int maxLineLength, maxLineCount, maxCharCount; int currentLineLength = 0; boolean needLineBreak = false; -// boolean hasAppendage = false; final Object insertLock = new Object(); public BufferedStyledDocument(int maxLineLength, int maxLineCount, @@ -320,37 +339,41 @@ class BufferedStyledDocument extends DefaultStyledDocument { } // monitor this so that it's only updated when needed (otherwise console - // updates every 250 ms when an app isn't even running.. see bug 180) - public boolean hasAppendage() { - return elements.size() > 0; + // updates every 250 ms when an app isn't even running... see bug 180) + public boolean insertQueued() { + // insert the text that's been added in the meantime + if (elements.size() > 0) { + insertAll(); + return true; + } + return false; } /** buffer a string for insertion at the end of the DefaultStyledDocument */ public void appendString(String str, AttributeSet a) { -// hasAppendage = true; + synchronized (insertLock) { + // process each line of the string + while (str.length() > 0) { + // newlines within an element have (almost) no effect, so we need to + // replace them with proper paragraph breaks (start and end tags) + if (needLineBreak || currentLineLength > maxLineLength) { + elements.add(new ElementSpec(a, ElementSpec.EndTagType)); + elements.add(new ElementSpec(a, ElementSpec.StartTagType)); + currentLineLength = 0; + } - // process each line of the string - while (str.length() > 0) { - // newlines within an element have (almost) no effect, so we need to - // replace them with proper paragraph breaks (start and end tags) - if (needLineBreak || currentLineLength > maxLineLength) { - elements.add(new ElementSpec(a, ElementSpec.EndTagType)); - elements.add(new ElementSpec(a, ElementSpec.StartTagType)); -// queuedLineCount.incrementAndGet(); - currentLineLength = 0; - } - - if (str.indexOf('\n') == -1) { - elements.add(new ElementSpec(a, ElementSpec.ContentType, - str.toCharArray(), 0, str.length())); - currentLineLength += str.length(); - needLineBreak = false; - str = str.substring(str.length()); // eat the string - } else { - elements.add(new ElementSpec(a, ElementSpec.ContentType, - str.toCharArray(), 0, str.indexOf('\n') + 1)); - needLineBreak = true; - str = str.substring(str.indexOf('\n') + 1); // eat the line + if (str.indexOf('\n') == -1) { + elements.add(new ElementSpec(a, ElementSpec.ContentType, + str.toCharArray(), 0, str.length())); + currentLineLength += str.length(); + needLineBreak = false; + str = ""; // reset the string + } else { + elements.add(new ElementSpec(a, ElementSpec.ContentType, + str.toCharArray(), 0, str.indexOf('\n') + 1)); + needLineBreak = true; + str = str.substring(str.indexOf('\n') + 1); // eat the line + } } } if (elements.size() > 1000) { @@ -360,21 +383,20 @@ class BufferedStyledDocument extends DefaultStyledDocument { /** insert the buffered strings */ public void insertAll() { - ElementSpec[] elementArray = elements.toArray(new ElementSpec[0]); + synchronized (insertLock) { + ElementSpec[] elementArray = elements.toArray(new ElementSpec[0]); - try { - synchronized (insertLock) { + try { checkLength(); insert(getLength(), elementArray); checkLength(); - } - } catch (BadLocationException e) { - // ignore the error otherwise this will cause an infinite loop - // maybe not a good idea in the long run? + } catch (BadLocationException e) { + // ignore the error otherwise this will cause an infinite loop + // maybe not a good idea in the long run? + } + elements.clear(); } - elements.clear(); -// hasAppendage = false; } private void checkLength() throws BadLocationException { diff --git a/app/src/processing/app/ui/EditorFooter.java b/app/src/processing/app/ui/EditorFooter.java index 2effe5ac6..855e97d38 100644 --- a/app/src/processing/app/ui/EditorFooter.java +++ b/app/src/processing/app/ui/EditorFooter.java @@ -42,6 +42,7 @@ import javax.swing.*; import processing.app.Mode; import processing.app.Sketch; import processing.app.contrib.ContributionManager; +import processing.data.StringDict; /** @@ -54,27 +55,24 @@ public class EditorFooter extends Box { static final int CURVE_RADIUS = Toolkit.zoom(6); - static final int TAB_TOP = Toolkit.zoom(2); - static final int TAB_BOTTOM = Toolkit.zoom(29); + static final int TAB_TOP = Toolkit.zoom(0); + static final int TAB_BOTTOM = Toolkit.zoom(26); // amount of extra space between individual tabs static final int TAB_BETWEEN = Toolkit.zoom(2); // amount of margin on the left/right for the text on the tab - static final int MARGIN = Toolkit.zoom(14); + static final int MARGIN = Toolkit.zoom(8); - static final int ICON_WIDTH = Toolkit.zoom(16); - static final int ICON_HEIGHT = Toolkit.zoom(16); - static final int ICON_TOP = Toolkit.zoom(7); - static final int ICON_MARGIN = Toolkit.zoom(7); + static final int ICON_WIDTH = Toolkit.zoom(14); + static final int ICON_HEIGHT = Toolkit.zoom(14); + static final int ICON_TOP = Toolkit.zoom(5); + static final int ICON_SIDE = Toolkit.zoom(7); - static final int UNSELECTED = 0; + static final int ENABLED = 0; static final int SELECTED = 1; Color[] textColor = new Color[2]; Color[] tabColor = new Color[2]; - Color updateColor; - int updateLeft; - Editor editor; List tabs = new ArrayList<>(); @@ -82,10 +80,6 @@ public class EditorFooter extends Box { Font font; int fontAscent; - Image offscreen; - int sizeW, sizeH; - int imageW, imageH; - Image gradient; Color bgColor; @@ -100,14 +94,14 @@ public class EditorFooter extends Box { super(BoxLayout.Y_AXIS); this.editor = eddie; - updateTheme(); - cardLayout = new CardLayout(); cardPanel = new JPanel(cardLayout); add(cardPanel); controller = new Controller(); add(controller); + + updateTheme(); } @@ -137,7 +131,7 @@ public class EditorFooter extends Box { public void setPanel(Component comp) { for (Tab tab : tabs) { if (tab.comp == comp) { - cardLayout.show(cardPanel, tab.name); + cardLayout.show(cardPanel, tab.title); repaint(); } } @@ -162,13 +156,11 @@ public class EditorFooter extends Box { public void updateTheme() { textColor[SELECTED] = Theme.getColor("footer.text.selected.color"); - textColor[UNSELECTED] = Theme.getColor("footer.text.unselected.color"); + textColor[ENABLED] = Theme.getColor("footer.text.enabled.color"); font = Theme.getFont("footer.text.font"); tabColor[SELECTED] = Theme.getColor("footer.tab.selected.color"); - tabColor[UNSELECTED] = Theme.getColor("footer.tab.unselected.color"); - - updateColor = Theme.getColor("footer.updates.color"); + tabColor[ENABLED] = Theme.getColor("footer.tab.enabled.color"); gradient = Theme.makeGradient("footer", 400, HIGH); // Set the default background color in case the window size reported @@ -176,6 +168,13 @@ public class EditorFooter extends Box { // https://github.com/processing/processing/issues/3919 bgColor = Theme.getColor("footer.gradient.bottom"); setBackground(bgColor); + + for (Tab tab : tabs) { + tab.updateTheme(); + } + + // replace colors for the "updates" indicator + controller.updateTheme(); } @@ -183,6 +182,10 @@ public class EditorFooter extends Box { class Controller extends JComponent { + Color updatesTextColor; + Color indicatorFieldColor; + Color indicatorTextColor; + int updateLeft; Controller() { addMouseListener(new MouseAdapter() { @@ -191,7 +194,7 @@ public class EditorFooter extends Box { for (Tab tab : tabs) { if (tab.contains(x)) { //editor.setFooterPanel(tab.index); - cardLayout.show(cardPanel, tab.name); + cardLayout.show(cardPanel, tab.title); repaint(); } } @@ -202,35 +205,18 @@ public class EditorFooter extends Box { }); } - public void paintComponent(Graphics screen) { - if (screen == null) return; + void updateTheme() { + updatesTextColor = Theme.getColor("footer.updates.text.color"); + indicatorFieldColor = Theme.getColor("footer.updates.indicator.field.color"); + indicatorTextColor = Theme.getColor("footer.updates.indicator.text.color"); + repaint(); + } + + public void paintComponent(Graphics g) { + if (g == null) return; Sketch sketch = editor.getSketch(); if (sketch == null) return; // possible? - Dimension size = getSize(); - if ((size.width != sizeW) || (size.height != sizeH)) { - // component has been resized - - if ((size.width > imageW) || (size.height > imageH)) { - // nix the image and recreate, it's too small - offscreen = null; - - } else { - // if the image is larger than necessary, no need to change - sizeW = size.width; - sizeH = size.height; - } - } - - if (offscreen == null) { - sizeW = size.width; - sizeH = size.height; - imageW = sizeW; - imageH = sizeH; - offscreen = Toolkit.offscreenGraphics(this, imageW, imageH); - } - - Graphics g = offscreen.getGraphics(); g.setFont(font); // need to set this each time through if (fontAscent == 0) { fontAscent = (int) Toolkit.getAscent(g); @@ -240,31 +226,28 @@ public class EditorFooter extends Box { g.setColor(tabColor[SELECTED]); // can't be done with lines, b/c retina leaves tiny hairlines - g.fillRect(0, 0, imageW, Toolkit.zoom(2)); + //g.fillRect(0, 0, getWidth(), Toolkit.zoom(2)); - g.drawImage(gradient, 0, Toolkit.zoom(2), imageW, imageH, this); + g.drawImage(gradient, 0, 0, getWidth(), getHeight(), this); // reset all tab positions for (Tab tab : tabs) { tab.textWidth = (int) - font.getStringBounds(tab.name, g2.getFontRenderContext()).getWidth(); + font.getStringBounds(tab.title, g2.getFontRenderContext()).getWidth(); } // now actually draw the tabs - drawTabs(Editor.LEFT_GUTTER, g2); + drawTabs(g2, Editor.LEFT_GUTTER); // the number of updates available in the Manager drawUpdates(g2); - - screen.drawImage(offscreen, 0, 0, imageW, imageH, null); } - /** * @param left starting position from the left * @param g graphics context, or null if we're not drawing */ - private void drawTabs(int left, Graphics2D g) { + private void drawTabs(Graphics2D g, int left) { int x = left; for (Tab tab : tabs) { @@ -281,7 +264,6 @@ public class EditorFooter extends Box { } } - private void drawUpdates(Graphics2D g2) { if (updateCount != 0) { FontRenderContext frc = g2.getFontRenderContext(); @@ -299,29 +281,26 @@ public class EditorFooter extends Box { float diameter = (float) (2 * (Math.max(countHeight, countWidth)/2 + CIRCULAR_PADDING)); float ex = getWidth() - Editor.RIGHT_GUTTER - diameter; float ey = (getHeight() - diameter) / 2; - g2.setColor(updateColor); + g2.setColor(indicatorFieldColor); g2.fill(new Ellipse2D.Float(ex, ey, diameter, diameter)); - g2.setColor(textColor[SELECTED]); + g2.setColor(indicatorTextColor); int baseline = (getHeight() + fontAscent) / 2; g2.drawString(updatesStr, (int) (ex + (diameter - countWidth)/2), baseline); double updatesWidth = font.getStringBounds(updateLabel, frc).getWidth(); - g2.setColor(textColor[UNSELECTED]); + g2.setColor(updatesTextColor); updateLeft = (int) (ex - updatesWidth - GAP); g2.drawString(updateLabel, updateLeft, baseline); } } - public Dimension getPreferredSize() { return new Dimension(Toolkit.zoom(300), HIGH); } - public Dimension getMinimumSize() { return getPreferredSize(); } - public Dimension getMaximumSize() { return new Dimension(super.getMaximumSize().width, HIGH); } @@ -332,7 +311,8 @@ public class EditorFooter extends Box { class Tab { - String name; + String title; + String icon; Component comp; boolean notification; @@ -343,20 +323,49 @@ public class EditorFooter extends Box { int right; int textWidth; - Tab(Component comp, String name, String icon) { + Tab(Component comp, String title, String icon) { this.comp = comp; - this.name = name; + this.title = title; + this.icon = icon; + updateTheme(); + } + + protected void updateTheme() { if (icon != null) { - Mode mode = editor.getMode(); - enabledIcon = mode.loadImageX(icon + "-enabled"); - selectedIcon = mode.loadImageX(icon + "-selected"); + enabledIcon = renderImage("enabled"); + selectedIcon = renderImage("selected"); if (selectedIcon == null) { - selectedIcon = enabledIcon; // use this as the default + selectedIcon = enabledIcon; // fallback } } } + protected Image renderImage(String state) { + Mode mode = editor.getMode(); + String xmlOrig = mode.loadString(icon + ".svg"); + + if (xmlOrig == null) { + // load image data from PNG files + return mode.loadImageX(icon + "-" + state); + } + + /* + final String ICON_COLOR = "silver"; + String iconColor = Theme.get("footer.icon." + state + ".color"); + + String xmlStr = xmlOrig.replace(ICON_COLOR, iconColor); + + final int m = Toolkit.highResMultiplier(); + return Toolkit.svgToImage(xmlStr, ICON_WIDTH * m, ICON_HEIGHT * m); + */ + StringDict replacements = new StringDict(new String[][] { + { "silver", Theme.get("footer.icon." + state + ".color") } + }); + return Toolkit.svgToImageMult(xmlOrig, ICON_WIDTH, ICON_HEIGHT, replacements); + + } + boolean contains(int x) { return x >= left && x <= right; } @@ -365,6 +374,7 @@ public class EditorFooter extends Box { return comp.isVisible(); } + /* boolean isFirst() { return tabs.get(0) == this; } @@ -372,11 +382,12 @@ public class EditorFooter extends Box { boolean isLast() { return tabs.get(tabs.size() - 1) == this; } + */ int getTextLeft() { int links = left; if (enabledIcon != null) { - links += ICON_WIDTH + ICON_MARGIN; + links += ICON_WIDTH + ICON_SIDE; } return links + ((right - links) - textWidth) / 2; } @@ -385,32 +396,28 @@ public class EditorFooter extends Box { return enabledIcon != null; } - void draw(Graphics g) { - int state = isCurrent() ? SELECTED : UNSELECTED; - g.setColor(tabColor[state]); -// if (notification) { -// g.setColor(errorColor); -// } - - Graphics2D g2 = (Graphics2D) g; + void draw(Graphics2D g2) { + int state = isCurrent() ? SELECTED : ENABLED; + g2.setColor(tabColor[state]); g2.fill(Toolkit.createRoundRect(left, TAB_TOP, right, TAB_BOTTOM, 0, 0, - isLast() ? CURVE_RADIUS : 0, - isFirst() ? CURVE_RADIUS : 0)); + CURVE_RADIUS, CURVE_RADIUS)); +// isLast() ? CURVE_RADIUS : 0, +// isFirst() ? CURVE_RADIUS : 0)); if (hasIcon()) { Image icon = (isCurrent() || notification) ? selectedIcon : enabledIcon; - g.drawImage(icon, left + MARGIN, ICON_TOP, ICON_WIDTH, ICON_HEIGHT, null); + g2.drawImage(icon, left + MARGIN, ICON_TOP, ICON_WIDTH, ICON_HEIGHT, null); } int textLeft = getTextLeft(); - if (notification && state == UNSELECTED) { - g.setColor(textColor[SELECTED]); + if (notification && state == ENABLED) { + g2.setColor(textColor[SELECTED]); } else { - g.setColor(textColor[state]); + g2.setColor(textColor[state]); } int tabHeight = TAB_BOTTOM - TAB_TOP; int baseline = TAB_TOP + (tabHeight + fontAscent) / 2; - g.drawString(name, textLeft, baseline); + g2.drawString(title, textLeft, baseline); } } } diff --git a/app/src/processing/app/ui/EditorHeader.java b/app/src/processing/app/ui/EditorHeader.java index e751998c0..453d35fd0 100644 --- a/app/src/processing/app/ui/EditorHeader.java +++ b/app/src/processing/app/ui/EditorHeader.java @@ -31,11 +31,7 @@ import java.util.Arrays; import javax.swing.*; -import processing.app.Language; -import processing.app.Messages; -import processing.app.Platform; -import processing.app.Sketch; -import processing.app.SketchCode; +import processing.app.*; /** @@ -43,21 +39,24 @@ import processing.app.SketchCode; */ public class EditorHeader extends JComponent { // height of this tab bar - static final int HIGH = Toolkit.zoom(29); + static final int HIGH = Toolkit.zoom(31); static final int ARROW_TAB_WIDTH = Toolkit.zoom(18); - static final int ARROW_TOP = Toolkit.zoom(11); + static final int ARROW_TOP = Toolkit.zoom(12); static final int ARROW_BOTTOM = Toolkit.zoom(18); static final int ARROW_WIDTH = Toolkit.zoom(6); static final int CURVE_RADIUS = Toolkit.zoom(6); static final int TAB_TOP = 0; - static final int TAB_BOTTOM = Toolkit.zoom(27); // amount of extra space between individual tabs - static final int TAB_BETWEEN = Toolkit.zoom(3); + static final int TAB_BETWEEN = Toolkit.zoom(2); + // space between tab and editor + static final int TAB_BELOW = TAB_BETWEEN; + // bottom position as determined by TAB_BELOW gap + static final int TAB_BOTTOM = HIGH - TAB_BELOW; // amount of margin on the left/right for the text on the tab - static final int TEXT_MARGIN = Toolkit.zoom(16); + static final int TEXT_MARGIN = Toolkit.zoom(13); // width of the tab when no text visible // (total tab width will be this plus TEXT_MARGIN*2) static final int NO_TEXT_WIDTH = Toolkit.zoom(16); @@ -84,10 +83,6 @@ public class EditorHeader extends JComponent { static final int UNSELECTED = 0; static final int SELECTED = 1; - Image offscreen; - int sizeW, sizeH; - int imageW, imageH; - String lastNoticeName; Image gradient; @@ -156,44 +151,34 @@ public class EditorHeader extends JComponent { } - public void paintComponent(Graphics screen) { - if (screen == null) return; + public void paintComponent(Graphics g) { + if (g == null) return; Sketch sketch = editor.getSketch(); - if (sketch == null) return; // possible? + if (sketch == null) return; // is this even possible? - Dimension size = getSize(); - if ((size.width != sizeW) || (size.height != sizeH)) { - // component has been resized - - if ((size.width > imageW) || (size.height > imageH)) { - // nix the image and recreate, it's too small - offscreen = null; - - } else { - // if the image is larger than necessary, no need to change - sizeW = size.width; - sizeH = size.height; - } - } - - if (offscreen == null) { - sizeW = size.width; - sizeH = size.height; - imageW = sizeW; - imageH = sizeH; - offscreen = Toolkit.offscreenGraphics(this, imageW, imageH); - } - - Graphics g = offscreen.getGraphics(); g.setFont(font); // need to set this each time through if (fontAscent == 0) { fontAscent = (int) Toolkit.getAscent(g); } - Graphics2D g2 = Toolkit.prepareGraphics(g); -// Toolkit.dpiStroke(g2); + Graphics2D g2 = Toolkit.prepareGraphics(g, false); - g.drawImage(gradient, 0, 0, imageW, imageH, this); + /* + Graphics2D g2 = (Graphics2D) g; + + if (!Toolkit.isRetina()) { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); +// g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, +// RenderingHints.VALUE_FRACTIONALMETRICS_ON); + } + */ + + g.drawImage(gradient, 0, 0, getWidth(), getHeight(), this); if (tabs.length != sketch.getCodeCount()) { tabs = new Tab[sketch.getCodeCount()]; @@ -212,14 +197,14 @@ public class EditorHeader extends JComponent { tab.textVisible = true; tab.lastVisited = code.lastVisited(); + tab.text = code.getFileName(); // hide extensions for .pde files - boolean hide = editor.getMode().hideExtension(code.getExtension()); - tab.text = hide ? code.getPrettyName() : code.getFileName(); - - // if modified, add the li'l glyph next to the name -// if (code.isModified()) { -// tab.text += " \u00A7"; -// } + if (editor.getMode().hideExtension(code.getExtension())) { + tab.text = code.getPrettyName(); + if (Preferences.getBoolean("sketch.name.replace_underscore")) { + tab.text = tab.text.replace('_', ' '); + } + } tab.textWidth = (int) font.getStringBounds(tab.text, g2.getFontRenderContext()).getWidth(); @@ -254,16 +239,18 @@ public class EditorHeader extends JComponent { menuRight = menuLeft + ARROW_TAB_WIDTH; } + /* // draw the two pixel line that extends left/right below the tabs g.setColor(tabColor[SELECTED]); // can't be done with lines, b/c retina leaves tiny hairlines g.fillRect(Editor.LEFT_GUTTER, TAB_BOTTOM, editor.getTextArea().getWidth() - Editor.LEFT_GUTTER, Toolkit.zoom(2)); + */ // draw the tab for the menu g.setColor(tabColor[UNSELECTED]); - drawTab(g, menuLeft, menuRight, false, true); + drawTab(g, menuLeft, menuRight, false, true, false); // draw the arrow on the menu tab g.setColor(arrowColor); @@ -275,8 +262,6 @@ public class EditorHeader extends JComponent { trianglePath.lineTo((x1 + x2) / 2, ARROW_BOTTOM); trianglePath.closePath(); g2.fill(trianglePath); - - screen.drawImage(offscreen, 0, 0, imageW, imageH, null); } @@ -284,116 +269,57 @@ public class EditorHeader extends JComponent { Sketch sketch = editor.getSketch(); int x = left; -// final int bottom = getHeight(); // - TAB_STRETCH; -// final int top = bottom - TAB_HEIGHT; -// GeneralPath path = null; - for (int i = 0; i < sketch.getCodeCount(); i++) { SketchCode code = sketch.getCode(i); Tab tab = tabs[i]; -// int pieceCount = 2 + (tab.textWidth / PIECE_WIDTH); -// if (tab.textVisible == false) { -// pieceCount = 4; -// } -// int pieceWidth = pieceCount * PIECE_WIDTH; - int state = (code == sketch.getCurrentCode()) ? SELECTED : UNSELECTED; -// if (g != null) { -// //g.drawImage(pieces[state][LEFT], x, 0, PIECE_WIDTH, PIECE_HEIGHT, null); -// path = new GeneralPath(); -// path.moveTo(x, bottom); -// path.lineTo(x, top + NOTCH); -// path.lineTo(x + NOTCH, top); -// } tab.left = x; x += TEXT_MARGIN; -// x += PIECE_WIDTH; -// int contentLeft = x; -// for (int j = 0; j < pieceCount; j++) { -// if (g != null) { -// g.drawImage(pieces[state][MIDDLE], x, 0, PIECE_WIDTH, PIECE_HEIGHT, null); -// } -// x += PIECE_WIDTH; -// } -// if (g != null) { int drawWidth = tab.textVisible ? tab.textWidth : NO_TEXT_WIDTH; x += drawWidth + TEXT_MARGIN; -// path.moveTo(x, top); -// } tab.right = x; if (g != null && tab.right < right) { g.setColor(tabColor[state]); - drawTab(g, tab.left, tab.right, i == 0, false); -// path.lineTo(x - NOTCH, top); -// path.lineTo(x, top + NOTCH); -// path.lineTo(x, bottom); -// path.closePath(); -// g.setColor(tabColor[state]); -// g.fill(path); -// // have to draw an extra outline to make things line up on retina -// g.draw(path); -// //g.drawImage(pieces[state][RIGHT], x, 0, PIECE_WIDTH, PIECE_HEIGHT, null); + drawTab(g, tab.left, tab.right, i == 0, false, state == SELECTED); if (tab.textVisible) { int textLeft = tab.left + ((tab.right - tab.left) - tab.textWidth) / 2; g.setColor(textColor[state]); -// int baseline = (int) Math.ceil((sizeH + fontAscent) / 2.0); - //int baseline = bottom - (TAB_HEIGHT - fontAscent)/2; int tabHeight = TAB_BOTTOM - TAB_TOP; - int baseline = TAB_TOP + (tabHeight + fontAscent) / 2; - //g.drawString(sketch.code[i].name, textLeft, baseline); + int baseline = TAB_TOP + (tabHeight + fontAscent) / 2 + 1; g.drawString(tab.text, textLeft, baseline); -// g.drawLine(tab.left, baseline-fontAscent, tab.right, baseline-fontAscent); -// g.drawLine(tab.left, baseline, tab.right, baseline); } if (code.isModified()) { g.setColor(modifiedColor); - //g.drawLine(tab.left + NOTCH, top, tab.right - NOTCH, top); - //g.drawLine(tab.left + (i == 0 ? CURVE_RADIUS : 0), TAB_TOP, tab.right-1, TAB_TOP); - g.drawLine(tab.right, TAB_TOP, tab.right, TAB_BOTTOM); + int barTop = TAB_TOP; + int barWidth = Toolkit.zoom(1); + int barHeight = (TAB_BOTTOM - barTop) + ((state == SELECTED) ? TAB_BELOW : 0); + int barLeft = tab.right - barWidth; + g.fillRect(barLeft, barTop, + barWidth, + barHeight); } } - -// if (g != null) { -// g.drawImage(pieces[state][RIGHT], x, 0, PIECE_WIDTH, PIECE_HEIGHT, null); -// } -// x += PIECE_WIDTH - 1; // overlap by 1 pixel x += TAB_BETWEEN; } - - // removed 150130 -// // Draw this last because of half-pixel overlaps on retina displays -// if (g != null) { -// g.setColor(tabColor[SELECTED]); -// g.fillRect(0, bottom, getWidth(), TAB_STRETCH); -// } - return x <= right; } private void drawTab(Graphics g, int left, int right, - boolean leftNotch, boolean rightNotch) { -// final int bottom = getHeight(); // - TAB_STRETCH; -// final int top = bottom - TAB_HEIGHT; -// g.fillRect(left, top, right - left, bottom - top); - + boolean leftNotch, boolean rightNotch, + boolean selected) { Graphics2D g2 = (Graphics2D) g; + final int bottom = TAB_BOTTOM + (selected ? TAB_BELOW : 0); g2.fill(Toolkit.createRoundRect(left, TAB_TOP, - right, TAB_BOTTOM, + right, bottom, leftNotch ? CURVE_RADIUS : 0, rightNotch ? CURVE_RADIUS : 0, 0, 0)); - -// path.moveTo(left, TAB_BOTTOM); -// if (left == MARGIN_WIDTH) { // first tab on the left -// path.lineTo(left, TAB_TOP - CURVE_RADIUS); -// } - } diff --git a/app/src/processing/app/ui/EditorStatus.java b/app/src/processing/app/ui/EditorStatus.java index 301daeb6a..02a85f10e 100644 --- a/app/src/processing/app/ui/EditorStatus.java +++ b/app/src/processing/app/ui/EditorStatus.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-19 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -24,23 +24,22 @@ package processing.app.ui; +import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; -import java.awt.Image; +import java.awt.Graphics2D; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionAdapter; +import java.awt.event.*; +import javax.swing.*; import javax.swing.plaf.basic.BasicSplitPaneDivider; import javax.swing.plaf.basic.BasicSplitPaneUI; -import processing.app.Mode; import processing.app.Platform; import processing.app.Preferences; import processing.core.PApplet; @@ -51,16 +50,12 @@ import processing.core.PApplet; */ public class EditorStatus extends BasicSplitPaneDivider { static final int HIGH = Toolkit.zoom(28); + static final int ICON_SIZE = Toolkit.zoom(16); static final int LEFT_MARGIN = Editor.LEFT_GUTTER; static final int RIGHT_MARGIN = Toolkit.zoom(20); - Color urlColor; Color[] fgColor; Color[] bgColor; - Image[] bgImage; - - // for beta 1, had to shut off the images because they were out of date - static final boolean USE_IMAGES = false; @SuppressWarnings("hiding") static public final int ERROR = 1; @@ -69,57 +64,47 @@ public class EditorStatus extends BasicSplitPaneDivider { static public final int CURSOR_LINE_WARNING = 4; static public final int NOTICE = 0; - static final int YES = 1; - static final int NO = 2; - static final int CANCEL = 3; - static final int OK = 4; - Editor editor; int mode; String message = ""; + int messageRight; String url; - int rightEdge; - int mouseX; - - static final int ROLLOVER_NONE = 0; - static final int ROLLOVER_URL = 1; - static final int ROLLOVER_COLLAPSE = 2; - static final int ROLLOVER_CLIPBOARD = 3; - int rolloverState; + static final int NONE = 0; + static final int URL_ROLLOVER = 1; + static final int URL_PRESSED = 2; + static final int COLLAPSE_ROLLOVER = 3; + static final int COLLAPSE_PRESSED = 4; + static final int CLIPBOARD_ROLLOVER = 5; + static final int CLIPBOARD_PRESSED = 6; + int mouseState; Font font; FontMetrics metrics; int ascent; - // actual Clipboard character not available [fry 180326] - //static final String CLIPBOARD_GLYPH = "\uD83D\uDCCB"; - // other apps seem to use this one as a hack - static final String CLIPBOARD_GLYPH = "\u2398"; + boolean shiftDown; - // https://en.wikipedia.org/wiki/Geometric_Shapes -// static final String COLLAPSE_GLYPH = "\u25B3"; // large up -// static final String EXPAND_GLYPH = "\u25BD"; // large down -// static final String COLLAPSE_GLYPH = "\u25B5"; // small up (unavailable) -// static final String EXPAND_GLYPH = "\u25BF"; // small down (unavailable) - static final String COLLAPSE_GLYPH = "\u25C1"; // left - static final String EXPAND_GLYPH = "\u25B7"; // right -// static final String COLLAPSE_GLYPH = "\u25F8"; // upper-left (unavailable) -// static final String EXPAND_GLYPH = "\u25FF"; // lower-right (unavailable) + ImageIcon[] clipboardIcon; + ImageIcon[] searchIcon; + ImageIcon[] collapseIcon; + ImageIcon[] expandIcon; - // a font that supports the Unicode glyphs we need - Font glyphFont; + float btnEnabledAlpha; + float btnRolloverAlpha; + float btnPressedAlpha; + + int urlEnabledAlpha; + int urlRolloverAlpha; + int urlPressedAlpha; - Image offscreen; int sizeW, sizeH; // size of the glyph buttons (width and height are identical) - int buttonSize; + int buttonEach; boolean collapsed = false; - int response; - boolean indeterminate; Thread thread; @@ -134,15 +119,20 @@ public class EditorStatus extends BasicSplitPaneDivider { @Override public void mouseEntered(MouseEvent e) { - updateMouse(); + updateMouse(e, false); } @Override public void mousePressed(MouseEvent e) { - if (rolloverState == ROLLOVER_URL) { + updateMouse(e, true); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (mouseState == URL_PRESSED) { Platform.openURL(url); - } else if (rolloverState == ROLLOVER_CLIPBOARD) { + } else if (mouseState == CLIPBOARD_PRESSED) { if (e.isShiftDown()) { // open the text in a browser window as a search final String fmt = Preferences.get("search.format"); @@ -156,32 +146,50 @@ public class EditorStatus extends BasicSplitPaneDivider { "Use shift-click to search the web instead."); } - } else if (rolloverState == ROLLOVER_COLLAPSE) { + } else if (mouseState == COLLAPSE_PRESSED) { setCollapsed(!collapsed); } + updateMouse(e, false); // no longer pressed } @Override public void mouseExited(MouseEvent e) { - mouseX = -100; - updateMouse(); + updateMouse(null, false); } - }); - addMouseMotionListener(new MouseMotionAdapter() { + addMouseMotionListener(new MouseMotionListener() { @Override public void mouseDragged(MouseEvent e) { // BasicSplitPaneUI.startDragging gets called even when you click but // don't drag, so we can't expand the console whenever that gets called // or the button wouldn't work. setCollapsed(false); + + updateMouse(e, true); } @Override public void mouseMoved(MouseEvent e) { - mouseX = e.getX(); - updateMouse(); + updateMouse(e, false); + } + }); + + editor.getTextArea().addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (shiftDown != e.isShiftDown()) { + shiftDown = e.isShiftDown(); + repaint(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + if (shiftDown != e.isShiftDown()) { + shiftDown = e.isShiftDown(); + repaint(); + } } }); } @@ -196,16 +204,37 @@ public class EditorStatus extends BasicSplitPaneDivider { } - void updateMouse() { - switch (rolloverState) { - case ROLLOVER_CLIPBOARD: - case ROLLOVER_URL: + void updateMouse(MouseEvent e, boolean pressed) { + mouseState = NONE; + shiftDown = false; + + if (e != null) { + int mouseX = e.getX(); + shiftDown = e.isShiftDown(); + + if (mouseX > sizeW - buttonEach && mouseX < sizeW) { + mouseState = pressed ? COLLAPSE_PRESSED : COLLAPSE_ROLLOVER; + + } else if (message != null && !message.isEmpty()) { + if (sizeW - 2 * buttonEach < mouseX) { + mouseState = pressed ? CLIPBOARD_PRESSED : CLIPBOARD_ROLLOVER; + + } else if (url != null && mouseX > LEFT_MARGIN && mouseX < messageRight) { + mouseState = pressed ? URL_PRESSED : URL_ROLLOVER; + } + } + } + + // only change on the rollover, no need to update on press + switch (mouseState) { + case CLIPBOARD_ROLLOVER: + case URL_ROLLOVER: setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); break; - case ROLLOVER_COLLAPSE: + case COLLAPSE_ROLLOVER: setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); break; - case ROLLOVER_NONE: + case NONE: setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); break; } @@ -223,7 +252,26 @@ public class EditorStatus extends BasicSplitPaneDivider { protected void updateTheme() { - urlColor = Theme.getColor("status.url.fgcolor"); + urlEnabledAlpha = 255 * Theme.getInteger("status.url.enabled.alpha") / 100; + urlRolloverAlpha = 255 * Theme.getInteger("status.url.rollover.alpha") / 100; + urlPressedAlpha = 255 * Theme.getInteger("status.url.pressed.alpha") / 100; + + String[] stateColors = new String[] { + Theme.get("status.notice.fgcolor"), + Theme.get("status.error.fgcolor"), + Theme.get("status.error.fgcolor"), + Theme.get("status.warning.fgcolor"), + Theme.get("status.warning.fgcolor") + }; + + clipboardIcon = renderIcons("status/copy-to-clipboard", stateColors); + searchIcon = renderIcons("status/search", stateColors); + collapseIcon = renderIcons("status/console-collapse", stateColors); + expandIcon = renderIcons("status/console-expand", stateColors); + + btnEnabledAlpha = Theme.getInteger("status.button.enabled.alpha") / 100f; + btnRolloverAlpha = Theme.getInteger("status.button.rollover.alpha") / 100f; + btnPressedAlpha = Theme.getInteger("status.button.pressed.alpha") / 100f; fgColor = new Color[] { Theme.getColor("status.notice.fgcolor"), @@ -241,21 +289,20 @@ public class EditorStatus extends BasicSplitPaneDivider { Theme.getColor("status.warning.bgcolor") }; - Mode mode = editor.getMode(); - bgImage = new Image[] { - mode.loadImage("/lib/status/notice.png"), - mode.loadImage("/lib/status/error.png"), - mode.loadImage("/lib/status/error.png"), - mode.loadImage("/lib/status/warning.png"), - mode.loadImage("/lib/status/warning.png") - }; - font = Theme.getFont("status.font"); - glyphFont = Theme.getFont("status.emoji.font"); metrics = null; } + static private ImageIcon[] renderIcons(String path, String[] hexColors) { + int count = hexColors.length; + ImageIcon[] outgoing = new ImageIcon[count]; + for (int i = 0; i < count; i++) { + outgoing[i] = Toolkit.renderIcon(path, hexColors[i], ICON_SIZE); + } + return outgoing; + } + public void empty() { mode = NOTICE; message = ""; @@ -275,49 +322,31 @@ public class EditorStatus extends BasicSplitPaneDivider { public void notice(String message) { message(message, NOTICE); -// mode = NOTICE; -// this.message = message; -// url = findURL(message); -// repaint(); } -// public void unnotice(String unmessage) { -// if (message.equals(unmessage)) empty(); -// } - - public void warning(String message) { message(message, WARNING); -// this.message = message; -// mode = WARNING; -// url = findURL(message); -// repaint(); } public void error(String message) { message(message, ERROR); -// this.message = message; -// mode = ERROR; -// url = findURL(message); -// repaint(); } public void startIndeterminate() { indeterminate = true; - thread = new Thread() { + thread = new Thread("Editor Status") { public void run() { while (Thread.currentThread() == thread) { repaint(); try { Thread.sleep(1000 / 10); - } catch (InterruptedException e) { } + } catch (InterruptedException ignored) { } } } }; - thread.setName("Editor Status"); thread.start(); } @@ -329,23 +358,11 @@ public class EditorStatus extends BasicSplitPaneDivider { } - //public void paintComponent(Graphics screen) { - public void paint(Graphics screen) { - Dimension size = getSize(); - if ((size.width != sizeW) || (size.height != sizeH)) { - // component has been resized - offscreen = null; - } - - if (offscreen == null) { - sizeW = size.width; - sizeH = size.height; - buttonSize = sizeH; - offscreen = Toolkit.offscreenGraphics(this, sizeW, sizeH); - } - - Graphics g = offscreen.getGraphics(); - /*Graphics2D g2 =*/ Toolkit.prepareGraphics(g); + public void paint(Graphics g) { + Toolkit.prepareGraphics(g); + sizeW = getWidth(); + sizeH = getHeight(); + buttonEach = sizeH; g.setFont(font); if (metrics == null) { @@ -353,44 +370,41 @@ public class EditorStatus extends BasicSplitPaneDivider { ascent = metrics.getAscent(); } - if (USE_IMAGES) { - g.drawImage(bgImage[mode], 0, 0, sizeW, sizeH, this); - } else { - g.setColor(bgColor[mode]); - g.fillRect(0, 0, sizeW, sizeH); - } - - rolloverState = ROLLOVER_NONE; - if (mouseX > sizeW - buttonSize && mouseX < sizeW) { - rolloverState = ROLLOVER_COLLAPSE; - - } else if (message != null && !message.isEmpty()) { - if (sizeW - 2*buttonSize < mouseX) { - rolloverState = ROLLOVER_CLIPBOARD; - - } else if (url != null && mouseX > LEFT_MARGIN && - // calculate right edge of the text for rollovers (otherwise the pane - // cannot be resized up or down whenever a URL is being displayed) - mouseX < (LEFT_MARGIN + g.getFontMetrics().stringWidth(message))) { - rolloverState = ROLLOVER_URL; - } - } + g.setColor(bgColor[mode]); + g.fillRect(0, 0, sizeW, sizeH); + messageRight = LEFT_MARGIN; // needs to be reset (even) if msg null // https://github.com/processing/processing/issues/3265 if (message != null) { // font needs to be set each time on osx g.setFont(font); - // set the highlight color on rollover so that the user's not surprised - // to see the web browser open when they click - g.setColor((rolloverState == ROLLOVER_URL) ? urlColor : fgColor[mode]); - g.drawString(message, LEFT_MARGIN, (sizeH + ascent) / 2); + // calculate right edge of the text for rollovers (otherwise the pane + // cannot be resized up or down whenever a URL is being displayed) + messageRight += g.getFontMetrics().stringWidth(message); + + // set the highlight color on rollover so that the user is + // not surprised to see the web browser open when they click + int alpha = 255; + if (url != null) { + if (mouseState == URL_ROLLOVER) { + alpha = urlRolloverAlpha; + } else if (mouseState == URL_PRESSED) { + alpha = urlPressedAlpha; + } else { + alpha = urlEnabledAlpha; + } + } + if (alpha == 255) { + g.setColor(fgColor[mode]); + } else { + g.setColor(new Color((alpha << 24) | (fgColor[mode].getRGB() & 0xFFFFFF), true)); + } + g.drawString(message, LEFT_MARGIN, (sizeH / 2) + (ascent / 4) + 1); } if (indeterminate) { - //int x = cancelButton.getX(); - //int w = cancelButton.getWidth(); int w = Toolkit.getButtonWidth(); - int x = getWidth() - Math.max(RIGHT_MARGIN, (int)(buttonSize*1.2)) - w; + int x = getWidth() - Math.max(RIGHT_MARGIN, (int)(buttonEach * 1.2)) - w; int y = sizeH / 3; int h = sizeH / 3; g.setColor(new Color(0x80000000, true)); @@ -400,46 +414,58 @@ public class EditorStatus extends BasicSplitPaneDivider { g.drawLine(r, y, r, y+h); } - } else if (!message.isEmpty()) { - g.setFont(glyphFont); - drawButton(g, CLIPBOARD_GLYPH, 1, rolloverState == ROLLOVER_CLIPBOARD); + } else if (message != null && !message.isEmpty()) { + ImageIcon glyph; + float alpha; + if (shiftDown) { + glyph = searchIcon[mode]; + } else { + glyph = clipboardIcon[mode]; + } + if (mouseState == CLIPBOARD_ROLLOVER) { + alpha = btnRolloverAlpha; + } else if (mouseState == CLIPBOARD_PRESSED) { + alpha = btnPressedAlpha; + } else { + alpha = btnEnabledAlpha; + } + drawButton(g, 1, glyph, alpha); g.setFont(font); } // draw collapse/expand button - String collapseGlyph = collapsed ? EXPAND_GLYPH : COLLAPSE_GLYPH; - drawButton(g, collapseGlyph, 0, rolloverState == ROLLOVER_COLLAPSE); - - screen.drawImage(offscreen, 0, 0, sizeW, sizeH, null); + ImageIcon glyph; + float alpha; + if (collapsed) { + glyph = expandIcon[mode]; + } else { + glyph = collapseIcon[mode]; + } + if (mouseState == COLLAPSE_ROLLOVER) { + alpha = btnRolloverAlpha; + } else if (mouseState == COLLAPSE_PRESSED) { + alpha = btnPressedAlpha; + } else { + alpha = btnEnabledAlpha; + } + drawButton(g, 0, glyph, alpha); } - //private final Color whitishTint = new Color(0x20eeeeee, true); - /** * @param pos A zero-based button index with 0 as the rightmost button */ - private void drawButton(Graphics g, String symbol, int pos, boolean highlight) { - int left = sizeW - (pos + 1) * buttonSize; - // Overlap very long errors - if (USE_IMAGES) { - g.drawImage(bgImage[mode], left, 0, buttonSize, sizeH, this); - } else { - g.setColor(bgColor[mode]); - g.fillRect(left, 0, buttonSize, sizeH); - } + //private void drawButton(Graphics g, String symbol, int pos, boolean highlight) { + private void drawButton(Graphics g, int pos, ImageIcon icon, float alpha) { + int left = sizeW - (pos + 1) * buttonEach; - if (highlight) { - // disabling since this doesn't match any of our other UI -// g.setColor(whitishTint); -// g.fillRect(left, 0, sizeH, sizeH); - g.setColor(urlColor); - } else { - g.setColor(fgColor[mode]); - } - g.drawString(symbol, - left + (buttonSize - g.getFontMetrics().stringWidth(symbol))/2, - (sizeH + ascent) / 2); + Graphics2D g2 = (Graphics2D) g.create(); + g2.setComposite(AlphaComposite.SrcAtop.derive(alpha)); + //icon.paintIcon(c, g2, x, y); + icon.paintIcon(this, g2, + left + (buttonEach - icon.getIconWidth()) / 2, + (buttonEach - icon.getIconHeight()) / 2); + g2.dispose(); } diff --git a/app/src/processing/app/ui/EditorToolbar.java b/app/src/processing/app/ui/EditorToolbar.java index 35265cc30..f8e81cbf3 100644 --- a/app/src/processing/app/ui/EditorToolbar.java +++ b/app/src/processing/app/ui/EditorToolbar.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -23,14 +23,7 @@ package processing.app.ui; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Image; +import java.awt.*; import java.awt.event.*; import java.util.ArrayList; import java.util.Arrays; @@ -39,11 +32,9 @@ import java.util.List; import javax.swing.Box; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JPopupMenu; import processing.app.Base; import processing.app.Language; -import processing.app.Messages; import processing.app.Mode; @@ -53,11 +44,9 @@ import processing.app.Mode; abstract public class EditorToolbar extends JPanel implements KeyListener { // haven't decided how to handle this/how to make public/consistency // for components/does it live in theme.txt - static final int HIGH = Toolkit.zoom(53); + static final int HIGH = Toolkit.zoom(56); // horizontal gap between buttons static final int GAP = Toolkit.zoom(9); - // corner radius on the mode selector - static final int RADIUS = Toolkit.zoom(3); protected Editor editor; protected Base base; @@ -69,6 +58,8 @@ abstract public class EditorToolbar extends JPanel implements KeyListener { protected EditorButton rolloverButton; protected JLabel rolloverLabel; + protected ModeSelector modeSelector; + protected Box box; protected Image gradient; @@ -95,26 +86,14 @@ abstract public class EditorToolbar extends JPanel implements KeyListener { for (EditorButton button : buttons) { box.add(button); box.add(Box.createHorizontalStrut(GAP)); -// registerButton(button); } -// // remove the last gap -// box.remove(box.getComponentCount() - 1); -// box.add(Box.createHorizontalStrut(LABEL_GAP)); box.add(rolloverLabel); -// currentButton = runButton; - -// runButton.setRolloverLabel(label); -// stopButton.setRolloverLabel(label); box.add(Box.createHorizontalGlue()); addModeButtons(box, rolloverLabel); -// Component items = createModeButtons(); -// if (items != null) { -// box.add(items); -// } - ModeSelector ms = new ModeSelector(); - box.add(ms); + + box.add(modeSelector = new ModeSelector(editor)); box.add(Box.createHorizontalStrut(Editor.RIGHT_GUTTER)); setLayout(new BorderLayout()); @@ -129,25 +108,17 @@ abstract public class EditorToolbar extends JPanel implements KeyListener { rolloverLabel.setFont(Theme.getFont("toolbar.rollover.font")); rolloverLabel.setForeground(Theme.getColor("toolbar.rollover.color")); + + for (Component c : box.getComponents()) { + if (c instanceof EditorButton) { + ((EditorButton) c).updateTheme(); + } + } + modeSelector.updateTheme(); + repaint(); } -// public void registerButton(EditorButton button) { - //button.setRolloverLabel(rolloverLabel); - //editor.getTextArea().addKeyListener(button); -// } - - -// public void setReverse(EditorButton button) { -// button.setGradient(reverseGradient); -// } - - -// public void setText(String text) { -// label.setText(text); -// } - - public void paintComponent(Graphics g) { Dimension size = getSize(); g.drawImage(gradient, 0, 0, size.width, size.height, this); @@ -186,22 +157,6 @@ abstract public class EditorToolbar extends JPanel implements KeyListener { } -// public Component createModeSelector() { -// return new ModeSelector(); -// } - - -// protected void swapButton(EditorButton replacement) { -// if (currentButton != replacement) { -// box.remove(currentButton); -// box.add(replacement, 1); // has to go after the strut -// box.revalidate(); -// box.repaint(); // may be needed -// currentButton = replacement; -// } -// } - - public void activateRun() { runButton.setSelected(true); repaint(); @@ -234,19 +189,15 @@ abstract public class EditorToolbar extends JPanel implements KeyListener { void setRollover(EditorButton button, InputEvent e) { rolloverButton = button; -// if (rolloverButton != null) { updateRollover(e); -// } else { -// rolloverLabel.setText(""); -// } } void updateRollover(InputEvent e) { - if (rolloverButton != null) { - rolloverLabel.setText(rolloverButton.getRolloverText(e)); - } else { + if (rolloverButton == null) { rolloverLabel.setText(""); + } else { + rolloverLabel.setText(rolloverButton.getRolloverText(e)); } } @@ -280,123 +231,4 @@ abstract public class EditorToolbar extends JPanel implements KeyListener { public Dimension getMaximumSize() { return new Dimension(super.getMaximumSize().width, HIGH); } - - - class ModeSelector extends JPanel { - Image offscreen; - int width, height; - - String title; - Font titleFont; - Color titleColor; - int titleAscent; - int titleWidth; - - final int MODE_GAP_WIDTH = Toolkit.zoom(13); - final int ARROW_GAP_WIDTH = Toolkit.zoom(6); - final int ARROW_WIDTH = Toolkit.zoom(6); - final int ARROW_TOP = Toolkit.zoom(12); - final int ARROW_BOTTOM = Toolkit.zoom(18); - - int[] triangleX = new int[3]; - int[] triangleY = new int[] { ARROW_TOP, ARROW_TOP, ARROW_BOTTOM }; - -// Image background; - Color backgroundColor; - Color outlineColor; - - public ModeSelector() { - title = mode.getTitle(); //.toUpperCase(); - - addMouseListener(new MouseAdapter() { - public void mousePressed(MouseEvent event) { - JPopupMenu popup = editor.getModePopup(); - popup.show(ModeSelector.this, event.getX(), event.getY()); - } - }); - - updateTheme(); - } - - public void updateTheme() { - titleFont = Theme.getFont("mode.title.font"); - titleColor = Theme.getColor("mode.title.color"); - - // getGraphics() is null (even for editor) and no offscreen yet - //titleWidth = getToolkit().getFontMetrics(titleFont).stringWidth(title); - //titleWidth = editor.getGraphics().getFontMetrics(titleFont).stringWidth(title); - - backgroundColor = Theme.getColor("mode.background.color"); - outlineColor = Theme.getColor("mode.outline.color"); - } - - @Override - public void paintComponent(Graphics screen) { - Dimension size = getSize(); - width = 0; - if (width != size.width || height != size.height) { - offscreen = Toolkit.offscreenGraphics(this, size.width, size.height); - width = size.width; - height = size.height; - } - - Graphics g = offscreen.getGraphics(); - Graphics2D g2 = Toolkit.prepareGraphics(g); - - g.setFont(titleFont); - if (titleAscent == 0) { - titleAscent = (int) Toolkit.getAscent(g); //metrics.getAscent(); - } - FontMetrics metrics = g.getFontMetrics(); - titleWidth = metrics.stringWidth(title); - - // clear the background - g.setColor(backgroundColor); - g.fillRect(0, 0, width, height); - - // draw the outline for this feller - g.setColor(outlineColor); - //Toolkit.dpiStroke(g2); - g2.draw(Toolkit.createRoundRect(1, 1, width-1, height-1, - RADIUS, RADIUS, RADIUS, RADIUS)); - - g.setColor(titleColor); - g.drawString(title, MODE_GAP_WIDTH, (height + titleAscent) / 2); - - int x = MODE_GAP_WIDTH + titleWidth + ARROW_GAP_WIDTH; - triangleX[0] = x; - triangleX[1] = x + ARROW_WIDTH; - triangleX[2] = x + ARROW_WIDTH/2; - g.fillPolygon(triangleX, triangleY, 3); - - screen.drawImage(offscreen, 0, 0, width, height, this); - } - - @Override - public Dimension getPreferredSize() { - int tempWidth = titleWidth; // with any luck, this is set - if (tempWidth == 0) { - Graphics g = getGraphics(); - // the Graphics object may not be ready yet, being careful - if (g != null) { - tempWidth = getFontMetrics(titleFont).stringWidth(title); - } else { - Messages.err("null Graphics in EditorToolbar.getPreferredSize()"); - } - } - return new Dimension(MODE_GAP_WIDTH + tempWidth + - ARROW_GAP_WIDTH + ARROW_WIDTH + MODE_GAP_WIDTH, - EditorButton.DIM); - } - - @Override - public Dimension getMinimumSize() { - return getPreferredSize(); - } - - @Override - public Dimension getMaximumSize() { - return getPreferredSize(); - } - } } \ No newline at end of file diff --git a/app/src/processing/app/ui/ErrorTable.java b/app/src/processing/app/ui/ErrorTable.java index de729ab05..927ce7ac2 100644 --- a/app/src/processing/app/ui/ErrorTable.java +++ b/app/src/processing/app/ui/ErrorTable.java @@ -106,6 +106,7 @@ public class ErrorTable extends JTable { protected void updateTheme() { + setBackground(Theme.getColor("errors.bgcolor")); getTableHeader().setDefaultRenderer(new GradyHeaderRenderer()); setDefaultRenderer(Object.class, new GradyRowRenderer()); } diff --git a/app/src/processing/app/ui/ExamplesFrame.java b/app/src/processing/app/ui/ExamplesFrame.java index 42de2b4d0..a2b9cc6c5 100644 --- a/app/src/processing/app/ui/ExamplesFrame.java +++ b/app/src/processing/app/ui/ExamplesFrame.java @@ -25,11 +25,8 @@ package processing.app.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; -import java.awt.Cursor; -import java.awt.FlowLayout; +import java.awt.Container; import java.awt.Point; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; @@ -38,13 +35,12 @@ import java.io.File; import java.io.IOException; import java.util.Enumeration; -import javax.swing.BorderFactory; +import javax.swing.Box; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; -import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; @@ -65,6 +61,7 @@ import processing.app.contrib.Contribution; import processing.app.contrib.ContributionManager; import processing.app.contrib.ContributionType; import processing.app.contrib.ExamplesContribution; + import processing.core.PApplet; import processing.data.StringDict; @@ -85,32 +82,12 @@ public class ExamplesFrame extends JFrame { examplesContribFolder = Base.getSketchbookExamplesFolder(); Toolkit.setIcon(this); - Toolkit.registerWindowCloseKeys(getRootPane(), new ActionListener() { - public void actionPerformed(ActionEvent e) { - setVisible(false); - } - }); + Toolkit.registerWindowCloseKeys(getRootPane(), e -> setVisible(false)); JPanel examplesPanel = new JPanel(); examplesPanel.setLayout(new BorderLayout()); examplesPanel.setBackground(Color.WHITE); - final JPanel openExamplesManagerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - JButton addExamplesButton = new JButton(Language.text("examples.add_examples")); - openExamplesManagerPanel.add(addExamplesButton); - openExamplesManagerPanel.setOpaque(false); - Border lineBorder = BorderFactory.createMatteBorder(0, 0, 1, 0, Color.LIGHT_GRAY); - Border paddingBorder = BorderFactory.createEmptyBorder(3, 5, 1, 4); - openExamplesManagerPanel.setBorder(BorderFactory.createCompoundBorder(lineBorder, paddingBorder)); - openExamplesManagerPanel.setAlignmentX(Component.LEFT_ALIGNMENT); - openExamplesManagerPanel.setCursor(new Cursor(Cursor.HAND_CURSOR)); - addExamplesButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ContributionManager.openExamples(); - } - }); - final JTree tree = new JTree(buildTree()); tree.setOpaque(true); @@ -124,7 +101,7 @@ public class ExamplesFrame extends JFrame { tree.setRootVisible(false); // After 2.0a7, no longer expanding each of the categories at Casey's - // request. He felt that the window was too complicated too quickly. + // request. He felt that the window was too complicated, too quickly. // for (int row = tree.getRowCount()-1; row >= 0; --row) { // tree.expandRow(row); // } @@ -140,7 +117,7 @@ public class ExamplesFrame extends JFrame { //if (node != null && node.isLeaf() && node.getPath().equals(selPath)) { if (node != null && node.isLeaf() && selRow != -1) { SketchReference sketch = (SketchReference) node.getUserObject(); - base.handleOpen(sketch.getPath()); + base.handleOpen(sketch.getPath(), mode); } } } @@ -157,7 +134,7 @@ public class ExamplesFrame extends JFrame { (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if (node != null && node.isLeaf()) { SketchReference sketch = (SketchReference) node.getUserObject(); - base.handleOpen(sketch.getPath()); + base.handleOpen(sketch.getPath(), mode); } } } @@ -176,13 +153,9 @@ public class ExamplesFrame extends JFrame { }); tree.setBorder(new EmptyBorder(0, 5, 5, 5)); - if (Platform.isMacOS()) { - tree.setToggleClickCount(2); - } else { - tree.setToggleClickCount(1); - } + tree.setToggleClickCount(Platform.isMacOS() ? 2 : 1); - // Special cell renderer that takes the UI zoom into account + // Special cell renderer that takes the Toolkit zoom setting into account tree.setCellRenderer(new ZoomTreeCellRenderer()); JScrollPane treePane = new JScrollPane(tree); @@ -192,9 +165,20 @@ public class ExamplesFrame extends JFrame { treePane.setBackground(Color.WHITE); treePane.setAlignmentX(Component.LEFT_ALIGNMENT); - examplesPanel.add(openExamplesManagerPanel,BorderLayout.PAGE_START); + //examplesPanel.add(openExamplesManagerPanel,BorderLayout.PAGE_START); examplesPanel.add(treePane, BorderLayout.CENTER); + Container buttons = Box.createHorizontalBox(); + JButton addButton = new JButton(Language.text("examples.add_examples")); + addButton.addActionListener(e -> ContributionManager.openExamples()); + buttons.add(Box.createHorizontalGlue()); + buttons.add(addButton); + buttons.add(Box.createHorizontalGlue()); + + JPanel buttonPanel = new JPanel(); // adds extra border + buttonPanel.add(buttons); + examplesPanel.add(buttonPanel, BorderLayout.SOUTH); + getContentPane().add(examplesPanel); pack(); restoreExpanded(tree); @@ -204,29 +188,32 @@ public class ExamplesFrame extends JFrame { public void setVisible() { // Space for the editor plus a li'l gap int roughWidth = getWidth() + 20; - Point p = null; // If no window open, or the editor is at the edge of the screen Editor editor = base.getActiveEditor(); - if (editor == null || - (p = editor.getLocation()).x < roughWidth) { - // Center the window on the screen + if (editor == null) { setLocationRelativeTo(null); } else { - // Open the window relative to the editor - setLocation(p.x - roughWidth, p.y); + Point p = editor.getLocation(); + if (p.x < roughWidth) { + // Center the window on the screen + setLocationRelativeTo(null); + } else { + // Open the window relative to the editor + setLocation(p.x - roughWidth, p.y); + } } setVisible(true); } protected void updateExpanded(JTree tree) { - Enumeration en = tree.getExpandedDescendants(new TreePath(tree.getModel().getRoot())); + Enumeration en = + tree.getExpandedDescendants(new TreePath(tree.getModel().getRoot())); //en.nextElement(); // skip the root "Examples" node StringBuilder s = new StringBuilder(); while (en.hasMoreElements()) { - //System.out.println(en.nextElement()); - TreePath tp = (TreePath) en.nextElement(); + TreePath tp = en.nextElement(); Object[] path = tp.getPath(); for (Object o : path) { DefaultMutableTreeNode p = (DefaultMutableTreeNode) o; @@ -358,10 +345,10 @@ public class ExamplesFrame extends JFrame { new DefaultMutableTreeNode(Language.text("examples.contributed")); try { - File[] subfolders = + File[] folders = ContributionType.EXAMPLES.listCandidates(examplesContribFolder); - if (subfolders != null) { - for (File sub : subfolders) { + if (folders != null) { + for (File sub : folders) { StringDict props = Contribution.loadProperties(sub, ContributionType.EXAMPLES); if (props != null) { @@ -371,8 +358,8 @@ public class ExamplesFrame extends JFrame { if (base.addSketches(subNode, sub, true)) { contribExamplesNode.add(subNode); - // TODO there has to be a simpler way of handling this along - // with addSketches() as well [fry 150811] + // TODO there has to be a simpler way of handling this + // along with addSketches() as well [fry 150811] int exampleNodeNumber = -1; // The contrib may have other items besides the examples folder for (int i = 0; i < subNode.getChildCount(); i++) { @@ -388,12 +375,6 @@ public class ExamplesFrame extends JFrame { subNode.add((DefaultMutableTreeNode) exampleNode.getChildAt(0)); } } - -// if (subNode.getChildCount() != 1) { -// System.err.println("more children than expected when one is enough"); -// } -// TreeNode exampleNode = subNode.getChildAt(0); -// subNode.add((DefaultMutableTreeNode) exampleNode.getChildAt(0)); } } } diff --git a/app/src/processing/app/ui/ModeSelector.java b/app/src/processing/app/ui/ModeSelector.java new file mode 100644 index 000000000..38d40ac99 --- /dev/null +++ b/app/src/processing/app/ui/ModeSelector.java @@ -0,0 +1,151 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-22 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, version 2. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package processing.app.ui; + +import java.awt.Font; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.*; + +import processing.app.Messages; + + +public class ModeSelector extends JPanel { + // corner radius for the dropdown + static final int RADIUS = Toolkit.zoom(3); + + String title; + Font titleFont; + Color titleColor; + int titleAscent; + int titleWidth; + + final int MODE_GAP_WIDTH = Toolkit.zoom(10); + final int ARROW_GAP_WIDTH = Toolkit.zoom(6); + final int ARROW_WIDTH = Toolkit.zoom(6); + final int ARROW_TOP = Toolkit.zoom(16); + final int ARROW_BOTTOM = Toolkit.zoom(22); + + int[] triangleX = new int[3]; + int[] triangleY = new int[] { ARROW_TOP, ARROW_TOP, ARROW_BOTTOM }; + + Color backgroundColor; + Color outlineColor; + + public ModeSelector(Editor editor) { + title = editor.getMode().getTitle(); //.toUpperCase(); + + addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent event) { + JPopupMenu popup = editor.getModePopup(); + popup.show(ModeSelector.this, event.getX(), event.getY()); + } + }); + + updateTheme(); + } + + public void updateTheme() { + titleFont = Theme.getFont("mode.title.font"); + titleColor = Theme.getColor("mode.title.color"); + + // getGraphics() is null (even for editor) and no offscreen yet + //titleWidth = getToolkit().getFontMetrics(titleFont).stringWidth(title); + //titleWidth = editor.getGraphics().getFontMetrics(titleFont).stringWidth(title); + + // Theme for mode popup is handled inside Editor.handleTheme() + // because Editor owns the parent object. + + backgroundColor = Theme.getColor("mode.background.color"); + outlineColor = Theme.getColor("mode.outline.color"); + } + + @Override + public void paintComponent(Graphics g) { + Graphics2D g2 = Toolkit.prepareGraphics(g); + + g.setFont(titleFont); + if (titleAscent == 0) { + titleAscent = (int) processing.app.ui.Toolkit.getAscent(g); //metrics.getAscent(); + } + FontMetrics metrics = g.getFontMetrics(); + titleWidth = metrics.stringWidth(title); + + final int width = getWidth(); + final int height = getHeight(); + final int inset = Toolkit.zoom(4); + final int outline = Toolkit.zoom(1); + + // clear the background + g.setColor(backgroundColor); + g.fillRect(0, 0, width, height); + + // draw the outline for this feller + g.setColor(outlineColor); + //Toolkit.dpiStroke(g2); + g2.draw(Toolkit.createRoundRect(outline, outline + inset, width - outline, height - outline - inset, + RADIUS, RADIUS, RADIUS, RADIUS)); + + g.setColor(titleColor); + g.drawString(title, MODE_GAP_WIDTH, (height + titleAscent) / 2 + 1); + + int x = MODE_GAP_WIDTH + titleWidth + ARROW_GAP_WIDTH; + triangleX[0] = x; + triangleX[1] = x + ARROW_WIDTH; + triangleX[2] = x + ARROW_WIDTH/2; + g.fillPolygon(triangleX, triangleY, 3); + } + + @Override + public Dimension getPreferredSize() { + int tempWidth = titleWidth; // with any luck, this is set + if (tempWidth == 0) { + Graphics g = getGraphics(); + // the Graphics object may not be ready yet, being careful + if (g != null) { + tempWidth = getFontMetrics(titleFont).stringWidth(title); + } else { + Messages.err("null Graphics in EditorToolbar.getPreferredSize()"); + } + } + return new Dimension(MODE_GAP_WIDTH + tempWidth + + ARROW_GAP_WIDTH + ARROW_WIDTH + MODE_GAP_WIDTH, + EditorButton.DIM); + } + + @Override + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + @Override + public Dimension getMaximumSize() { + return getPreferredSize(); + } +} \ No newline at end of file diff --git a/app/src/processing/app/ui/PreferencesFrame.java b/app/src/processing/app/ui/PreferencesFrame.java index 968817218..d4e7818fd 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-21 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -30,13 +30,14 @@ import java.util.*; import javax.swing.*; import javax.swing.border.*; -import javax.swing.event.*; +import com.formdev.flatlaf.FlatClientProperties; import processing.app.Base; import processing.app.Language; import processing.app.Messages; import processing.app.Platform; import processing.app.Preferences; +import processing.app.SketchName; import processing.awt.ShimAWT; import processing.core.*; @@ -46,21 +47,25 @@ import processing.core.*; */ public class PreferencesFrame { JFrame frame; - GroupLayout layout; + + static final int ROW_H_GAP = 5; + static final int ROW_V_GAP = 3; static final Integer[] FONT_SIZES = { 10, 12, 14, 18, 24, 36, 48 }; JTextField sketchbookLocationField; + JComboBox namingSelectionBox; + JTextField presentColor; - JTextField presentColorHex; - JCheckBox editorAntialiasBox; - JCheckBox deletePreviousBox; + //JCheckBox editorAntialiasBox; +// JCheckBox deletePreviousBox; JCheckBox memoryOverrideBox; JTextField memoryField; JCheckBox checkUpdatesBox; JComboBox fontSizeField; JComboBox consoleFontSizeField; JCheckBox inputMethodBox; +// JLabel inputRestartLabel; JCheckBox autoAssociateBox; ColorChooser selector; @@ -72,9 +77,15 @@ public class PreferencesFrame { JComboBox zoomSelectionBox; JCheckBox zoomAutoBox; +// JLabel zoomRestartLabel; + + JCheckBox hidpiDisableBox; +// JLabel hidpiRestartLabel; + JCheckBox syncSketchNameBox; JComboBox displaySelectionBox; JComboBox languageSelectionBox; + Map languageToCode = new HashMap<>(); int displayCount; int defaultDisplayNum; @@ -82,6 +93,9 @@ public class PreferencesFrame { String[] monoFontFamilies; JComboBox fontSelectionBox; + Map restartMap = new HashMap<>(); + JLabel restartLabel; + JButton okButton; /** Base object so that updates can be applied to the list of editors. */ @@ -93,26 +107,33 @@ public class PreferencesFrame { frame = new JFrame(Language.text("preferences")); Container pain = frame.getContentPane(); - layout = new GroupLayout(pain); - layout.setAutoCreateGaps(true); - layout.setAutoCreateContainerGaps(true); - pain.setLayout(layout); + //layout = new GroupLayout(pain); + //layout.setAutoCreateGaps(true); + //layout.setAutoCreateContainerGaps(true); + //pain.setLayout(layout); JLabel sketchbookLocationLabel; - JLabel languageRestartLabel; - JLabel zoomRestartLabel; +// JLabel languageRestartLabel; JButton browseButton; //, button2; - // Sketchbook location: + // Sketchbook folder: // [...............................] [ Browse ] - sketchbookLocationLabel = new JLabel(Language.text("preferences.sketchbook_location")+":"); + sketchbookLocationLabel = new JLabel(Language.text("preferences.sketchbook_location")); - sketchbookLocationField = new JTextField(40); + sketchbookLocationField = new JTextField(25); + /* + sketchbookLocationField.putClientProperty( + FlatClientProperties.TEXT_FIELD_TRAILING_ICON, + UIManager.getIcon("Tree.closedIcon") + ); + sketchbookLocationField.setEditable(false); + */ - browseButton = new JButton(Language.getPrompt("browse")); + //browseButton = new JButton(Language.getPrompt("browse")); + browseButton = new JButton(UIManager.getIcon("Tree.closedIcon")); browseButton.addActionListener(e -> { File defaultLocation = new File(sketchbookLocationField.getText()); ShimAWT.selectFolder(Language.text("preferences.sketchbook_location.popup"), @@ -120,10 +141,21 @@ public class PreferencesFrame { PreferencesFrame.this); }); + sketchbookLocationField.putClientProperty( + FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, + browseButton + ); + + + // Sketch Naming: [ Classic (sketch_220822a) ] + + JLabel namingLabel = new JLabel(Language.text("preferences.sketch_naming")); + namingSelectionBox = new JComboBox<>(SketchName.getOptions()); + // Language: [ English ] (requires restart of Processing) - JLabel languageLabel = new JLabel(Language.text("preferences.language")+": "); + JLabel languageLabel = new JLabel(Language.text("preferences.language")); languageSelectionBox = new JComboBox<>(); Map languages = Language.getLanguages(); @@ -131,17 +163,22 @@ public class PreferencesFrame { languageSelection[0] = languages.get(Language.getLanguage()); int i = 1; for (Map.Entry lang : languages.entrySet()) { - if(!lang.getKey().equals(Language.getLanguage())){ + languageToCode.put(lang.getValue(), lang.getKey()); + if (!lang.getKey().equals(Language.getLanguage())) { languageSelection[i++] = lang.getValue(); } } languageSelectionBox.setModel(new DefaultComboBoxModel<>(languageSelection)); - languageRestartLabel = new JLabel(" (" + Language.text("preferences.requires_restart") + ")"); +// languageRestartLabel = new JLabel(Language.text("preferences.requires_restart")); +// languageRestartLabel.setVisible(false); + //languageSelectionBox.addItemListener(e -> languageRestartLabel.setVisible(languageSelectionBox.getSelectedIndex() != 0)); + languageSelectionBox.addItemListener(e -> updateRestart("language", languageSelectionBox.getSelectedIndex() != 0)); + languageSelectionBox.setRenderer(new LanguageRenderer()); // Editor and console font [ Source Code Pro ] - JLabel fontLabel = new JLabel(Language.text("preferences.editor_and_console_font")+": "); + JLabel fontLabel = new JLabel(Language.text("preferences.editor_and_console_font")); final String fontTip = "" + Language.text("preferences.editor_and_console_font.tip"); fontLabel.setToolTipText(fontTip); // get a wide name in there before getPreferredSize() is called @@ -152,11 +189,11 @@ public class PreferencesFrame { // Editor font size [ 12 ] Console font size [ 10 ] - JLabel fontSizeLabel = new JLabel(Language.text("preferences.editor_font_size")+": "); + 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")+": "); + JLabel consoleFontSizeLabel = new JLabel(Language.text("preferences.console_font_size")); consoleFontSizeField = new JComboBox<>(FONT_SIZES); consoleFontSizeField.setSelectedItem(Preferences.getInteger("console.font.size")); @@ -169,18 +206,45 @@ public class PreferencesFrame { // Interface scale: [ 100% ] (requires restart of Processing) - JLabel zoomLabel = new JLabel(Language.text("preferences.zoom") + ": "); +// zoomRestartLabel = new JLabel(Language.text("preferences.requires_restart")); - zoomAutoBox = new JCheckBox(Language.text("preferences.zoom.auto")); - zoomAutoBox.addChangeListener(e -> zoomSelectionBox.setEnabled(!zoomAutoBox.isSelected())); + JLabel zoomLabel = new JLabel(Language.text("preferences.interface_scale")); + + zoomAutoBox = new JCheckBox(Language.text("preferences.interface_scale.auto")); + zoomAutoBox.addChangeListener(e -> { + zoomSelectionBox.setEnabled(!zoomAutoBox.isSelected()); + updateZoomRestartRequired(); + }); zoomSelectionBox = new JComboBox<>(); - zoomSelectionBox.setModel(new DefaultComboBoxModel<>(Toolkit.zoomOptions.array())); - zoomRestartLabel = new JLabel(" (" + Language.text("preferences.requires_restart") + ")"); + zoomSelectionBox.setModel(new DefaultComboBoxModel<>(Toolkit.zoomOptions.toArray())); + zoomSelectionBox.addActionListener(e -> updateZoomRestartRequired()); - // - JLabel backgroundColorLabel = new JLabel(Language.text("preferences.background_color")+": "); + // [ ] Disable HiDPI Scaling (requires restart) + + hidpiDisableBox = new JCheckBox("Disable HiDPI Scaling"); +// hidpiDisableBox.setVisible(false); // only for Windows +// hidpiRestartLabel = new JLabel(Language.text("preferences.requires_restart")); +// hidpiRestartLabel.setVisible(false); +// hidpiDisableBox.addChangeListener(e -> hidpiRestartLabel.setVisible(hidpiDisableBox.isSelected() != Splash.getDisableHiDPI())); + hidpiDisableBox.addChangeListener(e -> updateRestart("hidpi", hidpiDisableBox.isSelected() != Splash.getDisableHiDPI())); + + + // [ ] Keep sketch name and main tab name in sync + syncSketchNameBox = + new JCheckBox("Keep sketch name and main tab in sync"); + syncSketchNameBox.setToolTipText("" + + "This removes the requirement for the sketch name to be
" + + "the same as the main tab, which makes it easier to use
" + + "Processing sketches with version control systems like Git.
" + + "This is experimental: save often and report any issues!"); + //syncSketchNameBox.setVerticalTextPosition(SwingConstants.TOP); + + + // Colors + + JLabel backgroundColorLabel = new JLabel(Language.text("preferences.background_color")); final String colorTip = "" + Language.text("preferences.background_color.tip"); backgroundColorLabel.setToolTipText(colorTip); @@ -194,6 +258,7 @@ public class PreferencesFrame { presentColor.setBorder(cb); presentColor.setBackground(Preferences.getColor("run.present.bgcolor")); + /* presentColorHex = new JTextField(6); presentColorHex.setText(Preferences.get("run.present.bgcolor").substring(1)); presentColorHex.getDocument().addDocumentListener(new DocumentListener() { @@ -226,13 +291,12 @@ public class PreferencesFrame { @Override public void changedUpdate(DocumentEvent e) {} }); + */ selector = new ColorChooser(frame, false, Preferences.getColor("run.present.bgcolor"), Language.text("prompt.ok"), e -> { - String colorValue = selector.getHexColor(); - colorValue = colorValue.substring(1); // remove the # - presentColorHex.setText(colorValue); + String colorValue = selector.getHexColor().substring(1); presentColor.setBackground(new Color(PApplet.unhex(colorValue))); selector.hide(); }); @@ -254,20 +318,43 @@ public class PreferencesFrame { } }); - JLabel hashLabel = new JLabel("#"); + //JLabel hashLabel = new JLabel("#"); // [ ] Use smooth text in editor window - editorAntialiasBox = new JCheckBox(Language.text("preferences.use_smooth_text")); + //editorAntialiasBox = new JCheckBox(Language.text("preferences.use_smooth_text")); // [ ] Enable complex text input (for Japanese et al, requires restart) inputMethodBox = - new JCheckBox(Language.text("preferences.enable_complex_text_input")+ - " ("+Language.text("preferences.enable_complex_text_input_example")+ - ", "+Language.text("preferences.requires_restart")+")"); + new JCheckBox(Language.text("preferences.enable_complex_text")); + inputMethodBox.setToolTipText("" + Language.text("preferences.enable_complex_text.tip")); + + /* + JLabel inputMethodExample = + new JLabel("(" + Language.text("preferences.enable_complex_text_input_example") + ")"); + inputMethodExample.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + Platform.openURL("https://en.wikipedia.org/wiki/CJK_characters"); + } + + public void mouseEntered(MouseEvent e) { + inputMethodExample.setForeground(Theme.getColor("laf.accent.color")); + } + + // Set the text back to black when the mouse is outside + public void mouseExited(MouseEvent e) { + inputMethodExample.setForeground(sketchbookLocationLabel.getForeground()); + } + }); +// inputRestartLabel = new JLabel(Language.text("preferences.requires_restart")); +// inputRestartLabel.setVisible(false); + */ + +// inputMethodBox.addChangeListener(e -> inputRestartLabel.setVisible(inputMethodBox.isSelected() != Preferences.getBoolean("editor.input_method_support"))); + inputMethodBox.addChangeListener(e -> updateRestart("input_method", inputMethodBox.isSelected() != Preferences.getBoolean("editor.input_method_support"))); // [ ] Continuously check for errors - PDE X @@ -298,7 +385,7 @@ public class PreferencesFrame { // [ ] Increase maximum available memory to [______] MB - memoryOverrideBox = new JCheckBox(Language.text("preferences.increase_max_memory")+": "); + memoryOverrideBox = new JCheckBox(Language.text("preferences.increase_max_memory")); memoryField = new JTextField(4); memoryOverrideBox.addChangeListener(e -> memoryField.setEnabled(memoryOverrideBox.isSelected())); JLabel mbLabel = new JLabel("MB"); @@ -306,8 +393,8 @@ public class PreferencesFrame { // [ ] Delete previous application folder on export - deletePreviousBox = - new JCheckBox(Language.text("preferences.delete_previous_folder_on_export")); +// deletePreviousBox = +// new JCheckBox(Language.text("preferences.delete_previous_folder_on_export")); // [ ] Check for updates on startup @@ -318,7 +405,7 @@ public class PreferencesFrame { // Run sketches on display [ 1 ] - JLabel displayLabel = new JLabel(Language.text("preferences.run_sketches_on_display") + ": "); + JLabel displayLabel = new JLabel(Language.text("preferences.run_sketches_on_display")); final String tip = "" + Language.text("preferences.run_sketches_on_display.tip"); displayLabel.setToolTipText(tip); displaySelectionBox = new JComboBox<>(); @@ -329,38 +416,61 @@ public class PreferencesFrame { autoAssociateBox = new JCheckBox(Language.text("preferences.automatically_associate_pde_files")); - autoAssociateBox.setVisible(false); +// autoAssociateBox.setVisible(false); // More preferences are in the ... - JLabel morePreferenceLabel = new JLabel(Language.text("preferences.file") + ":"); - morePreferenceLabel.setForeground(Color.gray); + final JLabel morePreferenceLabel = new JLabel(Language.text("preferences.file")); + morePreferenceLabel.setForeground(UIManager.getColor("Label.disabledForeground")); + morePreferenceLabel.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + // Starting in 4.0.1, open the Wiki page about the prefs + Platform.openURL("https://github.com/processing/processing4/wiki/Preferences"); + } + + // Light this up in blue like a hyperlink + public void mouseEntered(MouseEvent e) { + frame.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + morePreferenceLabel.setForeground(Theme.getColor("laf.accent.color")); + } + + // Set the text back to black when the mouse is outside + public void mouseExited(MouseEvent e) { + frame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + morePreferenceLabel.setForeground(UIManager.getColor("Label.disabledForeground")); + } + }); JLabel preferencePathLabel = new JLabel(Preferences.getPreferencesPath()); final JLabel clickable = preferencePathLabel; preferencePathLabel.addMouseListener(new MouseAdapter() { - public void mousePressed(MouseEvent e) { - Platform.openFolder(Base.getSettingsFolder()); - } + public void mousePressed(MouseEvent e) { + Platform.openFolder(Base.getSettingsFolder()); + } - // Light this up in blue like a hyperlink - public void mouseEntered(MouseEvent e) { - clickable.setForeground(new Color(0, 0, 140)); - } + // Light this up in blue like a hyperlink + public void mouseEntered(MouseEvent e) { + frame.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + clickable.setForeground(Theme.getColor("laf.accent.color")); + } - // Set the text back to black when the mouse is outside - public void mouseExited(MouseEvent e) { - clickable.setForeground(Color.BLACK); - } - }); + // Set the text back to black when the mouse is outside + public void mouseExited(MouseEvent e) { + frame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + clickable.setForeground(UIManager.getColor("Label.foreground")); + } + }); - JLabel preferenceHintLabel = new JLabel("(" + Language.text("preferences.file.hint") + ")"); - preferenceHintLabel.setForeground(Color.gray); + JLabel preferenceHintLabel = new JLabel(Language.text("preferences.file.hint")); + //preferenceHintLabel.setForeground(Color.gray); + preferenceHintLabel.setEnabled(false); // [ OK ] [ Cancel ] + restartLabel = new JLabel(Language.text("preferences.restart_required")); + okButton = new JButton(Language.getPrompt("ok")); okButton.addActionListener(e -> { applyFrame(); @@ -370,134 +480,104 @@ public class PreferencesFrame { JButton cancelButton = new JButton(Language.getPrompt("cancel")); cancelButton.addActionListener(e -> disposeFrame()); - final int buttonWidth = Toolkit.getButtonWidth(); - layout.setHorizontalGroup(layout.createSequentialGroup() // sequential group for border + mainContent + border - .addGap(Toolkit.BORDER) - .addGroup(layout.createParallelGroup() // parallel group for rest of the components - .addComponent(sketchbookLocationLabel) - .addGroup(layout.createSequentialGroup() - .addComponent(sketchbookLocationField) - .addComponent(browseButton)) - .addGroup(layout.createSequentialGroup() - .addComponent(languageLabel) - .addComponent(languageSelectionBox,GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) // This makes the component non-resizable in the X direction - .addComponent(languageRestartLabel)) - .addGroup(layout.createSequentialGroup() - .addComponent(fontLabel) - .addComponent(fontSelectionBox, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) - .addGroup(GroupLayout.Alignment.LEADING, - layout.createSequentialGroup() - .addComponent(fontSizeLabel) - .addComponent(fontSizeField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(consoleFontSizeLabel) - .addComponent(consoleFontSizeField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addComponent(zoomLabel) - .addComponent(zoomAutoBox) - .addComponent(zoomSelectionBox, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(zoomRestartLabel)) - .addGroup(layout.createSequentialGroup() - .addComponent(backgroundColorLabel) - .addComponent(hashLabel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addGap(0) - .addComponent(presentColorHex, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(presentColor, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) - .addComponent(editorAntialiasBox) - .addComponent(inputMethodBox) - .addGroup(layout.createSequentialGroup() - .addComponent(errorCheckerBox) - .addComponent(warningsCheckerBox)) - .addComponent(warningsCheckerBox) - .addComponent(codeCompletionBox) - .addComponent(importSuggestionsBox) - .addGroup(layout.createSequentialGroup() - .addComponent(memoryOverrideBox) - .addComponent(memoryField, - GroupLayout.PREFERRED_SIZE, - GroupLayout.DEFAULT_SIZE, - GroupLayout.PREFERRED_SIZE) - .addComponent(mbLabel)) - .addComponent(deletePreviousBox) - .addComponent(checkUpdatesBox) - .addGroup(layout.createSequentialGroup() - .addComponent(displayLabel) - .addComponent(displaySelectionBox, - GroupLayout.PREFERRED_SIZE, - GroupLayout.DEFAULT_SIZE, - GroupLayout.PREFERRED_SIZE) - ) - .addComponent(autoAssociateBox) - .addComponent(morePreferenceLabel) - .addComponent(preferencePathLabel) - .addComponent(preferenceHintLabel) - .addGroup(GroupLayout.Alignment.TRAILING,layout.createSequentialGroup() // Trailing so that the buttons are to the right - .addComponent(okButton, buttonWidth, GroupLayout.DEFAULT_SIZE, buttonWidth) // Ok and Cancel button are now of size BUTTON_WIDTH - .addComponent(cancelButton, buttonWidth, GroupLayout.DEFAULT_SIZE, buttonWidth) - )) - .addGap(Toolkit.BORDER) - ); + Box axis = Box.createVerticalBox(); - layout.setVerticalGroup(layout.createSequentialGroup() // sequential group for border + mainContent + border - .addGap(Toolkit.BORDER) - .addComponent(sketchbookLocationLabel) - .addGroup(layout.createParallelGroup() - .addComponent(sketchbookLocationField) - .addComponent(browseButton)) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) - .addComponent(languageLabel) - .addComponent(languageSelectionBox) - .addComponent(languageRestartLabel)) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER). - addComponent(fontLabel) - .addComponent(fontSelectionBox)) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) - .addComponent(fontSizeLabel) - .addComponent(fontSizeField) - .addComponent(consoleFontSizeLabel) - .addComponent(consoleFontSizeField)) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) - .addComponent(zoomLabel) - .addComponent(zoomAutoBox) - .addComponent(zoomSelectionBox) - .addComponent(zoomRestartLabel)) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) - .addComponent(backgroundColorLabel) - .addComponent(hashLabel) - .addComponent(presentColorHex) - .addComponent(presentColor)) - .addComponent(editorAntialiasBox) - .addComponent(inputMethodBox) - .addGroup(layout.createParallelGroup() - .addComponent(errorCheckerBox) - .addComponent(warningsCheckerBox)) - .addComponent(codeCompletionBox) - .addComponent(importSuggestionsBox) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) - .addComponent(memoryOverrideBox) - .addComponent(memoryField) - .addComponent(mbLabel)) - .addComponent(deletePreviousBox) - .addComponent(checkUpdatesBox) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) - .addComponent(displayLabel) - .addComponent(displaySelectionBox)) - .addComponent(autoAssociateBox) - .addComponent(morePreferenceLabel) - .addGap(0) - .addComponent(preferencePathLabel) - .addGap(0) - .addComponent(preferenceHintLabel) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) - .addComponent(okButton) - .addComponent(cancelButton)) - .addGap(Toolkit.BORDER) - ); + addRow(axis, sketchbookLocationLabel, sketchbookLocationField); - if (Platform.isWindows()){ - autoAssociateBox.setVisible(true); + addRow(axis, namingLabel, namingSelectionBox); + + // + + JPanel layoutPanel = new JPanel(); + layoutPanel.setBorder(new TitledBorder("Interface and Fonts")); + layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS)); + + addRow(layoutPanel, fontLabel, fontSelectionBox); + + addRow(layoutPanel, fontSizeLabel, fontSizeField, + //Box.createHorizontalStrut(H_GAP), // uglier + consoleFontSizeLabel, consoleFontSizeField); + + //addRow(layoutPanel, zoomLabel, zoomAutoBox, zoomSelectionBox, zoomRestartLabel); + addRow(layoutPanel, zoomLabel, zoomAutoBox, zoomSelectionBox); + + if (Platform.isWindows()) { + //addRow(layoutPanel, hidpiDisableBox, hidpiRestartLabel); + addRow(layoutPanel, hidpiDisableBox); } - // closing the window is same as hitting cancel button + axis.add(layoutPanel); + + // + +// JPanel languagePanel = new JPanel(); +// languagePanel.setBorder(new TitledBorder("Language")); +// languagePanel.setLayout(new BoxLayout(languagePanel, BoxLayout.Y_AXIS)); + +// //addRow(layoutPanel, languageLabel, languageSelectionBox, languageRestartLabel); +// addRow(layoutPanel, languageLabel, languageSelectionBox); +// //addRow(layoutPanel, inputMethodBox, inputMethodExample, inputRestartLabel); +// addRow(layoutPanel, inputMethodBox, inputMethodExample); + + addRow(layoutPanel, languageLabel, languageSelectionBox, inputMethodBox); + +// axis.add(languagePanel); + + // + + JPanel codingPanel = new JPanel(); + codingPanel.setBorder(new TitledBorder("Coding")); + codingPanel.setLayout(new BoxLayout(codingPanel, BoxLayout.Y_AXIS)); + + addRow(codingPanel, errorCheckerBox, warningsCheckerBox); + addRow(codingPanel, codeCompletionBox, importSuggestionsBox); + + axis.add(codingPanel); + + // + + JPanel runningPanel = new JPanel(); + runningPanel.setBorder(new TitledBorder("Running")); + runningPanel.setLayout(new BoxLayout(runningPanel, BoxLayout.Y_AXIS)); + + addRow(runningPanel, displayLabel, displaySelectionBox); + addRow(runningPanel, backgroundColorLabel, presentColor); + addRow(runningPanel, memoryOverrideBox, memoryField, mbLabel); + + axis.add(runningPanel); + + // + +// addRow(axis, deletePreviousBox); + addRow(axis, checkUpdatesBox); + addRow(axis, syncSketchNameBox); + + if (Platform.isWindows()) { + addRow(axis, autoAssociateBox); + } + + // Put these in a separate container so there's no extra gap + // between the rows. + Box blurb = Box.createVerticalBox(); + blurb.add(morePreferenceLabel); + blurb.add(preferencePathLabel); + blurb.add(preferenceHintLabel); + addRow(axis, blurb); + + //JPanel row = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + Box row = Box.createHorizontalBox(); + row.add(Box.createHorizontalStrut(ROW_H_GAP)); + row.add(restartLabel); + row.add(Box.createHorizontalGlue()); + row.add(okButton); // buttonWidth + row.add(Box.createHorizontalStrut(ROW_H_GAP)); + row.add(cancelButton); // buttonWidth + axis.add(row); + + axis.setBorder(new EmptyBorder(13, 13, 13, 13)); + pain.add(axis); + + // closing the window is same as hitting cancel button frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { disposeFrame(); @@ -505,9 +585,12 @@ public class PreferencesFrame { }); ActionListener disposer = actionEvent -> disposeFrame(); - // finish up - Toolkit.registerWindowCloseKeys(frame.getRootPane(), disposer); + + // for good measure, and set link/highlight colors + updateTheme(); + + // finishing up Toolkit.setIcon(frame); frame.setResizable(false); frame.pack(); @@ -526,6 +609,54 @@ public class PreferencesFrame { } + static private void addRow(Container axis, Component... components) { + JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT, ROW_H_GAP, ROW_V_GAP)); + for (Component comp : components) { + row.add(comp); + } + axis.add(row); + } + + + private void updateRestart(String key, boolean value) { + restartMap.put(key, value); + restartLabel.setVisible(restartMap.containsValue(true)); + } + + + private void updateZoomRestartRequired() { + // TODO If this is too wide for the window, it may not appear. + // Redo layout in the window on change to be sure. + // May cause window to resize but need the message. [fry 220502] + //zoomRestartLabel.setVisible( + updateRestart("zoom", + zoomAutoBox.isSelected() != Preferences.getBoolean("editor.zoom.auto") || + !Preferences.get("editor.zoom").equals(String.valueOf(zoomSelectionBox.getSelectedItem())) + ); + } + + + class LanguageRenderer extends JLabel implements ListCellRenderer { + final int fontSize = languageSelectionBox.getFont().getSize(); + final Font sansFont = Toolkit.getSansFont(fontSize, Font.PLAIN); + final Font fallbackFont = new Font("Dialog", Font.PLAIN,fontSize); + + @Override + public Component getListCellRendererComponent(JList list, String text, int index, + boolean isSelected, boolean cellHasFocus) { + if (sansFont.canDisplayUpTo(text) == -1) { + // if the sans font can display the chars, use it + setFont(sansFont); + } else { + // otherwise, use the fallback font (Dialog) + setFont(fallbackFont); + } + setText(text); + return this; + } + } + + /** Callback for the folder selector. */ public void sketchbookCallback(File file) { if (file != null) { // null if cancel or closed @@ -545,11 +676,11 @@ public class PreferencesFrame { * then send a message to the editor saying that it's time to do the same. */ protected void applyFrame() { - Preferences.setBoolean("editor.smooth", //$NON-NLS-1$ - editorAntialiasBox.isSelected()); +// Preferences.setBoolean("editor.smooth", //$NON-NLS-1$ +// editorAntialiasBox.isSelected()); - Preferences.setBoolean("export.delete_target_folder", //$NON-NLS-1$ - deletePreviousBox.isSelected()); +// Preferences.setBoolean("export.delete_target_folder", //$NON-NLS-1$ +// deletePreviousBox.isSelected()); // if the sketchbook path has changed, rebuild the menus String oldPath = Preferences.getSketchbookPath(); @@ -558,20 +689,32 @@ public class PreferencesFrame { base.setSketchbookFolder(new File(newPath)); } + Preferences.set("sketch.name.approach", (String) namingSelectionBox.getSelectedItem()); + // setBoolean("editor.external", externalEditorBox.isSelected()); Preferences.setBoolean("update.check", checkUpdatesBox.isSelected()); //$NON-NLS-1$ // Save Language + /* Map languages = Language.getLanguages(); - String language = ""; + String language = null; for (Map.Entry lang : languages.entrySet()) { if (lang.getValue().equals(String.valueOf(languageSelectionBox.getSelectedItem()))) { language = lang.getKey().trim().toLowerCase(); break; } } - if (!language.equals(Language.getLanguage()) && !language.equals("")) { - Language.saveLanguage(language); + */ + // first entry is always the default language + if (languageSelectionBox.getSelectedIndex() != 0) { + /* + String languageCode = + Language.nameToCode(String.valueOf(languageSelectionBox.getSelectedItem())); + if (!Language.getLanguage().equals(languageCode)) { + Language.saveLanguage(languageCode); + } + */ + Language.saveLanguage(languageToCode.get((String) languageSelectionBox.getSelectedItem())); } // The preference will have already been reset when the window was created @@ -651,6 +794,11 @@ public class PreferencesFrame { Preferences.set("editor.zoom", String.valueOf(zoomSelectionBox.getSelectedItem())); + if (Platform.isWindows()) { + Splash.setDisableHiDPI(hidpiDisableBox.isSelected()); + } + Preferences.setBoolean("editor.sync_folder_and_filename", syncSketchNameBox.isSelected()); + Preferences.setColor("run.present.bgcolor", presentColor.getBackground()); Preferences.setBoolean("editor.input_method_support", inputMethodBox.isSelected()); //$NON-NLS-1$ @@ -672,16 +820,24 @@ public class PreferencesFrame { public void showFrame() { - editorAntialiasBox.setSelected(Preferences.getBoolean("editor.smooth")); //$NON-NLS-1$ + //editorAntialiasBox.setSelected(Preferences.getBoolean("editor.smooth")); //$NON-NLS-1$ inputMethodBox.setSelected(Preferences.getBoolean("editor.input_method_support")); //$NON-NLS-1$ errorCheckerBox.setSelected(Preferences.getBoolean("pdex.errorCheckEnabled")); warningsCheckerBox.setSelected(Preferences.getBoolean("pdex.warningsEnabled")); warningsCheckerBox.setEnabled(errorCheckerBox.isSelected()); codeCompletionBox.setSelected(Preferences.getBoolean("pdex.completion")); importSuggestionsBox.setSelected(Preferences.getBoolean("pdex.suggest.imports")); - deletePreviousBox.setSelected(Preferences.getBoolean("export.delete_target_folder")); //$NON-NLS-1$ +// deletePreviousBox.setSelected(Preferences.getBoolean("export.delete_target_folder")); //$NON-NLS-1$ sketchbookLocationField.setText(Preferences.getSketchbookPath()); + + namingSelectionBox.setSelectedItem(Preferences.get("sketch.name.approach")); + if (namingSelectionBox.getSelectedIndex() < 0) { + // If no selection, revert to the classic style, and set the pref as well + namingSelectionBox.setSelectedItem(SketchName.CLASSIC); + Preferences.set("sketch.name.approach", SketchName.CLASSIC); + } + checkUpdatesBox.setSelected(Preferences.getBoolean("update.check")); //$NON-NLS-1$ defaultDisplayNum = updateDisplayList(); @@ -712,9 +868,13 @@ public class PreferencesFrame { } else { zoomSelectionBox.setSelectedIndex(0); } + if (Platform.isWindows()) { + hidpiDisableBox.setSelected(Splash.getDisableHiDPI()); + } + syncSketchNameBox.setSelected(Preferences.getBoolean("editor.sync_folder_and_filename")); presentColor.setBackground(Preferences.getColor("run.present.bgcolor")); - presentColorHex.setText(Preferences.get("run.present.bgcolor").substring(1)); + //presentColorHex.setText(Preferences.get("run.present.bgcolor").substring(1)); memoryOverrideBox. setSelected(Preferences.getBoolean("run.options.memory")); //$NON-NLS-1$ @@ -729,15 +889,28 @@ public class PreferencesFrame { // PrefWindow is to be displayed frame.getRootPane().setDefaultButton(okButton); + // Prevent the location field from being highlighted by default + sketchbookLocationField.select(0, 0); + // Could make the Cancel button the default, but seems odd + okButton.requestFocusInWindow(); + // The pack is called again here second time to fix layout bugs - // the bugs are not due to groupLayout but due to HTML rendering of components - // more info can be found here -> https://netbeans.org/bugzilla/show_bug.cgi?id=79967 + // due to HTML rendering of components. [akarshit 150430] + // https://netbeans.org/bugzilla/show_bug.cgi?id=79967 frame.pack(); frame.setVisible(true); } + public void updateTheme() { + // Required to update changes to accent color or light/dark mode. + // (No custom components, so safe to call on this Window object.) + SwingUtilities.updateComponentTreeUI(frame); + restartLabel.setForeground(Theme.getColor("laf.accent.color")); + } + + // /** // * 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? @@ -789,7 +962,8 @@ public class PreferencesFrame { String[] items = new String[displayCount]; for (int i = 0; i < displayCount; i++) { DisplayMode mode = devices[i].getDisplayMode(); - String title = String.format("%d (%d \u2715 %d)", // or \u00d7? + //String title = String.format("%d (%d \u2715 %d)", // or \u00d7? + String title = String.format("%d (%d \u00d7 %d)", // or \u2715? i + 1, mode.getWidth(), mode.getHeight()); if (devices[i] == defaultDevice) { title += " default"; diff --git a/app/src/processing/app/ui/Recent.java b/app/src/processing/app/ui/Recent.java index b4d355046..8dea79dcd 100644 --- a/app/src/processing/app/ui/Recent.java +++ b/app/src/processing/app/ui/Recent.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-19 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -22,8 +22,6 @@ package processing.app.ui; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -83,16 +81,12 @@ public class Recent { BufferedReader reader = PApplet.createReader(file); String version = reader.readLine(); if (version != null && version.equals(VERSION)) { - String line = null; + String line; while ((line = reader.readLine()) != null) { -// String[] pieces = PApplet.split(line, '\t'); -// Record record = new Record(pieces[0], new EditorState(pieces[1])); -// Record record = new Record(pieces[0]); //, new EditorState(pieces[1])); -// records.add(record); if (new File(line).exists()) { // don't add ghost entries records.add(new Record(line)); } else { - Messages.log("ghost file: " + line); + Messages.log("Ghost file found in recent: " + line); } } } @@ -104,12 +98,16 @@ public class Recent { static protected void save() { - file.setWritable(true, false); + // Need to check whether file exists and may already be writable. + // Otherwise, setWritable() will actually return false. + if (file.exists() && !file.canWrite()) { + if (!file.setWritable(true, false)) { + System.err.println("Warning: could not set " + file + " to writable"); + } + } PrintWriter writer = PApplet.createWriter(file); writer.println(VERSION); for (Record record : records) { -// System.out.println(record.getPath() + "\t" + record.getState()); -// writer.println(record.path + "\t" + record.getState()); writer.println(record.path); // + "\t" + record.getState()); } writer.flush(); @@ -206,29 +204,14 @@ public class Recent { } } -// JMenuItem item = new JMenuItem(rec.getName() + " | " + purtyPath); JMenuItem item = new JMenuItem(purtyPath); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - // Base will call handle() (below) which will cause this entry to - // be removed from the list and re-added to the end. If already - // opened, Base will bring the window forward, and also call handle() - // so that it's re-queued to the newest slot in the Recent menu. - base.handleOpen(rec.path); -// if (rec.sketch == null) { -// // this will later call 'add' to put it back on the stack -// base.handleOpen(rec.path); //, rec.state); -//// int index = findRecord(rec); -//// if (index != -1) { -//// records.remove(index); // remove from the list -//// save(); // write the recent file with the latest -//// } -// } else { -//// System.out.println("sketch not null in handleOpen: " + record.getPath()); -// } - } + item.addActionListener(e -> { + // Base will call handle() (below) which will cause this entry to + // be removed from the list and re-added to the end. If already + // opened, Base will bring the window forward, and also call handle() + // so that it's re-queued to the newest slot in the Recent menu. + base.handleOpen(rec.path); }); - //menu.add(item); menu.insert(item, 0); } catch (Exception e) { @@ -240,47 +223,13 @@ public class Recent { synchronized static public void remove(Editor editor) { - int index = findRecord(editor.getSketch().getMainFilePath()); + int index = findRecord(editor.getSketch().getMainPath()); if (index != -1) { records.remove(index); } } -// synchronized void handleRename(String oldPath, String newPath) { -// int index = findRecord(oldPath); -// if (index != -1) { -// Record rec = records.get(index); -// rec.path = newPath; -// save(); -// } else { -// Base.log(this, "Could not find " + oldPath + -// " in list of recent sketches to replace with " + newPath); -// } -// } - - -// /** Opened from the recent sketches menu. */ -// synchronized void handleOpen(Record record) { -// if (record.sketch == null) { -//// Editor editor = base.handleOpen(record.path, record.state); -// base.handleOpen(record.path, record.state); -// int index = findRecord(record); -// if (index != -1) { -// records.remove(index); // remove from the list -//// if (editor != null) { -//// record.sketch = editor.getSketch(); -//// records.add(record); // move to the end of the line -//// } -// save(); // write the recent file with the latest -// } -// } else { -//// System.out.println("sketch not null in handleOpen: " + record.getPath()); -// } -// // otherwise the other handleOpen() will be called once it opens -// } - - /** * Called by Base when a new sketch is opened, to add the sketch to the last * entry on the Recent queue. If the sketch is already in the list, it is @@ -296,14 +245,8 @@ public class Recent { records.remove(0); // remove the first entry } -// new Exception("adding to recent: " + editor.getSketch().getMainFilePath()).printStackTrace(System.out); -// Record newRec = new Record(editor, editor.getSketch()); -// records.add(newRec); records.add(new Record(editor)); -// updateMenu(); save(); -// } else { -// new Exception("NOT adding to recent: " + editor.getSketch().getMainFilePath()).printStackTrace(System.out); } } @@ -332,78 +275,28 @@ public class Recent { } -// int findRecord(Record rec) { -// int index = records.indexOf(rec); -// if (index != -1) { -// return index; -// } -// return findRecord(rec.path); -//// index = 0; -//// for (Record r : records) { -//// System.out.println("checking " + r + "\n against " + rec); -//// if (r.equals(rec)) { -//// return index; -//// } -//// index++; -//// } -//// return -1; -// } - - static class Record { String path; // if not loaded, this is non-null -// EditorState state; // if not loaded, this is non-null - -// Sketch sketch; - -// Record(String path, EditorState state) { -// this.path = path; -// this.state = state; -// } Record(String path) { this.path = path; } -// Record(Editor editor, Sketch sketch) { -// this.editor = editor; -// this.sketch = sketch; -// } - Record(Editor editor) { - this(editor.getSketch().getMainFilePath()); + this(editor.getSketch().getMainPath()); } + /* String getName() { // Get the filename of the .pde (or .js or .py...) String name = path.substring(path.lastIndexOf(File.separatorChar) + 1); // Return the name with the extension removed return name.substring(0, name.indexOf('.')); } + */ String getPath() { return path; } - -// String getPath() { -// if (sketch != null) { -// return sketch.getMainFilePath(); -// } else { -// return path; -// } -// } - -// EditorState getState() { -//// return sketch == null ? state : editor.getEditorState(); -// if (editor != null) { // update state if editor is open -// state = editor.getEditorState(); -// } -// return state; -// } - -// public boolean equals(Object o) { -// Record r = (Record) o; -// return getPath().equals(r.getPath()); -// } } } diff --git a/app/src/processing/app/ui/SketchbookFrame.java b/app/src/processing/app/ui/SketchbookFrame.java index 8a214a102..3bcd3ca54 100644 --- a/app/src/processing/app/ui/SketchbookFrame.java +++ b/app/src/processing/app/ui/SketchbookFrame.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2013-19 The Processing Foundation + Copyright (c) 2013-22 The Processing Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,21 +22,10 @@ package processing.app.ui; -import java.awt.Color; -import java.awt.EventQueue; -import java.awt.Point; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import java.awt.*; +import java.awt.event.*; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTree; +import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeModel; @@ -44,30 +33,56 @@ import javax.swing.tree.TreeSelectionModel; import processing.app.Base; import processing.app.Language; -import processing.app.Mode; import processing.app.Platform; import processing.app.SketchReference; public class SketchbookFrame extends JFrame { protected Base base; - protected Mode mode; - public SketchbookFrame(final Base base, final Mode mode) { + public SketchbookFrame(final Base base) { super(Language.text("sketchbook")); this.base = base; - this.mode = mode; - final ActionListener listener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - setVisible(false); - } - }; + final ActionListener listener = e -> setVisible(false); Toolkit.registerWindowCloseKeys(getRootPane(), listener); Toolkit.setIcon(this); - final JTree tree = new JTree(mode.buildSketchbookTree()); + Container pane = getContentPane(); + pane.setLayout(new BorderLayout()); + + updateCenterPanel(); + + Container buttons = Box.createHorizontalBox(); + + JButton addButton = new JButton("Show Folder"); + addButton.addActionListener(e -> Platform.openFolder(Base.getSketchbookFolder())); + buttons.add(Box.createHorizontalGlue()); + buttons.add(addButton, BorderLayout.WEST); + + JButton refreshButton = new JButton("Refresh"); + refreshButton.addActionListener(e -> base.rebuildSketchbook()); + buttons.add(refreshButton, BorderLayout.EAST); + buttons.add(Box.createHorizontalGlue()); + + final int high = addButton.getPreferredSize().height; + final int wide = 4 * Toolkit.getButtonWidth() / 3; + addButton.setPreferredSize(new Dimension(wide, high)); + refreshButton.setPreferredSize(new Dimension(wide, high)); + + JPanel buttonPanel = new JPanel(); // adds extra border + // wasn't necessary to set a border b/c JPanel adds plenty + //buttonPanel.setBorder(new EmptyBorder(3, 0, 3, 0)); + buttonPanel.add(buttons); + pane.add(buttonPanel, BorderLayout.SOUTH); + + pack(); + } + + + private JTree rebuildTree() { + final JTree tree = new JTree(base.buildSketchbookTree()); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setShowsRootHandles(true); tree.expandRow(0); @@ -121,13 +136,25 @@ public class SketchbookFrame extends JFrame { // Special cell renderer that takes the UI zoom into account tree.setCellRenderer(new ZoomTreeCellRenderer()); - // Check whether sketch book is empty or not + return tree; + } + + + private void updateCenterPanel() { + JTree tree = rebuildTree(); + Container panel; + + // Check whether sketchbook is empty or not TreeModel treeModel = tree.getModel(); if (treeModel.getChildCount(treeModel.getRoot()) != 0) { - JScrollPane treePane = new JScrollPane(tree); + JScrollPane treePane = new JScrollPane(tree, + ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); treePane.setPreferredSize(Toolkit.zoom(250, 450)); treePane.setBorder(new EmptyBorder(0, 0, 0, 0)); - getContentPane().add(treePane); + + //getContentPane().add(treePane); + panel = treePane; } else { JPanel emptyPanel = new JPanel(); @@ -138,34 +165,51 @@ public class SketchbookFrame extends JFrame { emptyLabel.setForeground(Color.GRAY); emptyPanel.add(emptyLabel); - setContentPane(emptyPanel); + //setContentPane(emptyPanel); + panel = emptyPanel; } - pack(); + Container pane = getContentPane(); + //pane.setLayout(new BorderLayout()); + BorderLayout layout = (BorderLayout) pane.getLayout(); + Component comp = layout.getLayoutComponent(BorderLayout.CENTER); + if (comp != null) { + pane.remove(comp); + } + pane.add(panel, BorderLayout.CENTER); + } + + + public void rebuild() { + updateCenterPanel(); + // After replacing the tree/panel, this calls the layout manager, + // otherwise it'll just leave a frozen-looking component until + // the window is closed and re-opened. + getContentPane().validate(); } public void setVisible() { - // TODO The ExamplesFrame code doesn't do this, is it necessary? - // Either one is wrong or we're papering over something [fry 150811] - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - // Space for the editor plus a li'l gap - int roughWidth = getWidth() + 20; - Point p = null; - // If no window open, or the editor is at the edge of the screen - Editor editor = base.getActiveEditor(); - if (editor == null || - (p = editor.getLocation()).x < roughWidth) { + // TODO The ExamplesFrame code doesn't invokeLater(), is it necessary? + // Either one of them is wrong, or this is hiding a bug [fry 150811] + EventQueue.invokeLater(() -> { + // Space for the editor plus a li'l gap + int roughWidth = getWidth() + 20; + // If no window open, or the editor is at the edge of the screen + Editor editor = base.getActiveEditor(); + if (editor == null) { + setLocationRelativeTo(null); + } else { + Point p = editor.getLocation(); + if (p.x < roughWidth) { // Center the window on the screen setLocationRelativeTo(null); } else { // Open the window relative to the editor setLocation(p.x - roughWidth, p.y); } - setVisible(true); } + setVisible(true); }); } } diff --git a/app/src/processing/app/ui/Splash.java b/app/src/processing/app/ui/Splash.java old mode 100755 new mode 100644 index 5d9789ec9..a61586893 --- a/app/src/processing/app/ui/Splash.java +++ b/app/src/processing/app/ui/Splash.java @@ -1,63 +1,71 @@ package processing.app.ui; -import processing.app.Platform; - import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Image; import java.awt.MediaTracker; import java.awt.Toolkit; + +import java.io.File; +import java.io.FileOutputStream; + import javax.swing.JComponent; import javax.swing.JFrame; -import java.io.File; + +import processing.app.Platform; /** - * Show a splash screen window. Loosely based on SplashWindow.java from - * Werner Randelshofer, but rewritten to use Swing because the java.awt - * version doesn't render properly on Windows. + * Show a splash screen window. Loosely based on SplashWindow.java + * from Werner Randelshofer, but rewritten to use Swing because the + * java.awt version doesn't render properly on Windows. */ public class Splash extends JFrame { static private Splash instance; - private final Image image; - private Splash(File imageFile, boolean hidpi) { - this.image = - Toolkit.getDefaultToolkit().createImage(imageFile.getAbsolutePath()); +// private Splash(File imageFile, boolean hidpi) { + private Splash(File image2xFile) { + // Putting this inside try/catch because it's not essential, + // and it's definitely not essential enough to prevent startup. + try { + // change default java window icon to processing icon + processing.app.ui.Toolkit.setIcon(this); + } catch (Exception e) { + // ignored + } + + Image image2x = + Toolkit.getDefaultToolkit().createImage(image2xFile.getAbsolutePath()); MediaTracker tracker = new MediaTracker(this); - tracker.addImage(image,0); + tracker.addImage(image2x, 0); try { tracker.waitForID(0); } catch (InterruptedException ignored) { } - if (tracker.isErrorID(0)) { - // Abort on failure + if (tracker.isErrorID(0)) { // abort on failure setSize(0,0); System.err.println("Warning: SplashWindow couldn't load splash image."); synchronized (this) { notifyAll(); } } else { - final int imgWidth = image.getWidth(this); - final int imgHeight = image.getHeight(this); - final int imgScale = hidpi ? 2 : 1; + final int wide = image2x.getWidth(this) / 2; + final int high = image2x.getHeight(this) / 2; JComponent comp = new JComponent() { - final int wide = imgWidth / imgScale; - final int high = imgHeight / imgScale; - public void paintComponent(Graphics g) { - g.drawImage(image, 0, 0, wide, high, this); + processing.app.ui.Toolkit.prepareGraphics(g); + g.drawImage(image2x, 0, 0, wide, high, this); } public Dimension getPreferredSize() { return new Dimension(wide, high); } }; - comp.setSize(imgWidth, imgHeight); + comp.setSize(wide, high); setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0)); getContentPane().add(comp); setUndecorated(true); // before pack() @@ -68,9 +76,10 @@ public class Splash extends JFrame { /** Open a splash window using the specified image. */ - static void showSplash(File imageFile, boolean hidpi) { +// static void showSplash(File imageFile, boolean hidpi) { + static void showSplash(File image2xFile) { if (instance == null) { - instance = new Splash(imageFile, hidpi); + instance = new Splash(image2xFile); instance.setVisible(true); } } @@ -79,7 +88,6 @@ public class Splash extends JFrame { /** Closes the splash window when finished. */ static void disposeSplash() { if (instance != null) { - //instance.getOwner().dispose(); instance.dispose(); instance = null; } @@ -90,10 +98,11 @@ public class Splash extends JFrame { * Invokes the main method of the provided class name. * @param args the command line arguments */ + @SuppressWarnings("SameParameterValue") static void invokeMain(String className, String[] args) { try { Class.forName(className) - .getMethod("main", new Class[] {String[].class}) + .getMethod("main", String[].class) .invoke(null, new Object[] { args }); } catch (Exception e) { @@ -102,12 +111,65 @@ public class Splash extends JFrame { } - static public void main(String[] args) { +// /** +// * Load the optional properties.txt file from the 'lib' sub-folder +// * that can be used to pass entries to System.properties. +// */ +// static private void initProperties() { +// try { +// File propsFile = Platform.getContentFile("properties.txt"); +// if (propsFile != null && propsFile.exists()) { +// Settings props = new Settings(propsFile); +// for (Map.Entry entry : props.getMap().entrySet()) { +// System.setProperty(entry.getKey(), entry.getValue()); +// } +// } +// } catch (Exception e) { +// // No crying over spilt milk, but... +// e.printStackTrace(); +// } +// } + + + static public boolean getDisableHiDPI() { + File propsFile = Platform.getContentFile("disable_hidpi"); + return propsFile != null && propsFile.exists(); + } + + + // Should only be called from Windows, but not restricted to Windows + // so not enforced. Unlikely to work on macOS because it modifies + // a file inside the .app, but may be useful on Linux. + static public void setDisableHiDPI(boolean disabled) { try { - final boolean hidpi = processing.app.ui.Toolkit.highResImages(); - final String filename = "lib/about-" + (hidpi ? 2 : 1) + "x.png"; - File splashFile = Platform.getContentFile(filename); - showSplash(splashFile, hidpi); + File propsFile = Platform.getContentFile("disable_hidpi"); + if (propsFile != null) { + if (disabled) { + if (!propsFile.exists()) { // don't recreate if exists + new FileOutputStream(propsFile).close(); + } + } else if (propsFile.exists()) { + boolean success = propsFile.delete(); + if (!success) { + System.err.println("Could not delete disable_hidpi"); + } + } + } + } catch (Exception e) { + // No crying over spilt milk, but... + e.printStackTrace(); + } + } + + + static public void main(String[] args) { + // Has to be done before AWT is initialized, so the hack lives here + // instead of Base or anywhere else that might make more sense. + if (getDisableHiDPI()) { + System.setProperty("sun.java2d.uiScale.enabled", "false"); + } + try { + showSplash(Platform.getContentFile("lib/about-2x.png")); invokeMain("processing.app.Base", args); disposeSplash(); diff --git a/app/src/processing/app/ui/Theme.java b/app/src/processing/app/ui/Theme.java index 21546c790..edc14bf58 100644 --- a/app/src/processing/app/ui/Theme.java +++ b/app/src/processing/app/ui/Theme.java @@ -24,7 +24,7 @@ package processing.app.ui; import processing.app.Base; import processing.app.Messages; -import processing.app.Platform; +import processing.app.Preferences; import processing.app.Settings; import processing.app.syntax.SyntaxStyle; import processing.core.PApplet; @@ -39,36 +39,41 @@ import java.util.Arrays; public class Theme { + static final String DEFAULT_PATH = "Minerals/kyanite.txt"; static Settings theme; static public void init() { try { - File inputFile = Platform.getContentFile("lib/theme.txt"); - if (inputFile == null) { - throw new RuntimeException("Missing required file (theme.txt), you may need to reinstall."); + File inputFile = getDefaultFile(); + if (!inputFile.exists()) { + System.err.println("Missing required file (theme.txt), please reinstall Processing."); } - // First load the default theme data for the whole PDE. + // First load the default theme data, in case new parameters were added + // that may not be covered with a custom version found in the sketchbook. theme = new Settings(inputFile); - // A spot-check of Modes shows that theme.txt is not being overridden, - // so removing this (questionable, warned against) capability for 4.0a6. - /* - // The mode-specific theme.txt file should only contain additions, - // and in extremely rare cases, it might override entries from the - // main theme. Do not override for style changes unless they are - // objectively necessary for your Mode. - File modeTheme = new File(folder, "theme/theme.txt"); - if (modeTheme.exists()) { - // Override the built-in settings with what the theme provides - theme.load(modeTheme); - } - */ - // other things that have to be set explicitly for the defaults theme.setColor("run.window.bgcolor", SystemColor.control); - // pull in the version from the user's sketchbook folder - load(); + if (Preferences.get("theme") == null) { + // This is not being set in defaults.txt so that we have a way + // to reset the theme after the major changes in 4.0 beta 9. + // This does a one-time archival of the theme.txt file in the + // sketchbook folder, because most people have not customized + // their theme, but they probably made a selection. + // If they customized the theme, they can bring it back by + // renaming the file from theme.001 to theme.txt. + // If they were using a built-in theme, they will need to + // re-select it using the Theme Selector. + Preferences.set("theme", DEFAULT_PATH); + + if (getSketchbookFile().exists()) { + archiveCurrent(); + } + } + + // load sketchbook theme or the one specified in preferences + reload(); } catch (IOException e) { Messages.showError("Problem loading theme.txt", @@ -78,13 +83,37 @@ public class Theme { /** - * Load theme.txt from the user's sketchbook folder. + * Pull in the version from the user's sketchbook folder, + * or if none exists, use the setting from preferences. */ - static public void load() { + static public void reload() { + if (!loadSketchbookFile()) { + String prefTheme = Preferences.get("theme"); + try { + File prefFile = new File(getThemeFolder(), prefTheme); + if (prefFile.exists()) { + theme.load(prefFile); + } + } catch (IOException e) { + Messages.showWarning("Theme Reload Problem", + "Error while reloading the theme. Please report.", e); + } + } + } + + + /** + * Load theme.txt from the user's sketchbook folder. + * The caller is expected to make sure the file exists. + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + static public boolean loadSketchbookFile() { File sketchbookTheme = getSketchbookFile(); if (sketchbookTheme.exists()) { theme.load(sketchbookTheme); + return true; } + return false; } @@ -93,11 +122,55 @@ public class Theme { } + static public File getThemeFolder() throws IOException { + return Base.getLibFile("theme"); + } + + + /** + * Returns lib/theme/theme.txt in the Processing installation. + */ + static public File getDefaultFile() throws IOException { + return new File(getThemeFolder(), "theme.txt"); + } + + static public File getSketchbookFile() { return new File(Base.getSketchbookFolder(), "theme.txt"); } + /** + * If the user has a custom theme they've modified, rename it to theme.001, + * theme.002, etc. as a backup to avoid overwriting anything they've created. + */ + static public boolean archiveCurrent() { + File backupFile = nextArchiveFile(); + return getSketchbookFile().renameTo(backupFile); + } + + + static private File nextArchiveFile() { + int index = 0; + File backupFile; + do { + index++; + backupFile = new File(Base.getSketchbookFolder(), String.format("theme.%03d", index)); + } while (backupFile.exists()); + return backupFile; + } + + + static public void print() { + theme.print(); + } + + + static public String get(String attribute) { + return theme.get(attribute); + } + + static public boolean getBoolean(String attribute) { return theme.getBoolean(attribute); } @@ -129,6 +202,15 @@ public class Theme { static public Image makeGradient(String attribute, int wide, int high) { + if ("lab".equals(Preferences.get("theme.gradient.method"))) { + return makeGradientLab(attribute, wide, high); + } else { // otherwise go with the default + return makeGradientRGB(attribute, wide, high); + } + } + + + static private Image makeGradientRGB(String attribute, int wide, int high) { int top = getColor(attribute + ".gradient.top").getRGB(); int bot = getColor(attribute + ".gradient.bottom").getRGB(); @@ -143,4 +225,156 @@ public class Theme { } return outgoing; } + + + static private Image makeGradientLab(String attribute, int wide, int high) { + double[] top = xyzToLab(rgbToXyz(getColor(attribute + ".gradient.top"))); + double[] bot = xyzToLab(rgbToXyz(getColor(attribute + ".gradient.bottom"))); + + double diffL = bot[0] - top[0]; + double diffA = bot[1] - top[1]; + double diffB = bot[2] - top[2]; + + BufferedImage outgoing = + new BufferedImage(wide, high, BufferedImage.TYPE_INT_RGB); + int[] row = new int[wide]; + WritableRaster wr = outgoing.getRaster(); + for (int i = 0; i < high; i++) { + double amt = i / (high - 1.0); + double el = top[0] + amt * diffL; + double ay = top[1] + amt * diffA; + double be = top[2] + amt * diffB; + int rgb = argb(xzyToRgb(labToXyz(el, ay, be))); + Arrays.fill(row, rgb); + wr.setDataElements(0, i, wide, 1, row); + } + return outgoing; + } + + + // https://web.archive.org/web/20060213080500/http://www.easyrgb.com/math.php?MATH=M2#text2 + + // Observer= 2°, Illuminant= D65; + static final double REF_X = 95.047; + static final double REF_Y = 100.0; + static final double REF_Z = 108.883; + + + static private double[] rgbToXyz(Color color) { + double var_R = color.getRed() / 255.0; + double var_G = color.getGreen() / 255.0; + double var_B = color.getBlue() / 255.0; + + var_R = 100 * ((var_R > 0.04045) ? + Math.pow((var_R + 0.055) / 1.055, 2.4) : var_R / 12.92); + var_G = 100 * ((var_G > 0.04045) ? + Math.pow((var_G + 0.055) / 1.055, 2.4) : var_G / 12.92); + var_B = 100 * ((var_B > 0.04045) ? + Math.pow((var_B + 0.055) / 1.055, 2.4) : var_B / 12.92); + + // Observer = 2°, Illuminant = D65 + return new double[] { + var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805, + var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722, + var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505 + }; + } + + + static private double[] xyzToLab(double[] xyz) { + double var_X = xyz[0] / REF_X; // Observer= 2°, Illuminant= D65 + double var_Y = xyz[1] / REF_Y; + double var_Z = xyz[2] / REF_Z; + + var_X = (var_X > 0.008856) ? + Math.pow(var_X, 1/3.0) : (7.787*var_X + 16/116.0); + + var_Y = (var_Y > 0.008856) ? + Math.pow(var_Y, 1/3.0) : (7.787*var_Y + 16/116.0); + + var_Z = (var_Z > 0.008856) ? + Math.pow(var_Z, 1/3.0) : (7.787*var_Z + 16/116.0); + + return new double[] { + (116 * var_Y) - 16, + 500 * (var_X - var_Y), + 200 * (var_Y - var_Z) + }; + } + +// static private double[] labToXyz(double[] lab) { +// double var_Y = (lab[0] + 16) / 116.0; +// double var_X = lab[1] / 500 + var_Y; +// double var_Z = var_Y - lab[2] / 200.0; + static private double[] labToXyz(double el, double ay, double be) { + double var_Y = (el + 16) / 116; + double var_X = ay / 500 + var_Y; + double var_Z = var_Y - be / 200; + +// if ( var_Y^3 > 0.008856 ) var_Y = var_Y^3 +// else var_Y = ( var_Y - 16 / 116 ) / 7.787 +// if ( var_X^3 > 0.008856 ) var_X = var_X^3 +// else var_X = ( var_X - 16 / 116 ) / 7.787 +// if ( var_Z^3 > 0.008856 ) var_Z = var_Z^3 +// else var_Z = ( var_Z - 16 / 116 ) / 7.787 + + final double amt = Math.pow(0.008856, 1/3.0); + var_Y = (var_Y > amt) ? + Math.pow(var_Y, 3) : (var_Y - 16/116.0) / 7.787; + var_X = (var_X > amt) ? + Math.pow(var_X, 3) : (var_X - 16/116.0) / 7.787; + var_Z = (var_Z > amt) ? + Math.pow(var_Z, 3) : (var_Z - 16/116.0) / 7.787; + +// X = ref_X * var_X //ref_X = 95.047 Observer= 2°, Illuminant= D65 +// Y = ref_Y * var_Y //ref_Y = 100.000 +// Z = ref_Z * var_Z //ref_Z = 108.883 + return new double[] { + REF_X * var_X, + REF_Y * var_Y, + REF_Z * var_Z + }; + } + + + static private double[] xzyToRgb(double[] xyz) { + double var_X = xyz[0] / 100; // Where X = 0 ÷ 95.047 + double var_Y = xyz[1] / 100; // Where Y = 0 ÷ 100.000 + double var_Z = xyz[2] / 100; // Where Z = 0 ÷ 108.883 + + double var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986; + double var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415; + double var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570; + +// if ( var_R > 0.0031308 ) var_R = 1.055 * ( var_R ^ ( 1 / 2.4 ) ) - 0.055 +// else var_R = 12.92 * var_R +// if ( var_G > 0.0031308 ) var_G = 1.055 * ( var_G ^ ( 1 / 2.4 ) ) - 0.055 +// else var_G = 12.92 * var_G +// if ( var_B > 0.0031308 ) var_B = 1.055 * ( var_B ^ ( 1 / 2.4 ) ) - 0.055 +// else var_B = 12.92 * var_B + + var_R = (var_R > 0.0031308) ? + 1.055 * Math.pow(var_R, 1/2.4) - 0.055 : 12.92 * var_R; + var_G = (var_G > 0.0031308) ? + 1.055 * Math.pow(var_G, 1/2.4) - 0.055 : 12.92 * var_G; + var_B = (var_B > 0.0031308) ? + 1.055 * Math.pow(var_B, 1/2.4) - 0.055 : 12.92 * var_B; + + return new double[] { + var_R * 255, + var_G * 255, + var_B * 255 + }; + } + + + static private int bounded(double amount) { + return Math.max(0, Math.min((int) Math.round(amount), 255)); + } + + + static private int argb(double[] rgb) { + return 0xff000000 | + bounded(rgb[0]) << 16 | bounded(rgb[1]) << 8 | bounded(rgb[2]); + } } \ No newline at end of file diff --git a/app/src/processing/app/ui/Toolkit.java b/app/src/processing/app/ui/Toolkit.java index ea741334a..a31008759 100644 --- a/app/src/processing/app/ui/Toolkit.java +++ b/app/src/processing/app/ui/Toolkit.java @@ -30,8 +30,6 @@ import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; -import java.awt.GraphicsConfiguration; -import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Image; import java.awt.RenderingHints; @@ -54,13 +52,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; -import java.util.Locale; import java.util.regex.Pattern; import javax.swing.Action; -import javax.swing.Icon; import javax.swing.ImageIcon; -import javax.swing.JButton; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JMenu; @@ -69,15 +64,21 @@ import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JRootPane; import javax.swing.KeyStroke; -import javax.swing.border.EmptyBorder; +import javax.swing.text.html.HTMLEditorKit; +import javax.swing.text.html.StyleSheet; import processing.app.Language; import processing.app.Messages; import processing.app.Platform; import processing.app.Preferences; import processing.app.Util; +import processing.awt.PGraphicsJava2D; +import processing.awt.PShapeJava2D; import processing.core.PApplet; +import processing.core.PShape; +import processing.data.StringDict; import processing.data.StringList; +import processing.data.XML; /** @@ -155,7 +156,7 @@ 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: + * 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- */ static public JMenuItem newJMenuItemExt(String base) { @@ -254,8 +255,8 @@ public class Toolkit { * 'A'. *
  • If the first letters are all taken/non-ASCII, then it loops through the * ASCII letters in the item, widest to narrowest, seeing if any of them is not taken. - * To improve readability, it discriminates against decenders (qypgj), imagining they - * have 2/3 their actual width. (MS guidelines: avoid decenders). It also discriminates + * To improve readability, it discriminates against descenders (qypgj), imagining they + * have 2/3 their actual width. (MS guidelines: avoid descenders). It also discriminates * against vowels, imagining they have 2/3 their actual width. (MS and Gnome guidelines: * avoid vowels.)
  • *
  • Failing that, it will loop left-to-right for an available digit. This is a last @@ -281,8 +282,9 @@ public class Toolkit { // The English is http://techbase.kde.org/Projects/Usability/HIG/Keyboard_Accelerators, // made lowercase. - // Nothing but [a-z] except for '&' before mnemonics and regexes for changable text. - final String[] kdePreDefStrs = { "&file", "&new", "&open", "open&recent", + // Nothing but [a-z] except for '&' before mnemonics and regexes for changeable text. + final String[] kdePreDefStrs = { + "&file", "&new", "&open", "open&recent", "&save", "save&as", "saveacop&y", "saveas&template", "savea&ll", "reloa&d", "&print", "printpre&view", "&import", "e&xport", "&closefile", "clos&eallfiles", "&quit", "&edit", "&undo", "re&do", "cu&t", "©", @@ -298,9 +300,10 @@ public class Toolkit { "&newbookmarksfolder", "&tools", "&settings", "&toolbars", "configure&shortcuts", "configuretool&bars", "&configure.*", "&help", ".+&handbook", "&whatsthis", "report&bug", "&aboutprocessing", "about&kde", - "&beenden", "&suchen", // de - "&preferncias", "&sair", // Preferências; pt - "&rechercher" }; // fr + "&beenden", "&suchen", // de + "&preferncias", "&sair", // Preferências; pt + "&rechercher" // fr + }; Pattern[] kdePreDefPats = new Pattern[kdePreDefStrs.length]; for (int i = 0; i < kdePreDefStrs.length; i++) { kdePreDefPats[i] = Pattern.compile(kdePreDefStrs[i].replace("&","")); @@ -548,6 +551,18 @@ public class Toolkit { } + /* + static public String getLibString(String filename) { + File file = Platform.getContentFile("lib/" + filename); + if (file == null || !file.exists()) { + Messages.err("does not exist: " + file); + return null; + } + return PApplet.join(PApplet.loadStrings(file), "\n"); + } + */ + + /** * Get an icon of the format base-NN.png where NN is the size, but if it's * a hidpi display, get the NN*2 version automatically, sized at NN @@ -562,6 +577,9 @@ public class Toolkit { return null; } + // Not broken into separate class because it requires (and is + // optimized for) an image file, and the SVG version does not. + // Also moving away from images from files anyway. [fry 220501] return new ImageIcon(file.getAbsolutePath()) { @Override public int getIconWidth() { @@ -599,28 +617,28 @@ public class Toolkit { } - /** - * Create a JButton with an icon, and set its disabled and pressed images - * to be the same image, so that 2x versions of the icon work properly. - */ - static public JButton createIconButton(String title, String base) { - ImageIcon icon = Toolkit.getLibIconX(base); - return createIconButton(title, icon); - } - - - /** Same as above, but with no text title (follows JButton constructor) */ - static public JButton createIconButton(String base) { - return createIconButton(null, base); - } - - - static public JButton createIconButton(String title, Icon icon) { - JButton button = new JButton(title, icon); - button.setDisabledIcon(icon); - button.setPressedIcon(icon); - return button; - } +// /** +// * Create a JButton with an icon, and set its disabled and pressed images +// * to be the same image, so that 2x versions of the icon work properly. +// */ +// static public JButton createIconButton(String title, String base) { +// ImageIcon icon = Toolkit.getLibIconX(base); +// return createIconButton(title, icon); +// } +// +// +// /** Same as above, but with no text title (follows JButton constructor) */ +// static public JButton createIconButton(String base) { +// return createIconButton(null, base); +// } +// +// +// static public JButton createIconButton(String title, Icon icon) { +// JButton button = new JButton(title, icon); +// button.setDisabledIcon(icon); +// button.setPressedIcon(icon); +// return button; +// } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -629,9 +647,11 @@ public class Toolkit { static List iconImages; - // Deprecated version of the function, but can't get rid of it without - // breaking tools and modes (they'd only require a recompile, but they would - // no longer be backwards compatible. + /** + * Unnecessary version of the function, but can't get rid of it + * without breaking tools and modes (they'd only require a recompile, + * but they would no longer be backwards compatible). + */ static public void setIcon(Frame frame) { setIcon((Window) frame); } @@ -656,6 +676,124 @@ public class Toolkit { } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + /** + * Create an Icon object from an SVG path name. + * @param name subpath relative to lib/ + * @param color color to apply to the monochrome icon + * @param size size in pixels (handling for 2x done automatically) + */ + static public ImageIcon renderIcon(String name, String color, int size) { + return renderIcon(Platform.getContentFile("lib/" + name + ".svg"), color, size); + } + + + /** + * Create an Icon object from an SVG path name. + * @param file full path to icon + * @param color color to apply to the monochrome icon + * @param size size in pixels (handling for 2x done automatically) + */ + static public ImageIcon renderIcon(File file, String color, int size) { + Image image = renderMonoImage(file, color, size); + if (image == null) { + return null; + } + final int scale = Toolkit.highResImages() ? 2 : 1; + + return new ImageIcon(image) { + @Override + public int getIconWidth() { + return Toolkit.zoom(super.getIconWidth()) / scale; + } + + @Override + public int getIconHeight() { + return Toolkit.zoom(super.getIconHeight()) / scale; + } + + @Override + public synchronized void paintIcon(Component c, Graphics g, int x, int y) { + ImageObserver imageObserver = getImageObserver(); + if (imageObserver == null) { + imageObserver = c; + } + g.drawImage(getImage(), x, y, getIconWidth(), getIconHeight(), imageObserver); + } + }; + } + + + static protected Image renderMonoImage(File file, String color, int size) { + String xmlOrig = Util.loadFile(file); + + if (xmlOrig != null) { + StringDict replace = new StringDict(new String[][] { + { "#9B9B9B", color } + }); + return Toolkit.svgToImageMult(xmlOrig, size, size, replace); + } + return null; + } + + + /* + static private Image svgToImageMult(String xmlStr, int wide, int high) { + return svgToImage(xmlStr, highResMultiply(wide), highResMultiply(high)); + } + */ + + + static public Image svgToImageMult(String xmlStr, int wide, int high, StringDict replacements) { + /* + for (StringDict.Entry entry : replacements.entries()) { + xmlStr = xmlStr.replace(entry.key, entry.value); + } + */ + // 2-pass version to avoid re-assigning identical colors + // (Otherwise, if a color is set to #666666 before #666666 is + // re-assigned to its new color, the swap will happen twice.) + for (StringDict.Entry entry : replacements.entries()) { + xmlStr = xmlStr.replace(entry.key, "$" + entry.key.hashCode() + "$"); + } + for (StringDict.Entry entry : replacements.entries()) { + xmlStr = xmlStr.replace("$" + entry.key.hashCode() + "$", entry.value); + } + return svgToImage(xmlStr, highResMultiply(wide), highResMultiply(high)); + } + + + /** + * Render an SVG, passed in as a String, into an AWT Image at + * the specified width and height. Used for interface buttons. + */ + static private Image svgToImage(String xmlStr, int wide, int high) { + PGraphicsJava2D pg = new PGraphicsJava2D(); + pg.setPrimary(false); + pg.setSize(wide, high); + pg.smooth(); + + pg.beginDraw(); + + try { + XML xml = XML.parse(xmlStr); + PShape shape = new PShapeJava2D(xml); + pg.shape(shape, 0, 0, wide, high); + + } catch (Exception e) { + e.printStackTrace(); + } + + pg.endDraw(); + return pg.image; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static public Shape createRoundRect(float x1, float y1, float x2, float y2, float tl, float tr, float br, float bl) { GeneralPath path = new GeneralPath(); @@ -722,32 +860,65 @@ public class Toolkit { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + /* + static final boolean ISSUE_342 = false; + + //static private float dpiScale(Component comp) { + static private float dpiScale() { + if (Platform.isWindows()) { + return awtToolkit.getScreenResolution() / 96f; +// return comp.getToolkit().getScreenResolution() / 96f; + } + return Toolkit.isRetina() ? 2 : 1; + } + + + static private int dpiScale(int what) { + return (int) Math.floor(what * dpiScale()); + } + */ + + /** * Create an Image to be used as an offscreen drawing context, * automatically doubling the size if running on a retina display. */ static public Image offscreenGraphics(Component comp, int width, int height) { +// if (ISSUE_342) { +// return comp.createImage(dpiScale(width), dpiScale(height)); +// } int m = Toolkit.isRetina() ? 2 : 1; - //return comp.createImage(m * dpi(width), m * dpi(height)); return comp.createImage(m * width, m * height); } + static public Graphics2D prepareGraphics(Graphics g) { + return prepareGraphics(g, false); + } + + /** * Handles scaling for high-res displays, also sets text anti-aliasing * options to be far less ugly than the defaults. * Moved to a utility function because it's used in several classes. * @return a Graphics2D object, as a bit o sugar */ - static public Graphics2D prepareGraphics(Graphics g) { + static public Graphics2D prepareGraphics(Graphics g, boolean scale) { Graphics2D g2 = (Graphics2D) g; - //float z = zoom * (Toolkit.isRetina() ? 2 : 1); - if (Toolkit.isRetina()) { + if (scale && Toolkit.isRetina()) { // scale everything 2x, will be scaled down when drawn to the screen g2.scale(2, 2); } - //g2.scale(z, z); + +// float s = dpiScale(); +// if (s != 1) { +// if (ISSUE_342) { +// System.out.println("Toolkit.prepareGraphics() with dpi scale " + s); +// } +// g2.scale(s, s); +// } + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, @@ -759,7 +930,9 @@ public class Toolkit { g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); } - zoomStroke(g2); + if (scale) { + zoomStroke(g2); + } return g2; } @@ -847,6 +1020,7 @@ public class Toolkit { } + /* static public final int BORDER = Platform.isMacOS() ? 20 : 13; @@ -860,6 +1034,7 @@ public class Toolkit { comp.setBorder(new EmptyBorder(Toolkit.zoom(top), Toolkit.zoom(left), Toolkit.zoom(bottom), Toolkit.zoom(right))); } + */ static private float parseZoom() { @@ -909,6 +1084,16 @@ public class Toolkit { } + static public int highResMultiplier() { + return highResImages() ? 2 : 1; + } + + + static public int highResMultiply(int amount) { + return highResImages() ? 2*amount : amount; + } + + static public boolean isRetina() { if (retinaProp == null) { retinaProp = checkRetina(); @@ -921,13 +1106,12 @@ public class Toolkit { // A 5-minute search didn't turn up any such event in the Java API. // Also, should we use the Toolkit associated with the editor window? static private boolean checkRetina() { - GraphicsDevice graphicsDevice = GraphicsEnvironment - .getLocalGraphicsEnvironment() - .getDefaultScreenDevice(); - GraphicsConfiguration graphicsConfig = graphicsDevice - .getDefaultConfiguration(); + AffineTransform tx = GraphicsEnvironment + .getLocalGraphicsEnvironment() + .getDefaultScreenDevice() + .getDefaultConfiguration() + .getDefaultTransform(); - AffineTransform tx = graphicsConfig.getDefaultTransform(); return Math.round(tx.getScaleX()) == 2; } @@ -987,7 +1171,7 @@ public class Toolkit { families.appendUnique(font.getFamily()); } families.sort(); - return families.array(); + return families.toArray(); } @@ -999,11 +1183,7 @@ public class Toolkit { /** 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) - getMonoFont(12, Font.PLAIN); - } - return monoFont.getName(); + return getMonoFont(12, Font.PLAIN).getName(); } @@ -1015,28 +1195,30 @@ public class Toolkit { * 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 { - monoFont = createFont("SourceCodePro-Regular.ttf", size); - monoBoldFont = createFont("SourceCodePro-Bold.ttf", size); + // Prior to 4.0 beta 9, we had a manual override for + // individual languages to use SansSerif instead. + // In beta 9, that was moved to the language translation file. + // https://github.com/processing/processing/issues/2886 + // https://github.com/processing/processing/issues/4944 + String fontFamilyMono = Language.text("font.family.mono"); - // https://github.com/processing/processing/issues/2886 - // https://github.com/processing/processing/issues/4944 - String lang = Language.getLanguage(); - if ("el".equals(lang) || - "ar".equals(lang) || - Locale.CHINESE.getLanguage().equals(lang) || - Locale.JAPANESE.getLanguage().equals(lang) || - Locale.KOREAN.getLanguage().equals(lang)) { - sansFont = new Font("Monospaced", Font.PLAIN, size); - sansBoldFont = new Font("Monospaced", Font.BOLD, size); + if (monoFont == null || monoBoldFont == null) { + try { + if ("Source Code Pro".equals(fontFamilyMono)) { + monoFont = initFont("SourceCodePro-Regular.ttf", size); + monoBoldFont = initFont("SourceCodePro-Bold.ttf", size); } } catch (Exception e) { Messages.err("Could not load mono font", e); - monoFont = new Font("Monospaced", Font.PLAIN, size); - monoBoldFont = new Font("Monospaced", Font.BOLD, size); } } + + // If not using Source Code Pro above, or an Exception was thrown + if (monoFont == null || monoBoldFont == null) { + monoFont = new Font(fontFamilyMono, Font.PLAIN, size); + monoBoldFont = new Font(fontFamilyMono, Font.BOLD, size); + } + if (style == Font.BOLD) { if (size == monoBoldFont.getSize()) { return monoBoldFont; @@ -1054,45 +1236,43 @@ public class Toolkit { static public String getSansFontName() { - if (sansFont == null) { - // create a dummy version if the font has never been loaded (rare) - getSansFont(12, Font.PLAIN); - } - return sansFont.getName(); + return getSansFont(12, Font.PLAIN).getName(); } static public Font getSansFont(int size, int style) { - if (sansFont == null) { - try { - sansFont = createFont("ProcessingSansPro-Regular.ttf", size); - sansBoldFont = createFont("ProcessingSansPro-Semibold.ttf", size); + // Prior to 4.0 beta 9, we had a manual override for + // individual languages to use SansSerif instead. + // In beta 9, that was moved to the language translation file. + // https://github.com/processing/processing/issues/2886 + // https://github.com/processing/processing/issues/4944 + String fontFamilySans = Language.text("font.family.sans"); - // https://github.com/processing/processing/issues/2886 - // https://github.com/processing/processing/issues/4944 - String lang = Language.getLanguage(); - if ("el".equals(lang) || - "ar".equals(lang) || - Locale.CHINESE.getLanguage().equals(lang) || - Locale.JAPANESE.getLanguage().equals(lang) || - Locale.KOREAN.getLanguage().equals(lang)) { - sansFont = new Font("SansSerif", Font.PLAIN, size); - sansBoldFont = new Font("SansSerif", Font.BOLD, size); + if (sansFont == null || sansBoldFont == null) { + try { + if ("Processing Sans".equals(fontFamilySans)) { + sansFont = initFont("ProcessingSans-Regular.ttf", size); + sansBoldFont = initFont("ProcessingSans-Bold.ttf", size); } } catch (Exception e) { Messages.err("Could not load sans font", e); - sansFont = new Font("SansSerif", Font.PLAIN, size); - sansBoldFont = new Font("SansSerif", Font.BOLD, size); } } + + // If not using "Processing Sans" above, or an Exception was thrown + if (sansFont == null || sansBoldFont == null) { + sansFont = new Font(fontFamilySans, Font.PLAIN, size); + sansBoldFont = new Font(fontFamilySans, Font.BOLD, size); + } + if (style == Font.BOLD) { - if (size == sansBoldFont.getSize()) { + if (size == sansBoldFont.getSize() || size == 0) { return sansBoldFont; } else { return sansBoldFont.deriveFont((float) size); } } else { - if (size == sansFont.getSize()) { + if (size == sansFont.getSize() || size == 0) { return sansFont; } else { return sansFont.deriveFont((float) size); @@ -1102,12 +1282,12 @@ public class Toolkit { /** - * Get a font from the lib/fonts folder. Our default fonts are also - * installed there so that the monospace (and others) can be used by other - * font listing calls (i.e. it appears in the list of monospace fonts in - * the Preferences window, and can be used by HTMLEditorKit for WebFrame). + * Load a built-in font from the Processing lib/fonts folder and register + * it with the GraphicsEnvironment so that it's broadly available. + * (i.e. shows up in getFontList() works, so it appears in the list of fonts + * in the Preferences window, and can be used by HTMLEditorKit for WebFrame.) */ - static private Font createFont(String filename, int size) throws IOException, FontFormatException { + static private Font initFont(String filename, int size) throws IOException, FontFormatException { File fontFile = Platform.getContentFile("lib/fonts/" + filename); if (fontFile == null || !fontFile.exists()) { @@ -1128,8 +1308,8 @@ public class Toolkit { Font font = Font.createFont(Font.TRUETYPE_FONT, input); input.close(); - GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); - ge.registerFont(font); + // Register the font to be available for other function calls + GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font); return font.deriveFont((float) size); } @@ -1145,4 +1325,82 @@ public class Toolkit { //return new TextLayout("H", font, frc).getBounds().getHeight(); return new TextLayout("H", g.getFont(), frc).getBounds().getHeight(); } + + + static public String formatMessage(String message) { + String monoName = "Monospaced"; + try { + monoName = Toolkit.getMonoFontName(); + } catch (Exception ignored) { } + + // Necessary to replace \n with
    (even if pre) otherwise Java + // treats it as a closed tag and reverts to plain formatting. + return " " + + " " + + message.replaceAll("\n", "
    "); + } + + + static public String formatMessage(String primary, String secondary) { + // Pane formatting originally adapted from the Quaqua guide + // http://www.randelshofer.ch/quaqua/guide/joptionpane.html + + // This code originally disabled unless Java 1.5 is in use on OS X + // because of a Java bug that prevents the initial value of the + // dialog from being set properly (at least on my MacBook Pro). + // The bug causes the "Don't Save" option to be the highlighted, + // blinking, default. This sucks. But I'll tell you what doesn't + // suck--workarounds for the Mac and Apple's snobby attitude about it! + // I think it's nifty that they treat their developers like dirt. + +// String monoName = "Monospaced"; +// try { +// monoName = Toolkit.getMonoFontName(); +// } catch (Exception ignored) { } + + // Necessary to replace \n with
    (even if pre) otherwise Java + // treats it as a closed tag and reverts to plain formatting. + return (" " + + " " + + // Extra   because the right-hand side of the text is cutting off. + "" + primary + " " + + "

    " + secondary + "

    ").replaceAll("\n", "
    "); + } + + + static public HTMLEditorKit createHtmlEditorKit() { + return new HTMLEditorKit() { + private StyleSheet style; + + @Override + public StyleSheet getStyleSheet() { + return style == null ? super.getStyleSheet() : style; + } + + @Override + public void setStyleSheet(StyleSheet s) { + this.style = s; + } + + public StyleSheet getDefaultStyleSheet() { + return super.getStyleSheet(); + } + + public void setDefaultStyleSheet(StyleSheet s) { + super.setStyleSheet(s); + } + }; + } } diff --git a/app/src/processing/app/ui/Welcome.java b/app/src/processing/app/ui/Welcome.java index 26f113394..5e4c4cf71 100644 --- a/app/src/processing/app/ui/Welcome.java +++ b/app/src/processing/app/ui/Welcome.java @@ -24,10 +24,7 @@ package processing.app.ui; import java.awt.Color; import java.awt.EventQueue; import java.awt.Font; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; import java.io.File; import java.io.IOException; @@ -35,12 +32,11 @@ import javax.swing.Box; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; +import javax.swing.border.EmptyBorder; import processing.app.Base; -import processing.app.Language; import processing.app.Platform; import processing.app.Preferences; -import processing.awt.ShimAWT; public class Welcome { @@ -48,30 +44,23 @@ public class Welcome { WebFrame view; - public Welcome(Base base, boolean sketchbook) throws IOException { + public Welcome(Base base) throws IOException { this.base = base; - // TODO this should live inside theme or somewhere modifiable - Font dialogFont = Toolkit.getSansFont(14, Font.PLAIN); - JComponent panel = Box.createHorizontalBox(); - panel.setBackground(new Color(245, 245, 245)); - Toolkit.setBorder(panel, 15, 20, 15, 20); + //panel.setBackground(new Color(245, 245, 245)); + panel.setBackground(Color.WHITE); + panel.setOpaque(true); + panel.setBorder(new EmptyBorder(15, 20, 15, 20)); - //panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); - //panel.add(Box.createHorizontalStrut(20)); JCheckBox checkbox = new JCheckBox("Show this message on startup"); - checkbox.setFont(dialogFont); // handles the Help menu invocation, and also the pref not existing - checkbox.setSelected("true".equals(Preferences.get("welcome.show"))); - checkbox.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getStateChange() == ItemEvent.SELECTED) { - Preferences.setBoolean("welcome.show", true); - } else if (e.getStateChange() == ItemEvent.DESELECTED) { - Preferences.setBoolean("welcome.show", false); - } + checkbox.setSelected("true".equals(Preferences.get("welcome.four.show"))); + checkbox.addItemListener(e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + Preferences.setBoolean("welcome.four.show", true); + } else if (e.getStateChange() == ItemEvent.DESELECTED) { + Preferences.setBoolean("welcome.four.show", false); } }); panel.add(checkbox); @@ -80,15 +69,13 @@ public class Welcome { JButton button = new JButton("Get Started"); button.setFont(Toolkit.getSansFont(14, Font.PLAIN)); - button.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - view.handleClose(); - } - }); + button.addActionListener(e -> view.handleClose()); panel.add(button); - //panel.add(Box.createHorizontalGlue()); - view = new WebFrame(getIndexFile(sketchbook), 425, panel) { + File indexFile = getIndexFile(); + if (indexFile == null) return; // giving up; error already printed + + view = new WebFrame(getIndexFile(), 420, panel) { /* @Override public void handleSubmit(StringDict dict) { @@ -111,18 +98,43 @@ public class Welcome { @Override public void handleLink(String link) { // The link will already have the full URL prefix + /* if (link.endsWith("#sketchbook")) { File folder = new File(Preferences.getSketchbookPath()).getParentFile(); ShimAWT.selectFolder(Language.text("preferences.sketchbook_location.popup"), "sketchbookCallback", folder, this); + */ + + if (link.endsWith("#examples")) { + base.getDefaultMode().showExamplesFrame(); + + } else if (link.endsWith("#mouse")) { + openExample("Basics/Input/Mouse2D/Mouse2D.pde"); + + } else if (link.endsWith("#arctan")) { + openExample("Basics/Math/Arctangent/Arctangent.pde"); + + } else if (link.endsWith("#flocking")) { + openExample("Topics/Simulate/Flocking/Flocking.pde"); + + } else if (link.endsWith("#rotating")) { + openExample("Demos/Graphics/RotatingArcs/RotatingArcs.pde"); + } else { super.handleLink(link); } } + private void openExample(String examplePath) { + File examplesFolder = + Platform.getContentFile("modes/java/examples"); + File pdeFile = new File(examplesFolder, examplePath); + base.handleOpen(pdeFile.getAbsolutePath()); + } + @Override public void handleClose() { - Preferences.setBoolean("welcome.seen", true); + Preferences.setBoolean("welcome.four.seen", true); Preferences.save(); super.handleClose(); } @@ -131,39 +143,33 @@ public class Welcome { } - /** Callback for the folder selector. */ - public void sketchbookCallback(File folder) { - if (folder != null) { - if (base != null) { - base.setSketchbookFolder(folder); -// } else { -// System.out.println("user selected " + folder); - } - } - } - - -// @Override -// public void handleClose() { -// dispose(); +// /** Callback for the folder selector. */ +// public void sketchbookCallback(File folder) { +// if (folder != null) { +// if (base != null) { +// base.setSketchbookFolder(folder); +// } +// } // } - static private File getIndexFile(boolean sketchbook) { - String filename = - "welcome/" + (sketchbook ? "sketchbook.html" : "generic.html"); + static private File getIndexFile() { + String filename = "welcome/index.html"; - // version when running from command line for editing - File htmlFile = new File("../build/shared/lib/" + filename); + // version when running from IntelliJ for editing + File htmlFile = new File("build/shared/lib/" + filename); if (htmlFile.exists()) { return htmlFile; } + + /* needs to be tested/updated for 4.0 // processing/build/macosx/work/Processing.app/Contents/Java // version for Scott to use for OS X debugging htmlFile = Platform.getContentFile("../../../../../shared/lib/" + filename); if (htmlFile.exists()) { return htmlFile; } + */ try { return Base.getLibFile(filename); @@ -176,14 +182,18 @@ public class Welcome { static public void main(String[] args) { Platform.init(); + try { + Platform.setLookAndFeel(); // set font for checkbox and button + } catch (Exception e) { + e.printStackTrace(); + } + Preferences.init(); - EventQueue.invokeLater(new Runnable() { - public void run() { - try { - new Welcome(null, true); - } catch (IOException e) { - e.printStackTrace(); - } + EventQueue.invokeLater(() -> { + try { + new Welcome(null); + } catch (IOException e) { + e.printStackTrace(); } }); } diff --git a/build/.gitignore b/build/.gitignore index 162fa4ac6..b645a0c25 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,15 +1,2 @@ -work +# after running the 'ant doc' target 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/README.md b/build/README.md index fbe97d019..a155278a6 100644 --- a/build/README.md +++ b/build/README.md @@ -1,86 +1,138 @@ -Processing Build Instructions -============================== -The following instructions describe how to build and release Processing across Windows, OS X, and Linux. +# How to Build Processing -
    -
    +The short version: + +1. Download and install JDK 17 from +2. Make sure `ant` is [installed](https://ant.apache.org/) for your platform. +3. Open a Terminal window/Command Prompt/whatever and type: + + cd /path/to/processing4/build + ant run -Local Environment Setup ------------------------------- +### Updating an older 4.x checkout + +If you checked out this repository before, but now it's behaving strangely, try the following: + + git pull + git checkout main + ant clean + ant clean-libs + ant run + +Major changes happened during the 4.x series, including a reorganization of the *core* library, changes to how `.jar` files for libraries were downloaded, and the `master` branch was renamed to `main`. + +The `ant clean-libs` task (added in 4.0 beta 9) clears out downloaded support files (JOGL, Ant, etc) and makes sure they're the latest versions. + + +### Java version complaints + +You might have multiple versions of Java installed. Type `java -version` and if it says something other than 17, you'll need to set the `JAVA_HOME` environment variable. + +On macOS, you can use: + + export JAVA_HOME="`/usr/libexec/java_home -v 17`" + +If you need to go back to Java 8 (i.e. to build Processing 3), you can use: + + export JAVA_HOME="`/usr/libexec/java_home -v 1.8`" + +On Windows and Linux, you can set `JAVA_HOME` to point at the installation the way you would any other environment variable. + +On Linux (Ubuntu 20.04 in particular), the headless version of OpenJDK may be installed by default. If so, you may get errors when trying to run tests in core: + + java.lang.UnsatisfiedLinkError: Can't load library: /usr/lib/jvm/java-17-openjdk-amd64/lib/libawt_xawt.so + +If so, use `sudo apt install openjdk-17-jdk` to install a full version. You could also make use of the JDK that's downloaded by Processing itself to avoid duplication, but that's a little trickier to get everything bootstrapped and (sym)linked properly. + + +# The Long Version + +A more detailed explanation of how to build and release Processing across Windows, macOS, and Linux, and a bit more about how the build system works. + + +## Local Environment Setup + Processing's ant-based build chain can create executables natively runnable for Linux, Mac, and Windows. -
    +### Pre-Requisites +Although Processing will download and use its own copy of OpenJDK and OpenJFX, the build chain itself requires Java 17 and Ant in addition to getting a copy of the Processing source code. -**Pre-Requisites** -Although Processing will download and use its own copy of OpenJDK and OpenJFX, the build chain itself requires Java 11+ and Ant in addition to getting a copy of the Processing source code. - -
    - -**Getting Java and Ant** +### Getting Java and Ant You can choose to install these yourself or use the following guides below: - - [Instructions for installing Java](https://adoptopenjdk.net/installation.html?variant=openjdk11&jvmVariant=hotspot#x64_mac-jdk) - - [Instructions for installing Ant](http://ant.apache.org/manual/install.html) - - Instructions for modifying your environment variables on [windows](https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/), [ mac](https://medium.com/@himanshuagarwal1395/setting-up-environment-variables-in-macos-sierra-f5978369b255) and [linux](https://www.cyberciti.biz/faq/set-environment-variable-linux/). - -
    - -**Getting Processing** -One will also need to clone the repository for Processing itself. Some users who are simply building Processing but not contributing to it may prefer a "shallow" clone which does not copy the full history of the repository: - -``` -git clone --depth 1 https://github.com/processing/processing4.git -``` - -Users that are developing for the project may require a full clone: +* [Instructions for installing Java](https://adoptopenjdk.net/installation.html?variant=openjdk17&jvmVariant=hotspot#x64_mac-jdk) +* [Instructions for installing Ant](http://ant.apache.org/manual/install.html) +* Instructions for modifying your environment variables on [Windows](https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/), [macOS](https://medium.com/@himanshuagarwal1395/setting-up-environment-variables-in-macos-sierra-f5978369b255) and [Linux](https://www.cyberciti.biz/faq/set-environment-variable-linux/). +### Download the Processing source code +One will also need to clone the repository for Processing itself. ``` git clone https://github.com/processing/processing4.git ``` -
    -
    -Building ------------------------------- +## Building + One can either build for your own operating system (the "host" operating system) or, starting with Processing 4, one can "cross-build" from a host nix system (linux or mac) to any other "target os" build. Before continuing, please be aware that you are agreeing to the [license terms](https://github.com/sampottinger/processing/blob/master/LICENSE.md) and that the developers do not make any guarantee or warranty (implicit or express) for any purpose. -
    -**Overview of steps** +### Overview of steps Before actually building, it is worth outlining the steps of the build process briefly: - The modules outside of `build` are built first. During this process, a number of automated unit tests will be executed with results displayed on the command line. - The `build` module itself will built and results will go into `build/{os}/work` where `{os}` is the operating system for which you are building like "windows". Note that both ARM and x64 builds go into the same OS directory. - During the build of `build`, the [OpenJDK](https://openjdk.java.net/) and [OpenJFX](https://openjfx.io/) will be downloaded with their binaries copied into the distribution. If building for the first time, these automated downloads from [AdoptOpenJDK](https://adoptopenjdk.net/) and [Gluon](https://gluonhq.com/) may take some time. -Note that one may need to "clean" via `ant linux-clean` or equivalent. +Note that one may need to “clean” via `ant clean`, which removes previous files (i.e. from the `work` folder). -
    -**Building for the "host" operating system** +### Building for the "host" operating system If building for your own system, navigate to where where you pulled the Processing source and execute `ant build` on the command line. ``` -$ cd [path to processing repository] -$ cd build -$ ant build +cd /path/to/processing4 +cd build +ant build ``` The results will go into `build/{os}/work` where `{os}` matches the "host" operating system. -
    -**Executing a "cross-build"** -If building for another operating system (if you are on Linux and want to build for Windows), there are a number of "cross-build" targets available. Note that one can only execute "cross-builds" on a *nix system (Linux or Mac) but "cross-builds" are available for all targeted operating systems. +## Running + +The build can be run directly or through Ant. + +### Executing via Ant +If built for the host operating system, one can use the `ant run` target as shown below: + +``` +cd /path/to/processing4 +cd build +ant run +``` + +If not yet built, this will cause Processing to be built prior to running. + +#### Using Executable Directly +Except when doing a cross-build (below), the build process creates executables that can be run directly on the target operating system: + + - **macOS**: the `.app` file can be executed via a double click at `build/macosx/work/Processing.app` or `open build/macosx/work/Processing.app`. + - **Linux**: The resulting executable ends up at `build/linux/work/processing`. + - **Windows**: The resulting executable ends up at `build/windows/work/processing.exe`. + + +## Advanced: cross platform builds, tests, and distribution + +### Executing a "cross-build" +If building for another operating system (if you are on Linux and want to build for Windows), there are a number of "cross-build" targets available. Note that one can only execute "cross-builds" on a \*nix system (Linux or Mac) but "cross-builds" are available for all targeted operating systems. For example, here is how to execute a cross-build "targeting" Windows (the results will go into `build/windows/work`): ``` -$ cd [path to processing repository] -$ cd build -$ ant cross-build-windows +cd /path/to/processing4 +cd build +ant cross-build-windows ``` The following cross-build targets are available: @@ -90,60 +142,25 @@ The following cross-build targets are available: - cross-build-linux-aarch64 - cross-build-windows -
    -
    -Automated Tests ------------------------------- +### Automated Tests + Unit tests are available by running the test target: ``` -$ cd [path to processing repository] -$ cd build -$ ant test +cd /path/to/processing4 +cd build +ant test ``` These tests are not executed by default when running the `build` target but are recommended for developers contributing to the project. -
    -
    -Running ------------------------------- -The build can be run directly or through Ant. +### Distributing -
    - -**Executing via Ant** -If built for the host operating system, one can use the `ant run` target as shown below: - -``` -$ cd [path to processing repository] -$ cd build -$ ant run -``` - -If not yet built, this will cause Processing to be built prior to running. - -
    - -**Using Executable Directly** -Regardless of if cross-building, there are executables generated that can be run directly on the target operating system: - - - **Mac**: the `.app` file can be executed via a double click at `build/macosx/work/Processing.app` or `$ open build/macosx/work/Processing.app`. - - **Linux**: The resulting executable ends up at `build/linux/work/processing`. - - **Windows**: The resulting executable ends up at `build/windows/work/processing.exe`. - -
    -
    - -Distributing ------------------------------- A number of targets are provided for distribution of executables. If the executable is not yet built, it will be created prior to running the dist target. -
    - -**Available targets:** +**Available targets:** - macosx-dist - windows-dist @@ -151,38 +168,58 @@ A number of targets are provided for distribution of executables. If the executa One can also use `ant dist` to distribute for the host OS. -
    -**Examples** +### Examples For the host system, one can distribute like so: ``` -$ cd [path to processing repository] -$ cd build -$ ant dist +cd /path/to/processing4 +cd build +ant dist ``` -From a nix system, one can cross-build and distribute for linux like so: +From a \*nix system, one can cross-build and distribute for linux like so: ``` -$ cd [path to processing repository] -$ cd build -$ ant linux-dist +cd /path/to/processing4 +cd build +ant linux-dist ``` Regardless, the distributable ends up in `build/{os}/work` where `{os}` is the target OS. -
    -**Code Signing** -At present, only Mac builds require code signing to avoid an ["App Gateway"](https://support.apple.com/en-us/HT202491) issue. This is not executed by default by `ant dist` or `ant macosx-dist`. One can sign the resulting `.app` file though via: +### Code Signing + +Mac builds require code signing, due to [Apple requirements](https://support.apple.com/en-us/HT202491) issue. This is not executed by default by `ant dist` or `ant macosx-dist`. One can sign the resulting `.app` file though via: ``` -$ /usr/bin/codesign --force --sign "Developer ID Application: Certificate Common Name" Processing.app/Contents/PlugIns/jdk-... -$ /usr/bin/codesign --force --sign "Developer ID Application: Certificate Common Name" Processing.app +/usr/bin/codesign --force --sign "Developer ID Application: Certificate Common Name" Processing.app/Contents/PlugIns/jdk-... +/usr/bin/codesign --force --sign "Developer ID Application: Certificate Common Name" Processing.app ``` -Note that one will need to complete the `jdk-...` string to be something like `jdk-11.0.1+13` depending on the build. Anyway, this will require an [Apple Developer ID](https://developer.apple.com/developer-id/). +Note that one will need to complete the `jdk-...` string to be something like `jdk-17.0.2+8` depending on the build. Anyway, this will require an [Apple Developer ID](https://developer.apple.com/developer-id/). This is not strictly required especially if you are using your own app build. + +Eventually we'll want to sign [Windows releases](https://github.com/processing/processing4/issues/25), and [exported applications](https://github.com/processing/processing4/issues/173). If you have experience with this, please help! + + +## Using an IDE for development (Eclipse or IntelliJ) + +### Eclipse + +If you're using Eclipse, it'll complain about the lack of `jogl-all-src.jar`. Steps to create your own: + + git clone --recurse-submodules git://jogamp.org/srv/scm/jogl.git jogl + cd jogl + git checkout 0779f229b0e9538c640b18b9a4e095af1f5a35b3 + zip -r ../jogl-all-src.jar src + +Then copy that `jogl-all-src.jar` file to sit next to the `jogl-all.jar` folder inside `/path/to/processing/core/library`. + + +### IntelliJ + +Using Eclipse isn't supported, and I've switched to IntelliJ. However, IntelliJ is baffling enough that I don't have good instructions yet on how to develop inside there. If you and IntelliJ have a better relationship than I do, [please help!](https://github.com/processing/processing4/issues/275) diff --git a/build/build.xml b/build/build.xml index 4045d7987..079f358ef 100644 --- a/build/build.xml +++ b/build/build.xml @@ -9,8 +9,8 @@ - - + + @@ -27,169 +27,142 @@ - - - - + - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - + + - + + - - - - - + - - - - - - + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + - - + - - - + + + + + + value="macos/work/Processing.app/Contents/Java"> @@ -202,69 +175,95 @@ - + - - + - + + + + + + + + - + - + + - + - + @@ -282,14 +281,12 @@ - - @@ -305,21 +302,26 @@ - - - - - - + + + + + + + + + + + @@ -336,7 +338,6 @@ - @@ -348,14 +349,32 @@ + + + - - + - + + + + + + + + + + + + @@ -369,31 +388,6 @@ - - - - - - - - - @@ -449,23 +443,23 @@ - + - - + + - + - + - + ======================================================= - Processing for Mac can only be built on *nix systems. + Processing for Mac can only be built on macOS or Linux. Bye. ======================================================= @@ -476,12 +470,12 @@ - - + + - - + + - + - + - + + classpath="macos/appbundler-${os.arch}.jar" /> - @@ -517,19 +518,18 @@ - - + + + + - - + - - - + + + + @@ -593,16 +597,25 @@ - + - + - - + + - + Code signing will only work if you have a $99/yr Apple developer ID. - - With a proper ID, if code signing fails, you may need to use: - export CODESIGN_ALLOCATE="/Applications/Xcode.app/Contents/Developer/usr/bin/codesign_allocate" - + @@ -787,30 +777,26 @@ - + - - - - - - + + + - - - + - - - + @@ -827,15 +813,15 @@ - + - - + + @@ -844,17 +830,21 @@ + + - + @@ -877,7 +867,7 @@ - + - + @@ -910,8 +900,11 @@ + + + - + @@ -921,23 +914,29 @@ - + - + - - + + + + + + + + - + ======================================================= - Processing for Mac OS X was built. Grab it from + Processing for macOS was built. Grab it from - macosx/processing-${version}-macosx.zip + macos/${dist.filename} ======================================================= @@ -956,14 +955,14 @@ - + - ======================================================= - Processing for Linux can only be built on *nix systems. + ========================================================= + Processing for Linux can only be built on Linux or macOS. Bye. - ======================================================= + ========================================================= @@ -971,7 +970,7 @@ - + @@ -979,7 +978,7 @@ - + @@ -988,8 +987,16 @@ - + + + + + + + + - - - - - @@ -1059,17 +1045,17 @@ - + --> - + - + - - - + @@ -1095,17 +1079,16 @@ - - - - + + + - - + + - - + + + - + + + - - - - - - + + + - - - + + + + + + + + + + + + ======================================================= @@ -1147,98 +1136,93 @@ + + description="Build .deb of the Linux version"> - - + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + - - - - - - - - + - - - - - - - - - - - - - + - - - + + + + - + - - - - - - - - - - - - + + + - - - - - - - + + - - - + + + + + + + + + + + + - - + + + + + + + + + + + + + + ======================================================== @@ -1266,7 +1250,7 @@ - + @@ -1279,10 +1263,10 @@ https://github.com/processing/processing/issues/3624 --> - + - + @@ -1297,12 +1281,10 @@ - + - - - + @@ -1318,13 +1300,14 @@ - + + + - - - @@ -1336,14 +1319,14 @@ - + classpath="${launch4j.dir}/launch4j.jar; + ${launch4j.dir}/lib/xstream.jar" /> @@ -1366,14 +1349,19 @@ - + + - + + + - + @@ -1388,7 +1376,11 @@ - + + + + + - + - + - + @@ -1480,21 +1472,6 @@ - - - - - - - @@ -1511,7 +1488,7 @@ remove the spaces for depth since it should be double dash, but screws up commen - + @@ -1550,7 +1527,7 @@ remove the spaces for depth since it should be double dash, but screws up commen - + @@ -1579,9 +1556,6 @@ remove the spaces for depth since it should be double dash, but screws up commen - @@ -1591,7 +1565,7 @@ remove the spaces for depth since it should be double dash, but screws up commen - + @@ -1609,83 +1583,45 @@ remove the spaces for depth since it should be double dash, but screws up commen - - - - - - - - - - - - - - - - - - - - - - - + + depends="linux-clean, windows-clean, macos-clean, subprojects-clean"> + + + + + + diff --git a/build/linux/.gitignore b/build/linux/.gitignore index fb7b3db73..ff720a807 100644 --- a/build/linux/.gitignore +++ b/build/linux/.gitignore @@ -1,3 +1,4 @@ -jre-*.tgz +jdk-17*.tgz /processing-*-*.deb /processing-*-*.tgz +work diff --git a/build/linux/desktop.template b/build/linux/desktop.template index f8cf6d962..4a658f6b2 100644 --- a/build/linux/desktop.template +++ b/build/linux/desktop.template @@ -1,7 +1,7 @@ [Desktop Entry] Type=Application -Name=Processing IDE -GenericName=Processing IDE +Name=Processing +GenericName=Processing Comment=Open-source software prototyping platform Exec= Icon= @@ -9,4 +9,4 @@ Terminal=false Categories=Development;IDE;Programming; MimeType=text/x-processing; Keywords=sketching;software;animation;programming;coding; -StartupWMClass=processing-app-Base +StartupWMClass=processing-app-ui-Splash diff --git a/build/linux/processing b/build/linux/processing index 7f0d92fd5..7671bfdf9 100755 --- a/build/linux/processing +++ b/build/linux/processing @@ -106,13 +106,6 @@ then exit $? else # Start Processing in the same directory as this script - if [ "$1" ]; then - SKETCH=`readlink -f "$1"` - else - SKETCH= - fi cd "$APPDIR" - - #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,javafx.web -Xmx512m processing.app.Base "$SKETCH" & - java -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,javafx.web -Xmx512m processing.app.ui.Splash "$SKETCH" & + java -Djna.nosys=true -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true -Dpython.console.encoding=UTF-8 -Xmx512m --add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED processing.app.ui.Splash "$@" & fi diff --git a/build/macos/.gitignore b/build/macos/.gitignore new file mode 100644 index 000000000..6ca933523 --- /dev/null +++ b/build/macos/.gitignore @@ -0,0 +1,3 @@ +appbundler.jar +jdk-17* +work diff --git a/build/macos/appbundler-aarch64.jar b/build/macos/appbundler-aarch64.jar new file mode 100644 index 000000000..522119f2c Binary files /dev/null and b/build/macos/appbundler-aarch64.jar differ diff --git a/build/macosx/appbundler.jar b/build/macos/appbundler-x86_64.jar similarity index 71% rename from build/macosx/appbundler.jar rename to build/macos/appbundler-x86_64.jar index a7eb20d46..b9c8ee1a1 100644 Binary files a/build/macosx/appbundler.jar and b/build/macos/appbundler-x86_64.jar differ diff --git a/build/macosx/appbundler/.classpath b/build/macos/appbundler/.classpath similarity index 100% rename from build/macosx/appbundler/.classpath rename to build/macos/appbundler/.classpath diff --git a/build/macosx/appbundler/.gitignore b/build/macos/appbundler/.gitignore similarity index 100% rename from build/macosx/appbundler/.gitignore rename to build/macos/appbundler/.gitignore diff --git a/build/macosx/appbundler/.project b/build/macos/appbundler/.project similarity index 100% rename from build/macosx/appbundler/.project rename to build/macos/appbundler/.project diff --git a/build/macosx/appbundler/.settings/org.eclipse.jdt.core.prefs b/build/macos/appbundler/.settings/org.eclipse.jdt.core.prefs similarity index 100% rename from build/macosx/appbundler/.settings/org.eclipse.jdt.core.prefs rename to build/macos/appbundler/.settings/org.eclipse.jdt.core.prefs diff --git a/build/macos/appbundler/README.md b/build/macos/appbundler/README.md new file mode 100644 index 000000000..20fc50600 --- /dev/null +++ b/build/macos/appbundler/README.md @@ -0,0 +1,283 @@ +This is a (minor) fork of . Changes include: + +* For consistency with earlier releases, there's no `appbundler` subfolder. Everything remains in this folder. +* The output is `appbundler.jar` in the folder above this one, instead of `bin/appbundler-1.0ea`. +* `build.properties` has been removed, because only two of the lines are actually used. + +The code should only be built on Mojave or earlier, otherwise exported OpenGL applications will crash. [Please help!](https://github.com/processing/processing4/issues/284) + +appbundler +============= + +A fork of the [Java Application Bundler](https://svn.java.net/svn/appbundler~svn) +with the following changes: + +- Fixes [icon not showing bug](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7159381) in `JavaAppLauncher` +- Adds `LC_CTYPE` environment variable to the `Info.plist` file in order to fix an [issue with `File.exists()` in OpenJDK 7](http://java.net/jira/browse/MACOSX_PORT-165) **(Contributed by Steve Hannah)** +- Allows to specify the name of the executable instead of using the default `"JavaAppLauncher"` **(contributed by Karl von Randow)** +- Adds `classpathref` support to the `bundleapp` task +- Adds support for `JVMArchs` and `LSArchitecturePriority` keys +- Allows to specify a custom value for `CFBundleVersion` +- Allows specifying registered file extensions using `CFBundleDocumentTypes` and `UT[Ex|Im]portedTypeDeclarations` +- Passes to the Java application a set of system properties with the paths of + the OSX special folders, whether the application is running in the + sandbox (see below) and which modifier keys are held down while opening the app. With the latter, the Java application can mimic the behavior of e.g. iTunes or Photos to select another or create a new media library on startup with the option key. +- Allows overriding of passed JVM options by the bundled app itself via java.util.Preferences **(contributed by Hendrik Schreiber)** +- Allows writing arbitrary key-value pairs to `Info.plist` via `plistentry` +- Allows setting of environment variables via `Info.plist` +- Allows running the Application as `privileged` - e.g. for Setup **(Contributed by Gerry Weißbach)** +- Allows specifying a JNLP file (`jnlplaunchername`) as alternative to the `mainclassname` which can then be launched without hassle when the Application is signed. See [How to sign (dynamic) JNLP files for OSX 10.8.4 and Gatekeeper](http://stackoverflow.com/questions/16958130/how-to-sign-dynamic-jnlp-files-for-osx-10-8-4-and-gatekeeper) **(Contributed by Gerry Weißbach)** + +These are the system properties passed to the JVM: + +- `LibraryDirectory` +- `DocumentsDirectory` +- `CachesDirectory` +- `ApplicationSupportDirectory` +- `ApplicationDirectory` +- `AutosavedInformationDirectory` +- `DesktopDirectory` +- `DownloadsDirectory` +- `MoviesDirectory` +- `MusicDirectory` +- `PicturesDirectory` +- `SharedPublicDirectory` +- `SystemLibraryDirectory` +- `SystemApplicationSupportDirectory` +- `SystemCachesDirectory` +- `SystemApplicationDirectory` +- `SystemUserDirectory` +- `UserHome` (the user's home directory, even if running within a sandbox) +- `SandboxEnabled` (the String `true` or `false`) +- `LaunchModifierFlagCapsLock` (the String `true` or `false`) +- `LaunchModifierFlagShift` (the String `true` or `false`) +- `LaunchModifierFlagControl` (the String `true` or `false`) +- `LaunchModifierFlagOption` (the String `true` or `false`) +- `LaunchModifierFlagCommand` (the String `true` or `false`) +- `LaunchModifierFlagNumericPad` (the String `true` or `false`) +- `LaunchModifierFlagHelp` (the String `true` or `false`) +- `LaunchModifierFlagFunction` (the String `true` or `false`) +- `LaunchModifierFlags` (an Integer) + + +For more details, please refer to the [task documentation](http://htmlpreview.github.io/?https://github.com/TheInfiniteKind/appbundler/blob/master/appbundler/doc/appbundler.html). + +Example 1: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Example 2, use installed Java but require Java 8 (or later): + + + + + + + +Example 2, use installed Java but require Java 8 (or later) JRE and not a JDK: + + + + + + + +Example 3, bundle a stripped down JRE that only needs java.base and java.desktop for a non modularized app: + + + + + + + + + + + + + + + + + + + + + + + + + +Example 4, bundle a stripped down JRE that only needs java.base and java.desktop for a modularized app: + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/macosx/appbundler/TODO b/build/macos/appbundler/TODO similarity index 100% rename from build/macosx/appbundler/TODO rename to build/macos/appbundler/TODO diff --git a/build/macosx/appbundler/build.xml b/build/macos/appbundler/build.xml similarity index 71% rename from build/macosx/appbundler/build.xml rename to build/macos/appbundler/build.xml index 8f7dea90d..8e8f7392d 100644 --- a/build/macosx/appbundler/build.xml +++ b/build/macos/appbundler/build.xml @@ -28,22 +28,22 @@ questions. - + + - - + + - - + + + + @@ -72,25 +72,34 @@ questions. - - + + + + + + + + + + + + - + - + + - + - - - + + @@ -109,7 +118,7 @@ questions. - + @@ -127,9 +136,10 @@ questions. + classpath="${output.jar}"/> - + + + + + + + + + @@ -145,16 +173,17 @@ questions. + classpath="${output.jar}"/> - + diff --git a/build/macosx/appbundler/doc/appbundler.html b/build/macos/appbundler/doc/appbundler.html similarity index 100% rename from build/macosx/appbundler/doc/appbundler.html rename to build/macos/appbundler/doc/appbundler.html diff --git a/build/macosx/appbundler/native/main.m b/build/macos/appbundler/native/main.m similarity index 70% rename from build/macosx/appbundler/native/main.m rename to build/macos/appbundler/native/main.m index 1e3c41eec..22b820a0f 100644 --- a/build/macosx/appbundler/native/main.m +++ b/build/macos/appbundler/native/main.m @@ -28,6 +28,8 @@ #include #include #include +#include +#include #define JAVA_LAUNCH_ERROR "JavaLaunchError" @@ -38,6 +40,7 @@ #define JVM_DEFAULT_OPTIONS_KEY "JVMDefaultOptions" #define JVM_ARGUMENTS_KEY "JVMArguments" #define JVM_CLASSPATH_KEY "JVMClassPath" +#define JVM_MODULEPATH_KEY "JVMModulePath" #define JVM_VERSION_KEY "JVMVersion" #define JRE_PREFERRED_KEY "JREPreferred" #define JDK_PREFERRED_KEY "JDKPreferred" @@ -55,14 +58,12 @@ #define JVM_RUNTIME "$JVM_RUNTIME" #define JAVA_RUNTIME "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home" -#define LIBJLI_DY_LIB "lib/jli/libjli.dylib" +#define LIBJLI_DY_LIB "libjli.dylib" #define DEPLOY_LIB "lib/deploy.jar" -//* - #define DLog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) -/*/ - #define DLog(...) do { } while (0) -//*/ + +#define DLog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) + typedef int (JNICALL *JLI_Launch_t)(int argc, char ** argv, int jargc, const char** jargv, @@ -76,6 +77,9 @@ typedef int (JNICALL *JLI_Launch_t)(int argc, char ** argv, jboolean javaw, jint ergo); +static bool isVerbose = false; +static bool isDebugging = false; + static char** progargv = NULL; static int progargc = 0; static int launchCount = 0; @@ -90,16 +94,18 @@ int extractMajorVersion (NSString *); NSString * convertRelativeFilePath(NSString *); NSString * addDirectoryToSystemArguments(NSUInteger, NSSearchPathDomainMask, NSString *, NSMutableArray *); void addModifierFlagToSystemArguments(NSEventModifierFlags, NSString *, NSEventModifierFlags, NSMutableArray *); +static void Log(NSString *format, ...); +static void NSPrint(NSString *format, va_list args); int main(int argc, char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; int result; @try { - if ((argc > 1) && (launchCount == 0)) { - progargc = argc - 1; - progargv = &argv[1]; - } + if ((argc > 1) && (launchCount == 0)) { + progargc = argc - 1; + progargv = &argv[1]; + } launch(argv[0], progargc, progargv); result = 0; @@ -117,8 +123,28 @@ int main(int argc, char *argv[]) { return result; } + +// Get the amount of physical RAM on this machine +int64_t get_ram_size() { + int mib[2] = {CTL_HW, HW_MEMSIZE}; + int64_t physical_memory; + size_t length = sizeof(int64_t); + if(sysctl(mib, 2, &physical_memory, &length, NULL, 0)==0) { + return physical_memory; + } + return 0; +} + + int launch(char *commandName, int progargc, char *progargv[]) { + // check args for `--verbose` + for (int i = 0; i < progargc; i++) { + if (strcmp(progargv[i], "--verbose") == 0) { + isVerbose = true; + } + } + // Preparation for jnlp launcher arguments const char *const_jargs = NULL; const char *const_appclasspath = NULL; @@ -130,25 +156,19 @@ int launch(char *commandName, int progargc, char *progargv[]) { NSDictionary *infoDictionary = [mainBundle infoDictionary]; // Test for debugging (but only on the second runthrough) - bool isDebugging = [[infoDictionary objectForKey:@JVM_DEBUG_KEY] boolValue]; + bool isDebugging = (launchCount > 0) && [[infoDictionary objectForKey:@JVM_DEBUG_KEY] boolValue]; - if (isDebugging) { - DLog(@"\n\n\n\nLoading Application '%@'", [infoDictionary objectForKey:@"CFBundleName"]); - } + Log(@"\n\n\n\nLoading Application '%@'", [infoDictionary objectForKey:@"CFBundleName"]); // Set the working directory based on config, defaulting to the user's home directory NSString *workingDir = [infoDictionary objectForKey:@WORKING_DIR]; if (workingDir != nil) { workingDir = [workingDir stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]]; - } else { - workingDir = [[NSFileManager defaultManager] currentDirectoryPath]; - workingDir = NSHomeDirectory(); // REVIEW: Check which if these ones is realy the users home directory ... - } - if (isDebugging) { - DLog(@"Working Directory: '%@'", convertRelativeFilePath(workingDir)); - } - chdir([workingDir UTF8String]); + Log(@"Working Directory: '%@'", convertRelativeFilePath(workingDir)); + + chdir([workingDir UTF8String]); + } // execute privileged NSString *privileged = [infoDictionary objectForKey:@JVM_RUN_PRIVILEGED]; @@ -157,9 +177,7 @@ int launch(char *commandName, int progargc, char *progargv[]) { NSString *script = [NSString stringWithFormat:@"do shell script \"\\\"%@\\\" > /dev/null 2>&1 &\" with administrator privileges", [NSString stringWithCString:commandName encoding:NSASCIIStringEncoding]]; - if (isDebugging) { - DLog(@"script: %@", script); - } + Log(@"script: %@", script); NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script]; if ([appleScript executeAndReturnError:&error]) { @@ -178,7 +196,7 @@ int launch(char *commandName, int progargc, char *progargv[]) { bool jdkPreferred = [[infoDictionary objectForKey:@JDK_PREFERRED_KEY] boolValue]; if (jrePreferred && jdkPreferred) { - DLog(@"Specifying both JRE- and JDK-preferred means neither is preferred"); + Log(@"Specifying both JRE- and JDK-preferred means neither is preferred"); jrePreferred = false; jdkPreferred = false; } @@ -201,38 +219,41 @@ int launch(char *commandName, int progargc, char *progargv[]) { exactVersionMatch = true; jvmRequired = [NSString stringWithFormat:@"1.%i", required]; - DLog(@"Will Require a JVM version '%i' due to JNLP restrictions", required); + Log(@"Will Require a JVM version '%i' due to JNLP restrictions", required); } - NSString *javaDylib; + NSString *javaDylib = NULL; // If a runtime is set, we really want it. If it is not there, we will fail later on. if (runtime != nil) { - NSString *dylibRelPath = @"Contents/Home/jre"; - javaDylib = [[runtimePath stringByAppendingPathComponent:dylibRelPath] stringByAppendingPathComponent:@LIBJLI_DY_LIB]; - BOOL isDir; NSFileManager *fm = [[NSFileManager alloc] init]; - BOOL javaDylibFileExists = [fm fileExistsAtPath:javaDylib isDirectory:&isDir]; - if (!javaDylibFileExists || isDir) { - dylibRelPath = @"Contents/Home"; - javaDylib = [[runtimePath stringByAppendingPathComponent:dylibRelPath] stringByAppendingPathComponent:@LIBJLI_DY_LIB]; - javaDylibFileExists = [fm fileExistsAtPath:javaDylib isDirectory:&isDir]; - if (!javaDylibFileExists || isDir) { - javaDylib = NULL; - } - } - if (isDebugging) { - DLog(@"Java Runtime (%@) Relative Path: '%@' (dylib: %@)", runtime, runtimePath, javaDylib); + for (id dylibRelPath in @[@"Contents/Home/jre/lib/jli", @"Contents/Home/lib/jli", @"Contents/Home/jre/lib", @"Contents/Home/lib"]) { + NSString *candidate = [[runtimePath stringByAppendingPathComponent:dylibRelPath] stringByAppendingPathComponent:@LIBJLI_DY_LIB]; + BOOL isDir; + BOOL javaDylibFileExists = [fm fileExistsAtPath:candidate isDirectory:&isDir]; + if (javaDylibFileExists && !isDir) { + javaDylib = candidate; + break; + } } + + Log(@"Java Runtime (%@) Relative Path: '%@' (dylib: %@)", runtime, runtimePath, javaDylib); } else { // Search for the runtimePath, then make it a libjli.dylib path. runtimePath = findJavaDylib (jvmRequired, jrePreferred, jdkPreferred, isDebugging, exactVersionMatch); - javaDylib = [runtimePath stringByAppendingPathComponent:@LIBJLI_DY_LIB]; - - if (isDebugging) { - DLog(@"Java Runtime Dylib Path: '%@'", convertRelativeFilePath(javaDylib)); + NSFileManager *fm = [[NSFileManager alloc] init]; + for (id dylibRelPath in @[@"jre/lib/jli", @"jre/lib", @"lib/jli", @"lib"]) { + NSString *candidate = [[runtimePath stringByAppendingPathComponent:dylibRelPath] stringByAppendingPathComponent:@LIBJLI_DY_LIB]; + BOOL isDir; + BOOL javaDylibFileExists = [fm fileExistsAtPath:candidate isDirectory:&isDir]; + if (javaDylibFileExists && !isDir) { + javaDylib = candidate; + break; + } } + + Log(@"Java Runtime Dylib Path: '%@'", convertRelativeFilePath(javaDylib)); } const char *libjliPath = NULL; @@ -241,8 +262,7 @@ int launch(char *commandName, int progargc, char *progargv[]) { libjliPath = [javaDylib fileSystemRepresentation]; } - // Disable chatty log message that looks like an error in the Console - //DLog(@"Launchpath: %s", libjliPath); + Log(@"Launchpath: %s", libjliPath); void *libJLI = dlopen(libjliPath, RTLD_LAZY); @@ -272,11 +292,11 @@ int launch(char *commandName, int progargc, char *progargv[]) { msg = NSLocalizedString(@"JRELoadError", @UNSPECIFIED_ERROR); } - DLog(@"Error launching JVM Runtime (%@) Relative Path: '%@' (dylib: %@)\n error: %@", - runtime, runtimePath, javaDylib, msg); + Log(@"Error launching JVM Runtime (%@) Relative Path: '%@' (dylib: %@)\n error: %@", + runtime, runtimePath, javaDylib, msg); [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR - reason:msg userInfo:nil] raise]; + reason:msg userInfo:nil] raise]; } // Set the class path @@ -295,14 +315,13 @@ int launch(char *commandName, int progargc, char *progargv[]) { reason:NSLocalizedString(@"BundlePathContainsColon", @UNSPECIFIED_ERROR) userInfo:nil] raise]; } - if (isDebugging) { - NSLog(@"Main Bundle Path: '%@'", mainBundlePath); - } + Log(@"Main Bundle Path: '%@'", mainBundlePath); // Set the class path NSString *javaPath = [mainBundlePath stringByAppendingString:@"/Contents/Java"]; NSMutableArray *systemArguments = [[NSMutableArray alloc] init]; NSMutableString *classPath = [NSMutableString stringWithString:@"-Djava.class.path="]; + NSMutableString *modulePath = [NSMutableString stringWithFormat:@"--module-path="]; // Set the library path NSString *libraryPath = [NSString stringWithFormat:@"-Djava.library.path=%@/Contents/MacOS", mainBundlePath]; @@ -339,6 +358,11 @@ int launch(char *commandName, int progargc, char *progargv[]) { } defaultOptions = [defaults allValues]; } + + // Set the AppleWindowTabbingMode to not squash all new JFrames into tabs within + // a single window when the user has set SystemPrefs:General:PreferTabs:always-when-opening-documents + // which is unfortunately the default in macOS 11 + [[NSUserDefaults standardUserDefaults] setValue:@"manual" forKey:@"AppleWindowTabbingMode"]; // Get the application arguments NSMutableArray *arguments = [[infoDictionary objectForKey:@JVM_ARGUMENTS_KEY] mutableCopy]; @@ -353,6 +377,8 @@ int launch(char *commandName, int progargc, char *progargv[]) { // Get the main class name NSString *mainClassName = [infoDictionary objectForKey:@JVM_MAIN_CLASS_NAME_KEY]; + bool runningModule = [mainClassName rangeOfString:@"/"].location != NSNotFound; + if ( jnlplauncher != nil ) { const_appclasspath = [[runtimePath stringByAppendingPathComponent:@DEPLOY_LIB] fileSystemRepresentation]; @@ -401,41 +427,35 @@ int launch(char *commandName, int progargc, char *progargv[]) { // Add the jnlp as argument so that javaws.Main can read and delete it [arguments addObject:tempFileName]; + + } else { + // It is impossible to combine modules and jar launcher + if ( runningModule && jarlauncher != nil ) { + [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR + reason:@"Modules cannot be used in conjuction with jar launcher" + userInfo:nil] raise]; + } - } else - // Either mainClassName or jarLauncher has to be set since this is not a jnlpLauncher - if ( mainClassName == nil && jarlauncher == nil ) { - [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR - reason:NSLocalizedString(@"MainClassNameRequired", @UNSPECIFIED_ERROR) - userInfo:nil] raise]; + // Either mainClassName or jarLauncher has to be set since this is not a jnlpLauncher + if ( mainClassName == nil && jarlauncher == nil ) { + [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR + reason:NSLocalizedString(@"MainClassNameRequired", @UNSPECIFIED_ERROR) + userInfo:nil] raise]; + } } + + Log(@"Main Class Name: '%@'", mainClassName); - if (isDebugging) { - DLog(@"Main Class Name: '%@'", mainClassName); - } - - // If a jar file is defined as launcher, discard the javaPath + // If a jar file is defined as launcher, disacard the javaPath if ( jarlauncher != nil ) { [classPath appendFormat:@":%@/%@", javaPath, jarlauncher]; - } else { - + } else if ( !runningModule ) { 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 - - // This causes the Classes folder to be required. Removing for 4.0 alpha 6. - //[classPath appendFormat:@"%@/Classes", javaPath]; - + [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) @@ -459,10 +479,25 @@ int launch(char *commandName, int progargc, char *progargv[]) { [classPath appendString:file]; } } + } else { + NSArray *mp = [infoDictionary objectForKey:@JVM_MODULEPATH_KEY]; + if (mp == nil) { + // Implicit module path, so use the contents of the "Java" folder to build an explicit module path + [modulePath appendFormat:@"%@", javaPath]; + } else { + int k = 0; + for (NSString *file in mp) { + if (k++ > 0) [modulePath appendString:@":"]; // add separator if needed + file = [file stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]]; + [modulePath appendString:file]; + } + } } - if ( classPath != nil ) { + if ( classPath != nil && !runningModule ) { [systemArguments addObject:classPath]; + } else if (modulePath != nil && runningModule) { + [systemArguments addObject:modulePath]; } // Set OSX special folders @@ -513,26 +548,26 @@ int launch(char *commandName, int progargc, char *progargv[]) { BOOL isDarkMode = (osxMode != nil && [osxMode isEqualToString:@"Dark"]); NSString *darkModeEnabledVar = [NSString stringWithFormat:@"-DDarkMode=%s", - (isDarkMode ? "true" : "false")]; + (isDarkMode ? "true" : "false")]; [systemArguments addObject:darkModeEnabledVar]; - // Check for modifier keys on app launch + // Check for modifier keys on app launch - // Since [NSEvent modifierFlags] is only available since OS X 10.6., only add properties if supported. - if ([NSEvent respondsToSelector:@selector(modifierFlags)]) { - NSEventModifierFlags launchModifierFlags = [NSEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; + // Since [NSEvent modifierFlags] is only available since OS X 10.6., only add properties if supported. + if ([NSEvent respondsToSelector:@selector(modifierFlags)]) { + NSEventModifierFlags launchModifierFlags = [NSEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; - [systemArguments addObject:[NSString stringWithFormat:@"-DLaunchModifierFlags=%lu", (unsigned long)launchModifierFlags]]; + [systemArguments addObject:[NSString stringWithFormat:@"-DLaunchModifierFlags=%lu", (unsigned long)launchModifierFlags]]; - addModifierFlagToSystemArguments(NSEventModifierFlagCapsLock, @"LaunchModifierFlagCapsLock", launchModifierFlags, systemArguments); - addModifierFlagToSystemArguments(NSEventModifierFlagShift, @"LaunchModifierFlagShift", launchModifierFlags, systemArguments); - addModifierFlagToSystemArguments(NSEventModifierFlagControl, @"LaunchModifierFlagControl", launchModifierFlags, systemArguments); - addModifierFlagToSystemArguments(NSEventModifierFlagOption, @"LaunchModifierFlagOption", launchModifierFlags, systemArguments); - addModifierFlagToSystemArguments(NSEventModifierFlagCommand, @"LaunchModifierFlagCommand", launchModifierFlags, systemArguments); - addModifierFlagToSystemArguments(NSEventModifierFlagNumericPad, @"LaunchModifierFlagNumericPad", launchModifierFlags, systemArguments); - addModifierFlagToSystemArguments(NSEventModifierFlagHelp, @"LaunchModifierFlagHelp", launchModifierFlags, systemArguments); - addModifierFlagToSystemArguments(NSEventModifierFlagFunction, @"LaunchModifierFlagFunction", launchModifierFlags, systemArguments); - } + addModifierFlagToSystemArguments(NSEventModifierFlagCapsLock, @"LaunchModifierFlagCapsLock", launchModifierFlags, systemArguments); + addModifierFlagToSystemArguments(NSEventModifierFlagShift, @"LaunchModifierFlagShift", launchModifierFlags, systemArguments); + addModifierFlagToSystemArguments(NSEventModifierFlagControl, @"LaunchModifierFlagControl", launchModifierFlags, systemArguments); + addModifierFlagToSystemArguments(NSEventModifierFlagOption, @"LaunchModifierFlagOption", launchModifierFlags, systemArguments); + addModifierFlagToSystemArguments(NSEventModifierFlagCommand, @"LaunchModifierFlagCommand", launchModifierFlags, systemArguments); + addModifierFlagToSystemArguments(NSEventModifierFlagNumericPad, @"LaunchModifierFlagNumericPad", launchModifierFlags, systemArguments); + addModifierFlagToSystemArguments(NSEventModifierFlagHelp, @"LaunchModifierFlagHelp", launchModifierFlags, systemArguments); + addModifierFlagToSystemArguments(NSEventModifierFlagFunction, @"LaunchModifierFlagFunction", launchModifierFlags, systemArguments); + } @@ -570,53 +605,75 @@ int launch(char *commandName, int progargc, char *progargv[]) { setenv([key UTF8String], [newValue UTF8String], 1); } } - + + // replace any maximum memory parameters that specify a percentage of available ram + for(int i=0; i= 1 && percentAmt <= 200.0001) { + int64_t ram_size = get_ram_size(); + if(ram_size > 0 ) { + double ramToUse = (percentAmt/100) * ram_size; + ramToUse = ramToUse/1000000; // convert from bytes to megabytes + [options replaceObjectAtIndex:i withObject:[NSString stringWithFormat:@"-Xmx%0.0fm", ramToUse]]; + } + } + } + } + // Initialize the arguments to JLI_Launch() // +5 due to the special directories and the sandbox enabled property int argc = 1 + [systemArguments count] + [options count] + [defaultOptions count] + 1 + [arguments count] + newProgargc; + if (runningModule) + argc++; + char *argv[argc]; int i = 0; argv[i++] = commandName; for (NSString *systemArgument in systemArguments) { - argv[i++] = strdup([systemArgument UTF8String]); + argv[i++] = strdup([systemArgument UTF8String]); } for (NSString *option in options) { option = [option stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]]; option = [option stringByReplacingOccurrencesOfString:@JVM_RUNTIME withString:runtimePath]; argv[i++] = strdup([option UTF8String]); - if (isDebugging) { DLog(@"Option: %@",option); } + Log(@"Option: %@",option); } for (NSString *defaultOption in defaultOptions) { defaultOption = [defaultOption stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]]; argv[i++] = strdup([defaultOption UTF8String]); - if (isDebugging) { DLog(@"DefaultOption: %@",defaultOption); } + Log(@"DefaultOption: %@",defaultOption); } - argv[i++] = strdup([mainClassName UTF8String]); + if (runningModule) { + argv[i++] = "-m"; + argv[i++] = strdup([mainClassName UTF8String]); + } else + argv[i++] = strdup([mainClassName UTF8String]); for (NSString *argument in arguments) { argument = [argument stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]]; argv[i++] = strdup([argument UTF8String]); } - int ctr = 0; - for (ctr = 0; ctr < newProgargc; ctr++) { - argv[i++] = newProgargv[ctr]; - } - - // Print the full command line for debugging purposes... - if (isDebugging) { - DLog(@"Command line passed to application:"); - int j=0; - for(j=0; j= jvmRequired && !exactMatch) || (version == jvmRequired && exactMatch) ) { - if (isDebugging) { - DLog (@"JRE version qualifies"); - } + Log(@"JRE version qualifies"); return @JAVA_RUNTIME; } } } @catch (NSException *exception) { - DLog (@"JRE search exception: '%@'", [exception reason]); + Log(@"JRE search exception: '%@'", [exception reason]); } return nil; @@ -814,9 +861,9 @@ NSString * findJREDylib ( * Searches for a JDK dylib of the specified version or later. */ NSString * findJDKDylib ( - int jvmRequired, - bool isDebugging, - bool exactMatch) + int jvmRequired, + bool isDebugging, + bool exactMatch) { @try { @@ -845,26 +892,30 @@ NSString * findJDKDylib ( NSData *data2 = [errHandle readDataToEndOfFile]; NSString *outRead = [[NSString alloc] initWithData:data1 - encoding:NSUTF8StringEncoding]; + encoding:NSUTF8StringEncoding]; NSString *errRead = [[NSString alloc] initWithData:data2 - encoding:NSUTF8StringEncoding]; + encoding:NSUTF8StringEncoding]; - // If matching JDK not found, outRead will include something like - // "Unable to find any JVMs matching version "1.X"." + // If matching JDK not found, outRead will include something like + // "Unable to find any JVMs matching version "1.X"." if ( errRead != nil - && [errRead rangeOfString:@"Unable"].location != NSNotFound ) + && [errRead rangeOfString:@"Unable"].location != NSNotFound ) { - if (isDebugging) { DLog (@"No matching JDK found."); } + Log(@"No matching JDK found."); return nil; } int version = 0; + + // ... and outRead will include something like + // "/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home" or + // "/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home" NSRange vrange = [outRead rangeOfString:@"jdk1."]; if (vrange.location == NSNotFound) { - // try the changed version layout from version 9 - vrange = [outRead rangeOfString:@"jdk-"]; - vrange.location += 4; + // try the changed version layout from version 9 (e.g., jdk-9, zulu-12) + vrange = [outRead rangeOfString:@"-"]; + vrange.location += 1; } else { // otherwise remove the leading jdk vrange.location += 3; @@ -878,22 +929,18 @@ NSString * findJDKDylib ( version = extractMajorVersion(vstring); - if (isDebugging) { - DLog (@"Found a Java %@ JDK", vstring); - DLog (@"Looks like major version %d", extractMajorVersion(vstring)); - } + Log(@"Found a Java %@ JDK", vstring); + Log(@"Looks like major version %d", extractMajorVersion(vstring)); } if ( (version >= jvmRequired && !exactMatch) || (version == jvmRequired && exactMatch) ) { - if (isDebugging) { - DLog (@"JDK version qualifies"); - } + Log(@"JDK version qualifies"); NSString *outread2 = [outRead stringByTrimmingCharactersInSet:[NSCharacterSet - whitespaceAndNewlineCharacterSet]]; + whitespaceAndNewlineCharacterSet]]; - // Return location where LIBJLI_DY_LIB is located. Note that the path was - // shortemed between JDK 8 and JDK 9. + // Return location where LIBJLI_DY_LIB is located. Note that the path was + // shortemed between JDK 8 and JDK 9. if (version > 8) { return outread2; } @@ -904,7 +951,7 @@ NSString * findJDKDylib ( } @catch (NSException *exception) { - DLog (@"JDK search exception: '%@'", [exception reason]); + Log(@"JDK search exception: '%@'", [exception reason]); } return nil; @@ -918,24 +965,24 @@ NSString * findJDKDylib ( * string will return 0. */ int extractMajorVersion (NSString *vstring) { - if (vstring == nil) { return 0; } + if (vstring == nil) { return 0; } - // Expecting either a java version of form 1.X, 1.X.Y_ZZ or jdk1.X.Y_ZZ. - // Strip everything from start and ending that aren't part of the version number - NSCharacterSet* nonDigits = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet]; - vstring = [vstring stringByTrimmingCharactersInSet:nonDigits]; + // Expecting either a java version of form 1.X, 1.X.Y_ZZ or jdk1.X.Y_ZZ. + // Strip everything from start and ending that aren't part of the version number + NSCharacterSet* nonDigits = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet]; + vstring = [vstring stringByTrimmingCharactersInSet:nonDigits]; - if([vstring hasPrefix:@"1."]) { // this is the version < 9 layout. Remove the leading "1." - vstring = [vstring substringFromIndex:2]; - } + if([vstring hasPrefix:@"1."]) { // this is the version < 9 layout. Remove the leading "1." + vstring = [vstring substringFromIndex:2]; + } - // the next integer token should be the major version, so read everything up to the first decimal point, if any - NSUInteger versionEndLoc = [vstring rangeOfString:@"."].location; - if (versionEndLoc != NSNotFound) { - vstring = [vstring substringToIndex:versionEndLoc]; - } + // the next integer token should be the major version, so read everything up to the first decimal point, if any + NSUInteger versionEndLoc = [vstring rangeOfString:@"."].location; + if (versionEndLoc != NSNotFound) { + vstring = [vstring substringToIndex:versionEndLoc]; + } - return [vstring intValue]; + return [vstring intValue]; } @@ -944,19 +991,46 @@ NSString * convertRelativeFilePath(NSString * path) { } NSString * addDirectoryToSystemArguments(NSUInteger searchPath, NSSearchPathDomainMask domainMask, - NSString *systemProperty, NSMutableArray *systemArguments) { - NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath,domainMask, YES); + NSString *systemProperty, NSMutableArray *systemArguments) { + NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath,domainMask, YES); if ([paths count] > 0) { - NSString *basePath = [paths objectAtIndex:0]; - NSString *directory = [NSString stringWithFormat:@"-D%@=%@", systemProperty, basePath]; - [systemArguments addObject:directory]; - return basePath; + NSString *basePath = [paths objectAtIndex:0]; + NSString *directory = [NSString stringWithFormat:@"-D%@=%@", systemProperty, basePath]; + [systemArguments addObject:directory]; + return basePath; } return nil; } void addModifierFlagToSystemArguments(NSEventModifierFlags mask, NSString *systemProperty, NSEventModifierFlags modifierFlags, NSMutableArray *systemArguments) { - NSString *modifierFlagValue = (modifierFlags & mask) ? @"true" : @"false"; - NSString *modifierFlagVar = [NSString stringWithFormat:@"-D%@=%@", systemProperty, modifierFlagValue]; - [systemArguments addObject:modifierFlagVar]; + NSString *modifierFlagValue = (modifierFlags & mask) ? @"true" : @"false"; + NSString *modifierFlagVar = [NSString stringWithFormat:@"-D%@=%@", systemProperty, modifierFlagValue]; + [systemArguments addObject:modifierFlagVar]; +} + +static void Log(NSString *format, ...) +{ + va_list args; + va_start(args, format); + + if (isDebugging) { + NSLog(format, args); + } + + if (isVerbose) { + NSPrint(format, args); + } + + va_end(args); +} + +static void NSPrint(NSString *format, va_list args) +{ + NSString *string = [[NSString alloc] initWithFormat:format arguments:args]; + + fprintf(stdout, "%s\n", [string UTF8String]); + +#if !__has_feature(objc_arc) + [string release]; +#endif } diff --git a/build/macos/appbundler/processing4-appbundler.iml b/build/macos/appbundler/processing4-appbundler.iml new file mode 100644 index 000000000..c30dab7bf --- /dev/null +++ b/build/macos/appbundler/processing4-appbundler.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/macos/appbundler/res/be.lproj/Localizable.strings b/build/macos/appbundler/res/be.lproj/Localizable.strings new file mode 100644 index 000000000..292b8357f --- /dev/null +++ b/build/macos/appbundler/res/be.lproj/Localizable.strings @@ -0,0 +1,7 @@ +"JRELoadError" = "Немагчыма загрузіць Java."; +"JRExLoadError" = "Немагчыма загрузіць асяродзьдзе выкананьня Java %d."; +"JRExLoadFullError" = "Для гэтай праграмы патрабуецца Java %d або пазьнейшай вэрсіі на кампутары. Спампуйце і ўсталюйце апошнюю вэрсію Java з вэб-сайта www.java.com і паўтарэце спробу."; +"JDKxLoadFullError" = "Для гэтай праграмы патрабуецца Java %d JDK або пазьнейшай вэрсіі на кампутары. Спампуйце і ўсталюйце апошнюю вэрсію Java JDK з вэб-сайта Oracle.com і паўтарэце спробу."; +"MainClassNameRequired" = "Патрабуецца назва галоўнага кляса."; +"JavaDirectoryNotFound" = "Не атрымліваецца пералічыць зьмесьціва каталёга Java."; +"BundlePathContainsColon" = "Немагчыма запусьціць з каталёга, у назьве якога ёсьць сымбаль \"/\"."; diff --git a/build/macos/appbundler/res/cs.lproj/Localizable.strings b/build/macos/appbundler/res/cs.lproj/Localizable.strings new file mode 100644 index 000000000..cc3dbf1b3 --- /dev/null +++ b/build/macos/appbundler/res/cs.lproj/Localizable.strings @@ -0,0 +1,8 @@ +"JRELoadError" = "Není možné nahrát Java Runtime Environment."; +"JRExLoadError" = "Není možné nahrát Java %d Runtime Environment."; +"JRExLoadFullError" = "Tato aplikace vyžaduje, aby na Vašem počítači byla instalována Java %d nebo novější. Prosím stáhněte a instalujte nejnovější verzi Javy z adresy www.java.com a zkuste to znovu."; +"JDKxLoadFullError" = "Tato aplikace vyžaduje, aby na Vašem počítači byla instalována Java %d JDK nebo novější. Prosím stáhněte a instalujte nejnovější verzi Javy JDK z adresy Oracle.com a zkuste to znovu."; + +"MainClassNameRequired" = "Je požadován název hlavní třídy."; +"JavaDirectoryNotFound" = "Není možno vyčíst obsah adresáře Java."; +"BundlePathContainsColon" = "Není možno spustit z adresáře, který má znaky \"/\" ve svém názvu."; diff --git a/build/macosx/appbundler/res/de.lproj/Localizable.strings b/build/macos/appbundler/res/de.lproj/Localizable.strings similarity index 72% rename from build/macosx/appbundler/res/de.lproj/Localizable.strings rename to build/macos/appbundler/res/de.lproj/Localizable.strings index 59f745d0c..b406e155a 100644 --- a/build/macosx/appbundler/res/de.lproj/Localizable.strings +++ b/build/macos/appbundler/res/de.lproj/Localizable.strings @@ -1,7 +1,7 @@ -"JRELoadError" = "Die Java Laufzeitumgebung konnte nicht geladen werden."; -"JRExLoadError" = "Die Java %d Laufzeitumgebung konnte nicht geladen werden."; +"JRELoadError" = "Kann die Java Laufzeitumgebung nicht laden."; +"JRExLoadError" = "Kann die Java %d Laufzeitumgebung nicht laden."; "JRExLoadFullError" = "Diese Anwendung benötigt die Java %d Laufzeitumgebung oder höher auf Ihrem Computer installiert sein. Bitte installieren Sie die neueste Version von Java von www.java.com und erneut versuchen."; "JDKxLoadFullError" = "Diese Anwendung benötigt die Java %d Laufzeitumgebung oder höher auf Ihrem Computer installiert sein. Bitte installieren Sie die neueste JDK von Oracle.com und erneut versuchen."; "MainClassNameRequired" = "Hauptklassenname ist erforderlich."; -"JavaDirectoryNotFound" = "Das Java Verzeichnis ist nicht vorhanden."; +"JavaDirectoryNotFound" = "Kann den Inhalt des Java-Ordners nicht lesen."; "BundlePathContainsColon" = "Kann nicht vom einem Ordner aus starten, der \"/\" in seinem Namen enthält."; diff --git a/build/macosx/appbundler/res/en.lproj/Localizable.strings b/build/macos/appbundler/res/en.lproj/Localizable.strings similarity index 100% rename from build/macosx/appbundler/res/en.lproj/Localizable.strings rename to build/macos/appbundler/res/en.lproj/Localizable.strings diff --git a/build/macos/appbundler/res/fr.lproj/Localizable.strings b/build/macos/appbundler/res/fr.lproj/Localizable.strings new file mode 100644 index 000000000..a19cd1158 --- /dev/null +++ b/build/macos/appbundler/res/fr.lproj/Localizable.strings @@ -0,0 +1,7 @@ +"JRELoadError" = "Impossible de charger l'environnement d'exécution Java."; +"JRExLoadError" = "Impossible de charger l'environnement d'exécution Java %d."; +"JRExLoadFullError" = "Cette application nécessite la présence de Java %d ou supérieur sur votre ordinateur. Veuillez télécharger et installer la dernière version de Java depuis www.java.com et essayez à nouveau."; +"JDKxLoadFullError" = "Cette application nécessite la présence d'un JDK Java %d ou supérieur sur votre ordinateur. Veuillez télécharger et installer le dernier JDK Java depuis www.java.com et essayez à nouveau."; +"MainClassNameRequired" = "Le nom de la classe principale est obligatoire."; +"JavaDirectoryNotFound" = "Impossible d'énumérer le contenu du répertoire Java."; +"BundlePathContainsColon" = "Impossible de lancer à partir d'un dossier dont le nom comporte un « / »."; diff --git a/build/macos/appbundler/res/ia.lproj/Localizable.strings b/build/macos/appbundler/res/ia.lproj/Localizable.strings new file mode 100644 index 000000000..7e392c3bb --- /dev/null +++ b/build/macos/appbundler/res/ia.lproj/Localizable.strings @@ -0,0 +1,7 @@ +"JRELoadError" = "Incapace a cargar Java Runtime Environment."; +"JRExLoadError" = "Incapace a cargar Java %d Runtime Environment."; +"JRExLoadFullError" = "Iste application require que Java %d o plus recente sia installate sur tu computator. Per favor discarga e installa le version ultime de Java ex le sito www.java.com e prova de novo."; +"JDKxLoadFullError" = "Iste application require que un JDK Java %d o plus recente sia installate sur tu computator. Per favor discarga e installa le JDK ultime de Java ex le sito oracle.com e prova de novo."; +"MainClassNameRequired" = "Le 'Main class name' es requirite."; +"JavaDirectoryNotFound" = "Incapace a enumerar le contentos del plica de Java."; +"BundlePathContainsColon" = "Impossibile lancear ex un plica que contine un \"/\" in su nomine."; diff --git a/build/macos/appbundler/res/it.lproj/Localizable.strings b/build/macos/appbundler/res/it.lproj/Localizable.strings new file mode 100644 index 000000000..0a8ccf0fc --- /dev/null +++ b/build/macos/appbundler/res/it.lproj/Localizable.strings @@ -0,0 +1,7 @@ +"JRELoadError" = "Impossibile caricare Java Runtime Environment."; +"JRExLoadError" = "Impossibile caricare un Java %d Runtime Environment."; +"JRExLoadFullError" = "Questa applicazione necessita che Java %d o successivo sia installato nel computer. Scaricare e installare l'ultima versione di Java da www.java.com e riprovare."; +"JDKxLoadFullError" = "Questa applicazione necessita che Java %d JDK oo successivo sia installato sul computer. Scaricare e installare l'ultima versione di Java JDK da Oracle.com e riprovare."; +"MainClassNameRequired" = "È richiesto il nome della classe principale."; +"JavaDirectoryNotFound" = "Impossibile enumerare il contenuto della directory Java."; +"BundlePathContainsColon" = "Impossibile avviare dalla cartella che contiene nel proprio nome un \"/\"."; diff --git a/build/macos/appbundler/res/ja.lproj/Localizable.strings b/build/macos/appbundler/res/ja.lproj/Localizable.strings new file mode 100644 index 000000000..be1a45929 --- /dev/null +++ b/build/macos/appbundler/res/ja.lproj/Localizable.strings @@ -0,0 +1,7 @@ +"JRELoadError" = "Java 実行環境を起動できません。"; +"JRExLoadError" = "Java %d 実行環境を起動できません。"; +"JRExLoadFullError" = "このプログラムを実行するには、Java %d 以降がインストールされている必要があります。www.java.com から最新の Java をダウンロードしてインストールした後に、再度お試しください。"; +"JDKxLoadFullError" = "このプログラムを実行するには、Java %d 以降の JDK がインストールされている必要があります。Oracle.com から最新の Java JDK をダウンロードしてインストールした後に、再度お試しください。"; +"MainClassNameRequired" = "メインクラス名の指定は必須です。"; +"JavaDirectoryNotFound" = "Java ディレクトリのコンテンツを列挙できません。"; +"BundlePathContainsColon" = "名前に \"/\" 記号を含むフォルダーからは起動できません。"; diff --git a/build/macos/appbundler/res/nl.lproj/Localizable.strings b/build/macos/appbundler/res/nl.lproj/Localizable.strings new file mode 100644 index 000000000..5613b5fee --- /dev/null +++ b/build/macos/appbundler/res/nl.lproj/Localizable.strings @@ -0,0 +1,7 @@ +"JRELoadError" = "Niet mogelijk Java Runtime Environment te laden."; +"JRExLoadError" = "Niet mogelijk Java %d Runtime Environment te laden."; +"JRExLoadFullError" = "Deze toepassing vereist dat Java %d of later moet zijn geïnstalleerd op uw computer. Download en installeer de laatste versie van Java vanaf www.java.com en probeer opnieuw."; +"JDKxLoadFullError" = "Deze toepassing vereist dat Java %d JDK of later moet zijn geïnstalleerd op uw computer. Download en installeer de laatste Java JDK vanaf Oracle.com en probeer opnieuw."; +"MainClassNameRequired" = "Naam voor hoofdklasse is vereist."; +"JavaDirectoryNotFound" = "Niet mogelijk inhoud van map Java te nummeren."; +"BundlePathContainsColon" = "Kan niet starten vanuit een map die een \"/\" in zijn naam heeft."; diff --git a/build/macos/appbundler/res/no.lproj/Localizable.strings b/build/macos/appbundler/res/no.lproj/Localizable.strings new file mode 100644 index 000000000..7132cf67b --- /dev/null +++ b/build/macos/appbundler/res/no.lproj/Localizable.strings @@ -0,0 +1,7 @@ +"JRELoadError" = "Kunne ikke laste inn Java Runtime Environment."; +"JRExLoadError" = "Kunne ikke laste et Java %d Runtime Environment."; +"JRExLoadFullError" = "Dette programmet krever at Java %d eller nyere er installert på datamaskinen. Last ned og installer den nyeste versjonen av Java fra www.java.com og prøv på nytt."; +"JDKxLoadFullError" = "Dette programmet krever at en Java %d JDK eller nyere er installert på datamaskinen. Last ned og installer den nyeste Java JDK fra Oracle.com og prøv på nytt."; +"MainClassNameRequired" = "Hovednavn for klasse kreves."; +"JavaDirectoryNotFound" = "Kunne ikke liste opp innholdet i Java-mappen."; +"BundlePathContainsColon" = "Kan ikke kjøre fra en mappe som har en \"/\" i navnet."; diff --git a/build/macos/appbundler/res/pt_BR.lproj/Localizable.strings b/build/macos/appbundler/res/pt_BR.lproj/Localizable.strings new file mode 100644 index 000000000..158e504b4 --- /dev/null +++ b/build/macos/appbundler/res/pt_BR.lproj/Localizable.strings @@ -0,0 +1,7 @@ +"JRELoadError" = "Impossível carregar o Java Runtime Environment."; +"JRExLoadError" = "Impossível carregar um Java %d Runtime Environment."; +"JRExLoadFullError" = "Este programa requer o Java %d ou mais recente instalado no seu computador. Baixe e instale a última versão do Java em www.java.com e tente outra vez."; +"JDKxLoadFullError" = "Este programa requer o Java %d JDK ou mais recente instalado no seu computador. Baixe e instale a última versão do Java JDK em Oracle.com e tente outra vez."; +"MainClassNameRequired" = "Nome de classe principal é necessário."; +"JavaDirectoryNotFound" = "Impossível enumerar o conteúdo da pasta do Java."; +"BundlePathContainsColon" = "Impossível iniciar a partir de uma pasta que contém o caractere \"/\" em seu nome."; diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java b/build/macos/appbundler/src/com/oracle/appbundler/AppBundlerTask.java similarity index 75% rename from build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java rename to build/macos/appbundler/src/com/oracle/appbundler/AppBundlerTask.java index fcdc17859..6510603d8 100644 --- a/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java +++ b/build/macos/appbundler/src/com/oracle/appbundler/AppBundlerTask.java @@ -34,15 +34,14 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URL; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -55,9 +54,10 @@ 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; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * App bundler Ant task. */ @@ -78,8 +78,9 @@ public class AppBundlerTask extends Task { private String copyright = ""; private String privileged = null; private String workingDirectory = null; - private String minimumSystemVersion = null; + private String minimumSystemVersion = "10.7"; + private boolean requiresAquaAppearance = false; private String jvmRequired = null; private boolean jrePreferred = false; private boolean jdkPreferred = false; @@ -97,6 +98,7 @@ public class AppBundlerTask extends Task { private String jnlpLauncherName = null; private String jarLauncherName = null; private Runtime runtime = null; + private JLink jlink = null; private ArrayList classPath = new ArrayList<>(); private ArrayList libraryPath = new ArrayList<>(); private ArrayList + + + @@ -42,44 +35,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + @@ -90,38 +126,33 @@ + classname="PAppletMethods" + classpath="methods/methods.jar" /> - - - - - - - - - + - - + + + + @@ -160,7 +191,7 @@ + description="Build core library jar"> @@ -172,4 +203,19 @@ + + + + + + + + + + + + + + + diff --git a/core/different/Makefile b/core/different/Makefile new file mode 100644 index 000000000..57915adb3 --- /dev/null +++ b/core/different/Makefile @@ -0,0 +1,17 @@ +#!/usr/bin/make + +CC=gcc +JAVA_HOME="`/usr/libexec/java_home -v 11`" +JNI=-I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin/ + +default: universal_lib + +x64_lib: different.m + $(CC) ${JNI} different.m -o libDifferent_x86_64 -target x86_64-apple-macos10.12 -framework Cocoa -shared + +arm_lib: different.m + $(CC) ${JNI} different.m -o libDifferent_aarch64 -target arm64-apple-macos11 -framework Cocoa -shared + +# 'strip' throws errors, but compresses down to 2 Kb, so no big deal +universal_lib: x64_lib arm_lib + lipo -create -output ../src/processing/core/libDifferent.jnilib libDifferent_x86_64 libDifferent_aarch64 diff --git a/core/different/README.md b/core/different/README.md new file mode 100644 index 000000000..80899bb1c --- /dev/null +++ b/core/different/README.md @@ -0,0 +1,12 @@ +# Think Different + +Native code to go with the `ThinkDifferent` class that handles macOS-specific API calls. + +Currently only handles hiding the menu bar, and the basic structure of the native libraries evolved from the [jAppleMenuBar project](https://github.com/kritzikratzi/jAppleMenuBar) by Hansi Raber and was rewritten for Processing 4 by Ben Fry. + +Helpful reference used for the rewrite: + +* JNI Example (Mac OS) + * +* Building a Universal macOS Binary + * diff --git a/core/different/different.m b/core/different/different.m new file mode 100644 index 000000000..80124b3cb --- /dev/null +++ b/core/different/different.m @@ -0,0 +1,18 @@ +#import +#import + + +JNIEXPORT void JNICALL Java_processing_core_ThinkDifferent_hideMenuBar +(JNIEnv *env, jclass clazz, jboolean visible, jboolean kioskMode) +{ + NSApplicationPresentationOptions options = + NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar; + [NSApp setPresentationOptions:options]; +} + + +JNIEXPORT void JNICALL Java_processing_core_ThinkDifferent_showMenuBar +(JNIEnv *env, jclass clazz, jboolean visible, jboolean kioskMode) +{ + [NSApp setPresentationOptions:0]; +} diff --git a/core/different/libDifferent_aarch64 b/core/different/libDifferent_aarch64 new file mode 100755 index 000000000..d49bf2293 Binary files /dev/null and b/core/different/libDifferent_aarch64 differ diff --git a/core/src/japplemenubar/libjAppleMenuBar.jnilib b/core/different/libDifferent_x86_64 similarity index 72% rename from core/src/japplemenubar/libjAppleMenuBar.jnilib rename to core/different/libDifferent_x86_64 index 2c57f6465..9f5034c57 100755 Binary files a/core/src/japplemenubar/libjAppleMenuBar.jnilib and b/core/different/libDifferent_x86_64 differ diff --git a/core/done.txt b/core/done.txt index 64f5dfaa9..4691a15dd 100644 --- a/core/done.txt +++ b/core/done.txt @@ -1,3 +1,261 @@ +1286 (4.0.1) +X no changes to core + + +1285 (4.0) +X no changes in core + + +1284 (4.0b9) +X allow . to start a number in SVG paths (a little) +X still lots of work to do here, it's an SVG 1.1 (vs 1.0) thing + +data +X add getXxxxList() methods to JSONObject +X add toXxxxList() methods to JSONArray +X rename getXxxArray() to toXxxArray() in JSONArray for consistency +X deprecate the old versions +X add random() method to XxxList classes to return a random value from the list +X use toArray() instead of array() in LongList +X did not deprecate old version because it was actually broken +X use toArray() instead of array() in all other List classes +X lots of cleanup (and some bug fixing) in the List classes + + +1283 (4.0b8) +X lots of OpenGL updates + +macos-aarch64 +X JOGL is broken on aarch64 +X https://github.com/processing/processing4/issues/370 +X jogl build for m1 +X https://github.com/processing/processing4/issues/128 +X https://forum.jogamp.org/JOGL-for-Mac-ARM-Silicon-td4040887.html +X https://github.com/jzy3d/jogl/blob/feature/macosx-arm64/HOW-TO-ADD-ARM64-TO-2.4.md +X direct link to our reference https://github.com/jzy3d/jogl/blob/c43709921f9a616880782646ca7681a9eadee091/HOW-TO-ADD-ARM64-TO-2.4.md +X incorporated patches to make it work +X https://github.com/jzy3d/jogl/pull/17/files +X https://github.com/jzy3d/jogl/pull/16/files + +andres +X filter() calls with high pixel density in OpenGL make a tiny texture +X https://github.com/processing/processing4/issues/429 +X https://github.com/processing/processing4/pull/433 +X Renamed parameters controlling GL buffer settings +X https://github.com/processing/processing4/pull/435 +X PShape performance could be improved using buffer object streaming +X https://github.com/processing/processing4/issues/196 +X done earlier, but finalized in beta 7 + + +1282 (4.0b7) +X rename jAppleMenuBar, make part of build process +X should just merge into ThinkDifferent +o move that to a 'native' or 'platform' package +X stripped down to just show/hide methods (although show is never used) +X much cleaner setup in general, and all seems to be working +X windowRatio() +X size() function that scales to screen, keeps aspect, re-scales mouse coords +X rmouseX, rmouseY, rwidth, rheight +X also ratioLeft, ratioTop, ratioScale + +opengl +X POINTS PShape performance is drastically worse in 4.0.b3 +X https://github.com/processing/processing4/issues/345 +X https://github.com/processing/processing4/pull/432 + +cleaning +o crash on startup when "Mirror Displays" selected (cantfix?) +o suspect that this is a specific chipset since Oracle didn't reproduce +o AMD Radeon HD 6770M was in the Oracle bug report +o https://github.com/processing/processing/issues/2186 +o https://bugs.openjdk.java.net/browse/JDK-8027391 +o test with JG's 13" retina laptop +X closed back in 2014 +o NPE at java.awt.Window.init(Window.java:497) when using Airplay +X https://github.com/processing/processing/issues/5620 +o try to catch the NPE and warn the user about what's happening +X confirmed working on Mojave (fusion), Catalina (hamilton), Monterey (bell) +X even with Processing 3.5.4 + + +1281 (4.0b6) +X fix up "Display N does not exist, using the default display instead" +X if more than one display, shows list of available displays (size and index) +X properly set names for windowMove() and windowResize() +X PDF export not working with Processing 4.0 beta 5 +X https://github.com/processing/processing4/issues/395 +X RGB image created with “createImage()" has alpha channel +X https://github.com/processing/processing4/issues/388 +X finalize the solution for frame/surface methods +X https://github.com/processing/processing4/issues/53 +X need to update windowX/Y on first open +X finalize naming/behavior +X make a note that these are actually working now + +pixel density, et al +X Windows scaling at 125% is stretching things to look gross +X https://github.com/processing/processing4/issues/378 +X pixelDensity() not working in exported Windows applications? +X https://github.com/processing/processing/issues/5414#issuecomment-841088518 +X should be fixed in 4.0 beta 3, and if not, beta 4 +o notes from jetbrains +o https://intellij-support.jetbrains.com/hc/en-us/articles/360007994999-HiDPI-configuration +X implement sketch scaling into PApplet +X https://github.com/processing/processing/issues/4897 +X Sketches on Windows don't take UI sizing into account +X https://github.com/processing/processing/issues/4894 + +contribs +X CODED is inconsistent between the default renderer and P2D/P3D +X https://github.com/processing/processing4/issues/376 + +earlier +X Window freezes on resize in Windows 10 (Default & P2D renderers) +X https://github.com/processing/processing/issues/5579 +X https://github.com/processing/processing4/pull/298 +X fullScreen() not working on a second display with P2D or P3D +X https://github.com/processing/processing4/issues/352 + + +1280 (4.0b5) +X replace japplemenubar with something that's more likely to work in the future +X https://github.com/processing/processing4/issues/221 +o seems to be working fine; not sure, but have to roll with it for now +X breaks on aarch64 +X https://github.com/processing/processing4/issues/372 + +resizing windows and sketches +X why does GL flash red when resizing? +X this may be because of gaps between EDT and updates +X fixing the surface.setSize() issue may fix it +X https://github.com/processing/processing4/issues/386 +X Resizing window outside setup causes NPE when saving surface pixels +X https://github.com/processing/processing4/issues/162 +o Window Manager issue when trying to resize window +X https://github.com/processing/processing/issues/4129 +X same goes for window resize events: queue them for when it's safe +o also probably need to be extending the display with the background color +o so that it's not neither stretching the viewport temporarily +o or leaving a gap at the edge of the window +X this was mostly already in place +X 'ArrayIndexOutOfBoundsException: Coordinate out of bounds!' +X when resizing sketch while also saving frame in Java2D (default renderer) +X https://github.com/processing/processing4/issues/186 +X working on better methods for surface.setXxxx() but incomplete +o should it be requestSize() because the change is not immediate? +X nope, too confusing for folks anyway +X windowTitle(), windowSize(), windowResizable() +X windowLocation() or maybe windowPosition() and windowPositioned() +X or windowMove()? +X do we need a windowResized() event? +X windowMove(d) would go with windowResize(d) +X windowResized and windowPositioned ok but imperfect +X but windowSize() is awkward with size() +o moveWindow better than windowMove +o but breaks alphabetical +o and then titleWindow()? ugh + +images +X rework saveImpl() for images and how it interacts with the ShimAWT default +X remove the ancient TIFF saving code +X add more plumbing to set the compression level on JPEG and the DPI for PNG + +design +X core/src/icon-NN.png should be the exported application icon +X currently it's the p5 icon since the export just looks black + + +1279 (4.0b4) +X put opengl libs for core into platform-specific subfolders +X get us ready for other platforms (M1 in particular) +X fixes Windows antivirus slowdowns +X https://github.com/processing/processing/issues/4783#issuecomment-269328168 +X https://github.com/processing/processing/issues/4239 +X https://jogamp.org/bugzilla/show_bug.cgi?id=1301 +X https://stackoverflow.com/a/51100411 + +api changes (in previous releases) +X static versions of selectInput/selectOutput/selectFolder removed from PApplet +X noted in the README +X java.awt.Frame object "frame" removed from PApplet (been warning since 2015) +X https://github.com/processing/processing4/issues/54 +X protected PImage.checkAlpha() now public +X all AWT calls have been moved out of PImage +X this may be a problem for anything that was relying on those internals +X removed MouseEvent.getClickCount() and MouseEvent.getAmount() +X these had been deprecated, not clear they were used anywhere + +earlier +X Intel HD Graphics 3000 workaround is causing a big fat warning +X https://github.com/processing/processing4/issues/50 + +decisions +o possibly move jogl back to a separate library +o setting up deps for it with projects that don't use it is a headache +X just not possible +X PShader would have to change packages +X that much messier to link back with projects that rely on JOGL +o would really be nice to pull out the OpenGL stuff +o though would require just a single PShader class in .opengl package +o way too messy, just need to stick with our base platforms + +opengl regressions +X PShape doesn't allow attibutes of int type anymore +X https://github.com/processing/processing4/issues/344 +X https://github.com/processing/processing4/pull/363 +X using P2D with createShape causing GLException +X https://github.com/processing/processing4/issues/353 +X https://github.com/processing/processing4/pull/367 +X Error in internal utility function to invert scaling +X https://github.com/processing/processing4/issues/366 +X https://github.com/processing/processing4/issues/217 +X https://github.com/processing/processing4/pull/367 + + +1278 (4.0b3) +X support extensions for directories with listFiles/listPaths +X otherwise asking for .jpg will return root folders (unless 'only files' specified) +X but because macOS actually puts extensions on folders (.app, etc), support it +X fix for disableStyle() with 2D shapes in P3D +X allow GEOMETRY (not just PATH) with contains() in PShape +X the method is still incomplete, but added a note about that too +X added workaround in PShapeOpenGL to get access to vertex data +X still need to find the root cause +X also need to check for other reports about this +X textSize() losing some symbols +X actually a problem that the default font isn't being set +X https://github.com/processing/processing4/issues/303 +X https://github.com/processing/processing/issues/4956 +X add warning when default font set and drawing unavailable characters + +X multiple windows with GL +X https://github.com/processing/processing4/issues/312 +X https://github.com/processing/processing4/pull/313 +X https://jogamp.org/deployment/jogamp-next/javadoc/jogl/javadoc/com/jogamp/opengl/GLSharedContextSetter.html +X made DrawListener public in PSurfaceJOGL +o probably change that back +X nah, useful to have it there + +andres +X Implement buffer object streaming and finalize attribute API in PShape +X https://github.com/processing/processing4/pull/314 +X Adds support to PATH shapes in P2D renderer +X https://github.com/processing/processing4/pull/316 +X https://github.com/processing/processing/issues/6009 + + +1277 (4.0b2) +X fix apparent problem in PShape with QUADRATIC_VERTEX (wrong vertex index) +X mouseButton not reporting correctly in 4.0b1 (Windows and Linux) +X https://github.com/processing/processing4/issues/281 +X https://github.com/processing/processing4/issues/283 + +contribs +X Remove two redundant variable assignments in PShader +X https://github.com/processing/processing4/pull/172 + + 1276 (4.0b1) X no other changes to core @@ -3135,7 +3393,7 @@ A http://code.google.com/p/processing/issues/detail?id=534 C some docs missing (therefore not syntax highlighting) C http://code.google.com/p/processing/issues/detail?id=84 C constants that are not highlighting (WINDOWS the only one?) -C http://dev.processing.org/bugs/show_bug.cgi?id=662 +C https://download.processing.org/bugzilla/662.html 0206 core (2.0a7) @@ -3326,7 +3584,7 @@ o this will help for exported applets that don't need 'present' X only doing this via sketchXxxx() methods, but should work well o add option to use full screen exclusive mode on present inside pde? o override default setting of macosx (yes) and linux/win (no) -o http://dev.processing.org/bugs/show_bug.cgi?id=1043 +o https://download.processing.org/bugzilla/1043.html o linux present mode isn't working properly X removing FSEM, too buggy and problematic X --display not working on osx @@ -3446,7 +3704,7 @@ o http://code.google.com/p/processing/issues/detail?id=646 o http://code.google.com/p/chromium/issues/detail?id=62004 o http://code.google.com/p/chromium/issues/detail?id=79939 A ortho() behaving differently in P3D vs OPENGL -A http://dev.processing.org/bugs/show_bug.cgi?id=100 +A https://download.processing.org/bugzilla/100.html A http://code.google.com/p/processing/issues/detail?id=37 A shows a blank canvas A (was only happening once b/c was drawing first in perspective) @@ -3464,12 +3722,12 @@ A Resizing window in OPENGL breaks ImageCaches A http://code.google.com/p/processing/issues/detail?id=184 X need to make sure the createFont() reference is up to date for charset o opengl + resize window => window content garbled -o http://dev.processing.org/bugs/show_bug.cgi?id=1360 +o https://download.processing.org/bugzilla/1360.html o P2D transformation bug from ira -X http://dev.processing.org/bugs/show_bug.cgi?id=1175 +X https://download.processing.org/bugzilla/1175.html X resize not working in revision 5707 X camera() and perspective() were commented out in setSize() -X http://dev.processing.org/bugs/show_bug.cgi?id=1391 +X https://download.processing.org/bugzilla/1391.html X this was fixed for 0176 o changing vertex alpha in P3D in a QUAD_STRIP is ignored o with smoothing, it works fine, but with PTriangle, it's not @@ -3479,17 +3737,17 @@ o P3D also seems to have trouble w/ textures edges.. bad math? o No textures render with hint(ENABLE_ACCURATE_TEXTURES) X http://code.google.com/p/processing/issues/detail?id=129 o textAlign(JUSTIFY) (with implementation) -o http://dev.processing.org/bugs/show_bug.cgi?id=1309 +o https://download.processing.org/bugzilla/1309.html X http://code.google.com/p/processing/issues/detail?id=186 X decided with casey not to include o resizing opengl destroys context and textures -o http://dev.processing.org/bugs/show_bug.cgi?id=1176 +o https://download.processing.org/bugzilla/1176.html o in P2D, two vertex() line calls with fill() causes duplicate output o works fine in other renderers, has to do with tesselation -X http://dev.processing.org/bugs/show_bug.cgi?id=1191 +X https://download.processing.org/bugzilla/1191.html X http://code.google.com/p/processing/issues/detail?id=162 o extra triangles being seen in P2D -X http://dev.processing.org/bugs/show_bug.cgi?id=1192 +X https://download.processing.org/bugzilla/1192.html X http://code.google.com/p/processing/issues/detail?id=163 A implement repeating textures A http://code.google.com/p/processing/issues/detail?id=94 @@ -3570,7 +3828,7 @@ X textures truly did get worse in P3D o problem is that bilinear is turned on by default starting in 0124(?) X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Programs;action=display;num=1205171649 o Implement support for complex shapes when using P2D and P3D -X http://dev.processing.org/bugs/show_bug.cgi?id=1053 +X https://download.processing.org/bugzilla/1053.html X http://code.google.com/p/processing/issues/detail?id=145 o smooth in P3D has zbuffer glitches X http://code.google.com/p/processing/issues/detail?id=131 @@ -3579,13 +3837,13 @@ X http://code.google.com/p/processing/issues/detail?id=132 o textured sphere example needs to set normals o also needs fix for last edge and the seam o text() not setting zbuffer in P3D because not fully opaque -X http://dev.processing.org/bugs/show_bug.cgi?id=696 +X https://download.processing.org/bugzilla/696.html X http://code.google.com/p/processing/issues/detail?id=88 o osx 10.5 (not 10.4) performing text width calculation differently -o http://dev.processing.org/bugs/show_bug.cgi?id=972 +o https://download.processing.org/bugzilla/972.html X http://code.google.com/p/processing/issues/detail?id=128 o Automatically use textMode(SCREEN) with text() when possible -o http://dev.processing.org/bugs/show_bug.cgi?id=1020 +o https://download.processing.org/bugzilla/1020.html X http://code.google.com/p/processing/issues/detail?id=134 o writing image file (missing a flush() call?) on exit() fails o lots of zero length files @@ -3602,7 +3860,7 @@ o catch OutOfMemoryError inside size() and let the user know o http://processing.org/discourse/yabb/YaBB.cgi?board=Proce55ing_software_bugs;action=display;num=1038847001 o remove some of the bloat, how can we make things more compact? o i.e. if not using 3D, can leave out PGraphics3, PTriangle, PLine -o http://dev.processing.org/bugs/show_bug.cgi?id=127 +o https://download.processing.org/bugzilla/127.html o fix-up the curve_init() and the rest to use matrices o and not have ugly names (i.e. just g.curveDetail is good) o weird ellipse bug with an alpha line in same image @@ -3624,14 +3882,14 @@ o mgorbet stroke transparency problem o http://processing.org/discourse/yabb/YaBB.cgi?board=Proce55ing_software_bugs;action=display;num=1076383048;start=0 o make sure line() commands don't try to have a fill o make thick lines draw perpendicular to the screen with P3D -o http://dev.processing.org/bugs/show_bug.cgi?id=956 +o https://download.processing.org/bugzilla/956.html o ewjordan suggests building the quad in screen coords after perspective o images are losing pixels at the edges X http://code.google.com/p/processing/issues/detail?id=38 o odd error with some pixels from images not drawing properly o http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1115681453 o clipping not implemented -o http://dev.processing.org/bugs/show_bug.cgi?id=1393 +o https://download.processing.org/bugzilla/1393.html X http://code.google.com/p/processing/issues/detail?id=210 o http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1114184516 o http://processing.org/discourse/yabb/YaBB.cgi?board=Proce55ing_software_bugs;action=display;num=1058491568;start=0 @@ -3679,7 +3937,7 @@ o disabling error reporting (what's taking it so long?) o maybe use a hint() to enable it? or a getError() function? o make cached display list of filled/non-filled ellipse.. rect.. etc o these don't work when the vertex colors change, but.. -o http://dev.processing.org/bugs/show_bug.cgi?id=657 +o https://download.processing.org/bugzilla/657.html o or maybe should be using vertex arrays? o maybe break out a separate timage object in textures? o needs tindex and tbuffer, both gl specific @@ -3695,22 +3953,22 @@ o cache for type should be per-renderer o make the opengl textmode shape stuff get better and use PShape o because opengl needs vectors, but also the image cache for textures X stroke showing above fill when used with P3D -X http://dev.processing.org/bugs/show_bug.cgi?id=1032 +X https://download.processing.org/bugzilla/1032.html X fixed in release 0160 o Stroked polygons losing stroke pixels due to z-buffer issues in P3D o http://code.google.com/p/processing/issues/detail?id=73 X refactor PApplet.main() and Runner.startInternal() to remove duplication -X http://dev.processing.org/bugs/show_bug.cgi?id=245 +X https://download.processing.org/bugzilla/245.html o PFont.size not available.. other font accessors? -o http://dev.processing.org/bugs/show_bug.cgi?id=1510 +o https://download.processing.org/bugzilla/1510.html o implement method for lightweight components with processing applets -o http://dev.processing.org/bugs/show_bug.cgi?id=686 +o https://download.processing.org/bugzilla/686.html X closed as LATER in the original bugs db o add getSketchSize() and getSketchRenderer() o these could simply have the defaults at the outset X added in the more recent revisions X Use getContextClassLoader() instead of Class.forName() -X http://dev.processing.org/bugs/show_bug.cgi?id=514 +X https://download.processing.org/bugzilla/514.html X cursor functions don't work in present mode X just add a note to the reference X http://code.google.com/p/processing/issues/detail?id=160 @@ -3727,7 +3985,7 @@ X should beginRecord inherit settings from its parent renderer? X textFont() is null on beginRecord X same would be the case for strokeWeight, background, etc. X add note to begin/endRecord, that settings are not inherited -X http://dev.processing.org/bugs/show_bug.cgi?id=346 +X https://download.processing.org/bugzilla/346.html o or actually inherit the settings X decision: too many minor glitches possible, just note in the ref X i.e. some fonts don't work with PDF, or bg color can't be re-set @@ -3800,7 +4058,7 @@ X XMLElemnt.parse() or new XMLElement(xmldata)? X same goes for PShape.. parse from a string? X http://code.google.com/p/processing/issues/detail?id=277 o Writing XML files (clean up the API) -o http://dev.processing.org/bugs/show_bug.cgi?id=964 +o https://download.processing.org/bugzilla/964.html X XMLElement lacks add/set/remove methods X http://code.google.com/p/processing/issues/detail?id=440 X PNode is final(?) name, get that implemented @@ -3828,7 +4086,7 @@ o updatePixels() is slow to create a BufferedImage o therefore the incomplete rendering o could this be an issue fixed by a MediaTracker? o first line of applets is missing on java 1.4+ on the mac -X http://dev.processing.org/bugs/show_bug.cgi?id=283 +X https://download.processing.org/bugzilla/283.html o make the PFont index lookup use numbers up to 256? X change JAVA2D surface to be RGB instead of ARGB X seeing some strange behavior, hopefully this won't be a regression @@ -3848,7 +4106,7 @@ A OPENGL2 record only saves one line in a LINES shape A http://code.google.com/p/processing/issues/detail?id=579 X add isClosed() and getChildren() methods to PShape X Frame skipping with processor intensive applets using 1.6 -X http://dev.processing.org/bugs/show_bug.cgi?id=766 +X https://download.processing.org/bugzilla/766.html X may be fixed, but not verified o definite present-mode weirdness with background colors X normal() command commented out in sphere() method @@ -3885,7 +4143,7 @@ X present mode is no longer centering on macosx X problem with setExtendedState(), which doesn't seem to be necessary o only top left 100 x 100 pixels are displayed in presentation mode (Linux 1.1+) o also was just crashing in main() for me, check on this later -o http://dev.processing.org/bugs/show_bug.cgi?id=1560 +o https://download.processing.org/bugzilla/1560.html 0192 core (pre) @@ -3899,12 +4157,12 @@ X appears to have been some old code that wasn't removed X anglebetween regression X http://code.google.com/p/processing/issues/detail?id=435 X more chatter with this -X http://dev.processing.org/bugs/show_bug.cgi?id=131 +X https://download.processing.org/bugzilla/131.html X http://code.google.com/p/processing/issues/detail?id=43 X shearX and shearY not implemented with P2D and JAVA2D X http://code.google.com/p/processing/issues/detail?id=452 L PGraphicsJava2D.resize() not working the same as PGraphics2D.resize() -L http://dev.processing.org/bugs/show_bug.cgi?id=1107 +L https://download.processing.org/bugzilla/1107.html X http://code.google.com/p/processing/issues/detail?id=150 X resize is not available with renderers, use createGraphics() X added a note to the reference @@ -3915,11 +4173,11 @@ X http://code.google.com/p/processing/issues/detail?id=467 stop/destroy/dispose L stop() not working very well L as a result, dispose() methods aren't being called on libraries -L http://dev.processing.org/bugs/show_bug.cgi?id=131 -L http://dev.processing.org/bugs/show_bug.cgi?id=77 (dupe) -L http://dev.processing.org/bugs/show_bug.cgi?id=183 (dupe) +L https://download.processing.org/bugzilla/131.html +L https://download.processing.org/bugzilla/77.html (dupe) +L https://download.processing.org/bugzilla/183.html (dupe) L double stop() called with noLoop() -L http://dev.processing.org/bugs/show_bug.cgi?id=1270 +L https://download.processing.org/bugzilla/1270.html L http://code.google.com/p/processing/issues/detail?id=180 @@ -3954,12 +4212,12 @@ X changing getIntAttribute() et al to getInt() X need to make the reference change for the next release X add listChildren() method X XML attributes named "xmlns" always return NULL -X http://dev.processing.org/bugs/show_bug.cgi?id=1196 +X https://download.processing.org/bugzilla/1196.html X http://code.google.com/p/processing/issues/detail?id=166 X when re-saving and ods file, not including all the name spaces at the top X several fixes to how namespaces are handled (or not handled) X save xml elements back out again -X http://dev.processing.org/bugs/show_bug.cgi?id=963 +X https://download.processing.org/bugzilla/963.html X http://code.google.com/p/processing/issues/detail?id=126 X or make the call that it's read-only, and remove the saving code X adding write() method? @@ -3987,7 +4245,7 @@ X more Linux PDF fixes from Matthias Breuer fixed in 0185 X PDF library matrix is not reset between frames -X http://dev.processing.org/bugs/show_bug.cgi?id=1227 +X https://download.processing.org/bugzilla/1227.html 0185 core (pre) @@ -3995,10 +4253,10 @@ X fix two bugs with fonts created with specific charsets X fix from jdf for PImage(java.awt.Image img) and ARGB images X public PImage(java.awt.Image) sets format to RGB (but could be ARGB) X large number of beginShape(POINTS) not rendering correctly on first frame -X http://dev.processing.org/bugs/show_bug.cgi?id=1572 +X https://download.processing.org/bugzilla/1572.html X pass actual exceptions from InvocationTargetException in registered methods o svg issue with implicit path command -o http://dev.processing.org/bugs/show_bug.cgi?id=1561 +o https://download.processing.org/bugzilla/1561.html o http://code.google.com/p/processing/issues/detail?id=257 X appears to be faulty code X remove call to requestFocus() that broke the ColorSelector (and Editor) @@ -4025,7 +4283,7 @@ X implement support for svgz files 0184 core (pre) X copy and blend scale when unnecessary -X http://dev.processing.org/bugs/show_bug.cgi?id=1482 +X https://download.processing.org/bugzilla/1482.html X add Pattern caching to match() to speed things up X make saveStream() return a boolean to indicate success @@ -4040,9 +4298,9 @@ X fix other problems with font use in PDF X remove debug message from PDF X add skewX/skewY o do them as shearX/Y? -X http://dev.processing.org/bugs/show_bug.cgi?id=1448 +X https://download.processing.org/bugzilla/1448.html X PImage.copy() not including the bottom and rightmost pixels of srcImage -X http://dev.processing.org/bugs/show_bug.cgi?id=1174 +X https://download.processing.org/bugzilla/1174.html 0181 core (pre-release) @@ -4051,15 +4309,15 @@ X no changes for 0181 0180 core (pre-release) X add implicit path commands to svg loader, thanks to Ben S -X http://dev.processing.org/bugs/show_bug.cgi?id=1503 +X https://download.processing.org/bugzilla/1503.html X don't allow sketch windows to open with the title bar off screen -X http://dev.processing.org/bugs/show_bug.cgi?id=1508 +X https://download.processing.org/bugzilla/1508.html 0179 core (1.1) X screenWidth/Height instead of screenW/H X open up the pdf library more (philho) -X http://dev.processing.org/bugs/show_bug.cgi?id=1343 +X https://download.processing.org/bugzilla/1343.html X cache font information for the PDF library to improve setup time X when using createFont("xxxx.ttf"), should use textMode(SHAPE) with PDF X because ttf files will not be installed on the system when opening pdf @@ -4077,7 +4335,7 @@ o or maybe pdf just has its own default? X create characters on the fly when createFont() is used o memory leak problem with fonts in JAVA2D X can't get this to crash anymore -o http://dev.processing.org/bugs/show_bug.cgi?id=1252 +o https://download.processing.org/bugzilla/1252.html earlier X if no draw() method, and renderer is not displayable, then exit @@ -4087,7 +4345,7 @@ X static mode PDFs shouldn't just hang 0178 core (private) X filter(DILATE/ERODE) X dilate(boolean) has bug in clamping of top kernel coordinate -X http://dev.processing.org/bugs/show_bug.cgi?id=1477 +X https://download.processing.org/bugzilla/1477.html X deprecated 'screen', adding screenW and screenH X write implementation for get/set methods inside PImage (w/o pixels[]) @@ -4099,19 +4357,19 @@ X no changes 0176 core (private) X opengl sketches run at 30fps in present mode on OS X o still having problems w/ full screen non-FSEM -X http://dev.processing.org/bugs/show_bug.cgi?id=1425 +X https://download.processing.org/bugzilla/1425.html X PFont not working well with lots of characters X only create bitmap chars on the fly when needed (in createFont) X Implement better caching mechanism when creating large fonts -X http://dev.processing.org/bugs/show_bug.cgi?id=1111 +X https://download.processing.org/bugzilla/1111.html X fix problem with "create font" still showing anti-aliased text X don't make fonts power of 2 X PGraphics3D: beginDraw does not release old textures -X http://dev.processing.org/bugs/show_bug.cgi?id=1423 +X https://download.processing.org/bugzilla/1423.html X fix from taifun_browser X removing camera() and perspective() from setSize() trashes resize X probably regression b/c camera/perspective can't be called then -X http://dev.processing.org/bugs/show_bug.cgi?id=1391 +X https://download.processing.org/bugzilla/1391.html 0175 core (private) @@ -4120,13 +4378,13 @@ X changed createInputRaw() to only bother checking URLs if : present 0174 core (private) X svg paths that use 'e' (exponent) not handled properly -X http://dev.processing.org/bugs/show_bug.cgi?id=1408 +X https://download.processing.org/bugzilla/1408.html 0173 core (private) X Re-enabled hack for temporary clipping. X Clipping still needs to be implemented properly, however. Please help! -X http://dev.processing.org/bugs/show_bug.cgi?id=1393 +X https://download.processing.org/bugzilla/1393.html 0172 core (private) @@ -4136,14 +4394,14 @@ X no changes to the core (or were there?) 0171 core (1.0.9) X Blurred PImages in OPENGL sketches X removed NPOT texture support (for further testing) -X http://dev.processing.org/bugs/show_bug.cgi?id=1352 +X https://download.processing.org/bugzilla/1352.html 0170 core (1.0.8) X added some min/max functions that work with doubles X not sure if those are staying in or not X filter(RGB) supposed to be filter(OPAQUE) -X http://dev.processing.org/bugs/show_bug.cgi?id=1346 +X https://download.processing.org/bugzilla/1346.html X implement non-power-of-2 textures X fix get() when used with save() in OpenGL mode X immediately update projection with OpenGL @@ -4151,7 +4409,7 @@ X in the past, projection updates required a new frame X also prevents camera/project from being reset with setSize() (regression?) X which was a problem anyway because it made GL calls outside draw() X partial fix for texture edge problems with opengl -o http://dev.processing.org/bugs/show_bug.cgi?id=602 +o https://download.processing.org/bugzilla/602.html X some camera problems may be coming from "cameraNear = -8" line X this may cause other problems with drawing/clipping however X opengl camera does not update on current frame (has to do a second frame) @@ -4162,18 +4420,18 @@ X remove methods from PApplet that use doubles X remove major try/catch block from PApplet.main() X hopefully will allow some exception stuff to come through properly X PVector.angleDistance() returns NaN -X http://dev.processing.org/bugs/show_bug.cgi?id=1316 +X https://download.processing.org/bugzilla/1316.html 0168 core (1.0.6) X getImage() setting the wrong image type -X http://dev.processing.org/bugs/show_bug.cgi?id=1282 +X https://download.processing.org/bugzilla/1282.html X strokeWeight() makes lines 2x too thick with P2D -X http://dev.processing.org/bugs/show_bug.cgi?id=1283 +X https://download.processing.org/bugzilla/1283.html X image() doesn't work with P2D, P3D, and OPENGL with noFill() -X http://dev.processing.org/bugs/show_bug.cgi?id=1299 +X https://download.processing.org/bugzilla/1299.html o maybe using rgb instead of tint inside drawing loops? -X http://dev.processing.org/bugs/show_bug.cgi?id=1222 +X https://download.processing.org/bugzilla/1222.html 0167 core (1.0.5) @@ -4187,32 +4445,32 @@ X also causes problems with PDF X preliminary thread() implementation X double param version of map() X PImage cacheMap problem when using PImage.get() -X http://dev.processing.org/bugs/show_bug.cgi?id=1245 +X https://download.processing.org/bugzilla/1245.html X problems with > 512 points and P3D/OPENGL (thx DopeShow) -X http://dev.processing.org/bugs/show_bug.cgi?id=1255 +X https://download.processing.org/bugzilla/1255.html X imageMode(CENTER) doesn't work properly with P2D -X http://dev.processing.org/bugs/show_bug.cgi?id=1232 +X https://download.processing.org/bugzilla/1232.html X reset matrices when using beginRecord() with PDF -X http://dev.processing.org/bugs/show_bug.cgi?id=1227 +X https://download.processing.org/bugzilla/1227.html X resizing window distorts gl graphics X patch from Pablo Funes -X http://dev.processing.org/bugs/show_bug.cgi?id=1176 +X https://download.processing.org/bugzilla/1176.html X significant point() and set() slowdown on OS X -X http://dev.processing.org/bugs/show_bug.cgi?id=1094 +X https://download.processing.org/bugzilla/1094.html 0165 core (1.0.3) X update to itext 2.1.4 X endRecord or endRaw produces RuntimeException with PDF library -X http://dev.processing.org/bugs/show_bug.cgi?id=1169 +X https://download.processing.org/bugzilla/1169.html X problem with beginRaw/endRaw and OpenGL -X http://dev.processing.org/bugs/show_bug.cgi?id=1171 +X https://download.processing.org/bugzilla/1171.html X set strokeWeight with begin/endRaw -X http://dev.processing.org/bugs/show_bug.cgi?id=1172 +X https://download.processing.org/bugzilla/1172.html X fix strokeWeight with P3D (better version of line hack) X make P3D use proper weights X ArrayIndexOutOfBoundsException with point() -X http://dev.processing.org/bugs/show_bug.cgi?id=1168 +X https://download.processing.org/bugzilla/1168.html 0164 core (1.0.2) @@ -4223,55 +4481,55 @@ o add documentation o add this for text in a rectangle? X minor bug fix to svg files that weren't being resized properly X OpenGL is rendering darker in 0149+ -X http://dev.processing.org/bugs/show_bug.cgi?id=958 +X https://download.processing.org/bugzilla/958.html X the fix has been found, just incorporate it X thanks to dave bollinger X OutOfMemoryError with ellipse() in P3D and OPENGL -X http://dev.processing.org/bugs/show_bug.cgi?id=1086 +X https://download.processing.org/bugzilla/1086.html X should also fix problem with AIOOBE -X http://dev.processing.org/bugs/show_bug.cgi?id=1117 +X https://download.processing.org/bugzilla/1117.html X point(x,y) ignores noStroke() (in some renderers) -X http://dev.processing.org/bugs/show_bug.cgi?id=1090 +X https://download.processing.org/bugzilla/1090.html X fix startup problem when scheme coloring was odd -X http://dev.processing.org/bugs/show_bug.cgi?id=1109 +X https://download.processing.org/bugzilla/1109.html X fix several point() problems with P3D -X http://dev.processing.org/bugs/show_bug.cgi?id=1110 +X https://download.processing.org/bugzilla/1110.html X nextPage() not working properly with PDF as the renderer -X http://dev.processing.org/bugs/show_bug.cgi?id=1131 +X https://download.processing.org/bugzilla/1131.html X save styles when nextPage() is called X beginRaw() broken (no DXF, etc working) -X http://dev.processing.org/bugs/show_bug.cgi?id=1099 -X http://dev.processing.org/bugs/show_bug.cgi?id=1144 +X https://download.processing.org/bugzilla/1099.html +X https://download.processing.org/bugzilla/1144.html X Fix algorithm for quadratic to cubic curve conversion X wrong algorithm in PGraphicsOpenGL and PShapeSVG X (thanks to user 'shambles') -X http://dev.processing.org/bugs/show_bug.cgi?id=1122 +X https://download.processing.org/bugzilla/1122.html X tint() not working in P2D -X http://dev.processing.org/bugs/show_bug.cgi?id=1132 +X https://download.processing.org/bugzilla/1132.html X blend() y coordinates inverted when using OpenGL -X http://dev.processing.org/bugs/show_bug.cgi?id=1137 +X https://download.processing.org/bugzilla/1137.html invalid/wontfix/dupe X Processing will not start with non-standard disk partitioning on OS X -X http://dev.processing.org/bugs/show_bug.cgi?id=1127 +X https://download.processing.org/bugzilla/1127.html X Got NoClassDefFoundError exception when accessing quicktime API -X http://dev.processing.org/bugs/show_bug.cgi?id=1128 -X http://dev.processing.org/bugs/show_bug.cgi?id=1129 -X http://dev.processing.org/bugs/show_bug.cgi?id=1130 +X https://download.processing.org/bugzilla/1128.html +X https://download.processing.org/bugzilla/1129.html +X https://download.processing.org/bugzilla/1130.html X not using present mode correctly -X http://dev.processing.org/bugs/show_bug.cgi?id=1138 +X https://download.processing.org/bugzilla/1138.html X apparent graphics driver conflict on vista -X http://dev.processing.org/bugs/show_bug.cgi?id=1140 +X https://download.processing.org/bugzilla/1140.html X PImage.save() does not create parent directories -X http://dev.processing.org/bugs/show_bug.cgi?id=1124 +X https://download.processing.org/bugzilla/1124.html X sketch turning blue and not working on osx -X http://dev.processing.org/bugs/show_bug.cgi?id=1164 +X https://download.processing.org/bugzilla/1164.html X dupe: blend() inaccurary -X http://dev.processing.org/bugs/show_bug.cgi?id=1008 +X https://download.processing.org/bugzilla/1008.html X glPushAttrib style function -X http://dev.processing.org/bugs/show_bug.cgi?id=1150 +X https://download.processing.org/bugzilla/1150.html X calling saveFrame() in noLoop() (update reference) -X http://dev.processing.org/bugs/show_bug.cgi?id=1155 +X https://download.processing.org/bugzilla/1155.html xml X fix for xml elements that have null names @@ -4281,9 +4539,9 @@ X added optional toString(boolean) parameter to enable/disable indents 0163 core (1.0.1) X do not parse split() with regexp -X http://dev.processing.org/bugs/show_bug.cgi?id=1060 +X https://download.processing.org/bugzilla/1060.html X ArrayIndexOutOfBoundsException in ellipseImpl() with 1.0 -X http://dev.processing.org/bugs/show_bug.cgi?id=1068 +X https://download.processing.org/bugzilla/1068.html 0162 core (1.0) @@ -4293,12 +4551,12 @@ X no additional changes for 1.0 0161 core X present on macosx causing strange flicker/jump while window opens X using validate() for present mode, pack() otherwise -X http://dev.processing.org/bugs/show_bug.cgi?id=1051 +X https://download.processing.org/bugzilla/1051.html 0160 core X stroked lines drawing on top of everything else in P3D -X http://dev.processing.org/bugs/show_bug.cgi?id=1032 +X https://download.processing.org/bugzilla/1032.html X 100x100 sketches not working @@ -4312,7 +4570,7 @@ X do we need to do glEnable for multisample with gl? o just need to test this on a few platforms X doesn't seem to be the case X Present mode and OPENGL not working in 0156 with Windows Vista -X http://dev.processing.org/bugs/show_bug.cgi?id=1009 +X https://download.processing.org/bugzilla/1009.html X Seems to be limited to Java 6u10 (but verify) o -Dsun.java2d.d3d=false seems to fix the problem o can probably add that to PApplet.main() @@ -4322,18 +4580,18 @@ X does not work 0158 core X beginShape(TRIANGLE_FAN), and therefore arc(), broken in P2D X also a typo in the ColorWheel example -X http://dev.processing.org/bugs/show_bug.cgi?id=1019 +X https://download.processing.org/bugzilla/1019.html X P2D - null pointer exception drawing line with alpha stroke -X http://dev.processing.org/bugs/show_bug.cgi?id=1023 +X https://download.processing.org/bugzilla/1023.html X P2D endShape() is working like endShape(CLOSE) -X http://dev.processing.org/bugs/show_bug.cgi?id=1021 -X http://dev.processing.org/bugs/show_bug.cgi?id=1028 (mark as dupe) +X https://download.processing.org/bugzilla/1021.html +X https://download.processing.org/bugzilla/1028.html (mark as dupe) X arc() center transparent -X http://dev.processing.org/bugs/show_bug.cgi?id=1027 +X https://download.processing.org/bugzilla/1027.html X mark as dupe of bug #200 X but fixed for the P2D case, still afflicts P2D X P2D - alpha stroke rendered at full opacity -X http://dev.processing.org/bugs/show_bug.cgi?id=1024 +X https://download.processing.org/bugzilla/1024.html X smooth() on single line ellipses not coming out smooth X sort of works, just not great X improve (slightly) the quality of ellipse() and arc() with P2D @@ -4342,7 +4600,7 @@ X split out ellipseImpl and arcImpl from PGraphics into P2D and P3D X figure out better model for adaptive sizing of circles X also goes for arcs, though will be weighted based on arc size X Bring back CENTER_RADIUS but deprecate it -X http://dev.processing.org/bugs/show_bug.cgi?id=1035 +X https://download.processing.org/bugzilla/1035.html X enable smoothing by default in opengl X change hints to instead only be able to DISABLE 2X or 4X X bring back .width and .height fields for PShape @@ -4355,36 +4613,36 @@ X rev the version number 0157 core X SVG polygon shapes not drawing since loadShape() and PShape changes -X http://dev.processing.org/bugs/show_bug.cgi?id=1005 +X https://download.processing.org/bugzilla/1005.html X image(a, x, y) not honoring imageMode(CENTER) X need to just pass image(a, x, y) to image(a, x, y, a.width, a.height) X rather than passing it directly to imageImpl() -X http://dev.processing.org/bugs/show_bug.cgi?id=1013 +X https://download.processing.org/bugzilla/1013.html o disable P2D before releasing (unless implementation is finished) 0156 core X clarify the "no variables in size() command" rule -X http://dev.processing.org/bugs/show_bug.cgi?id=992 +X https://download.processing.org/bugzilla/992.html X present mode broken in general? X placement of elements in present mode is messed up X "stop" button hops around, window not centered on screen -X http://dev.processing.org/bugs/show_bug.cgi?id=923 +X https://download.processing.org/bugzilla/923.html X disable P2D for size() and createGraphics() before releasing X already was disabled, just a bug in detecting it 0155 core X hint(DISABLE_DEPTH_TEST) throws null pointer exception with OpenGL -X http://dev.processing.org/bugs/show_bug.cgi?id=984 +X https://download.processing.org/bugzilla/984.html 0154 core X only set apple.awt.graphics.UseQuartz on osx, otherwise security error -X http://dev.processing.org/bugs/show_bug.cgi?id=976 +X https://download.processing.org/bugzilla/976.html X add skewX() and skewY() to PMatrix X Add support style attribute for path tag to Candy SVG (ricard) -X http://dev.processing.org/bugs/show_bug.cgi?id=771 +X https://download.processing.org/bugzilla/771.html X remove setX/Y/Z from PVector, copy() (use get() instead), add mult/div X remove copy() from PVector? X add mult() and div() with vector inputs? @@ -4400,22 +4658,22 @@ X no changes to 0152 core 0151 core X NullPointerException on curveVertex() when using more than 128 vertices -X http://dev.processing.org/bugs/show_bug.cgi?id=952 +X https://download.processing.org/bugzilla/952.html X Text not bounding correctly with x, y, w, h -X http://dev.processing.org/bugs/show_bug.cgi?id=954 +X https://download.processing.org/bugzilla/954.html o dataFolder() might be flawed b/c it's referring to contents of jar file o for input, better to use openStream X clear up the javadoc on this X odd-size height will make blue line across image in saveFrame() -X http://dev.processing.org/bugs/show_bug.cgi?id=944 +X https://download.processing.org/bugzilla/944.html X probably the pixel flipping code (and endian sorting) not happening X because the blue comes from the byte order flip X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=OpenGL;action=post;num=1219196429 X ArrayIndexOutOfBoundsException in PLine -X http://dev.processing.org/bugs/show_bug.cgi?id=246 -X http://dev.processing.org/bugs/show_bug.cgi?id=462 +X https://download.processing.org/bugzilla/246.html +X https://download.processing.org/bugzilla/462.html X random AIOOBE with P3D and points -X http://dev.processing.org/bugs/show_bug.cgi?id=937 +X https://download.processing.org/bugzilla/937.html cleanup X alter bezier and curve matrices to use PMatrix @@ -4444,18 +4702,18 @@ X remove MACOS9 constant from processing.core.* X because code no longer runs on os9 since dropping 1.1 support X specular() with alpha has been removed X GLDrawableFactory.chooseGraphicsConfiguration() error with OpenGL -X http://dev.processing.org/bugs/show_bug.cgi?id=891 -X http://dev.processing.org/bugs/show_bug.cgi?id=908 +X https://download.processing.org/bugzilla/891.html +X https://download.processing.org/bugzilla/908.html X fix problem with unregisterXxxx() -X http://dev.processing.org/bugs/show_bug.cgi?id=910 +X https://download.processing.org/bugzilla/910.html X make parseFloat() with String[] array return NaN for missing values X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Syntax;action=display;num=1220880375 X fix problems with text in a rectangle -X http://dev.processing.org/bugs/show_bug.cgi?id=893 -X http://dev.processing.org/bugs/show_bug.cgi?id=899 +X https://download.processing.org/bugzilla/893.html +X https://download.processing.org/bugzilla/899.html X finish getImage() method inside PImage X delay() is broken -X http://dev.processing.org/bugs/show_bug.cgi?id=894 +X https://download.processing.org/bugzilla/894.html X remove extra ~ files from core.jar X also fix for other libraries in the make scripts across platforms X fix problems in PApplet regarding signed code @@ -4478,11 +4736,11 @@ X also add a note to the hint() reference X DISABLE_DEPTH_TEST bad results X have to clear the zbuffer to make it work X this may be specific to lines -X http://dev.processing.org/bugs/show_bug.cgi?id=827 +X https://download.processing.org/bugzilla/827.html X though it also seems to be a problem with opengl X also make a note of disabling as a way to help font appearance o Font width calculation fails when using vectorfonts and PDF -o http://dev.processing.org/bugs/show_bug.cgi?id=920 +o https://download.processing.org/bugzilla/920.html X um, this is gross: getMatrix((PMatrix2D) null) X no changing point sizes when using beginShape(POINTS) X this is an opengl restriction, should we stick with it elsewhere? @@ -4503,7 +4761,7 @@ o since camera functions don't even look at it or set it o registerSize() in arcball is causing trouble o some sort of infinite loop issue o textMode(SCREEN) is broken for opengl -o http://dev.processing.org/bugs/show_bug.cgi?id=426 +o https://download.processing.org/bugzilla/426.html o textSpace(SCREEN) for opengl and java2d o don't use loadPixels for either o use font code to set the cached color of the font, then call set() @@ -4523,7 +4781,7 @@ X need to implement point() as actual gl points X this means adding points to the pipeline X point() doesn't work with some graphics card setups X particularly with opengl and java2d -X http://dev.processing.org/bugs/show_bug.cgi?id=121 +X https://download.processing.org/bugzilla/121.html X point() issues o point() being funneled through beginShape is terribly slow X go the other way 'round @@ -4534,7 +4792,7 @@ X fix getFontMetrics() warning X need to have a getGraphics() to get the actual Graphics object X where drawing is happening. parent.getGraphics() works except for OpenGL X Java2D textLinePlacedImpl should check for ENABLE_NATIVE_FONTS hint -X http://dev.processing.org/bugs/show_bug.cgi?id=633 +X https://download.processing.org/bugzilla/633.html X also try to check native font on textFont() commands X in case the hint() has been enabled in the meantime X or rather, always load native font, even w/o the hint() being set @@ -4578,7 +4836,7 @@ X bring svg into main lib X need to straighten out 'table' object for quick object lookup X maybe add to all parent tables? (no, this gets enormous quickly) X fix svg caps/joins for opengl with svg library -X http://dev.processing.org/bugs/show_bug.cgi?id=628 +X https://download.processing.org/bugzilla/628.html X save/restore more of the p5 graphics drawing state X not setting colorMode(), strokeCap, etc. X ignoreStyles is broken - how to handle @@ -4603,12 +4861,12 @@ X so really, flush is required whenever the depth sort is called X need to check this--isn't this referring to DISABLE_DEPTH_TEST? X ENABLE_DEPTH_SORT causing trouble with MovieMaker X need to make the flush() api accessible -X http://dev.processing.org/bugs/show_bug.cgi?id=692 +X https://download.processing.org/bugzilla/692.html X image smoothing X straighten out default vs. ACCURATE vs. whatever X Java2D and P3D and OpenGL are all inconsistent o need to be able to do hint() to do nearest neighbor filtering -X http://dev.processing.org/bugs/show_bug.cgi?id=685 +X https://download.processing.org/bugzilla/685.html o imageDetail(), textDetail(), etc? o decisions on image smoothing vs. text smoothing vs. all @@ -4619,11 +4877,11 @@ X tweaks to PGraphicsOpenGL to support GLGraphics 0147 core X processing sketches not restarting after switching pages in safari -X http://dev.processing.org/bugs/show_bug.cgi?id=581 +X https://download.processing.org/bugzilla/581.html cleaning X misshapen fonts on osx (apple fixed their bug) -X http://dev.processing.org/bugs/show_bug.cgi?id=404 +X https://download.processing.org/bugzilla/404.html 0146 core @@ -4638,9 +4896,9 @@ X add imageMode(CENTER) implementation X add a bunch of changes to the reference to support this X Duplicate 3d faces in beginRaw() export X this was actually doubling geometry, potentially a bit of a disaster -X http://dev.processing.org/bugs/show_bug.cgi?id=737 +X https://download.processing.org/bugzilla/737.html o bezierVertex YZ Plane fill problem -X http://dev.processing.org/bugs/show_bug.cgi?id=752 +X https://download.processing.org/bugzilla/752.html X weird coplanar stuff, shouldn't be doing 3D anyway cleanup @@ -4648,14 +4906,14 @@ X switch to requestImage() (done in 0145) o can bug 77 be fixed with a finalizer? (no, it cannot) X make sure that images with alpha still have alpha in current code X text in rectangle is drawing outside the box -X http://dev.processing.org/bugs/show_bug.cgi?id=844 +X https://download.processing.org/bugzilla/844.html X points missing in part of screen -X http://dev.processing.org/bugs/show_bug.cgi?id=269 +X https://download.processing.org/bugzilla/269.html 0145 core X separate x/y axis on sphereDetail() params -X http://dev.processing.org/bugs/show_bug.cgi?id=856 +X https://download.processing.org/bugzilla/856.html X make sure docs for PImage use createImage X add notes about JOGLAppletLauncher X http://download.java.net/media/jogl/builds/nightly/javadoc_public/com/sun/opengl/util/JOGLAppletLauncher.html @@ -4665,7 +4923,7 @@ o exit() will actually leave the application o this may in fact only be internal X this is just the stop() method, though maybe we need to document it X text(String, float, float, float, float) draws text outside box -X http://dev.processing.org/bugs/show_bug.cgi?id=844 +X https://download.processing.org/bugzilla/844.html X implement textAlign() for y coords in text rect X need to add reference for this X textAlign() bottom now takes textDescent() into account @@ -4673,7 +4931,7 @@ X remove gzip-related hint() threading X major threading overhaul before 1.0 (compendium) -X http://dev.processing.org/bugs/show_bug.cgi?id=511 +X https://download.processing.org/bugzilla/511.html X ewjordan has added a good trace of the badness X need to move off anim off the main event thread X move away from using method like display @@ -4681,41 +4939,41 @@ X look into opengl stuff for dealing with this X move addListeners() calls back out of PGraphics X resize window will nuke font setting X textFont() used in setup() is null once draw() arrives -X http://dev.processing.org/bugs/show_bug.cgi?id=726 +X https://download.processing.org/bugzilla/726.html X colorMode() set inside setup() sometimes not set once draw() arrives -X http://dev.processing.org/bugs/show_bug.cgi?id=767 +X https://download.processing.org/bugzilla/767.html X applet sizing issues with external vm X could this possibly be related to the linux bug? -X http://dev.processing.org/bugs/show_bug.cgi?id=430 +X https://download.processing.org/bugzilla/430.html X alloc() stuff not fixed because of thread halting X problem where alloc happens inside setup(), so, uh.. -X http://dev.processing.org/bugs/show_bug.cgi?id=369 +X https://download.processing.org/bugzilla/369.html X should instead create new buffer, and swap it in next time through X sonia (and anything awt) is locking up on load in rev 91 X prolly something w/ the threading issues X paint is synchronized in 0091 X however this is a necessity, otherwise nasty flickering ensues X and using a "glock" object seems to completely deadlock -X http://dev.processing.org/bugs/show_bug.cgi?id=46 +X https://download.processing.org/bugzilla/46.html o claim that things are much slower in 107 vs 92 o http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Suggestions;action=display;num=1141763531 X hopefully this is cleaned up by removing some of the synchronization X sketches lock up when system time is changed -X http://dev.processing.org/bugs/show_bug.cgi?id=639 +X https://download.processing.org/bugzilla/639.html X can draw() not be run on awt event thread? X look into opengl stuff for dealing with this o "is there a better way to do this" thread in lists folder X framerate that's reported is out of joint with actual -X http://dev.processing.org/bugs/show_bug.cgi?id=512 +X https://download.processing.org/bugzilla/512.html X accuracy of frame timer is incorrect X seems to be aliasing effect of low resolution on millis() X so rates coming out double or half of their actual X probably need to integrate over a rolling array of 10 frames or so X frameRate() speeds up temporarily if CPU load drops dramatically -X http://dev.processing.org/bugs/show_bug.cgi?id=297 +X https://download.processing.org/bugzilla/297.html o perhaps add a hint(DISABLE_FRAMERATE_DAMPER) X fix the flicker in java2d mode -X http://dev.processing.org/bugs/show_bug.cgi?id=122 +X https://download.processing.org/bugzilla/122.html X framerate(30) is still flickery and jumpy.. X not clear what's happening here X appears to be much worse (unfinished drawing) on macosx @@ -4740,7 +4998,7 @@ X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action 0143 core X some fonts broken in java 1.5 on osx have changed again -X http://dev.processing.org/bugs/show_bug.cgi?id=407 +X https://download.processing.org/bugzilla/407.html X filed as bug #4769141 with apple http://bugreport.apple.com/ X appears that asking for the postscript name no longer works o fix "create font" and associated font stuff to straighten it out @@ -4765,7 +5023,7 @@ X add resize() method to PImage 0142 core X update iText in PDF library to 2.1.2u X loadStrings(".") should not list directory contents -X http://dev.processing.org/bugs/show_bug.cgi?id=716 +X https://download.processing.org/bugzilla/716.html 0141 core @@ -4783,7 +5041,7 @@ X no changes to core in 0139 0138 core X improve tessellation accuracy by using doubles internally -X http://dev.processing.org/bugs/show_bug.cgi?id=774 +X https://download.processing.org/bugzilla/774.html 0137 core @@ -4796,49 +5054,49 @@ X deprecate openStream() naming 0136 core X add static version of saveStream() (uses a File and InputStream object) X A "noLoop()" sketch may be unresponsive to exit request -X http://dev.processing.org/bugs/show_bug.cgi?id=694 +X https://download.processing.org/bugzilla/694.html X Fixed bug with subset() when no length parameter was specified X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Syntax;action=display;num=1196366408 -X http://dev.processing.org/bugs/show_bug.cgi?id=707 +X https://download.processing.org/bugzilla/707.html X figure out why tiff images won't open with after effects -X http://dev.processing.org/bugs/show_bug.cgi?id=153 +X https://download.processing.org/bugzilla/153.html X open with photoshop, resave, see which tags change X specifically, which tags that were in the original image file X perhaps something gets corrected? X had a fix, but decided not to re-implement the loadTIFF stuff too X fix for bezierTangent() problem from dave bollinger -X http://dev.processing.org/bugs/show_bug.cgi?id=710 +X https://download.processing.org/bugzilla/710.html X implement curveTangent (thanks to davbol) -X http://dev.processing.org/bugs/show_bug.cgi?id=715 +X https://download.processing.org/bugzilla/715.html X fix problem with get() when imageMode(CORNERS) was in use X fix bug with splice() and arrays -X http://dev.processing.org/bugs/show_bug.cgi?id=734 +X https://download.processing.org/bugzilla/734.html X 'screen' is now static X undo this--may need to be better about specifying screen and all X PImage mask doesn't work after first call -X http://dev.processing.org/bugs/show_bug.cgi?id=744 +X https://download.processing.org/bugzilla/744.html X load and save tga results in upside down tga -X http://dev.processing.org/bugs/show_bug.cgi?id=742 +X https://download.processing.org/bugzilla/742.html X fix problem with g.smooth always returning false in JAVA2D -X http://dev.processing.org/bugs/show_bug.cgi?id=762 +X https://download.processing.org/bugzilla/762.html X add XMLElement (not filename) constructor to SVG lib -X http://dev.processing.org/bugs/show_bug.cgi?id=773 +X https://download.processing.org/bugzilla/773.html 0135 core X modelX/Y/Z still having trouble -X http://dev.processing.org/bugs/show_bug.cgi?id=486 +X https://download.processing.org/bugzilla/486.html X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Suggestions;action=display;num=1186614415 X add docs for model/screen/Y/Z X added for model, along with example. X screen was already complete X bring back opengl mipmaps (create them myself? try w/ newer jogl?) X opengl mipmaps are leaking (regression in spite of #150 fix) -X http://dev.processing.org/bugs/show_bug.cgi?id=610 +X https://download.processing.org/bugzilla/610.html X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=OpenGL;action=display;num=1193967684 X seems to not actually be a problem on mbp, try desktop? X copy() was needing updatePixels() when used with OPENGL or JAVA2D -X http://dev.processing.org/bugs/show_bug.cgi?id=681 +X https://download.processing.org/bugzilla/681.html X check on the bug report for this one as well X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Syntax;action=display;num=1173394373 @@ -4863,17 +5121,17 @@ X fix mini p5 bugs for eugene 0134 core X add noLights() method -X http://dev.processing.org/bugs/show_bug.cgi?id=666 +X https://download.processing.org/bugzilla/666.html X redraw the screen after resize events (even with noLoop) -X http://dev.processing.org/bugs/show_bug.cgi?id=664 +X https://download.processing.org/bugzilla/664.html o problem with transparency in mask() -o http://dev.processing.org/bugs/show_bug.cgi?id=674 +o https://download.processing.org/bugzilla/674.html X mark bug as invalid, using fill() instead of tint() X move to UTF-8 as native format for core file i/o operations X update reference to document the change o jogl glibc 2.4 problem on linux o move back to previous jogl until 1.1.1 is released -X http://dev.processing.org/bugs/show_bug.cgi?id=675 +X https://download.processing.org/bugzilla/675.html o deal with opengl applet export changes o what is deployment suggestion for all the different platforms? o can the jar files actually provide the DLLs properly? @@ -4884,7 +5142,7 @@ X revert back to jogl 1.0 X fix problem with "export to application" and opengl X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=OpenGL;action=display;num=1193142573 X add focus requests so that better chance of keys etc working -X http://dev.processing.org/bugs/show_bug.cgi?id=645 +X https://download.processing.org/bugzilla/645.html X argh, these make things worse (P3D run internal) X add stuff about classversionerrors to the reference X also update the libraries page @@ -4901,7 +5159,7 @@ o this causes everything to be reset once the draw occurs o kinda seems problematic and prone to errors? X nope, just need to say to do all commands inside begin/endDraw o finish implementation so 1.1 support can return -X http://dev.processing.org/bugs/show_bug.cgi?id=125 +X https://download.processing.org/bugzilla/125.html o check into ricard's problems with beginRecord() X i believe these were caused by linux java2d issues X beginFrame()/beginDraw() and defaults() @@ -4969,10 +5227,10 @@ o if loadImage() with spaces when online(), throw an error o get tiff exporter for p5 to support argb and gray X would also need to modify the tiff loading code, X because the header would be different -X http://dev.processing.org/bugs/show_bug.cgi?id=343 +X https://download.processing.org/bugzilla/343.html X map() is not colored, neither is norm X image outofmemoryerror for casey's students -X http://dev.processing.org/bugs/show_bug.cgi?id=355 +X https://download.processing.org/bugzilla/355.html X this may be related to a ton of other memory bugs X 1.5.0_07 and 1.4.2_12 contain the -XX:+HeapDumpOnOutOfMemoryError switch X invaluable for tracking down memory leaks @@ -4980,7 +5238,7 @@ X can't replicate anymore X texture mapping X very odd, "doom era" stuff X would it be possible to have a 'slow but accurate' mode? -X http://dev.processing.org/bugs/show_bug.cgi?id=103 +X https://download.processing.org/bugzilla/103.html X fixed in release 0125 X add hint() and unhint() X also add unhint() to the keywords file @@ -4993,25 +5251,25 @@ X seems to be fixed in later java versions X rewrite getImpl/setImpl inside opengl X placement of 100x100 items is odd X happens with P3D and maybe also P2D? -X http://dev.processing.org/bugs/show_bug.cgi?id=128 +X https://download.processing.org/bugzilla/128.html 0132 core X an image marked RGB but with 0s for the alpha won't draw in JAVA2D X images with 0x00 in their high bits being drawn transparent X also if transparency set but RGB is setting, still honors transparency -X http://dev.processing.org/bugs/show_bug.cgi?id=351 +X https://download.processing.org/bugzilla/351.html X improve memory requirements for drawing images with JAVA2D X now using significantly less memory tint/textures X tint() and noTint() switching problem in P2D X this should be a quick fix -X http://dev.processing.org/bugs/show_bug.cgi?id=222 +X https://download.processing.org/bugzilla/222.html X related to the fill bugs: when fill is identical, no fill applied X actually tint() should take over for fill as per-vertex color X when textured images are being used -X http://dev.processing.org/bugs/show_bug.cgi?id=169 +X https://download.processing.org/bugzilla/169.html X tint() should set texture color, fill() is ignored with textures X add to the reference X fix tint() for PGraphics3 (what could be wrong?) @@ -5019,12 +5277,12 @@ X tint() honoring alpha but not colored tint X maybe not setting fill color when drawing textures X guessing it's an implementation issue in sami's renderer X check with the a_Displaying example and tint(255, 0, 0, 100); -X http://dev.processing.org/bugs/show_bug.cgi?id=90 +X https://download.processing.org/bugzilla/90.html 0131 core X hint(DISABLE_DEPTH_TEST) not behaving consistently -X http://dev.processing.org/bugs/show_bug.cgi?id=483 +X https://download.processing.org/bugzilla/483.html o make hints static - cannot do that X clean up hinting mechanism a bit X look into jogl anti-aliasing on osx @@ -5037,7 +5295,7 @@ X update to JOGL 1.1.0 X add gluegen-rt back to the applet export X http://download.java.net/media/jogl/builds/nightly/javadoc_public/com/sun/opengl/util/JOGLAppletLauncher.html X add print() and println() for long and double -X http://dev.processing.org/bugs/show_bug.cgi?id=652 +X https://download.processing.org/bugzilla/652.html X fix minor bug in saveStream() (undocumented) X "this file is named" errors don't like subdirectories X need to strip off past the file separator or something @@ -5047,16 +5305,16 @@ X need to strip off past the file separator or something X fixed problem with size() and the default renderer X the renderer was being reset after setup(), X so anything that occurred inside setup() was completely ignored. -X http://dev.processing.org/bugs/show_bug.cgi?id=646 -X http://dev.processing.org/bugs/show_bug.cgi?id=648 -X http://dev.processing.org/bugs/show_bug.cgi?id=649 +X https://download.processing.org/bugzilla/646.html +X https://download.processing.org/bugzilla/648.html +X https://download.processing.org/bugzilla/649.html X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1192873557 X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1192731014 0129 core X width and height not always set properly -X http://dev.processing.org/bugs/show_bug.cgi?id=642 +X https://download.processing.org/bugzilla/642.html X bring back background() with alpha @@ -5077,14 +5335,14 @@ o should loadFont() work with ttf files (instead of createFont?) X otherwise loadFont() would work with font names as well, awkward X better to use createFont() for both o jogl issues with some cards on linux, might be fixed with newer jogl -X http://dev.processing.org/bugs/show_bug.cgi?id=367 +X https://download.processing.org/bugzilla/367.html X remove methods for background() to include an alpha value X this is undefined territory because of zbuffer and all X if you want that effect, use a rect() X saveFrame() produces a black background because bg not set correctly: -X http://dev.processing.org/bugs/show_bug.cgi?id=421 +X https://download.processing.org/bugzilla/421.html X background not being set properly -X http://dev.processing.org/bugs/show_bug.cgi?id=454 +X https://download.processing.org/bugzilla/454.html X having shut off the defaults reset, background not getting called for java2d X so this will make that other stuff regress again X in particular, when the applet is resized, but renderer not changed @@ -5092,14 +5350,14 @@ X why aren't background() / defaults() being called for opengl? o http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Contribution_3DOpenGL;action=display;num=1118784331 o simon reports borders on P3D and OPENGL if background() not called X window fixed at 100x100 pixels -X http://dev.processing.org/bugs/show_bug.cgi?id=197 +X https://download.processing.org/bugzilla/197.html X width/height on problem machine prints out as: X 100x100 issues are exhibited on bh140c PCs, test and fix! X 100,200 and 128,200 o AIOOBE on PLine 757.. halts renderer o fix erik's problems with export (also in bugs db) X draw() called twice in vista with java 1.6 -X http://dev.processing.org/bugs/show_bug.cgi?id=587 +X https://download.processing.org/bugzilla/587.html cleanup from previous releases X P3D not doing bilinear interpolation in text and images @@ -5109,7 +5367,7 @@ X probably just allow smooth() but don't smooth anything o pdf export ignoring transparency on linux o check to see if this is the case on other linux machines o seems to be working fine on windows -o http://dev.processing.org/bugs/show_bug.cgi?id=345 +o https://download.processing.org/bugzilla/345.html X change how java version is determined on mac X http://developer.apple.com/technotes/tn2002/tn2110.html X (this tn provides no guidance for macos8/9.. gah) @@ -5124,11 +5382,11 @@ X set upper bound on framerate so as not to completely hose things? fixed in 0127 X ARGB problems with createGraphics o P3D from createGraphics needs to be marked RGB not ARGB -X http://dev.processing.org/bugs/show_bug.cgi?id=160 -X http://dev.processing.org/bugs/show_bug.cgi?id=428 -X http://dev.processing.org/bugs/show_bug.cgi?id=482 -X http://dev.processing.org/bugs/show_bug.cgi?id=530 -X http://dev.processing.org/bugs/show_bug.cgi?id=527 +X https://download.processing.org/bugzilla/160.html +X https://download.processing.org/bugzilla/428.html +X https://download.processing.org/bugzilla/482.html +X https://download.processing.org/bugzilla/530.html +X https://download.processing.org/bugzilla/527.html reasons to drop 1.1 support (and support 1.3+) X remove reflection from createFont() constructor/methods in PFont @@ -5151,9 +5409,9 @@ X get(), set(), copy(), blend(), loadPixels, updatePixels() X set(x, y, image) y reversed in openGL X background(image) also broken X also textMode(SCREEN) -X http://dev.processing.org/bugs/show_bug.cgi?id=91 +X https://download.processing.org/bugzilla/91.html o replaceAll() not supported by 1.1 -o http://dev.processing.org/bugs/show_bug.cgi?id=561 +o https://download.processing.org/bugzilla/561.html o make version of loadBytes that checks length of the stream first o this might not be worth it o the number of cases where this works is small (half of url streams) @@ -5163,10 +5421,10 @@ o (i.e. will it be the uncompressed or compressed size of the data?) createGraphics() issues X offscreen buffers fail with texture mapping X pixels not being set opaque (only with textures?) -X http://dev.processing.org/bugs/show_bug.cgi?id=594 +X https://download.processing.org/bugzilla/594.html X add note to createGraphics() docs that opacity at edges is binary X PGraphics problem with fillColor -X http://dev.processing.org/bugs/show_bug.cgi?id=468 +X https://download.processing.org/bugzilla/468.html fixed earlier X add to open() reference problems with mac @@ -5178,16 +5436,16 @@ X they can call Runtime.getRuntime().exec() if they want more control fixed earlier (in 0125) by ewjordan X accuracy in P3D is very low -X http://dev.processing.org/bugs/show_bug.cgi?id=95 +X https://download.processing.org/bugzilla/95.html X textured polys throwing a lot of exceptions (ouch) -X http://dev.processing.org/bugs/show_bug.cgi?id=546 +X https://download.processing.org/bugzilla/546.html X polygons in z axis with nonzero x or y not filling properly -X http://dev.processing.org/bugs/show_bug.cgi?id=547 +X https://download.processing.org/bugzilla/547.html 0126 core o rect() after strokeWeight(1) gives wrong coords -X http://dev.processing.org/bugs/show_bug.cgi?id=535 +X https://download.processing.org/bugzilla/535.html X bug in osx java 1.4 vs 1.5 X fix bug in str() methods that take arrays X method was not working properly, was using the object reference @@ -5196,7 +5454,7 @@ X implement auto-gzip and gunzip X fix bug where sort() on 0 length array would return null X instead, returns an array X unregisterXxxx() calls to remove methods from libs -X http://dev.processing.org/bugs/show_bug.cgi?id=312 +X https://download.processing.org/bugzilla/312.html X implemented by ewjordan X sort() on strings ignores case X mention the change in the reference @@ -5212,13 +5470,13 @@ o add auto-gunzip to loadStrings() X others for auto-gunzip? X do the same for createWriter() et al X disable mipmaps in 0126 -X http://dev.processing.org/bugs/show_bug.cgi?id=610 +X https://download.processing.org/bugzilla/610.html X add match() method that returns an array of matched items 0125 core X more blend() modes (the five listed on the thread below?) -X http://dev.processing.org/bugs/show_bug.cgi?id=132 +X https://download.processing.org/bugzilla/132.html X figure out what the modes should actually be: X photoshop: normal, dissolve; darken, multiply, color burn, X linear burn; lighten, screen, color dodge, linear @@ -5241,7 +5499,7 @@ X OVERLAY: C = B < 128 ? (2*A*B/255) : 255-2*(255-A)*(255-B)/255 X HARD_LIGHT: C = A < 128 ? (2*A*B/255) : 255-2*(255-A)*(255-B)/255 X SOFT_LIGHT: C = B < 128 ? 2*((A>>1)+64)*B/255 : 255-(2*(255-((A>>1)+64))*(255-B)/255) X jre 1.5.0_10 is still default at java.com.. blech -X http://dev.processing.org/bugs/show_bug.cgi?id=513 +X https://download.processing.org/bugzilla/513.html X constant CENTER_RADIUS will be changed to just RADIUS X CENTER_RADIUS is being deprecated, not removed X remove CENTER_RADIUS from any p5 code (i.e. examples) @@ -5280,29 +5538,29 @@ X given to andy 0125p3 X PImage.save() method is not working with get() -X http://dev.processing.org/bugs/show_bug.cgi?id=558 +X https://download.processing.org/bugzilla/558.html X NullPointerException in Create Font with "All Characters" enabled -X http://dev.processing.org/bugs/show_bug.cgi?id=564 +X https://download.processing.org/bugzilla/564.html X added min() and max() for float and int arrays X need to update reference X moved around min/max functions X opengl image memory leaking X when creating a new PImage on every frame, slurps a ton of memory X workaround is to write the code properly, but suggests something bad -X http://dev.processing.org/bugs/show_bug.cgi?id=150 +X https://download.processing.org/bugzilla/150.html X opengl keeping memory around.. X could this be in vertices that have an image associated X or the image buffer used for textures X that never gets cleared fully? X registerSize() was registering as pre() instead -X http://dev.processing.org/bugs/show_bug.cgi?id=582 +X https://download.processing.org/bugzilla/582.html X set() doesn't bounds check X this shouldn't actually be the case -X http://dev.processing.org/bugs/show_bug.cgi?id=522 +X https://download.processing.org/bugzilla/522.html X get()/set() in PGraphicsJava2D don't bounds check X was actually a dumb error in setDataElements() X set modified to true on endDraw() so that image updates properly -X http://dev.processing.org/bugs/show_bug.cgi?id=526 +X https://download.processing.org/bugzilla/526.html X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Syntax;action=display;num=1171574044 0125p4 (in progress) @@ -5315,29 +5573,29 @@ X probably threading issue, 98 doesn't have any trouble X signs point to Runner or PApplet changes between 98 and 99 X commenting out applet.setupFrameResizeListener() X in line 307 from Runner.java solved the problem -X http://dev.processing.org/bugs/show_bug.cgi?id=282 +X https://download.processing.org/bugzilla/282.html X size of sketch different in setup() and draw() on linux X make sure that the sketch isn't being sized based on bad insets X problem with resizing the component when the frame wasn't resizable -X http://dev.processing.org/bugs/show_bug.cgi?id=341 +X https://download.processing.org/bugzilla/341.html X major rework of the open() command X add gnome-open/kde-open for with PApplet.open() X add open (-a?) on osx to the open() command X make changes in the javadoc and reference X opengl crashes when depth sorting more than two textured shapes -X http://dev.processing.org/bugs/show_bug.cgi?id=560 +X https://download.processing.org/bugzilla/560.html ewjordan stuff (changes checked in) X rect() changes size as it changes position -X http://dev.processing.org/bugs/show_bug.cgi?id=95 +X https://download.processing.org/bugzilla/95.html X strange texture warping in P3D X hint(ENABLE_ACCURATE_TEXTURES) -X http://dev.processing.org/bugs/show_bug.cgi?id=103 +X https://download.processing.org/bugzilla/103.html X lines skip on 200x200 surface because of fixed point rounding error -X http://dev.processing.org/bugs/show_bug.cgi?id=267 +X https://download.processing.org/bugzilla/267.html X this may be same as 95 X Polygons parallel to z-axis not always filling with nonzero x or y -X http://dev.processing.org/bugs/show_bug.cgi?id=547 +X https://download.processing.org/bugzilla/547.html 0124 core @@ -5351,9 +5609,9 @@ X fix weird situation where fonts used in more than one renderer wouldn't show X opengl doesn't draw a background for the raw recorder X change P3D to smooth images nicely (bilinear was disabled) X ambientLight(r,g,b) was still ambientLight(r,g,r) -X http://dev.processing.org/bugs/show_bug.cgi?id=465 +X https://download.processing.org/bugzilla/465.html X fix from dave bollinger for the POSTERIZE filter -X http://dev.processing.org/bugs/show_bug.cgi?id=399 +X https://download.processing.org/bugzilla/399.html X fix PImage regression in 0124 and the cache crap X added a version of trim() that handles an entire array X removed contract(), one can use expand() and subset() instead @@ -5364,7 +5622,7 @@ X otherwise people have to use strange hacks to get around it X also chop off ? from url detritus X also just pass off to default createImage() if no extension available X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Syntax;action=display;num=1165174666 -X http://dev.processing.org/bugs/show_bug.cgi?id=500 +X https://download.processing.org/bugzilla/500.html X java 1.5.0_10 breaks jogl X add error message to p5 about it X http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6504460 @@ -5377,10 +5635,10 @@ X no disk in drive error X was this something that changed with the java updates? (1.5_10) X doesn't seem to be, still not sure X problem was the floppy drive.. gak -X http://dev.processing.org/bugs/show_bug.cgi?id=478 +X https://download.processing.org/bugzilla/478.html X copy() sort of broken in JAVA2D X example sketch posted with bug report -X http://dev.processing.org/bugs/show_bug.cgi?id=372 +X https://download.processing.org/bugzilla/372.html o saveStrings(filename, strings, count) o otherwise the save is kinda wonky o or maybe that should just be done with the array fxns @@ -5388,14 +5646,14 @@ o or maybe that should just be done with the array fxns fixed earlier o sketches often freeze when stop is hit on an HT machine o need to test the examples cited on pardis' machine -o http://dev.processing.org/bugs/show_bug.cgi?id=232 +o https://download.processing.org/bugzilla/232.html X debug NumberFormat InterruptedException on dual proc machine X use notify() instead of interrupt()? X or Thread.currentThread() should be checked first? o svg loader is on the list for 1.0 o maybe include as part of PApplet (casey thinks so) X using gl, lines don't show up in pdf with record (they're ok with p3d) -X http://dev.processing.org/bugs/show_bug.cgi?id=325 +X https://download.processing.org/bugzilla/325.html o with network connection o download a copy of the source for 0069, get the renderer o svn mv PGraphics2 PGraphicsJava @@ -5404,7 +5662,7 @@ o version of BApplet that replaces g. with ai. or pdf. 0123 core X setup() and basic mode apps not working -X http://dev.processing.org/bugs/show_bug.cgi?id=463 +X https://download.processing.org/bugzilla/463.html 0122 core @@ -5417,13 +5675,13 @@ o though that's awkward b/c colors always RGB X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Suggestions;action=display;num=1160096087 X regression in P3D that prevents point() from drawing X problem is with setup_vertex() not adding similar points -X http://dev.processing.org/bugs/show_bug.cgi?id=444 +X https://download.processing.org/bugzilla/444.html X if doing openstream on url, says that "the file" is missing or invalid X add notes about it being a url fixed earlier, bug cleaning X gray background in pdf (using both gl and p3d) -X http://dev.processing.org/bugs/show_bug.cgi?id=324 +X https://download.processing.org/bugzilla/324.html X verified as fixed in 0122 @@ -5435,7 +5693,7 @@ X how to clear the screen with alpha? background(0, 0, 0, 0)? o background(EMPTY) -> background(0x01000000) or something? X size(), beginRecords(), beginRaw(), createGraphics() X broken for file-based renderers in 0120 -X http://dev.processing.org/bugs/show_bug.cgi?id=434 +X https://download.processing.org/bugzilla/434.html 0120 core @@ -5443,25 +5701,25 @@ X fixed error when using hint(ENABLE_NATIVE_FONTS) with JAVA2D X java.lang.IllegalArgumentException: X null incompatible with Global antialiasing enable key X fix issue where ambientLight(r, g, b) was instead ambientLight(r, g, r) -X http://dev.processing.org/bugs/show_bug.cgi?id=412 +X https://download.processing.org/bugzilla/412.html X createFont() should always use native fonts X need to warn that fonts may not be installed X recommend that people include the ttf if that's the thing X or rather, that this is only recommended for offline use X fix 3D tessellation problems with curveVertex and bezierVertex X actually was z = Float.MAX_VALUE regression -X http://dev.processing.org/bugs/show_bug.cgi?id=390 +X https://download.processing.org/bugzilla/390.html X two examples in sketchbook X this has been reported several times X concave polygons having trouble if points come back to meet X tesselator/triangulator gets confused when points doubled up X might need to avoid previous vertex hitting itself -X http://dev.processing.org/bugs/show_bug.cgi?id=97 +X https://download.processing.org/bugzilla/97.html X graphics gems 5 has more about tessellation X polygons perpendicular to axis not drawing X is this a clipping error? X probably a triangulation error, because triangles work ok -X http://dev.processing.org/bugs/show_bug.cgi?id=111 +X https://download.processing.org/bugzilla/111.html X problem is that the area of the polygon isn't taking into account z X lookat is now camera(), but not fixed in the docs X add notes to the faq about the camera changes on the changes page @@ -5478,7 +5736,7 @@ X passing applet into createGraphics.. problems with re-adding listeners X since the listeners are added to the PApplet X i think the listeners aren't re-added, but need to double check X createGraphics() having problems with JAVA2D, and sometimes with P3D -X http://dev.processing.org/bugs/show_bug.cgi?id=419 +X https://download.processing.org/bugzilla/419.html X with default renderer, no default background color? X only sometimes.. why is this? X only call defaults() when it's part of a PApplet canvas @@ -5496,14 +5754,14 @@ o otherwise the end will look like it's time to update o which may not actually be the case o i.e. calling filter() inside begin/end block X get creating new PGraphics/2/3 working again -X http://dev.processing.org/bugs/show_bug.cgi?id=92 +X https://download.processing.org/bugzilla/92.html X maybe createGraphics(200, 200) to create same as source X createGraphics(200, 200, P2D) to create 2D from 3D X also, drawing a PGraphics2 doesn't seem to work X new PGraphics2 objects are set as RGB, but on loadPixels/updatePixels X they're drawn as transparent and don't have their high bits set X problems between modelX between alpha and beta -X http://dev.processing.org/bugs/show_bug.cgi?id=386 +X https://download.processing.org/bugzilla/386.html X y may be flipped in modelX/Y/Z stuff on opengl X is this the same bug? assuming that it is @@ -5528,7 +5786,7 @@ X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Contribution_3DOpen 0119 core X add saveStream() method X change to handle Java 1.5 f-up where URLs now give FileNotFoundException -X http://dev.processing.org/bugs/show_bug.cgi?id=403 +X https://download.processing.org/bugzilla/403.html X add unlerp() method @@ -5555,16 +5813,16 @@ X see if reflection will allow expand for all class types X expand, append, contract, subset, splice, concat, reverse X typed version of array functions: X append(), shorten(), splice, slice, subset, concat, reverse -X http://dev.processing.org/bugs/show_bug.cgi?id=115 +X https://download.processing.org/bugzilla/115.html X fix issue where processing applets would run extremely fast X after having been starved of resources where there framerate dropped -X http://dev.processing.org/bugs/show_bug.cgi?id=336 +X https://download.processing.org/bugzilla/336.html X added color/stroke/tint/fill(#FF8800, 30); X test imageio with images that have alpha (does it work?) X nope, doesn't, didn't finish support -X http://dev.processing.org/bugs/show_bug.cgi?id=350 +X https://download.processing.org/bugzilla/350.html X openStream() fails with java plug-in because non-null stream returned -X http://dev.processing.org/bugs/show_bug.cgi?id=359 +X https://download.processing.org/bugzilla/359.html X update jogl to latest beta 5 X make framerate into frameRate (to match frameCount) X AIOOBE in P3D during defaults/background/clear @@ -5574,7 +5832,7 @@ X actually it'll work if it's only on size() X the sync on the mac hangs an applet running by itself X even though it seems to be ok for a component X thread sync problem with allocation -X http://dev.processing.org/bugs/show_bug.cgi?id=369 +X https://download.processing.org/bugzilla/369.html X major threading change to use wait()/notifyAll() instead of interrupt/sleep X noLoop() at end of setup is prolly b/c of interruptedex X need to not call Thread.interrupt() @@ -5582,20 +5840,20 @@ X opengl + noLoop() causes InterruptedException X check to see noLoop() breakage is fixed in 92 vs 91 X checked, not fixed X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1115330568 -X http://dev.processing.org/bugs/show_bug.cgi?id=164 +X https://download.processing.org/bugzilla/164.html X remove image(filename) and textFont(filename) et al. X revision 115 may be saving raw files as TIFF format X may be a bug specific to java 1.5 (nope) -X http://dev.processing.org/bugs/show_bug.cgi?id=378 +X https://download.processing.org/bugzilla/378.html X saveFrame() not working for casey X problem with tiff loading in photoshop etc X check http:// stuff to see if it's a url first on openStream() X it's the most harmless, since prolly just a MFUEx X fix problem where root of exported sketch won't be checked -X http://dev.processing.org/bugs/show_bug.cgi?id=389 +X https://download.processing.org/bugzilla/389.html X createFont not working from applets (only with .ttf?) X throws a security exception because of the reflection stuff -X http://dev.processing.org/bugs/show_bug.cgi?id=101 +X https://download.processing.org/bugzilla/101.html X urls with ampersands don't work with link() X Runtime.getRuntime().exec("cmd /c start " + url.replaceAll("&","^&")); X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1149974261 @@ -5610,15 +5868,15 @@ X jpeg loading may be extremely slow (loadBytes?) X seems specific to 0115 versus the others X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1158111639 X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1154714548 -X http://dev.processing.org/bugs/show_bug.cgi?id=392 +X https://download.processing.org/bugzilla/392.html X loadImage() problems with png and jpg X actually it's an issue with some types of jpeg files -X http://dev.processing.org/bugs/show_bug.cgi?id=279 +X https://download.processing.org/bugzilla/279.html X java.lang.IllegalArgumentException: X Width (-1) and height (-1) cannot be <= 0 X identical to what happens when the image data is bad X for instance, trying to load a tiff image with the jpg loader -X http://dev.processing.org/bugs/show_bug.cgi?id=305 +X https://download.processing.org/bugzilla/305.html o blend() mode param should be moved to the front X nah, works better with the other format X make sure there's parity with the copy() functions @@ -5627,7 +5885,7 @@ o blend() should prolly have its mode be the first param X move blend() to blendColor() when applying it to a color X added lerpColor(), though it needs a new name X went back to old image i/o (sometimes caused trouble when exported) -X http://dev.processing.org/bugs/show_bug.cgi?id=376 +X https://download.processing.org/bugzilla/376.html X change reader() to createReader() for consistency? X though printwriter is odd for output.. X also createWriter() and the rest @@ -5655,13 +5913,13 @@ X also clean up some of the casting silliness more recent X only update mouse pos on moved and dragged -X http://dev.processing.org/bugs/show_bug.cgi?id=170 +X https://download.processing.org/bugzilla/170.html X also updates a bug that caused sketches to jump in funny ways fixed in 0115 / quicktime 7.1 X color values on camera input flipped on intel macs X checked in a change for this recommended on qtjava list -X http://dev.processing.org/bugs/show_bug.cgi?id=313 +X https://download.processing.org/bugzilla/313.html really old stuff o get loop, noLoop, redraw, and framerate all working in opengl @@ -5684,13 +5942,13 @@ X saveTIFF, saveHeaderTIFF, saveTGA no longer public/static in PImage X this was a mistake to expose the api this way X more image file i/o in java 1.4 X add dynamic loading of the jpeg, png, and gif(?) encoder classes -X http://dev.processing.org/bugs/show_bug.cgi?id=165 +X https://download.processing.org/bugzilla/165.html X http://java.sun.com/products/java-media/jai/index.jsp X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Programs;action=display;num=1120174647 X P5 cannot read files generated by saveFrame() X need to update docs re: tga X and add support for reading its own uncompressed TIFs -X http://dev.processing.org/bugs/show_bug.cgi?id=260 +X https://download.processing.org/bugzilla/260.html 0114 core @@ -5702,7 +5960,7 @@ X remove "max texture size" debug message X flicker with depth sort enabled X implement basic depth sorting for triangles in P3D and OPENGL X add option to sort triangles back to front so alpha works -X http://dev.processing.org/bugs/show_bug.cgi?id=176 +X https://download.processing.org/bugzilla/176.html o at least add it to the faq, or this would be a test case w/ the sorting @@ -5712,7 +5970,7 @@ X fix for open() on macosx submitted by chandler 0112 core X saveFrame() issues with JAVA2D on osx -X http://dev.processing.org/bugs/show_bug.cgi?id=189 +X https://download.processing.org/bugzilla/189.html o implement hint(NO_DEPTH_TEST) for opengl X already done hint(DISABLE_DEPTH_TEXT); X check min/max texture sizes when binding to avoid problems @@ -5720,7 +5978,7 @@ X minimum texture size may be 64x64 X maximum appears to be 2048, on macs maybe 512 X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Contribution_3DOpenGL;action=display;num=1137130317 X fix non-bound textures from mangling everything else -X http://dev.processing.org/bugs/show_bug.cgi?id=322 +X https://download.processing.org/bugzilla/322.html X fix enable/disable textures for some objects X also a big problem for fonts X calling updatePixels() on each frame fixes the issue (sorta) @@ -5755,9 +6013,9 @@ o Unsupported control type: Master Gain o what's actually causing this error? o http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1115467831 o PSound.play() won't play the sound a 2nd time (reopened) -o http://dev.processing.org/bugs/show_bug.cgi?id=208 +o https://download.processing.org/bugzilla/208.html o loadSound apparently broken in java 1.5? -o http://dev.processing.org/bugs/show_bug.cgi?id=285 +o https://download.processing.org/bugzilla/285.html X need to just remove PSound altogether @@ -5766,11 +6024,11 @@ X need to have a better way of getting/figuring out the endian X use ByteOrder class in jdk 1.4, since issue is local to JOGL X security ex when run as an applet X also can no longer assume that macosx is big endian -X http://dev.processing.org/bugs/show_bug.cgi?id=309 +X https://download.processing.org/bugzilla/309.html o making 'run' synchronized caused a freeze on start w/ opengl X display() as a function name is problematic X causes nothing to show up.. either rename or mark it final -X http://dev.processing.org/bugs/show_bug.cgi?id=213 +X https://download.processing.org/bugzilla/213.html X fix for lights throwing a BufferOverflowException @@ -5849,7 +6107,7 @@ X add dispose() call to the shutdown part of PApplet X user.dir wasn't getting set properly X when graphics can be resized, resize rather than creating new context X change savePath() et al a great deal, include better docs -X http://dev.processing.org/bugs/show_bug.cgi?id=199 +X https://download.processing.org/bugzilla/199.html X straighten out save() and saveFrame() o use File object for when people know what they're doing? X same issue occurs with pdf and creating graphics obj @@ -5876,26 +6134,26 @@ X removed playing() method from PSound X integrate destroy() method from shiffman as dispose() in PSound2 X ComponentListener is probably what's needed for resize() X make sure that components can be resized properly via size() -X http://dev.processing.org/bugs/show_bug.cgi?id=209 +X https://download.processing.org/bugzilla/209.html X or that it properly responds to a setBounds() call X calling size() elsewhere in the app doesn't quite work X A second call to size almost works. X The buffer apparently gets allocated and saveFrame saves the X new size but drawing appears to be disabled. -X http://dev.processing.org/bugs/show_bug.cgi?id=243 +X https://download.processing.org/bugzilla/243.html 0098 core X change recordShapes() to just record() and recordRaw() X width, height set to zero in static mode -X http://dev.processing.org/bugs/show_bug.cgi?id=198 +X https://download.processing.org/bugzilla/198.html X probably only set when resize() is called, and it's not happening X be careful when fixing this, bugs 195/197 were a result: -X http://dev.processing.org/bugs/show_bug.cgi?id=195 -X http://dev.processing.org/bugs/show_bug.cgi?id=197 +X https://download.processing.org/bugzilla/195.html +X https://download.processing.org/bugzilla/197.html X PSound.play() won't play the sound a 2nd time X (have to call stop() first) -X http://dev.processing.org/bugs/show_bug.cgi?id=208 +X https://download.processing.org/bugzilla/208.html 0097 core @@ -5910,9 +6168,9 @@ X also need to add to PGraphicsGL X access logs are being spammed because openStream() gets a 404 X the default should be to check the .jar file X openStream() doesn't work with subfolders -X http://dev.processing.org/bugs/show_bug.cgi?id=218 +X https://download.processing.org/bugzilla/218.html X screwed up movie loading paths (quick fix) -X http://dev.processing.org/bugs/show_bug.cgi?id=216 +X https://download.processing.org/bugzilla/216.html X additional cleanup in the Movie class X make path thing into getPath() or something? X sketchPath(), dataPath(), savePath(), createPath() @@ -5928,7 +6186,7 @@ X "not available in P3D" should read "OPENGL" in opengl lib X keypressed ref: repeating keys X also remove "no drawing inside keypressed" X text block wrap problem with manual break character (\n) -X http://dev.processing.org/bugs/show_bug.cgi?id=188 +X https://download.processing.org/bugzilla/188.html draw mode issues X when run externally without a draw, applets will exit immediately @@ -5937,16 +6195,16 @@ X shouldn't quit draw mode apps immediately X otherwise something gets drawn then the applet exits X should instead use exit() when they need to exit X NullPointerException when no draw() -X http://dev.processing.org/bugs/show_bug.cgi?id=210 +X https://download.processing.org/bugzilla/210.html X window closing immediately with library imports -X http://dev.processing.org/bugs/show_bug.cgi?id=204 +X https://download.processing.org/bugzilla/204.html X check into loadImage() with jars bug, very frustrating o when using loadImage() on a jar, turn off "no cache" option? X image no load halts the program (rather than returning null) X note in the reference: png images work with java 1.3+ X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=WebsiteBugs;action=display;num=1125968697 X add bug re: gif image break missing to revisions.txt -X http://dev.processing.org/bugs/show_bug.cgi?id=217 +X https://download.processing.org/bugzilla/217.html image pile X get loadImage() to work properly with data folder @@ -5987,7 +6245,7 @@ X undo the fix that causes the width/height to be properly set 0094 core X fix bug that was causing font sizes not to be set on opengl -X http://dev.processing.org/bugs/show_bug.cgi?id=174 +X https://download.processing.org/bugzilla/174.html X apply fix from toxi to make targa files less picky X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1127999630 X "folder" has been renamed to "path" to match savePath(). @@ -5998,7 +6256,7 @@ X (i.e. if needing a File object) X width, height set to zero in static mode X probably only set when resize() is called, and it's not happening X g.smooth is always false in opengl -X http://dev.processing.org/bugs/show_bug.cgi?id=192 +X https://download.processing.org/bugzilla/192.html o triangleColors are different because they're per-triangle o as opposed to per-vertex, because it's based on the facet of the tri @@ -6024,7 +6282,7 @@ X textMode(SHAPE) throws ex when drawing and font not installed X fix a bug with filename capitalization error throwing X add NO_DEPTH_TEST to PGraphics3 X java 1.4 code snuck into PApplet, causing problems on the mac -X http://dev.processing.org/bugs/show_bug.cgi?id=146 +X https://download.processing.org/bugzilla/146.html X prevent PGraphics.save() from inserting a file prefix X so that people can use absolute paths X or add a version that takes a file object diff --git a/core/library-test/byte-buddy-1.11.19.jar b/core/library-test/byte-buddy-1.11.19.jar new file mode 100644 index 000000000..ff79833e0 Binary files /dev/null and b/core/library-test/byte-buddy-1.11.19.jar differ diff --git a/core/library-test/hamcrest-core-1.3.jar b/core/library-test/hamcrest-core-1.3.jar new file mode 100644 index 000000000..9d5fe16e3 Binary files /dev/null and b/core/library-test/hamcrest-core-1.3.jar differ diff --git a/core/library-test/junit-4.13.2.jar b/core/library-test/junit-4.13.2.jar new file mode 100644 index 000000000..6da55d8b8 Binary files /dev/null and b/core/library-test/junit-4.13.2.jar differ diff --git a/core/library-test/junit-4.8.1.jar b/core/library-test/junit-4.8.1.jar deleted file mode 100644 index 524cd65ce..000000000 Binary files a/core/library-test/junit-4.8.1.jar and /dev/null differ diff --git a/core/library-test/mockito-all-1.10.19.jar b/core/library-test/mockito-all-1.10.19.jar deleted file mode 100644 index c831489cd..000000000 Binary files a/core/library-test/mockito-all-1.10.19.jar and /dev/null differ diff --git a/core/library-test/mockito-core-4.0.0.jar b/core/library-test/mockito-core-4.0.0.jar new file mode 100644 index 000000000..d191b44ea Binary files /dev/null and b/core/library-test/mockito-core-4.0.0.jar differ diff --git a/core/library-test/objenesis-3.2.jar b/core/library-test/objenesis-3.2.jar new file mode 100644 index 000000000..1888e2ea1 Binary files /dev/null and b/core/library-test/objenesis-3.2.jar differ diff --git a/core/library/.gitignore b/core/library/.gitignore index 858865596..46a6dd5bc 100644 --- a/core/library/.gitignore +++ b/core/library/.gitignore @@ -1,3 +1,10 @@ core.jar + jogl-all-src.jar +/macos-aarch64 +/linux-arm +/linux-aarch64 +/macos-x86_64 +/linux-amd64 +/windows-amd64 diff --git a/core/library/export.txt b/core/library/export.txt deleted file mode 100644 index 58ddf6078..000000000 --- a/core/library/export.txt +++ /dev/null @@ -1,17 +0,0 @@ -# If you want to support more platforms, visit jogamp.org to get the -# natives libraries for the platform in question (i.e. Solaris). - -name = Core - -# 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.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.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.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 - -# 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/methods/processing4-core-preproc.iml b/core/methods/processing4-core-preproc.iml new file mode 100644 index 000000000..bea76e7d8 --- /dev/null +++ b/core/methods/processing4-core-preproc.iml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/processing4-core.iml b/core/processing4-core.iml new file mode 100644 index 000000000..dc6e9d3da --- /dev/null +++ b/core/processing4-core.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/icon/icon-1024.png b/core/src/icon/icon-1024.png index 7d67a3307..cde360504 100644 Binary files a/core/src/icon/icon-1024.png and b/core/src/icon/icon-1024.png differ diff --git a/core/src/icon/icon-128.png b/core/src/icon/icon-128.png index 580eb61ab..41550fd6c 100644 Binary files a/core/src/icon/icon-128.png and b/core/src/icon/icon-128.png differ diff --git a/core/src/icon/icon-16.png b/core/src/icon/icon-16.png index 560b9db0b..b79f0db0c 100644 Binary files a/core/src/icon/icon-16.png and b/core/src/icon/icon-16.png differ diff --git a/core/src/icon/icon-256.png b/core/src/icon/icon-256.png index afd815770..77b0ab0cd 100644 Binary files a/core/src/icon/icon-256.png and b/core/src/icon/icon-256.png differ diff --git a/core/src/icon/icon-32.png b/core/src/icon/icon-32.png index ec65430d5..15f333df0 100644 Binary files a/core/src/icon/icon-32.png and b/core/src/icon/icon-32.png differ diff --git a/core/src/icon/icon-48.png b/core/src/icon/icon-48.png index a0c50ceb6..d6ac1b4a6 100644 Binary files a/core/src/icon/icon-48.png and b/core/src/icon/icon-48.png differ diff --git a/core/src/icon/icon-512.png b/core/src/icon/icon-512.png index ec7ceb476..453ab876b 100644 Binary files a/core/src/icon/icon-512.png and b/core/src/icon/icon-512.png differ diff --git a/core/src/icon/icon-64.png b/core/src/icon/icon-64.png index 77a356a12..f8613893a 100644 Binary files a/core/src/icon/icon-64.png and b/core/src/icon/icon-64.png differ diff --git a/core/src/japplemenubar/JAppleMenuBar.java b/core/src/japplemenubar/JAppleMenuBar.java deleted file mode 100644 index 148eed94d..000000000 --- a/core/src/japplemenubar/JAppleMenuBar.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2011-12 hansi raber, released under LGPL under agreement - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation, version 2.1. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General - Public License along with this library; if not, write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA -*/ -package japplemenubar; - -import java.io.*; - -import processing.core.PApplet; - - -/** - * Starting point for the application. General initialization should be done - * inside the ApplicationController's init() method. If certain kinds of - * non-Swing initialization takes too long, it should happen in a new Thread - * and off the Swing event dispatch thread (EDT). - * - * @author hansi - */ -public class JAppleMenuBar { - static JAppleMenuBar instance; - static final String FILENAME = "libjAppleMenuBar.jnilib"; - - static { - try { - File temp = File.createTempFile("processing", "menubar"); - temp.delete(); // remove the file itself - temp.mkdirs(); // create a directory out of it - temp.deleteOnExit(); - - File jnilibFile = new File(temp, FILENAME); - InputStream input = JAppleMenuBar.class.getResourceAsStream(FILENAME); - if (input != null) { - if (PApplet.saveStream(jnilibFile, input)) { - System.load(jnilibFile.getAbsolutePath()); - instance = new JAppleMenuBar(); - - } else { - sadness("Problem saving " + FILENAME + " for full screen use."); - } - } else { - sadness("Could not load " + FILENAME + " from core.jar"); - } - } catch (IOException e) { - sadness("Unknown error, here's the stack trace."); - e.printStackTrace(); - } - } - - - static void sadness(String msg) { - System.err.println("Full screen mode disabled. " + msg); - } - - -// static public void show() { -// instance.setVisible(true); -// } - - - static public void hide() { - instance.setVisible(false, false); - } - - - public native void setVisible(boolean visibility, boolean kioskMode); - - -// public void setVisible(boolean visibility) { -// // Keep original API in-tact. Default kiosk-mode to off. -// setVisible(visibility, false); -// } -} diff --git a/core/src/processing/awt/PGraphicsJava2D.java b/core/src/processing/awt/PGraphicsJava2D.java index 6c3bbafac..92b10cb33 100644 --- a/core/src/processing/awt/PGraphicsJava2D.java +++ b/core/src/processing/awt/PGraphicsJava2D.java @@ -50,18 +50,8 @@ import processing.core.*; * Advanced debugging notes for Java2D. */ public class PGraphicsJava2D extends PGraphics { -//// BufferStrategy strategy; -//// BufferedImage bimage; -//// VolatileImage vimage; -// Canvas canvas; -//// boolean useCanvas = true; -// boolean useCanvas = false; -//// boolean useRetina = true; -//// boolean useOffscreen = true; // ~40fps -// boolean useOffscreen = false; - public Graphics2D g2; -// protected BufferedImage offscreen; + private Dimension sizeChange; Composite defaultComposite; @@ -102,8 +92,6 @@ public class PGraphicsJava2D extends PGraphics { public boolean strokeGradient; public Paint strokeGradientObject; - Font fontObject; - ////////////////////////////////////////////////////////////// @@ -123,21 +111,19 @@ public class PGraphicsJava2D extends PGraphics { //public void setPath(String path) -// /** -// * Called in response to a resize event, handles setting the -// * new width and height internally, as well as re-allocating -// * the pixel buffer for the new size. -// * -// * Note that this will nuke any cameraMode() settings. -// */ -// @Override -// public void setSize(int iwidth, int iheight) { // ignore -// width = iwidth; -// height = iheight; -// -// allocate(); -// reapplySettings(); -// } + /** + * Queues a size change, won't happen until beginDraw(). + */ + @Override + public void setSize(int w, int h) { // ignore + // If this is the initial setup, need to assign width/height; + // especially necessary for renderer subclasses. + // https://github.com/processing/processing4/issues/395 + if (width == 0 || height == 0) { + super.setSize(w, h); + } + sizeChange = new Dimension(w, h); + } // @Override @@ -188,7 +174,7 @@ public class PGraphicsJava2D extends PGraphics { if (useOffscreen) { // Needs to be RGB otherwise there's a major performance hit [0204] - // http://code.google.com/p/processing/issues/detail?id=729 + // https://github.com/processing/processing/issues/768 image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // GraphicsConfiguration gc = parent.getGraphicsConfiguration(); // image = gc.createCompatibleImage(width, height); @@ -294,47 +280,24 @@ public class PGraphicsJava2D extends PGraphics { // Graphics2D g2old; public Graphics2D checkImage() { + if (sizeChange != null) { + // Size changes are queued here where they're safe to run. + // https://github.com/processing/processing4/issues/186 + super.setSize(sizeChange.width, sizeChange.height); + sizeChange = null; + } if (image == null || ((BufferedImage) image).getWidth() != width*pixelDensity || ((BufferedImage) image).getHeight() != height*pixelDensity) { -// ((VolatileImage) image).getWidth() != width || -// ((VolatileImage) image).getHeight() != height) { -// image = new BufferedImage(width * pixelFactor, height * pixelFactor -// format == RGB ? BufferedImage.TYPE_INT_ARGB); -// Commenting this out, because we are not drawing directly to the screen [jv 2018-06-01] -// -// GraphicsConfiguration gc = null; -// if (surface != null) { -// Component comp = null; //surface.getComponent(); -// if (comp == null) { -//// System.out.println("component null, but parent.frame is " + parent.frame); -// comp = parent.frame; -// } -// if (comp != null) { -// gc = comp.getGraphicsConfiguration(); -// } -// } -// // If not realized (off-screen, i.e the Color Selector Tool), gc will be null. -// if (gc == null) { -// //System.err.println("GraphicsConfiguration null in initImage()"); -// GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); -// gc = ge.getDefaultScreenDevice().getDefaultConfiguration(); -// } - - // Formerly this was broken into separate versions based on offscreen or - // not, but we may as well create a compatible image; it won't hurt, right? - // P.S.: Three years later, I'm happy to report it did in fact hurt [jv 2018-06-01] int wide = width * pixelDensity; int high = height * pixelDensity; -// System.out.println("re-creating image"); - // For now we expect non-premultiplied INT ARGB and the compatible image + // For now, we expect non-pre-multiplied INT ARGB and the compatible image // might not be it... create the image directly. It's important that the // image has all four bands, otherwise we get garbage alpha during blending // (see https://github.com/processing/processing/pull/2645, // https://github.com/processing/processing/pull/3523) - // // image = gc.createCompatibleImage(wide, high, Transparency.TRANSLUCENT); image = new BufferedImage(wide, high, BufferedImage.TYPE_INT_ARGB); } @@ -353,8 +316,11 @@ public class PGraphicsJava2D extends PGraphics { g2.setStroke(strokeObject); } // https://github.com/processing/processing/issues/2617 - if (fontObject != null) { - g2.setFont(fontObject); + if (textFont != null) { + Font fontObject = (Font) textFont.getNative(); + if (fontObject != null) { + g2.setFont(fontObject); + } } // https://github.com/processing/processing/issues/4019 if (blendMode != 0) { @@ -609,7 +575,7 @@ public class PGraphicsJava2D extends PGraphics { super.hint(which); // Avoid badness when drawing shorter strokes. - // http://code.google.com/p/processing/issues/detail?id=1068 + // https://github.com/processing/processing/issues/1106 // Unfortunately cannot always be enabled, because it makes the // stroke in many standard Processing examples really gross. if (which == ENABLE_STROKE_PURE) { @@ -994,11 +960,11 @@ public class PGraphicsJava2D extends PGraphics { /** * - * Blends the pixels in the display window according to a defined mode. - * There is a choice of the following modes to blend the source pixels (A) - * with the ones of pixels already in the display window (B). Each pixel's - * final color is the result of applying one of the blend modes with each - * channel of (A) and (B) independently. The red channel is compared with + * Blends the pixels in the display window according to a defined mode. + * There is a choice of the following modes to blend the source pixels (A) + * with the ones of pixels already in the display window (B). Each pixel's + * final color is the result of applying one of the blend modes with each + * channel of (A) and (B) independently. The red channel is compared with * red, green with green, and blue with blue.
    *
    * BLEND - linear interpolation of colors: C = A*factor + B. This is the default.
    @@ -1021,16 +987,15 @@ public class PGraphicsJava2D extends PGraphics { *
    * REPLACE - the pixels entirely replace the others and don't utilize alpha (transparency) values
    *
    - * We recommend using blendMode() and not the previous blend() - * function. However, unlike blend(), the blendMode() function - * does not support the following: HARD_LIGHT, SOFT_LIGHT, OVERLAY, DODGE, - * BURN. On older hardware, the LIGHTEST, DARKEST, and DIFFERENCE modes might - * not be available as well. + * We recommend using blendMode() and not the previous blend() + * function. However, unlike blend(), the blendMode() function + * does not support the following: HARD_LIGHT, SOFT_LIGHT, OVERLAY, DODGE, + * BURN. On older hardware, the LIGHTEST, DARKEST, and DIFFERENCE modes might + * not be available as well. * * * @webref Rendering * @webBrief Blends the pixels in the display window according to a defined mode - * @param mode the blending mode to use */ @Override protected void blendModeImpl() { @@ -1038,15 +1003,7 @@ public class PGraphicsJava2D extends PGraphics { g2.setComposite(defaultComposite); } else { - g2.setComposite(new Composite() { - - @Override - public CompositeContext createContext(ColorModel srcColorModel, - ColorModel dstColorModel, - RenderingHints hints) { - return new BlendingContext(blendMode); - } - }); + g2.setComposite((srcColorModel, dstColorModel, hints) -> new BlendingContext(blendMode)); } } @@ -1055,7 +1012,7 @@ public class PGraphicsJava2D extends PGraphics { // demo and terrific writeup on blending modes in Java 2D. // http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/ private static final class BlendingContext implements CompositeContext { - private int mode; + private final int mode; private BlendingContext(int mode) { this.mode = mode; @@ -1818,7 +1775,10 @@ public class PGraphicsJava2D extends PGraphics { // op.filter(image, image); } } else { // !tint - if (targetType == RGB && (source.pixels[0] >> 24 == 0)) { + // Java2D must always use ARGB, so we need to ensure RGB pixels + // have their high bits set to 0xFF, which requires some hackery. + // https://github.com/processing/processing4/issues/388 + if (targetType == RGB && anyAlpha(source.pixels)) { // If it's an RGB image and the high bits aren't set, need to set // the high bits to opaque because we're drawing ARGB images. source.filter(OPAQUE); @@ -1832,19 +1792,32 @@ public class PGraphicsJava2D extends PGraphics { } this.tinted = tint; this.tintedColor = tintColor; - -// GraphicsConfiguration gc = parent.getGraphicsConfiguration(); -// compat = gc.createCompatibleImage(image.getWidth(), -// image.getHeight(), -// Transparency.TRANSLUCENT); -// -// Graphics2D g = compat.createGraphics(); -// g.drawImage(image, 0, 0, null); -// g.dispose(); } } + static private boolean anyAlpha(int[] pixels) { + // The upper-left corner is a good bet to test first, but not sufficient. + if ((pixels[0] & 0xFF000000) != 0xFF000000) { + return true; + } + + // Otherwise, take a random sample for our best guess. This is much + // faster (and hopefully reliable enough) to avoid exhaustively rewriting + // all pixel alpha values, which would be dreadful for performance. + int count = (int) Math.sqrt(pixels.length); + for (int i = 0; i < count; i++) { + // select a random pixel + int index = (int) (Math.random() * pixels.length); + // if it has alpha, return true + if ((pixels[index] & 0xFF000000) != 0xFF000000) { + return true; + } + } + return false; + } + + ////////////////////////////////////////////////////////////// @@ -1902,7 +1875,6 @@ public class PGraphicsJava2D extends PGraphics { Font font = (Font) textFont.getNative(); if (font != null) { - //return getFontMetrics(font).getAscent(); return g2.getFontMetrics(font).getAscent(); } return super.textAscent(); @@ -1916,7 +1888,6 @@ public class PGraphicsJava2D extends PGraphics { } Font font = (Font) textFont.getNative(); if (font != null) { - //return getFontMetrics(font).getDescent(); return g2.getFontMetrics(font).getDescent(); } return super.textDescent(); @@ -1951,21 +1922,17 @@ public class PGraphicsJava2D extends PGraphics { protected void handleTextSize(float size) { // if a native version available, derive this font Font font = (Font) textFont.getNative(); - // don't derive again if the font size has not changed if (font != null) { + // don't derive again if the font size has not changed if (font.getSize2D() != size) { - Map map = - new HashMap<>(); + Map map = new HashMap<>(); map.put(TextAttribute.SIZE, size); - map.put(TextAttribute.KERNING, - TextAttribute.KERNING_ON); -// map.put(TextAttribute.TRACKING, -// TextAttribute.TRACKING_TIGHT); + map.put(TextAttribute.KERNING, TextAttribute.KERNING_ON); + // map.put(TextAttribute.TRACKING, TextAttribute.TRACKING_TIGHT); font = font.deriveFont(map); } g2.setFont(font); textFont.setNative(font); - fontObject = font; /* Map attrs = font.getAttributes(); @@ -2061,8 +2028,20 @@ public class PGraphicsJava2D extends PGraphics { protected void textLineImpl(char[] buffer, int start, int stop, float x, float y) { Font font = (Font) textFont.getNative(); -// if (font != null && (textFont.isStream() || hints[ENABLE_NATIVE_FONTS])) { if (font != null) { + // If using the default font, warn the user when their code calls + // text() called with unavailable characters. Not done with all + // fonts because it would be too slow, but useful/acceptable for + // the default case because it will hit beginners/casual use. + if (textFont.getName().equals(defaultFontName)) { + if (font.canDisplayUpTo(buffer, start, stop) != -1) { + final String msg = + "Some characters not available in the current font, " + + "use createFont() to specify a typeface the includes them."; + showWarning(msg); + } + } + /* // save the current setting for text smoothing. note that this is // different from the smooth() function, because the font smoothing @@ -2115,7 +2094,7 @@ public class PGraphicsJava2D extends PGraphics { //g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, textAntialias); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialias); - } else { // otherwise just do the default + } else { // otherwise, just do the default super.textLineImpl(buffer, start, stop, x, y); } } @@ -2603,13 +2582,13 @@ public class PGraphicsJava2D extends PGraphics { // (an array for width*height would waste lots of memory if it stayed // resident, and would terrify the gc if it were re-created on each trip // to background(). -// WritableRaster raster = ((BufferedImage) image).getRaster(); -// WritableRaster raster = image.getRaster(); WritableRaster raster = getRaster(); if ((clearPixels == null) || (clearPixels.length < imageWidth)) { clearPixels = new int[imageWidth]; } - Arrays.fill(clearPixels, 0, imageWidth, backgroundColor); + Arrays.fill(clearPixels, 0, imageWidth, color); + + // Clear the raster/image for this renderer, one line at a time for (int i = 0; i < imageHeight; i++) { raster.setDataElements(0, i, imageWidth, 1, clearPixels); } @@ -2796,9 +2775,6 @@ public class PGraphicsJava2D extends PGraphics { pixels[i] = 0xff000000 | pixels[i]; } } - //((BufferedImage) image).getRGB(0, 0, width, height, pixels, 0, width); -// WritableRaster raster = ((BufferedImage) (useOffscreen && primarySurface ? offscreen : image)).getRaster(); -// WritableRaster raster = image.getRaster(); } diff --git a/core/src/processing/awt/PImageAWT.java b/core/src/processing/awt/PImageAWT.java index 801069567..e08d8c1fe 100644 --- a/core/src/processing/awt/PImageAWT.java +++ b/core/src/processing/awt/PImageAWT.java @@ -229,8 +229,10 @@ public class PImageAWT extends PImage { } + /* @Override protected boolean saveImpl(String path) { return ShimAWT.saveImage(this, path); } + */ } \ No newline at end of file diff --git a/core/src/processing/awt/PSurfaceAWT.java b/core/src/processing/awt/PSurfaceAWT.java index b78ce362a..71e0ef2be 100644 --- a/core/src/processing/awt/PSurfaceAWT.java +++ b/core/src/processing/awt/PSurfaceAWT.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2014-15 The Processing Foundation + Copyright (c) 2014-22 The Processing Foundation This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,22 +22,7 @@ package processing.awt; -import java.awt.Canvas; -import java.awt.Color; -import java.awt.Cursor; -import java.awt.Dimension; -import java.awt.Frame; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.GraphicsConfiguration; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.awt.Image; -import java.awt.Insets; -import java.awt.Label; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.Toolkit; +import java.awt.*; import java.awt.event.*; import java.awt.geom.Rectangle2D; import java.awt.image.*; @@ -49,6 +34,7 @@ import java.util.ArrayList; import java.util.List; import javax.swing.JFrame; +import javax.swing.SwingUtilities; import processing.core.PApplet; import processing.core.PConstants; @@ -164,11 +150,11 @@ public class PSurfaceAWT extends PSurfaceNone { // // request focus for its canvas inside beginDraw(). // // http://java.sun.com/j2se/1.4.2/docs/api/java/awt/doc-files/FocusSpec.html // // Disabling for 0185, because it causes an assertion failure on OS X -// // http://code.google.com/p/processing/issues/detail?id=258 +// // https://github.com/processing/processing/issues/297 // // requestFocus(); // // // Changing to this version for 0187 -// // http://code.google.com/p/processing/issues/detail?id=279 +// // https://github.com/processing/processing/issues/318 // //requestFocusInWindow(); // // // For 3.0, just call this directly on the Canvas object @@ -304,6 +290,8 @@ public class PSurfaceAWT extends PSurfaceNone { GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice defaultDevice = + environment.getDefaultScreenDevice(); int displayNum = sketch.sketchDisplay(); // System.out.println("display from sketch is " + displayNum); @@ -314,18 +302,30 @@ public class PSurfaceAWT extends PSurfaceNone { } else { System.err.format("Display %d does not exist, " + "using the default display instead.%n", displayNum); - for (int i = 0; i < devices.length; i++) { - System.err.format("Display %d is %s%n", (i+1), devices[i]); + if (devices.length > 1) { + System.err.println("Available displays:"); + // The code below is cribbed from the version in PreferencesFrame, + // though it uses \u00d7 and removes the space between because + // it's printing in a monospace font to the console. + for (int i = 0; i < devices.length; i++) { + DisplayMode mode = devices[i].getDisplayMode(); + String title = String.format("%d (%d\u00d7%d)", // \u00d7 or \u2715 + i + 1, mode.getWidth(), mode.getHeight()); + if (devices[i] == defaultDevice) { + title += " default"; + } + System.err.println(title); + } } } } if (displayDevice == null) { - displayDevice = environment.getDefaultScreenDevice(); + displayDevice = defaultDevice; } // Need to save the window bounds at full screen, // because pack() will cause the bounds to go to zero. - // http://dev.processing.org/bugs/show_bug.cgi?id=923 + // https://download.processing.org/bugzilla/923.html boolean spanDisplays = sketch.sketchDisplay() == PConstants.SPAN; screenRect = spanDisplays ? getDisplaySpan() : displayDevice.getDefaultConfiguration().getBounds(); @@ -388,10 +388,10 @@ public class PSurfaceAWT extends PSurfaceNone { // For 0149, moving this code (up to the pack() method) before init(). // For OpenGL (and perhaps other renderers in the future), a peer is // needed before a GLDrawable can be created. So pack() needs to be - // called on the Frame before applet.init(), which itself calls size(), + // called on the Frame before init(), which itself calls size(), // and launches the Thread that will kick off setup(). - // http://dev.processing.org/bugs/show_bug.cgi?id=891 - // http://dev.processing.org/bugs/show_bug.cgi?id=908 + // https://download.processing.org/bugzilla/891.html + // https://download.processing.org/bugzilla/908.html frame.add(canvas); setSize(sketchWidth / windowScaleFactor, sketchHeight / windowScaleFactor); @@ -421,7 +421,6 @@ public class PSurfaceAWT extends PSurfaceNone { } */ frame.setLayout(null); - //frame.add(applet); // Need to pass back our new sketchWidth/Height here, because it may have // been overridden by numbers we calculated above if fullScreen and/or @@ -441,7 +440,7 @@ public class PSurfaceAWT extends PSurfaceNone { //frame.validate(); // disabling resize has to happen after pack() to avoid apparent Apple bug - // http://code.google.com/p/processing/issues/detail?id=467 + // https://github.com/processing/processing/issues/506 frame.setResizable(false); frame.addWindowListener(new WindowAdapter() { @@ -653,63 +652,10 @@ public class PSurfaceAWT extends PSurfaceNone { label.setSize(labelSize); label.setLocation(20, screenRect.height - labelSize.height - 20); } - -// if (sketch.getGraphics().displayable()) { -// setVisible(true); -// } } - /* - @Override - public void placeWindow(int[] location) { - setFrameSize(); //sketchWidth, sketchHeight); - - if (location != null) { - // a specific location was received from the Runner - // (applet has been run more than once, user placed window) - frame.setLocation(location[0], location[1]); - - } else { // just center on screen - // Can't use frame.setLocationRelativeTo(null) because it sends the - // frame to the main display, which undermines the --display setting. - frame.setLocation(screenRect.x + (screenRect.width - sketchWidth) / 2, - screenRect.y + (screenRect.height - sketchHeight) / 2); - } - Point frameLoc = frame.getLocation(); - if (frameLoc.y < 0) { - // Windows actually allows you to place frames where they can't be - // closed. Awesome. http://dev.processing.org/bugs/show_bug.cgi?id=1508 - frame.setLocation(frameLoc.x, 30); - } - -// if (backgroundColor != null) { -// ((JFrame) frame).getContentPane().setBackground(backgroundColor); -// } - - setCanvasSize(); //sketchWidth, sketchHeight); - - frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - System.exit(0); - } - }); - - // handle frame resizing events - setupFrameResizeListener(); - - // all set for rockin - if (sketch.getGraphics().displayable()) { - frame.setVisible(true); - } - } - */ - - private void setCanvasSize() { -// System.out.format("setting canvas size %d %d%n", sketchWidth, sketchHeight); -// new Exception().printStackTrace(System.out); int contentW = Math.max(sketchWidth, MIN_WINDOW_WIDTH); int contentH = Math.max(sketchHeight, MIN_WINDOW_HEIGHT); @@ -784,7 +730,7 @@ public class PSurfaceAWT extends PSurfaceNone { if (!sketch.sketchFullScreen()) { if (location != null) { // a specific location was received from the Runner - // (applet has been run more than once, user placed window) + // (sketch has been run more than once, user placed window) frame.setLocation(location[0], location[1]); } else if (editorLocation != null) { @@ -820,9 +766,11 @@ public class PSurfaceAWT extends PSurfaceNone { Point frameLoc = frame.getLocation(); if (frameLoc.y < 0) { // Windows actually allows you to place frames where they can't be - // closed. Awesome. http://dev.processing.org/bugs/show_bug.cgi?id=1508 + // closed. Awesome. https://download.processing.org/bugzilla/1508.html frame.setLocation(frameLoc.x, 30); } + // make sure that windowX and windowY are set on startup + sketch.postWindowMoved(frame.getX(), frame.getY()); } canvas.setBounds((contentW - sketchWidth)/2, @@ -995,23 +943,24 @@ public class PSurfaceAWT extends PSurfaceNone { */ - /** - * Set this sketch to communicate its state back to the PDE. - *

    - * This uses the stderr stream to write positions of the window - * (so that it will be saved by the PDE for the next run) and - * notify on quit. See more notes in the Worker class. - */ - @Override - public void setupExternalMessages() { - frame.addComponentListener(new ComponentAdapter() { - @Override - public void componentMoved(ComponentEvent e) { - Point where = ((Frame) e.getSource()).getLocation(); - sketch.frameMoved(where.x, where.y); - } - }); - } +// /** +// * Set this sketch to communicate its state back to the PDE. +// *

    +// * This uses the stderr stream to write positions of the window +// * (so that it will be saved by the PDE for the next run) and +// * notify on quit. See more notes in the Worker class. +// */ +// @Override +// public void setupExternalMessages() { +// frame.addComponentListener(new ComponentAdapter() { +// @Override +// public void componentMoved(ComponentEvent e) { +// Point where = ((Frame) e.getSource()).getLocation(); +// //sketch.frameMoved(where.x, where.y); +// sketch.queueWindowPosition(where.x, where.y); +// } +// }); +// } /** @@ -1019,9 +968,8 @@ public class PSurfaceAWT extends PSurfaceNone { * in cases where frame.setResizable(true) is called. */ private void setupFrameResizeListener() { - // 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 + // Detect when the frame is resized to handle a macOS bug: + // 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 @@ -1044,9 +992,9 @@ public class PSurfaceAWT extends PSurfaceNone { @Override public void componentResized(ComponentEvent e) { // Ignore bad resize events fired during setup to fix - // http://dev.processing.org/bugs/show_bug.cgi?id=341 + // https://download.processing.org/bugzilla/341.html // This should also fix the blank screen on Linux bug - // http://dev.processing.org/bugs/show_bug.cgi?id=282 + // https://download.processing.org/bugzilla/282.html if (frame.isResizable()) { // might be multiple resize calls before visible (i.e. first // when pack() is called, then when it's resized for use). @@ -1065,12 +1013,22 @@ public class PSurfaceAWT extends PSurfaceNone { int w = windowSize.width - currentInsets.left - currentInsets.right; int h = windowSize.height - currentInsets.top - currentInsets.bottom; setSize(w / windowScaleFactor, h / windowScaleFactor); + // notify the sketch that the window has been resized + sketch.postWindowResized(w / windowScaleFactor, h / windowScaleFactor); // correct the location when inset size changes setLocation(x - currentInsets.left, y - currentInsets.top); + //sketch.postWindowMoved(x - currentInsets.left, y - currentInsets.top); + sketch.postWindowMoved(x, y); // presumably user wants drawing area } } } + + @Override + public void componentMoved(ComponentEvent e) { + Point where = ((Frame) e.getSource()).getLocation(); + sketch.postWindowMoved(where.x, where.y); + } }); } @@ -1193,13 +1151,14 @@ public class PSurfaceAWT extends PSurfaceNone { int modifiers = nativeEvent.getModifiersEx(); int peButton = 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) { + // Because there isn't a button "press" associated with drag, + // pass this over to SwingUtilities to properly decode the button. + // https://github.com/processing/processing4/issues/281 + if (SwingUtilities.isLeftMouseButton(nativeEvent)) { peButton = PConstants.LEFT; - } else if (nativeEvent.getButton() == 2) { + } else if (SwingUtilities.isMiddleMouseButton(nativeEvent)) { peButton = PConstants.CENTER; - } else if (nativeEvent.getButton() == 3) { + } else if (SwingUtilities.isRightMouseButton(nativeEvent)) { peButton = PConstants.RIGHT; } @@ -1350,6 +1309,7 @@ public class PSurfaceAWT extends PSurfaceNone { if (PApplet.platform == PConstants.MACOS && kind == PConstants.MOVE) { kind = PConstants.HAND; } + //noinspection MagicConstant canvas.setCursor(Cursor.getPredefinedCursor(kind)); cursorVisible = true; this.cursorType = kind; @@ -1384,6 +1344,7 @@ public class PSurfaceAWT extends PSurfaceNone { // will be stuck b/c p5 thinks the cursor is set to one particular thing. if (!cursorVisible) { cursorVisible = true; + //noinspection MagicConstant canvas.setCursor(Cursor.getPredefinedCursor(cursorType)); } } @@ -1411,6 +1372,12 @@ public class PSurfaceAWT extends PSurfaceNone { } + @Override + public boolean openLink(String url) { + return ShimAWT.openLink(url); + } + + @Override public Thread createThread() { return new AnimationThread() { diff --git a/core/src/processing/awt/ShimAWT.java b/core/src/processing/awt/ShimAWT.java index 3b9582c34..a8c64e1a9 100644 --- a/core/src/processing/awt/ShimAWT.java +++ b/core/src/processing/awt/ShimAWT.java @@ -3,14 +3,13 @@ package processing.awt; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.image.*; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.URI; import java.net.URISyntaxException; +import java.util.HashMap; import java.util.Iterator; import java.awt.geom.AffineTransform; +import java.util.Map; import javax.imageio.IIOImage; import javax.imageio.ImageIO; @@ -51,10 +50,10 @@ public class ShimAWT implements PConstants { */ static private ShimAWT instance; - private GraphicsDevice[] displayDevices; + final private GraphicsDevice[] displayDevices; - private int displayWidth; - private int displayHeight; + final private int displayWidth; + final private int displayHeight; /** Only needed for display functions */ @@ -192,7 +191,7 @@ public class ShimAWT implements PConstants { new PixelGrabber(img, 0, 0, out.width, out.height, out.pixels, 0, out.width); try { pg.grabPixels(); - } catch (InterruptedException e) { } + } catch (InterruptedException ignored) { } } out.pixelDensity = 1; out.pixelWidth = out.width; @@ -279,7 +278,7 @@ public class ShimAWT implements PConstants { if (w < targetWidth) { w = targetWidth; } - } else if (targetWidth >= w) { + } else { //if (targetWidth >= w) { w = targetWidth; } if (h > targetHeight) { @@ -287,7 +286,7 @@ public class ShimAWT implements PConstants { if (h < targetHeight) { h = targetHeight; } - } else if (targetHeight >= h) { + } else { //if (targetHeight >= h) { h = targetHeight; } if (scratchImage == null || isTranslucent) { @@ -304,9 +303,9 @@ public class ShimAWT implements PConstants { outgoing = scratchImage; } while (w != targetWidth || h != targetHeight); - if (g2 != null) { - g2.dispose(); - } + //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 @@ -322,7 +321,7 @@ public class ShimAWT implements PConstants { } - static protected String[] loadImageFormats; // list of ImageIO formats + static protected String[] loadImageExtensions; // list of ImageIO formats static public PImage loadImage(PApplet sketch, String filename, Object... args) { @@ -359,7 +358,9 @@ public class ShimAWT implements PConstants { if (input == null) return null; PImage image = PImage.loadTGA(input); - image.parent = sketch; + if (image != null) { + image.parent = sketch; + } return image; } catch (IOException e) { @@ -368,15 +369,18 @@ public class ShimAWT implements PConstants { } } + // Disabling for 4.0 beta 5, we're now using ImageIO for TIFF + /* if (extension.equals("tif") || extension.equals("tiff")) { InputStream input = sketch.createInput(filename); PImage image = (input == null) ? null : PImage.loadTIFF(input); return image; } + */ // For jpeg, gif, and png, load them using createImage(), // because the javax.imageio code was found to be much slower. - // http://dev.processing.org/bugs/show_bug.cgi?id=392 + // https://download.processing.org/bugzilla/392.html try { if (extension.equals("jpg") || extension.equals("jpeg") || extension.equals("gif") || extension.equals("png") || @@ -430,19 +434,23 @@ public class ShimAWT implements PConstants { e.printStackTrace(); } - if (loadImageFormats == null) { - loadImageFormats = ImageIO.getReaderFormatNames(); + if (loadImageExtensions == null) { + loadImageExtensions = ImageIO.getReaderFormatNames(); } - if (loadImageFormats != null) { - for (int i = 0; i < loadImageFormats.length; i++) { - if (extension.equals(loadImageFormats[i])) { + if (loadImageExtensions != null) { + for (String loadImageExtension : loadImageExtensions) { + if (extension.equals(loadImageExtension)) { return loadImageIO(sketch, filename); } } - } - // failed, could not load image after all those attempts - System.err.println("Could not find a method to load " + filename); + // failed, could not load image after all those attempts + System.err.println("Could not load " + filename + ", " + + "make sure it ends with a supported extension " + + "(" + PApplet.join(loadImageExtensions, ", ") + ")"); + } else { + System.err.println("Could not load " + filename); + } return null; } @@ -489,21 +497,24 @@ public class ShimAWT implements PConstants { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - static public boolean saveImage(PImage image, String path) { - if (saveImageFormats == null) { - saveImageFormats = javax.imageio.ImageIO.getWriterFormatNames(); + static public boolean saveImage(PImage image, String path, String... args) { + if (saveImageExtensions == null) { + saveImageExtensions = javax.imageio.ImageIO.getWriterFormatNames(); } try { - if (saveImageFormats != null) { - for (int i = 0; i < saveImageFormats.length; i++) { - if (path.endsWith("." + saveImageFormats[i])) { - if (!saveImageIO(image, path)) { + if (saveImageExtensions != null) { + for (String saveImageFormat : saveImageExtensions) { + if (path.endsWith("." + saveImageFormat)) { + if (!saveImageIO(image, path, args)) { System.err.println("Error while saving image."); return false; } return true; } } + System.err.println("Could not save " + path + ", " + + "make sure it ends with a supported extension " + + "(" + PApplet.join(saveImageExtensions, ", ") + ")"); } } catch (IOException e) { e.printStackTrace(); @@ -512,7 +523,7 @@ public class ShimAWT implements PConstants { } - static protected String[] saveImageFormats; + static protected String[] saveImageExtensions; /** @@ -521,17 +532,31 @@ public class ShimAWT implements PConstants { * To get a list of the supported formats for writing, use:
    * println(javax.imageio.ImageIO.getReaderFormatNames()) */ - static protected boolean saveImageIO(PImage image, String path) throws IOException { + static protected boolean saveImageIO(PImage image, String path, String... args) throws IOException { try { int outputFormat = (image.format == ARGB) ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB; + Map params = new HashMap<>(); + params.put("quality", 0.9f); // default JPEG quality + params.put("dpi", 100.0); // default DPI for PNG + + if (args != null) { + for (String arg : args) { + if (arg.startsWith("quality=")) { + params.put("quality", Float.parseFloat(arg.substring(8))); + } else if (arg.startsWith("dpi=")) { + params.put("dpi", Double.parseDouble(arg.substring(4))); + } + } + } + String extension = path.substring(path.lastIndexOf('.') + 1).toLowerCase(); // JPEG and BMP images that have an alpha channel set get pretty unhappy. // BMP just doesn't write, and JPEG writes it as a CMYK image. - // http://code.google.com/p/processing/issues/detail?id=415 + // https://github.com/processing/processing/issues/454 if (extension.equals("bmp") || extension.equals("jpg") || extension.equals("jpeg")) { outputFormat = BufferedImage.TYPE_INT_RGB; } @@ -553,24 +578,24 @@ public class ShimAWT implements PConstants { // it's a completely different algorithm. param = writer.getDefaultWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - param.setCompressionQuality(0.9f); + //param.setCompressionQuality(0.9f); + param.setCompressionQuality((Float) params.get("quality")); } } if (extension.equals("png")) { if ((writer = imageioWriter("png")) != null) { param = writer.getDefaultWriteParam(); - if (false) { - metadata = imageioDPI(writer, param, 100); - } + metadata = imageioDPI(writer, param, (Double) params.get("dpi")); } } if (writer != null) { - BufferedOutputStream output = - new BufferedOutputStream(PApplet.createOutput(file)); + OutputStream output = PApplet.createOutput(file); + if (output == null) { + return false; + } writer.setOutput(ImageIO.createImageOutputStream(output)); -// writer.write(null, new IIOImage(bimage, null, null), param); writer.write(metadata, new IIOImage(bimage, null, metadata), param); writer.dispose(); @@ -597,6 +622,7 @@ public class ShimAWT implements PConstants { } + @SuppressWarnings("SameParameterValue") static private IIOMetadata imageioDPI(ImageWriter writer, ImageWriteParam param, double dpi) { // http://stackoverflow.com/questions/321736/how-to-set-dpi-information-in-an-image ImageTypeSpecifier typeSpecifier = @@ -695,10 +721,8 @@ public class ShimAWT implements PConstants { static public void selectInput(String prompt, String callbackMethod, File file, Object callbackObject) { - EventQueue.invokeLater(() -> { - selectImpl(prompt, callbackMethod, file, - callbackObject, null, FileDialog.LOAD); - }); + EventQueue.invokeLater(() -> selectImpl(prompt, callbackMethod, file, + callbackObject, null, FileDialog.LOAD)); } @@ -719,10 +743,8 @@ public class ShimAWT implements PConstants { static public void selectOutput(String prompt, String callbackMethod, File file, Object callbackObject) { - EventQueue.invokeLater(() -> { - selectImpl(prompt, callbackMethod, file, - callbackObject, null, FileDialog.SAVE); - }); + EventQueue.invokeLater(() -> selectImpl(prompt, callbackMethod, file, + callbackObject, null, FileDialog.SAVE)); } @@ -800,10 +822,8 @@ public class ShimAWT implements PConstants { final String callbackMethod, final File defaultSelection, final Object callbackObject) { - EventQueue.invokeLater(() -> { - selectFolderImpl(prompt, callbackMethod, defaultSelection, - callbackObject, null); - }); + EventQueue.invokeLater(() -> selectFolderImpl(prompt, callbackMethod, + defaultSelection, callbackObject, null)); } @@ -883,7 +903,7 @@ public class ShimAWT implements PConstants { // Which also is not scaled properly with HiDPI interfaces. try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception e) { } + } catch (Exception ignored) { } } lookAndFeelCheck = true; } @@ -891,7 +911,7 @@ public class ShimAWT implements PConstants { // TODO maybe call this with reflection from inside PApplet? - // longer term, develop a more general method for other platforms + // longer term, develop a more general method for other platforms static public File getWindowsDesktop() { return FileSystemView.getFileSystemView().getHomeDirectory(); } @@ -900,12 +920,13 @@ public class ShimAWT implements PConstants { static public boolean openLink(String url) { try { if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(new URI(url)); - return true; + Desktop desktop = Desktop.getDesktop(); + if (desktop.isSupported(Desktop.Action.BROWSE)) { + desktop.browse(new URI(url)); + return true; + } } - } catch (IOException e) { - e.printStackTrace(); - } catch (URISyntaxException e) { + } catch (IOException | URISyntaxException e) { e.printStackTrace(); } return false; diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java index b53354ad9..549661a2a 100644 --- a/core/src/processing/core/PApplet.java +++ b/core/src/processing/core/PApplet.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-20 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -81,7 +81,7 @@ 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"}) +@SuppressWarnings({"unused", "FinalStaticMethod", "ManualMinMaxCalculation"}) 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). */ @@ -141,8 +141,8 @@ public class PApplet implements PConstants { public PGraphics g; /** - * System variable that stores the width of the computer screen. For - * example, if the current screen resolution is 1920x1080, + * System variable that stores the width of the computer screen. + * For example, if the current screen resolution is 1920x1080, * displayWidth is 1920 and displayHeight is 1080. * * @webref environment @@ -153,8 +153,8 @@ public class PApplet implements PConstants { public int displayWidth; /** - * System variable that stores the height of the computer screen. For - * example, if the current screen resolution is 1920x1080, + * System variable that stores the height of the computer screen. + * For example, if the current screen resolution is 1920x1080, * displayWidth is 1920 and displayHeight is 1080. * * @webref environment @@ -164,6 +164,9 @@ public class PApplet implements PConstants { */ public int displayHeight; + public int windowX; + public int windowY; + /** A leech graphics object that is echoing all events. */ public PGraphics recorder; @@ -175,12 +178,11 @@ public class PApplet implements PConstants { public String[] args; /** - * Path to sketch folder. Previously undocumented, made private in 3.0a5 - * so that people use the sketchPath() method and it's initialized properly. - * Call sketchPath() once to set the default. + * Path to sketch folder. Previously undocumented, and made private + * in 3.0 alpha 5 so that people use the sketchPath() method which + * will initialize it properly. Call sketchPath() once to set it. */ private String sketchPath; -// public String sketchPath; static final boolean DEBUG = false; // static final boolean DEBUG = true; @@ -190,7 +192,6 @@ public class PApplet implements PConstants { static public final int DEFAULT_HEIGHT = 100; /** - * * The pixels[] array contains the values for all the pixels in the * display window. These values are of the color datatype. This array is * defined by the size of the display window. For example, if the window is @@ -200,14 +201,13 @@ public class PApplet implements PConstants { * will change. See the reference for pixelWidth or pixelHeight * for more information. *

    - * Before accessing this array, the data must loaded with the loadPixels() + * Before accessing this array, the data must be loaded with the loadPixels() * function. Failure to do so may result in a NullPointerException. Subsequent * changes to the display window will not be reflected in pixels until * loadPixels() is called again. After pixels has been modified, * the updatePixels() function must be run to update the content of the * display window. * - * * @webref image:pixels * @webBrief Array containing the values for all the pixels in the display window * @see PApplet#loadPixels() @@ -264,7 +264,6 @@ public class PApplet implements PConstants { * array, for instance, because the number of elements in the array will * be pixelWidth*pixelHeight, not width*height. * - * * @webref environment * @webBrief The actual pixel width when using high resolution display * @see PApplet#pixelHeight @@ -275,7 +274,6 @@ public class PApplet implements PConstants { /** - * * When pixelDensity(2) is used to make use of a high resolution * display (called a Retina display on OS X or high-dpi on Windows and * Linux), the width and height of the sketch do not change, but the @@ -295,13 +293,38 @@ public class PApplet implements PConstants { */ public int pixelHeight; + // Making this private until we have a compelling reason to make it public. + // Seems problematic/weird for it to be possible to set windowRatio = false + // relative to how other API works. And not sure what the use case would be. + private boolean windowRatio; + + /** + * Version of mouseX/mouseY to use with windowRatio(). + */ + public int rmouseX; + public int rmouseY; + + /** + * Version of width/height to use with windowRatio(). + */ + public int rwidth; + public int rheight; + + /** Offset from left when windowRatio is in use. */ + public float ratioLeft; + + /** Offset from the top when windowRatio is in use. */ + public float ratioTop; + + /** Amount of scaling to be applied for the window ratio. */ + public float ratioScale; + /** * Keeps track of ENABLE_KEY_REPEAT hint */ protected boolean keyRepeatEnabled = false; /** - * * The system variable mouseX always contains the current horizontal * coordinate of the mouse. *

    @@ -325,15 +348,12 @@ public class PApplet implements PConstants { * @see PApplet#mouseDragged() * @see PApplet#mouseButton * @see PApplet#mouseWheel(MouseEvent) - * - * */ public int mouseX; /** - * - * The system variable mouseY always contains the current vertical - * coordinate of the mouse. + * The system variable mouseY always contains the current + * vertical coordinate of the mouse. *

    * Note that Processing can only track the mouse position when the pointer * is over the current window. The default value of mouseY is 0, @@ -360,7 +380,6 @@ public class PApplet implements PConstants { public int mouseY; /** - * * The system variable pmouseX always contains the horizontal * position of the mouse in the frame previous to the current frame.
    *
    @@ -398,7 +417,6 @@ public class PApplet implements PConstants { public int pmouseX; /** - * * The system variable pmouseY always contains the vertical position * of the mouse in the frame previous to the current frame. More detailed * information about how pmouseY is updated inside of draw() @@ -452,7 +470,6 @@ public class PApplet implements PConstants { public boolean firstMouse = true; /** - * * When a mouse button is pressed, the value of the system variable * mouseButton is set to either LEFT, RIGHT, or * CENTER, depending on which button is pressed. (If no button is @@ -461,10 +478,9 @@ public class PApplet implements PConstants { * pressed, and only then test the value of mouseButton, as shown in * the examples above.) * - * *

    Advanced:

    * - * If running on Mac OS, a ctrl-click will be interpreted as the right-hand + * If running on macOS, a ctrl-click will be interpreted as the right-hand * mouse button (unlike Java, which reports it as the left mouse). * @webref input:mouse * @webBrief Shows which mouse button is pressed @@ -483,7 +499,6 @@ public class PApplet implements PConstants { public int mouseButton; /** - * * The mousePressed() function is called once after every time a * mouse button is pressed. The mouseButton variable (see the * related reference entry) can be used to determine which button has @@ -509,12 +524,12 @@ public class PApplet implements PConstants { */ public boolean mousePressed; - // MACOSX: CTRL + Left Mouse is converted to Right Mouse. This boolean keeps - // track of whether the conversion happened on PRESS, because we should report - // the same button during DRAG and on RELEASE, even though CTRL might have - // 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 + // macOS: Ctrl + Left Mouse is converted to Right Mouse. + // This boolean tracks whether the conversion happened on PRESS, + // to report the same button during DRAG and on RELEASE, + // even though CTRL might have been released already. + // Otherwise, the events are inconsistent. + // https://github.com/processing/processing/issues/5672 private boolean macosCtrlClick; @@ -523,7 +538,6 @@ public class PApplet implements PConstants { public MouseEvent mouseEvent; /** - * * The system variable key always contains the value of the most * recent key on the keyboard that was used (either pressed or released). *

    @@ -558,7 +572,6 @@ public class PApplet implements PConstants { public char key; /** - * * The variable keyCode is used to detect special keys such as the * UP, DOWN, LEFT, RIGHT arrow keys and ALT, CONTROL, SHIFT. *

    @@ -580,20 +593,19 @@ public class PApplet implements PConstants { * KeyEvent * reference. *

    - * There are issues with how keyCode behaves across different renderers - * and operating systems. Watch out for unexpected behavior as you switch - * renderers and operating systems and you are using keys are aren't mentioned - * in this reference entry. + * There are issues with how keyCode behaves across different + * renderers and operating systems. Watch out for unexpected behavior + * as you switch renderers and operating systems, and also whenever + * you are using keys not mentioned in this reference entry. *

    * If you are using P2D or P3D as your renderer, use the * NEWT KeyEvent constants. * - * *

    Advanced

    * When "key" is set to CODED, this will contain a Java key code. *

    * For the arrow keys, keyCode will be one of UP, DOWN, LEFT and RIGHT. - * Also available are ALT, CONTROL and SHIFT. A full set of constants + * ALT, CONTROL and SHIFT are also available. A full set of constants * can be obtained from java.awt.event.KeyEvent, from the VK_XXXX variables. * * @webref input:keyboard @@ -606,9 +618,8 @@ public class PApplet implements PConstants { public int keyCode; /** - * - * The boolean system variable keyPressed is true if any key - * is pressed and false if no keys are pressed. + * The boolean system variable keyPressed is true + * if any key is pressed and false if no keys are pressed. *

    * Note that there is a similarly named function called keyPressed(). * See its reference page for more information. @@ -642,19 +653,8 @@ public class PApplet implements PConstants { */ public boolean focused = false; -// /** -// * Confirms if a Processing program is running inside a web browser. This -// * variable is "true" if the program is online and "false" if not. -// */ -// @Deprecated -// public boolean online = false; -// // This is deprecated because it's poorly named (and even more poorly -// // understood). Further, we'll probably be removing applets soon, in which -// // case this won't work at all. If you want this feature, you can check -// // whether getAppletContext() returns null. - /** - * Time in milliseconds when the applet was started. + * Time in milliseconds when the sketch was started. *

    * Used by the millis() function. */ @@ -677,14 +677,13 @@ public class PApplet implements PConstants { protected boolean looping = true; - /** flag set to true when a redraw is asked for by the user */ + /** flag set to true when redraw() is called by the user */ protected boolean redraw = true; /** - * - * The system variable frameCount contains the number of frames - * displayed since the program started. Inside setup() the value is - * 0 and and after the first iteration of draw it is 1, etc. + * The system variable frameCount contains the number o + * frames displayed since the program started. Inside setup() + * the value is 0 and during the first iteration of draw it is 1, etc. * * @webref environment * @webBrief The system variable that contains the number of frames @@ -700,12 +699,6 @@ public class PApplet implements PConstants { /** used by the UncaughtExceptionHandler, so has to be static */ static Throwable uncaughtThrowable; - // public, but undocumented.. removing for 3.0a5 -// /** -// * true if the animation thread is paused. -// */ -// public volatile boolean paused; - /** * true if exit() has been called so that things shut down * once the main thread kicks off. @@ -719,16 +712,16 @@ public class PApplet implements PConstants { /** * Position of the upper left-hand corner of the editor window - * that launched this applet. + * that launched this sketch. */ static public final String ARGS_EDITOR_LOCATION = "--editor-location"; static public final String ARGS_EXTERNAL = "--external"; /** - * Location for where to position the applet window on screen. + * Location for where to position the sketch window on screen. *

    - * This is used by the editor to when saving the previous applet + * This is used by the editor to when saving the previous sketch * location, or could be used by other classes to launch at a * specific position on-screen. */ @@ -762,7 +755,7 @@ public class PApplet implements PConstants { */ static public final String ARGS_SKETCH_FOLDER = "--sketch-path"; - static public final String ARGS_DENSITY = "--density"; + static public final String ARGS_UI_SCALE = "--ui-scale"; /** * When run externally to a PdeEditor, @@ -771,7 +764,7 @@ public class PApplet implements PConstants { static public final String EXTERNAL_STOP = "__STOP__"; /** - * When run externally to a PDE Editor, this is sent by the applet + * When run externally to a PDE Editor, this is sent by the sketch * whenever the window is moved. *

    * This is used so that the editor can re-open the sketch window @@ -797,13 +790,6 @@ public class PApplet implements PConstants { } -// /** -// * A dummy frame to keep compatibility with 2.x code -// * and encourage users to update. -// */ -// public Frame frame; - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -814,11 +800,9 @@ public class PApplet implements PConstants { boolean fullScreen; int display = -1; // use default -// GraphicsDevice[] displayDevices; // Unlike the others above, needs to be public to support // the pixelWidth and pixelHeight fields. public int pixelDensity = 1; - int suggestedDensity = -1; boolean present; @@ -834,7 +818,7 @@ public class PApplet implements PConstants { /** * @param method "size" or "fullScreen" - * @param args parameters passed to the function so we can show the user + * @param args parameters passed to the function to show the user * @return true if safely inside the settings() method */ boolean insideSettings(String method, Object... args) { @@ -895,17 +879,25 @@ public class PApplet implements PConstants { } catch (InterruptedException ignored) { } if (resultCode == 1) { - System.err.println("Could not check the status of “Displays have separate spaces.”"); - System.err.format("Received message '%s' and result code %d.%n", trim(stderr.toString()), resultCode); + String msg = trim(stderr.toString()); + // This message is confusing, so don't print if it's something typical + if (!(msg.contains("The domain/default pair") && msg.contains("does not exist"))) { + System.err.println("Could not check the status of “Displays have separate spaces.”"); + System.err.println("Result for 'defaults read' was " + resultCode); + System.err.println(msg); + } } String processOutput = trim(stdout.toString()); - // It looks like on Catalina, the option may not be set, so resultCode - // will be 1 (an error, since the param doesn't exist. But "Displays - // have separate spaces" is on by default, so show the message. + // On Catalina, the option may not be set, so resultCode + // will be 1 (an error, since the param doesn't exist.) + // But "Displays have separate spaces" is on by default. + // For Monterey, it appears to not be set until the user + // has visited the Mission Control preference pane once. if (resultCode == 1 || "0".equals(processOutput)) { - System.err.println("To use fullScreen(SPAN), first turn off “Displays have separate spaces”"); - System.err.println("in System Preferences \u2192 Mission Control. Then log out and log back in."); + System.err.println("To use fullScreen(SPAN), visit System Preferences \u2192 Mission Control"); + System.err.println("and make sure that “Displays have separate spaces” is turned off."); + System.err.println("Then log out and log back in."); } } @@ -914,32 +906,31 @@ public class PApplet implements PConstants { /** - * - * The settings() function is new with Processing 3.0. - * It's not needed in most sketches. It's only useful when it's - * absolutely necessary to define the parameters to size() - * with a variable. Alternately, the settings() function - * is necessary when using Processing code outside of the - * Processing Development Environment (PDE). For example, when - * using the Eclipse code editor, it's necessary to use - * settings() to define the size() and - * smooth() values for a sketch.. - *

    - * The settings() method runs before the sketch has been - * set up, so other Processing functions cannot be used at that - * point. For instance, do not use loadImage() inside settings(). - * The settings() method runs "passively" to set a few variables, - * compared to the setup() command that call commands in - * the Processing API. - * - * @webref environment - * @webBrief Used when absolutely necessary to define the parameters to size() - * with a variable - * @see PApplet#fullScreen() - * @see PApplet#setup() - * @see PApplet#size(int,int) - * @see PApplet#smooth() - */ + * The settings() function is new with Processing 3.0. + * It's not needed in most sketches. It's only useful when it's + * absolutely necessary to define the parameters to size() + * with a variable. Alternately, the settings() function + * is necessary when using Processing code outside the + * Processing Development Environment (PDE). For example, when + * using the Eclipse code editor, it's necessary to use + * settings() to define the size() and + * smooth() values for a sketch.. + *

    + * The settings() method runs before the sketch has been + * set up, so other Processing functions cannot be used at that + * point. For instance, do not use loadImage() inside settings(). + * The settings() method runs "passively" to set a few variables, + * compared to the setup() command that call commands in + * the Processing API. + * + * @webref environment + * @webBrief Used when absolutely necessary to define the parameters to size() + * with a variable + * @see PApplet#fullScreen() + * @see PApplet#setup() + * @see PApplet#size(int,int) + * @see PApplet#smooth() + */ public void settings() { // is this necessary? (doesn't appear to be, so removing) //size(DEFAULT_WIDTH, DEFAULT_HEIGHT, JAVA2D); @@ -1007,7 +998,6 @@ public class PApplet implements PConstants { * and a "1" if not. This information is useful for a program to adapt to * run at double the pixel density on a screen that supports it. * - * * @webref environment * @webBrief Returns "2" if the screen is high-density and "1" if not * @see PApplet#pixelDensity(int) @@ -1062,31 +1052,30 @@ public class PApplet implements PConstants { /** - * This function is new with Processing 3.0. It makes it - * possible for Processing to render using all of the - * pixels on high resolutions screens like Apple Retina - * displays and Windows High-DPI displays. This function - * can only be run once within a program and it must be - * used right after size() in a program without a setup() - * and used within setup() when a program has one. The + * This function makes it possible to render using all the pixels + * on high resolutions screens like Apple Retina and Windows HiDPI. + * This function can only be run once within a program, and must + * be called right after size() in a program without a + * setup() function, or within setup() if present. + *

    * pixelDensity() should only be used with hardcoded * numbers (in almost all cases this number will be 2) * or in combination with displayDensity() as in the * third example above. - * - * When the pixel density is set to more than 1, it - * changes all of the pixel operations including the way - * get(), set(), blend(), copy(), and updatePixels() + *

    + * When the pixel density is set to more than 1, it changes the + * pixel operations including the way get(), set(), + * blend(), copy(), and updatePixels() * all work. See the reference for pixelWidth and * pixelHeight for more information. - * + *

    * To use variables as the arguments to pixelDensity() * function, place the pixelDensity() function within * the settings() function. There is more information * about this on the settings() reference page. * * @webref environment - * @webBrief It makes it possible for Processing to render using all of the + * @webBrief It makes it possible for Processing to render using all the * pixels on high resolutions screens * @param density 1 or 2 * @see PApplet#pixelWidth @@ -1146,7 +1135,7 @@ public class PApplet implements PConstants { * in a different way. The level parameter increases * the amount of smoothness. This is the level of over * sampling applied to the graphics buffer. - * + *

    * With the P2D and P3D renderers, smooth(2) is the * default, this is called "2x anti-aliasing." The code * smooth(4) is used for 4x anti-aliasing and smooth(8) @@ -1154,20 +1143,21 @@ public class PApplet implements PConstants { * anti-aliasing level is determined by the hardware of * the machine that is running the software, so smooth(4) * and smooth(8) will not work with every computer. - * + *

    * The default renderer uses smooth(3) by default. This * is bicubic smoothing. The other option for the default * renderer is smooth(2), which is bilinear smoothing. - * - * With Processing 3.0, smooth() is different than before. - * It was common to use smooth() and noSmooth() to turn on + *

    + * With Processing 3.0, smooth() is handled differently + * than in earlier releases. In 2.x and earlier, it was possible + * to use smooth() and noSmooth() to turn on * and off antialiasing within a sketch. Now, because of * how the software has changed, smooth() can only be set * once within a sketch. It can be used either at the top * of a sketch without a setup(), or after the size() * function when used in a sketch with setup(). The * noSmooth() function also follows the same rules. - * + *

    * When smooth() is used with a PGraphics object, it should * be run right after the object is created with * createGraphics(), as shown in the Reference in the third @@ -1191,7 +1181,7 @@ public class PApplet implements PConstants { * edges and images with hard edges between the pixels * when enlarged rather than interpolating pixels. Note * that smooth() is active by default, so it is necessary - * to call noSmooth() to disable smoothing of geometry, + * to call noSmooth() to disable smoothing of geometry, * fonts, and images. Since the release of Processing 3.0, * the noSmooth() function can only be run once for each * sketch, either at the top of a sketch without a setup(), @@ -1238,16 +1228,9 @@ public class PApplet implements PConstants { /** - * Called by the browser or applet viewer to inform this applet that it - * should start its execution. It is called after the init method and - * each time the applet is revisited in a Web page. - *

    - * Called explicitly via the first call to PApplet.paint(), because - * PAppletGL needs to have a usable screen before getting things rolling. + * Called by the application context to start running (or resume) the sketch. */ public void start() { -// paused = false; // unpause the thread // removing for 3.0a5, don't think we want this here - resume(); handleMethods("resume"); surface.resumeThread(); @@ -1255,44 +1238,14 @@ public class PApplet implements PConstants { /** - * Called by the browser or applet viewer to inform - * this applet that it should stop its execution. - *

    - * Unfortunately, there are no guarantees from the Java spec - * when or if stop() will be called (i.e. on browser quit, - * or when moving between web pages), and it's not always called. + * Called by the application context to stop running (or pause) the sketch. */ public void stop() { - // this used to shut down the sketch, but that code has - // been moved to destroy/dispose() - -// if (paused) { -// synchronized (pauseObject) { -// try { -// pauseObject.wait(); -// } catch (InterruptedException e) { -// // waiting for this interrupt on a start() (resume) call -// } -// } -// } - - //paused = true; // causes animation thread to sleep // 3.0a5 pause(); handleMethods("pause"); // calling this down here, since it's another thread it's safer to call // pause() and the registered pause methods first. surface.pauseThread(); - - // actual pause will happen in the run() method - -// synchronized (pauseObject) { -// debug("stop() calling pauseObject.wait()"); -// try { -// pauseObject.wait(); -// } catch (InterruptedException e) { -// // waiting for this interrupt on a start() (resume) call -// } -// } } @@ -1312,23 +1265,6 @@ public class PApplet implements PConstants { public void resume() { } -// /** -// * Called by the browser or applet viewer to inform this applet -// * that it is being reclaimed and that it should destroy -// * any resources that it has allocated. -// *

    -// * destroy() supposedly gets called as the applet viewer -// * is shutting down the applet. stop() is called -// * first, and then destroy() to really get rid of things. -// * no guarantees on when they're run (on browser quit, or -// * when moving between pages), though. -// */ -// @Override -// public void destroy() { -// this.dispose(); -// } - - ////////////////////////////////////////////////////////////// @@ -1336,19 +1272,6 @@ public class PApplet implements PConstants { Map registerMap = new ConcurrentHashMap<>(); - /* - static class RegisteredMethod { - Object object; - Method method; - - RegisteredMethod(Object object, Method method) { - this.object = object; - this.method = method; - } - } - */ - - class RegisteredMethods { /** * List of the objects for which the method is registered. @@ -1388,13 +1311,12 @@ public class PApplet implements PConstants { } catch (Exception e) { // check for wrapped exception, get root exception Throwable t; - if (e instanceof InvocationTargetException) { - InvocationTargetException ite = (InvocationTargetException) e; + if (e instanceof InvocationTargetException ite) { t = ite.getCause(); } else { t = e; } - // check for RuntimeException, and allow to bubble up + // check for RuntimeException, and allow it to bubble up if (t instanceof RuntimeException) { // re-throw exception throw (RuntimeException) t; @@ -1435,7 +1357,7 @@ public class PApplet implements PConstants { entries.remove(object); methods.remove(object); } else { - // Currently iterating the list of methods, remove this afterwards + // Iterates the list of methods, remove this afterwards removals.add(object); } } @@ -1453,7 +1375,7 @@ public class PApplet implements PConstants { *

  • resume – called when the sketch is resumed *
  • dispose – when the sketch is shutting down (definitely not safe to draw) *
      - * In addition, the new (for 2.0) processing.event classes are passed to + * In addition, the new (for 2.0) processing.event classes are passed to * the following event types: *
        *
      • mouseEvent @@ -1466,17 +1388,11 @@ public class PApplet implements PConstants { * @param target the target object that should receive the event */ public void registerMethod(String methodName, Object target) { - if (methodName.equals("mouseEvent")) { - registerWithArgs("mouseEvent", target, new Class[] { processing.event.MouseEvent.class }); - - } else if (methodName.equals("keyEvent")) { - registerWithArgs("keyEvent", target, new Class[] { processing.event.KeyEvent.class }); - - } else if (methodName.equals("touchEvent")) { - registerWithArgs("touchEvent", target, new Class[] { processing.event.TouchEvent.class }); - - } else { - registerNoArgs(methodName, target); + switch (methodName) { + case "mouseEvent" -> registerWithArgs("mouseEvent", target, new Class[] { MouseEvent.class }); + case "keyEvent" -> registerWithArgs("keyEvent", target, new Class[] { KeyEvent.class }); + case "touchEvent" -> registerWithArgs("touchEvent", target, new Class[] { TouchEvent.class }); + default -> registerNoArgs(methodName, target); } } @@ -1551,7 +1467,7 @@ public class PApplet implements PConstants { * The setup() function is run once, when the program starts. It's used * 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 + * setup() function for each program, and it shouldn't be called again * after its initial execution.
        *
        * If the sketch is a different dimension than the default, the size() @@ -1561,7 +1477,6 @@ public class PApplet implements PConstants { * Note: Variables declared within setup() are not accessible within * other functions, including draw(). * - * * @webref structure * @webBrief The setup() function is called once when the program starts * @usage web_application @@ -1670,7 +1585,7 @@ public class PApplet implements PConstants { * program on (e.g. 1, 2, 3...). When used with two parameters, the first * defines the renderer to use (e.g. P2D) and the second defines the screen. * The SPAN parameter can be used in place of a screen number to draw - * the sketch as a full-screen window across all of the attached displays if + * the sketch as a full-screen window across all the attached displays if * there are more than one.
        *
        * Prior to Processing 3.0, a full-screen program was defined with @@ -1854,7 +1769,7 @@ public class PApplet implements PConstants { /** * * Creates and returns a new PGraphics object. Use this class if you - * need to draw into an off-screen graphics buffer. The first two parameters + * need to draw into an offscreen graphics buffer. The first two parameters * define the width and height in pixels. The third, optional parameter * specifies the renderer. It can be defined as P2D, P3D, PDF, or SVG. If the * third parameter isn't used, the default renderer is set. The PDF and SVG @@ -2037,7 +1952,7 @@ public class PApplet implements PConstants { (e instanceof IllegalAccessException)) { if (e.getMessage().contains("cannot be <= 0")) { // IllegalArgumentException will be thrown if w/h is <= 0 - // http://code.google.com/p/processing/issues/detail?id=983 + // https://github.com/processing/processing/issues/1021 throw new RuntimeException(e); } else { @@ -2079,7 +1994,6 @@ public class PApplet implements PConstants { * Advanced users please note that createImage() should be used instead of * the syntax new PImage(). * - * *

        Advanced

        * Preferred method of creating new PImage objects, ensures that a * reference to the parent PApplet is included, which makes save() work @@ -2110,24 +2024,8 @@ public class PApplet implements PConstants { public void handleDraw() { - //debug("handleDraw() " + g + " " + looping + " " + redraw + " valid:" + this.isValid() + " visible:" + this.isVisible()); - - // canDraw = g != null && (looping || redraw); if (g == null) return; if (!looping && !redraw) return; -// System.out.println("looping/redraw = " + looping + " " + redraw); - - // no longer in use by any of our renderers -// if (!g.canDraw()) { -// debug("g.canDraw() is false"); -// // Don't draw if the renderer is not yet ready. -// // (e.g. OpenGL has to wait for a peer to be on screen) -// return; -// } - - // Store the quality setting in case it's changed during draw and the - // drawing context needs to be re-built before the next frame. -// int pquality = g.smooth; if (insideDraw) { System.err.println("handleDraw() called before finishing"); @@ -2140,22 +2038,21 @@ public class PApplet implements PConstants { recorder.beginDraw(); } + // apply window ratio if set + if (windowRatio) { + float aspectH = width / (float) rwidth; + float aspectV = height / (float) rheight; + ratioScale = min(aspectH, aspectV); + ratioTop = (height - ratioScale* rheight) / 2; + ratioLeft = (width - ratioScale* rwidth) / 2; + translate(ratioLeft, ratioTop); + scale(ratioScale); + } + long now = System.nanoTime(); if (frameCount == 0) { - // 3.0a5 should be no longer needed; handled by PSurface - //surface.checkDisplaySize(); - -// try { - //println("Calling setup()"); setup(); - //println("Done with setup()"); - -// } catch (RendererChangeException e) { -// // Give up, instead set the new renderer and re-attempt setup() -// return; -// } -// defaultSize = false; } else { // frameCount > 0, meaning an actual draw() // update the current frameRate @@ -2188,18 +2085,17 @@ public class PApplet implements PConstants { frameRate = (float) (1.0 / avgFrameTimeSecs); } - //if (frameCount != 0) { // always the case for this block + // post move and resize events to the sketch here + dequeueWindowEvents(); + handleMethods("pre"); - //} // use dmouseX/Y as previous mouse pos, since this is the // last position the mouse was in during the previous draw. pmouseX = dmouseX; pmouseY = dmouseY; - //println("Calling draw()"); draw(); - //println("Done calling draw()"); // dmouseX/Y is updated only once per frame (unlike emouseX/Y) dmouseX = mouseX; @@ -2218,10 +2114,6 @@ public class PApplet implements PConstants { } g.endDraw(); -// if (pquality != g.smooth) { -// surface.setSmooth(g.smooth); -// } - if (recorder != null) { recorder.endDraw(); } @@ -2370,12 +2262,8 @@ public class PApplet implements PConstants { while (!eventQueue.isEmpty()) { Event e = eventQueue.remove(); switch (e.getFlavor()) { - case Event.MOUSE: - handleMouseEvent((MouseEvent) e); - break; - case Event.KEY: - handleKeyEvent((KeyEvent) e); - break; + case Event.MOUSE -> handleMouseEvent((MouseEvent) e); + case Event.KEY -> handleKeyEvent((KeyEvent) e); } } } @@ -2396,7 +2284,7 @@ public class PApplet implements PConstants { // 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. + // 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 preceding move, @@ -2407,13 +2295,17 @@ public class PApplet implements PConstants { action == MouseEvent.PRESS) { pmouseX = emouseX; pmouseY = emouseY; + if (windowRatio) { + rmouseX = floor((event.getX() - ratioLeft) / ratioScale); + rmouseY = floor((event.getY() - ratioTop) / ratioScale); + } mouseX = event.getX(); mouseY = event.getY(); } int button = event.getButton(); - // If running on Mac OS, allow ctrl-click as right mouse click. + // If running on macOS, 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. @@ -2473,43 +2365,21 @@ public class PApplet implements PConstants { // boolean for mousePressed. switch (action) { - case MouseEvent.PRESS: - mousePressed = true; - break; - case MouseEvent.RELEASE: - mousePressed = false; - break; + case MouseEvent.PRESS -> mousePressed = true; + case MouseEvent.RELEASE -> mousePressed = false; } handleMethods("mouseEvent", event); switch (action) { - case MouseEvent.PRESS: -// mousePressed = true; - mousePressed(event); - break; - case MouseEvent.RELEASE: -// mousePressed = false; - mouseReleased(event); - break; - case MouseEvent.CLICK: - mouseClicked(event); - break; - case MouseEvent.DRAG: - mouseDragged(event); - break; - case MouseEvent.MOVE: - mouseMoved(event); - break; - case MouseEvent.ENTER: - mouseEntered(event); - break; - case MouseEvent.EXIT: - mouseExited(event); - break; - case MouseEvent.WHEEL: - mouseWheel(event); - break; + case MouseEvent.PRESS -> mousePressed(event); + case MouseEvent.RELEASE -> mouseReleased(event); + case MouseEvent.CLICK -> mouseClicked(event); + case MouseEvent.DRAG -> mouseDragged(event); + case MouseEvent.MOVE -> mouseMoved(event); + case MouseEvent.ENTER -> mouseEntered(event); + case MouseEvent.EXIT -> mouseExited(event); + case MouseEvent.WHEEL -> mouseWheel(event); } if ((action == MouseEvent.DRAG) || @@ -2602,7 +2472,7 @@ public class PApplet implements PConstants { * *

        Advanced

        When the mouse is clicked, mousePressed() will be called, * then mouseReleased(), then mouseClicked(). Note that mousePressed is - * already false inside of mouseClicked(). + * already false inside mouseClicked(). * * @webref input:mouse * @webBrief Called once after a mouse button has been pressed and then @@ -2670,7 +2540,6 @@ public class PApplet implements PConstants { * Without draw(), the code is only run once and then stops listening * for events. * - * * @webref input:mouse * @webBrief Called every time the mouse moves and a mouse button is not * pressed @@ -2764,20 +2633,18 @@ public class PApplet implements PConstants { keyCode = event.getKeyCode(); switch (event.getAction()) { - case KeyEvent.PRESS: - Long hash = ((long) keyCode << Character.SIZE) | key; - if (!pressedKeys.contains(hash)) pressedKeys.add(hash); - keyPressed = true; - keyPressed(keyEvent); - break; - case KeyEvent.RELEASE: - pressedKeys.remove(((long) keyCode << Character.SIZE) | key); - keyPressed = !pressedKeys.isEmpty(); - keyReleased(keyEvent); - break; - case KeyEvent.TYPE: - keyTyped(keyEvent); - break; + case KeyEvent.PRESS -> { + Long hash = ((long) keyCode << Character.SIZE) | key; + if (!pressedKeys.contains(hash)) pressedKeys.add(hash); + keyPressed = true; + keyPressed(keyEvent); + } + case KeyEvent.RELEASE -> { + pressedKeys.remove(((long) keyCode << Character.SIZE) | key); + keyPressed = !pressedKeys.isEmpty(); + keyReleased(keyEvent); + } + case KeyEvent.TYPE -> keyTyped(keyEvent); } /* @@ -2854,7 +2721,7 @@ public class PApplet implements PConstants { * multiple calls to keyPressed(), because the OS repeat takes over. *

        * Examples for key handling: (Tested on Windows XP, please notify if - * different on other platforms, I have a feeling Mac OS and Linux may do + * different on other platforms, I have a feeling macOS and Linux may do * otherwise) * *

        @@ -2888,7 +2755,7 @@ public class PApplet implements PConstants {
            *    keyReleased with key == CODED and keyCode == SHIFT
            *    (note there is no keyTyped)
            *
        -   * 6. Pressing the tab key in an applet with Java 1.4 will
        +   * 6. Pressing the tab key in a Component with Java 1.4 will
            *    normally do nothing, but PApplet dynamically shuts
            *    this behavior off if Java 1.4 is in use (tested 1.4.2_05 Windows).
            *    Java 1.1 (Microsoft VM) passes the TAB key through normally.
        @@ -2968,9 +2835,9 @@ public class PApplet implements PConstants {
         
           //////////////////////////////////////////////////////////////
         
        -  // i am focused man, and i'm not afraid of death.
        -  // and i'm going all out. i circle the vultures in a van
        -  // and i run the block.
        +  // I am focused man, and I'm not afraid of death.
        +  // and I'm going all out. I circle the vultures in a van
        +  // and I run the block.
         
         
           public void focusGained() { }
        @@ -2991,18 +2858,16 @@ public class PApplet implements PConstants {
           /**
            *
            * Returns the number of milliseconds (thousandths of a second) since
        -   * starting an applet. This information is often used for timing animation
        +   * starting the sketch. This information is often used for timing animation
            * sequences.
            *
        -   *
            * 

        Advanced

        - *

        * This is a function, rather than a variable, because it may * change multiple times per frame. * * @webref input:time date * @webBrief Returns the number of milliseconds (thousandths of a second) since - * starting an applet + * the sketch started. * @see PApplet#second() * @see PApplet#minute() * @see PApplet#hour() @@ -3018,10 +2883,10 @@ public class PApplet implements PConstants { /** * * Processing communicates with the clock on your computer. The - * second() function returns the current second as a value from 0 - 59. + * second() function returns the current second as a value from 0 to 59. * * @webref input:time date - * @webBrief Returns the current second as a value from 0 - 59 + * @webBrief Returns the current second as a value from 0 to 59 * @see PApplet#millis() * @see PApplet#minute() * @see PApplet#hour() @@ -3036,11 +2901,9 @@ public class PApplet implements PConstants { /** * * Processing communicates with the clock on your computer. The - * minute() function returns the current minute as a value from 0 - 59. - * - * + * minute() function returns the current minute as a value from 0 to 59. * @webref input:time date - * @webBrief Returns the current minute as a value from 0 - 59 + * @webBrief Returns the current minute as a value from 0 to 59 * @see PApplet#millis() * @see PApplet#second() * @see PApplet#hour() @@ -3056,10 +2919,10 @@ public class PApplet implements PConstants { /** * * Processing communicates with the clock on your computer. The - * hour() function returns the current hour as a value from 0 - 23. + * hour() function returns the current hour as a value from 0 to 23. * * @webref input:time date - * @webBrief Returns the current hour as a value from 0 - 23 + * @webBrief Returns the current hour as a value from 0 to 23 * @see PApplet#millis() * @see PApplet#second() * @see PApplet#minute() @@ -3075,8 +2938,7 @@ public class PApplet implements PConstants { /** * * Processing communicates with the clock on your computer. The - * day() function returns the current day as a value from 1 - 31. - * + * day() function returns the current day as a value from 1 to 31. * *

        Advanced

        * Get the current day of the month (1 through 31). @@ -3085,7 +2947,7 @@ public class PApplet implements PConstants { * or day of the year (1..365) then use java's Calendar.get() * * @webref input:time date - * @webBrief Returns the current day as a value from 1 - 31 + * @webBrief Returns the current day as a value from 1 to 31 * @see PApplet#millis() * @see PApplet#second() * @see PApplet#minute() @@ -3100,11 +2962,10 @@ public class PApplet implements PConstants { /** * * Processing communicates with the clock on your computer. The - * month() function returns the current month as a value from 1 - 12. - * + * month() function returns the current month as a value from 1 to 12. * * @webref input:time date - * @webBrief Returns the current month as a value from 1 - 12 + * @webBrief Returns the current month as a value from 1 to 12 * @see PApplet#millis() * @see PApplet#second() * @see PApplet#minute() @@ -3118,12 +2979,10 @@ public class PApplet implements PConstants { } /** - * * Processing communicates with the clock on your computer. The * year() function returns the current year as an integer (2003, * 2004, 2005, etc). * - * * @webref input:time date * @webBrief Returns the current year as an integer (2003, * 2004, 2005, etc) @@ -3150,11 +3009,11 @@ public class PApplet implements PConstants { * Delay times are specified in thousandths of a second. For example, * running delay(3000) will stop the program for three seconds and * delay(500) will stop the program for a half-second. - * + *

        * The screen only updates when the end of draw() is reached, so delay() * cannot be used to slow down drawing. For instance, you cannot use delay() * to control the timing of an animation. - * + *

        * The delay() function should only be used for pausing scripts (i.e. * a script that needs to pause a few seconds before attempting a download, * or a sketch that needs to wait a few milliseconds before reading from @@ -3209,12 +3068,6 @@ public class PApplet implements PConstants { * Links to a webpage either in the same window or in a new window. The * complete URL must be specified. * - *

        Advanced

        - * Link to an external page without all the muss. - *

        - * When run with an applet, uses the browser to open the url, - * for applications, attempts to launch a browser with the url. - * * @param url the complete URL, as a String in quotes */ public void link(String url) { @@ -3261,7 +3114,7 @@ public class PApplet implements PConstants { * @webBrief Attempts to open an application or file using your platform's * launcher * @param args - * arguments to the launcher, eg. a filename. + * arguments to the launcher, e.g. a filename. * @usage Application */ static public Process launch(String... args) { @@ -3459,7 +3312,7 @@ public class PApplet implements PConstants { } else { shell = "/bin/sh"; runCmd = "-c"; - // attempt emulate the behavior of an interactive shell + // attempt to emulate the behavior of an interactive shell // can't use -i or -l since the version of bash shipped with macOS does not support this together with -c // also we want to make sure no motd or similar gets returned as stdout argList.append("if [ -f /etc/profile ]; then . /etc/profile >/dev/null 2>&1; fi;"); @@ -3508,8 +3361,8 @@ public class PApplet implements PConstants { /** - * Function for an applet/application to kill itself and - * display an error. Mostly this is here to be improved later. + * Function for an application to kill itself and display an error. + * Mostly this is here to be improved later. */ public void die(String what) { dispose(); @@ -3580,11 +3433,7 @@ public class PApplet implements PConstants { * they have in mind when cleaning up. */ public void exitActual() { - try { - System.exit(0); - } catch (SecurityException e) { - // don't care about applet security exceptions - } + System.exit(0); } @@ -3593,7 +3442,7 @@ public class PApplet implements PConstants { * Destroys the thread, dispose the renderer,and notify listeners. *

        * Not to be called or overridden by users. If called multiple times, - * will only notify listeners once. Register a dispose listener instead. + * will only notify listeners once. Register a "dispose" listener instead. */ public void dispose() { // moved here from stop() @@ -3735,12 +3584,7 @@ public class PApplet implements PConstants { /** */ public void saveFrame() { - try { - g.save(savePath("screen-" + nf(frameCount, 4) + ".tif")); - } catch (SecurityException se) { - System.err.println("Can't use saveFrame() when running in a browser, " + - "unless using a signed applet."); - } + g.save(savePath("screen-" + nf(frameCount, 4) + ".tif")); } @@ -3780,12 +3624,7 @@ public class PApplet implements PConstants { * ".tga", ".jpg", or ".png" */ public void saveFrame(String filename) { - try { - g.save(savePath(insertFrame(filename))); - } catch (SecurityException se) { - System.err.println("Can't use saveFrame() when running in a browser, " + - "unless using a signed applet."); - } + g.save(savePath(insertFrame(filename))); } @@ -4111,15 +3950,14 @@ public class PApplet implements PConstants { * for occasional messages, but does not support high-speed, * real-time output (such as at 60 frames per second). * - * - * @webref output:text area - * @webBrief Writes array data to the text - * area of the Processing environment's console. - * @param what one-dimensional array - * @usage IDE - * @see PApplet#print(byte) - * @see PApplet#println() - */ + * @webref output:text area + * @webBrief Writes array data to the text + * area of the Processing environment's console. + * @param what one-dimensional array + * @usage IDE + * @see PApplet#print(byte) + * @see PApplet#println() + */ static public void printArray(Object what) { if (what == null) { // special case since this does fugly things on > 1.1 @@ -4129,75 +3967,64 @@ public class PApplet implements PConstants { String name = what.getClass().getName(); if (name.charAt(0) == '[') { switch (name.charAt(1)) { - case '[': - // don't even mess with multi-dimensional arrays (case '[') - // or anything else that's not int, float, boolean, char - System.out.println(what); - break; - - case 'L': - // print a 1D array of objects as individual elements - Object[] poo = (Object[]) what; - for (int i = 0; i < poo.length; i++) { - if (poo[i] instanceof String) { - System.out.println("[" + i + "] \"" + poo[i] + "\""); - } else { - System.out.println("[" + i + "] " + poo[i]); + case '[' -> + // don't even mess with multi-dimensional arrays (case '[') + // or anything else that's not int, float, boolean, char + System.out.println(what); + case 'L' -> { + // print a 1D array of objects as individual elements + Object[] poo = (Object[]) what; + for (int i = 0; i < poo.length; i++) { + if (poo[i] instanceof String) { + System.out.println("[" + i + "] \"" + poo[i] + "\""); + } else { + System.out.println("[" + i + "] " + poo[i]); + } } } - break; - - case 'Z': // boolean - boolean[] zz = (boolean[]) what; - for (int i = 0; i < zz.length; i++) { - System.out.println("[" + i + "] " + zz[i]); + case 'Z' -> { // boolean + boolean[] zz = (boolean[]) what; + for (int i = 0; i < zz.length; i++) { + System.out.println("[" + i + "] " + zz[i]); + } } - break; - - case 'B': // byte - byte[] bb = (byte[]) what; - for (int i = 0; i < bb.length; i++) { - System.out.println("[" + i + "] " + bb[i]); + case 'B' -> { // byte + byte[] bb = (byte[]) what; + for (int i = 0; i < bb.length; i++) { + System.out.println("[" + i + "] " + bb[i]); + } } - break; - - case 'C': // char - char[] cc = (char[]) what; - for (int i = 0; i < cc.length; i++) { - System.out.println("[" + i + "] '" + cc[i] + "'"); + case 'C' -> { // char + char[] cc = (char[]) what; + for (int i = 0; i < cc.length; i++) { + System.out.println("[" + i + "] '" + cc[i] + "'"); + } } - break; - - case 'I': // int - int[] ii = (int[]) what; - for (int i = 0; i < ii.length; i++) { - System.out.println("[" + i + "] " + ii[i]); + case 'I' -> { // int + int[] ii = (int[]) what; + for (int i = 0; i < ii.length; i++) { + System.out.println("[" + i + "] " + ii[i]); + } } - break; - - case 'J': // int - long[] jj = (long[]) what; - for (int i = 0; i < jj.length; i++) { - System.out.println("[" + i + "] " + jj[i]); + case 'J' -> { // int + long[] jj = (long[]) what; + for (int i = 0; i < jj.length; i++) { + System.out.println("[" + i + "] " + jj[i]); + } } - break; - - case 'F': // float - float[] ff = (float[]) what; - for (int i = 0; i < ff.length; i++) { - System.out.println("[" + i + "] " + ff[i]); + case 'F' -> { // float + float[] ff = (float[]) what; + for (int i = 0; i < ff.length; i++) { + System.out.println("[" + i + "] " + ff[i]); + } } - break; - - case 'D': // double - double[] dd = (double[]) what; - for (int i = 0; i < dd.length; i++) { - System.out.println("[" + i + "] " + dd[i]); + case 'D' -> { // double + double[] dd = (double[]) what; + for (int i = 0; i < dd.length; i++) { + System.out.println("[" + i + "] " + dd[i]); + } } - break; - - default: - System.out.println(what); + default -> System.out.println(what); } } else { // not an array System.out.println(what); @@ -4231,7 +4058,7 @@ public class PApplet implements PConstants { // MATH // lots of convenience methods for math with floats. - // doubles are overkill for processing applets, and casting + // doubles are overkill for processing sketches, and casting // things all the time is annoying, thus the functions below. /** @@ -4239,7 +4066,6 @@ public class PApplet implements PConstants { * Calculates the absolute value (magnitude) of a number. The absolute * value of a number is always positive. * - * * @webref math:calculation * @webBrief Calculates the absolute value (magnitude) of a number * @param n number to compute @@ -4639,7 +4465,7 @@ public class PApplet implements PConstants { * returned as a float in the range from PI to -PI. * The atan2() function is most often used for orienting geometry to * the position of the cursor. Note: The y-coordinate of the point is the - * first parameter and the x-coordinate is the second due the the structure + * first parameter and the x-coordinate is the second due the structure * of calculating the tangent. * * @webref math:trigonometry @@ -4809,7 +4635,7 @@ public class PApplet implements PConstants { * Normalizes a number from another range into a value between 0 and 1. * Identical to map(value, low, high, 0, 1).
        *
        - * Numbers outside of the range are not clamped to 0 and 1, because + * Numbers outside the range are not clamped to 0 and 1, because * out-of-range values are often intentional and useful. (See the second * example above.) * @@ -4837,9 +4663,9 @@ public class PApplet implements PConstants { * range of 0 to 100 into a value that ranges from the left edge of the window * (0) to the right edge (width).
        *
        - * As shown in the second example, numbers outside of the range are not - * clamped to the minimum and maximum parameters values, because out-of-range - * values are often intentional and useful. + * As shown in the second example, numbers outside the range are + * not clamped to the minimum and maximum parameters values, + * because out-of-range values are often intentional and useful. * * @webref math:calculation * @webBrief Re-maps a number from one range to another @@ -5087,7 +4913,6 @@ public class PApplet implements PConstants { * Processing. For clarification, it's an implementation of "classic Perlin * noise" from 1983, and not the newer "simplex noise" method from 2001. * - * * @webref math:random * @webBrief Returns the Perlin noise value at specified coordinates * @param x @@ -5189,8 +5014,8 @@ public class PApplet implements PConstants { *
        * By default, noise is computed over 4 octaves with each octave contributing * exactly half than its predecessor, starting at 50% strength for the first - * octave. This falloff amount can be changed by adding an additional function - * parameter. For example, a falloff factor of 0.75 means each octave will now + * octave. This falloff amount can be changed by adding a function parameter. + * For example, a falloff factor of 0.75 means each octave will now * have 75% impact (25% less) of the previous lower octave. While any number * between 0.0 and 1.0 is valid, note that values greater than 0.5 may result * in noise() returning values greater than 1.0.
        @@ -5281,7 +5106,6 @@ public class PApplet implements PConstants { * redirect to a password prompt, because loadImage() will attempt to * interpret the HTML as image data. * - * * @webref image:loading & displaying * @webBrief Loads an image into a variable of type PImage * @param filename @@ -5308,6 +5132,11 @@ public class PApplet implements PConstants { g.awaitAsyncSaveCompletion(filename); } + // Hack so that calling loadImage() in settings() will work + // https://github.com/processing/processing4/issues/299 + if (surface == null) { + return ShimAWT.loadImage(this, filename, extension); + } return surface.loadImage(filename, extension); } @@ -5336,7 +5165,6 @@ public class PApplet implements PConstants { * where the image filename does not end with a proper extension. Specify the * extension as the second parameter to requestImage(). * - * * @webref image:loading & displaying * @webBrief Loads images on a separate thread so that your sketch does not * freeze while images load during setup() @@ -5738,7 +5566,7 @@ public class PApplet implements PConstants { /** - * Reads the contents of a file or URL and creates an Table object with its + * Reads the contents of a file or URL and creates a Table object with its * values. If a file is specified, it must be located in the sketch's "data" * folder. The filename parameter can also be a URL to a file found online. * The filename must either end in an extension or an extension must be @@ -5778,12 +5606,12 @@ public class PApplet implements PConstants { /** * Options may contain "header", "tsv", "csv", or "bin" separated by commas. - * + *

        * Another option is "dictionary=filename.tsv", which allows users to * specify a "dictionary" file that contains a mapping of the column titles * and the data types used in the table file. This can be far more efficient * (in terms of speed and memory usage) for loading and parsing tables. The - * dictionary file can only be tab separated values (.tsv) and its extension + * dictionary file can only be tab-separated values (.tsv) and its extension * will be ignored. This option was added in Processing 2.0.2. * * @param options may contain "header", "tsv", "csv", or "bin" separated by commas @@ -6053,21 +5881,6 @@ public class PApplet implements PConstants { } - /* - static public void selectInput(String prompt, String callbackMethod, - File file, Object callbackObject, Frame parent, - PApplet sketch) { - selectImpl(prompt, callbackMethod, file, callbackObject, parent, FileDialog.LOAD, sketch); - } - - - static public void selectInput(String prompt, String callbackMethod, - File file, Object callbackObject, Frame parent) { - selectImpl(prompt, callbackMethod, file, callbackObject, parent, FileDialog.LOAD, null); - } - */ - - /** * Opens a platform-specific file chooser dialog to select a file for output. * After the selection is made, the selected File will be passed to the @@ -6097,77 +5910,6 @@ public class PApplet implements PConstants { } - /* - static public void selectOutput(String prompt, String callbackMethod, - File file, Object callbackObject, Frame parent) { - selectImpl(prompt, callbackMethod, file, callbackObject, parent, FileDialog.SAVE, null); - } - - - static public void selectOutput(String prompt, String callbackMethod, - File file, Object callbackObject, Frame parent, - PApplet sketch) { - selectImpl(prompt, callbackMethod, file, callbackObject, parent, FileDialog.SAVE, sketch); - } - - - // Will remove the 'sketch' parameter once we get an upstream JOGL fix - // https://github.com/processing/processing/issues/3831 - static protected void selectImpl(final String prompt, - final String callbackMethod, - final File defaultSelection, - final Object callbackObject, - final Frame parentFrame, - final int mode, - final PApplet sketch) { - EventQueue.invokeLater(new Runnable() { - public void run() { - File selectedFile = null; - - boolean hide = (sketch != null) && - (sketch.g instanceof PGraphicsOpenGL) && (platform == WINDOWS); - if (hide) sketch.surface.setVisible(false); - - if (useNativeSelect) { - FileDialog dialog = new FileDialog(parentFrame, prompt, mode); - if (defaultSelection != null) { - dialog.setDirectory(defaultSelection.getParent()); - dialog.setFile(defaultSelection.getName()); - } - - dialog.setVisible(true); - String directory = dialog.getDirectory(); - String filename = dialog.getFile(); - if (filename != null) { - selectedFile = new File(directory, filename); - } - - } else { - JFileChooser chooser = new JFileChooser(); - chooser.setDialogTitle(prompt); - if (defaultSelection != null) { - chooser.setSelectedFile(defaultSelection); - } - - int result = -1; - if (mode == FileDialog.SAVE) { - result = chooser.showSaveDialog(parentFrame); - } else if (mode == FileDialog.LOAD) { - result = chooser.showOpenDialog(parentFrame); - } - if (result == JFileChooser.APPROVE_OPTION) { - selectedFile = chooser.getSelectedFile(); - } - } - - if (hide) sketch.surface.setVisible(true); - selectCallback(selectedFile, callbackMethod, callbackObject); - } - }); - } - */ - - /** * Opens a platform-specific file chooser dialog to select a folder. * After the selection is made, the selection will be passed to the @@ -6198,68 +5940,6 @@ public class PApplet implements PConstants { } - /* - static public void selectFolder(final String prompt, - final String callbackMethod, - final File defaultSelection, - final Object callbackObject, - final Frame parentFrame) { - selectFolder(prompt, callbackMethod, defaultSelection, callbackObject, parentFrame, null); - } - - - // Will remove the 'sketch' parameter once we get an upstream JOGL fix - // https://github.com/processing/processing/issues/3831 - static public void selectFolder(final String prompt, - final String callbackMethod, - final File defaultSelection, - final Object callbackObject, - final Frame parentFrame, - final PApplet sketch) { - EventQueue.invokeLater(new Runnable() { - public void run() { - File selectedFile = null; - - boolean hide = (sketch != null) && - (sketch.g instanceof PGraphicsOpenGL) && (platform == WINDOWS); - if (hide) sketch.surface.setVisible(false); - - if (platform == MACOS && useNativeSelect != false) { - FileDialog fileDialog = - new FileDialog(parentFrame, prompt, FileDialog.LOAD); - if (defaultSelection != null) { - fileDialog.setDirectory(defaultSelection.getAbsolutePath()); - } - System.setProperty("apple.awt.fileDialogForDirectories", "true"); - fileDialog.setVisible(true); - System.setProperty("apple.awt.fileDialogForDirectories", "false"); - String filename = fileDialog.getFile(); - if (filename != null) { - selectedFile = new File(fileDialog.getDirectory(), fileDialog.getFile()); - } - } else { - checkLookAndFeel(); - JFileChooser fileChooser = new JFileChooser(); - fileChooser.setDialogTitle(prompt); - fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - if (defaultSelection != null) { - fileChooser.setCurrentDirectory(defaultSelection); - } - - int result = fileChooser.showOpenDialog(parentFrame); - if (result == JFileChooser.APPROVE_OPTION) { - selectedFile = fileChooser.getSelectedFile(); - } - } - - if (hide) sketch.surface.setVisible(true); - selectCallback(selectedFile, callbackMethod, callbackObject); - } - }); - } - */ - - static public void selectCallback(File selectedFile, String callbackMethod, Object callbackObject) { @@ -6374,6 +6054,16 @@ public class PApplet implements PConstants { } + static private boolean listFilesExt(String name, String[] extensions) { + for (String ext : extensions) { + if (name.toLowerCase().endsWith(ext)) { + return true; + } + } + return false; + } + + static void listFilesImpl(File folder, boolean recursive, String[] extensions, boolean hidden, boolean directories, boolean files, @@ -6390,17 +6080,13 @@ public class PApplet implements PConstants { listFilesImpl(item, recursive, extensions, hidden, directories, files, list); } if (directories) { - list.add(item); + if (extensions == null || listFilesExt(item.getName(), extensions)) { + list.add(item); + } } } else if (files) { - if (extensions == null) { + if (extensions == null || listFilesExt(item.getName(), extensions)) { list.add(item); - } else { - for (String ext : extensions) { - if (item.getName().toLowerCase().endsWith(ext)) { - list.add(item); - } - } } } } @@ -6496,7 +6182,7 @@ public class PApplet implements PConstants { /** * @nowebref * I want to read lines from a stream. If I have to type the - * following lines any more I'm gonna send Sun my medical bills. + * following lines anymore I'm gonna send Sun my medical bills. */ static public BufferedReader createReader(InputStream input) { InputStreamReader isr = @@ -6530,7 +6216,6 @@ public class PApplet implements PConstants { * encoding for your platform was used, which causes problems when files * are moved to other platforms. * - * * @webref output:files * @webBrief Creates a new file in the sketch folder, and a PrintWriter object * to write to it @@ -6606,7 +6291,7 @@ public class PApplet implements PConstants { * will be printed to the console. This helps prevent issues that appear * when a sketch is exported to the web, where case sensitivity matters, as * opposed to running from inside the Processing Development Environment on - * Windows or Mac OS, where case sensitivity is preserved but ignored.
        + * Windows or macOS, where case sensitivity is preserved but ignored.
        *
        * If the file ends with .gz, the stream will automatically be gzip * decompressed. If you don't want the automatic decompression, use the @@ -6625,7 +6310,7 @@ public class PApplet implements PConstants { * methods to take more control of how the stream is read. *

        * If the requested item doesn't exist, null is returned. - * (Prior to 0096, die() would be called, killing the applet) + * (Prior to 0096, die() would be called, killing the sketch) *

        * For 0096+, the "data" folder is exported intact with subfolders, * and openStream() properly handles subdirectories from the data folder @@ -6633,7 +6318,7 @@ public class PApplet implements PConstants { * If not online, this will also check to see if the user is asking * for a file whose name isn't properly capitalized. This helps prevent * issues when a sketch is exported to the web, where case sensitivity - * matters, as opposed to Windows and the Mac OS default where + * matters, as opposed to Windows and the macOS default where * case sensitivity is preserved but ignored. *

        * It is strongly recommended that libraries use this method to open @@ -6699,8 +6384,7 @@ public class PApplet implements PConstants { URL url = new URL(filename); URLConnection conn = url.openConnection(); - if (conn instanceof HttpURLConnection) { - HttpURLConnection httpConn = (HttpURLConnection) conn; + if (conn instanceof HttpURLConnection httpConn) { // Will not handle a protocol change (see below) httpConn.setInstanceFollowRedirects(true); int response = httpConn.getResponseCode(); @@ -6715,11 +6399,11 @@ public class PApplet implements PConstants { return url.openStream(); } } catch (MalformedURLException mfue) { - // not a url, that's fine + // not a URL, that's fine } catch (FileNotFoundException fnfe) { // Added in 0119 b/c Java 1.5 throws FNFE when URL not available. - // http://dev.processing.org/bugs/show_bug.cgi?id=403 + // https://download.processing.org/bugzilla/403.html } catch (IOException e) { // changed for 0117, shouldn't be throwing exception @@ -6734,7 +6418,7 @@ public class PApplet implements PConstants { // Moved this earlier than the getResourceAsStream() checks, because // calling getResourceAsStream() on a directory lists its contents. - // http://dev.processing.org/bugs/show_bug.cgi?id=716 + // https://download.processing.org/bugzilla/716.html try { // First see if it's in a data folder. This may fail by throwing // a SecurityException. If so, this whole block will be skipped. @@ -6788,7 +6472,7 @@ public class PApplet implements PConstants { // this is an irritation of sun's java plug-in, which will return // a non-null stream for an object that doesn't exist. like all good // things, this is probably introduced in java 1.5. awesome! - // http://dev.processing.org/bugs/show_bug.cgi?id=359 + // https://download.processing.org/bugzilla/359.html if (!cn.equals("sun.plugin.cache.EmptyInputStream")) { return stream; } @@ -6796,7 +6480,7 @@ public class PApplet implements PConstants { // When used with an online script, also need to check without the // data folder, in case it's not in a subfolder called 'data'. - // http://dev.processing.org/bugs/show_bug.cgi?id=389 + // https://download.processing.org/bugzilla/389.html stream = cl.getResourceAsStream(filename); if (stream != null) { String cn = stream.getClass().getName(); @@ -6806,8 +6490,7 @@ public class PApplet implements PConstants { } try { - // attempt to load from a local file, used when running as - // an application, or as a signed applet + // attempt to load from a local file try { // first try to catch any security exceptions try { return new FileInputStream(dataPath(filename)); @@ -6900,8 +6583,7 @@ public class PApplet implements PConstants { URLConnection conn = url.openConnection(); int length = -1; - if (conn instanceof HttpURLConnection) { - HttpURLConnection httpConn = (HttpURLConnection) conn; + if (conn instanceof HttpURLConnection httpConn) { // Will not handle a protocol change (see below) httpConn.setInstanceFollowRedirects(true); int response = httpConn.getResponseCode(); @@ -6938,7 +6620,7 @@ public class PApplet implements PConstants { } catch (FileNotFoundException fnfe) { // Java 1.5+ throws FNFE when URL not available - // http://dev.processing.org/bugs/show_bug.cgi?id=403 + // https://download.processing.org/bugzilla/403.html } catch (IOException e) { printStackTrace(e); @@ -7098,7 +6780,6 @@ public class PApplet implements PConstants { * encoding for your platform was used, which causes problems when files are * moved to other platforms. * - * *

        Advanced

        Load data from a file and shove it into a String array. *

        * Exceptions are handled internally, when an error, occurs, an exception is @@ -7106,8 +6787,8 @@ public class PApplet implements PConstants { * running. This is a tradeoff between 1) showing the user that there was a * problem but 2) not requiring that all i/o code is contained in try/catch * blocks, for the sake of new users (or people who are just trying to get - * things done in a "scripting" fashion. If you want to handle exceptions, use - * Java methods for I/O. + * things done in an informal "scripting" fashion). If you want to handle + * exceptions, use Java methods for I/O. * * @webref input:files * @webBrief Reads the contents of a file or url and creates a String array of @@ -7245,7 +6926,6 @@ public class PApplet implements PConstants { * that, unlike some other functions, this will not automatically compress or * uncompress gzip files.) * - * * @webref output:files * @webBrief Save the contents of a stream to a file in the sketch folder * @param target @@ -7339,7 +7019,6 @@ public class PApplet implements PConstants { * any location on the computer by using an absolute path (something that * starts with / on Unix and Linux, or a drive letter on Windows). * - * * @webref output:files * @webBrief Opposite of loadBytes(), will write an entire array of * bytes to a file @@ -7548,10 +7227,6 @@ public class PApplet implements PConstants { * passed in. External libraries should use this function to save to * the sketch folder. *

        - * Note that when running as an applet inside a web browser, - * the sketchPath will be set to null, because security restrictions - * prevent applets from accessing that information. - *

        * This will also cause an error if the sketch is not inited properly, * meaning that init() was never called on the PApplet when hosted * my some other main() or by other code. For proper use of init(), @@ -7581,12 +7256,12 @@ public class PApplet implements PConstants { /** - * Returns a path inside the applet folder to save to. Like sketchPath(), + * Returns a path adjacent the application to save to. Like sketchPath(), * but creates any in-between folders so that things save properly. *

        * All saveXxxx() functions use the path to the sketch folder, rather than * its data folder. Once exported, the data folder will be found inside the - * jar file of the exported application or applet. In this case, it's not + * jar file of the exported application. In this case, it's not * possible to save data into the jar file, because it will often be running * from a server, or marked in-use if running from a local file system. * With this in mind, saving to the data path doesn't make sense anyway. @@ -7857,7 +7532,7 @@ public class PApplet implements PConstants { * individually. This function only copies references, which means that for * most purposes it only copies one-dimensional arrays (a single set of * brackets). If used with a two (or three or more) dimensional array, it will - * only copy the references at the first level, because a two dimensional + * only copy the references at the first level, because a two-dimensional * array is simply an "array of arrays". This does not produce an error, * however, because this is often the desired behavior. Internally, this * function calls Java's newSize parameter - * provides precise control over the increase in size.
        - *
        + * provides precise control over the increase in size. + *

        * When using an array of objects, the data returned from the function must be * cast to the object array's data type. For example: SomeClass[] items = * (SomeClass[]) expand(originalArray) * - * * @webref data:array functions * @webBrief Increases the size of an array * @param list @@ -8065,7 +7739,6 @@ public class PApplet implements PConstants { * be cast to the object array's data type. For example: SomeClass[] * items = (SomeClass[]) append(originalArray, element). * - * * @webref data:array functions * @webBrief Expands an array by one element and adds data to the new position * @param array array to append @@ -8119,7 +7792,6 @@ public class PApplet implements PConstants { * be cast to the object array's data type. For example: SomeClass[] * items = (SomeClass[]) shorten(originalArray). * - * * @webref data:array functions * @webBrief Decreases an array by one element and returns the shortened array * @param list array to shorten @@ -8899,12 +8571,12 @@ public class PApplet implements PConstants { /** * - * This function is used to apply a regular expression to a piece of text, and - * return a list of matching groups (elements found inside parentheses) as a - * two-dimensional String array. If there are no matches, a null value will be - * returned. If no groups are specified in the regular expression, but the - * sequence matches, a two dimensional array is still returned, but the second - * dimension is only of length one.
        + * This function is used to apply a regular expression to a piece of text, + * and return a list of matching groups (elements found inside parentheses) + * as a two-dimensional String array. If there are no matches, a null + * value will be returned. If no groups are specified in the regular + * expression, but the sequence matches, a two-dimensional array is still + * returned, but the second dimension is only of length one.
        *
        * To use the function, first check to see if the result is null. If the * result is null, then the sequence did not match at all. If the sequence did @@ -9125,7 +8797,10 @@ public class PApplet implements PConstants { /** * Parse a String to an int, and provide an alternate value that - * should be used when the number is invalid. + * should be used when the number is invalid. If there's a decimal place, + * it will be truncated, making this more of a toInt() than parseInt() + * function. This is because the method is used internally for casting. + * Not ideal, but the name was chosen before that clarification was made. */ static final public int parseInt(String what, int otherwise) { try { @@ -9177,11 +8852,11 @@ public class PApplet implements PConstants { /** * Make an array of int elements from an array of String objects. * If the String can't be parsed as a number, it will be set to zero. - * + *

            * String s[] = { "1", "300", "44" };
            * int numbers[] = parseInt(s);
        -   *
        -   * numbers will contain { 1, 300, 44 }
        +   * // numbers will contain { 1, 300, 44 }
        +   * 
        */ static public int[] parseInt(String[] what) { return parseInt(what, 0); @@ -9191,11 +8866,11 @@ public class PApplet implements PConstants { * Make an array of int elements from an array of String objects. * If the String can't be parsed as a number, its entry in the * array will be set to the value of the "missing" parameter. - * + *
            * String s[] = { "1", "300", "apple", "44" };
            * int numbers[] = parseInt(s, 9999);
        -   *
        -   * numbers will contain { 1, 300, 9999, 44 }
        +   * // numbers will contain { 1, 300, 9999, 44 }
        +   * 
        */ static public int[] parseInt(String[] what, int missing) { int[] output = new int[what.length]; @@ -9267,55 +8942,55 @@ public class PApplet implements PConstants { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - static final public String str(boolean x) { - return String.valueOf(x); + static final public String str(boolean value) { + return String.valueOf(value); } - static final public String str(byte x) { - return String.valueOf(x); + static final public String str(byte value) { + return String.valueOf(value); } - static final public String str(char x) { - return String.valueOf(x); + static final public String str(char value) { + return String.valueOf(value); } - static final public String str(int x) { - return String.valueOf(x); + static final public String str(int value) { + return String.valueOf(value); } - static final public String str(float x) { - return String.valueOf(x); + static final public String str(float value) { + return String.valueOf(value); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - static final public String[] str(boolean[] x) { - String[] s = new String[x.length]; - for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); + static final public String[] str(boolean[] values) { + String[] s = new String[values.length]; + for (int i = 0; i < values.length; i++) s[i] = String.valueOf(values[i]); return s; } - static final public String[] str(byte[] x) { - String[] s = new String[x.length]; - for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); + static final public String[] str(byte[] values) { + String[] s = new String[values.length]; + for (int i = 0; i < values.length; i++) s[i] = String.valueOf(values[i]); return s; } - static final public String[] str(char[] x) { - String[] s = new String[x.length]; - for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); + static final public String[] str(char[] values) { + String[] s = new String[values.length]; + for (int i = 0; i < values.length; i++) s[i] = String.valueOf(values[i]); return s; } - static final public String[] str(int[] x) { - String[] s = new String[x.length]; - for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); + static final public String[] str(int[] values) { + String[] s = new String[values.length]; + for (int i = 0; i < values.length; i++) s[i] = String.valueOf(values[i]); return s; } - static final public String[] str(float[] x) { - String[] s = new String[x.length]; - for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); + static final public String[] str(float[] values) { + String[] s = new String[values.length]; + for (int i = 0; i < values.length; i++) s[i] = String.valueOf(values[i]); return s; } @@ -9454,7 +9129,7 @@ public class PApplet implements PConstants { /** * Utility function for formatting numbers into strings. Similar to - * nf() but leaves a blank space in front of positive numbers so + * 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 * two versions, one for formatting floats and one for formatting ints. The * values for the digits, left, and right parameters @@ -9690,7 +9365,6 @@ public class PApplet implements PConstants { * Converts a String representation of a hexadecimal number to its * equivalent integer value. * - * * @webref data:conversion * @webBrief Converts a String representation of a hexadecimal number to its * equivalent integer value @@ -9958,7 +9632,6 @@ public class PApplet implements PConstants { * because otherwise numbers outside the range will produce strange and * unexpected colors. * - * * @webref color:creating & reading * @webBrief Calculates a color or colors between two colors at a specific * increment @@ -9991,6 +9664,7 @@ public class PApplet implements PConstants { ////////////////////////////////////////////////////////////// + /* public void frameMoved(int x, int y) { if (!fullScreen) { System.err.println(EXTERNAL_MOVE + " " + x + " " + y); @@ -10001,6 +9675,112 @@ public class PApplet implements PConstants { public void frameResized(int w, int h) { } + */ + + + ////////////////////////////////////////////////////////////// + + // WINDOW METHODS + + Map windowEventQueue = new ConcurrentHashMap<>(); + + + public void windowTitle(String title) { + surface.setTitle(title); + } + + + public void windowResize(int newWidth, int newHeight) { + surface.setSize(newWidth, newHeight); + } + + + /** + * Internal use only: called by Surface objects to queue a resize + * event to call windowResized() when it's safe, which is after + * the beginDraw() call and before the draw(). Note that this is + * only the notification that the resize has happened. + */ + public void postWindowResized(int newWidth, int newHeight) { + windowEventQueue.put("w", newWidth); + windowEventQueue.put("h", newHeight); + } + + + /** Called when window is resized. */ + public void windowResized() { } + + + public void windowResizable(boolean resizable) { + surface.setResizable(resizable); + } + + + public void windowMove(int x, int y) { + surface.setLocation(x, y); + } + + + /** + * When running from the PDE, this saves the window position for + * next time the sketch is run. Needs to remain a separate method + * so that it can be overridden by Python Mode. + */ + public void frameMoved(int newX, int newY) { + System.err.println(EXTERNAL_MOVE + " " + newX + " " + newY); + System.err.flush(); // doesn't seem to help or hurt + } + + + /** + * Internal use only: called by Surface objects to queue a position + * event to call windowPositioned() when it's safe, which is after + * the beginDraw() call and before the draw(). Note that this is + * only the notification that the window is in a new position. + */ + public void postWindowMoved(int newX, int newY) { + if (external && !fullScreen) { + frameMoved(newX, newY); + } + + windowEventQueue.put("x", newX); + windowEventQueue.put("y", newY); + } + + + /** Called when the window is moved */ + public void windowMoved() { } + + + private void dequeueWindowEvents() { + if (windowEventQueue.containsKey("x")) { + windowX = windowEventQueue.remove("x"); + windowY = windowEventQueue.remove("y"); + windowMoved(); + } + if (windowEventQueue.containsKey("w")) { + // these should already match width/height + //windowResized(windowEventQueue.remove("w"), + // windowEventQueue.remove("h")); + windowEventQueue.remove("w"); + windowEventQueue.remove("h"); + windowResized(); + } + } + + + /** + * Scale the sketch as if it fits this specific width and height. + * This will also scale the mouseX and mouseY variables (as well as + * pmouseX and pmouseY). Note that it will not have an effect on + * MouseEvent objects (i.e. event.getX() and event.getY()) because + * their exact behavior may interact strangely with other libraries. + */ + public void windowRatio(int wide, int high) { + rwidth = wide; + rheight = high; + windowRatio = true; + } ////////////////////////////////////////////////////////////// @@ -10031,13 +9811,13 @@ public class PApplet implements PConstants { *
            * Parameters useful for launching or also used by the PDE:
            *
        -   * --location=x,y         Upper left-hand corner of where the applet
        +   * --location=x,y         Upper left-hand corner of where the sketch
            *                        should appear on screen. If not used,
            *                        the default is to center on the main screen.
            *
            * --present              Presentation mode: blanks the entire screen and
            *                        shows the sketch by itself. If the sketch is
        -   *                        smaller than the screen, the background around it
        +   *                        smaller than the screen, the surrounding area
            *                        will use the --window-color setting.
            *
            * --hide-stop            Use to hide the stop button in situations where
        @@ -10045,7 +9825,7 @@ public class PApplet implements PConstants {
            *                        see the FAQ on information for capturing the ESC
            *                        key when running in presentation mode.
            *
        -   * --stop-color=#xxxxxx   Color of the 'stop' text used to quit an
        +   * --stop-color=#xxxxxx   Color of the 'stop' text used to quit a
            *                        sketch when it's in present mode.
            *
            * --window-color=#xxxxxx Background color of the window. The color used
        @@ -10068,10 +9848,10 @@ public class PApplet implements PConstants {
            *
            * Parameters used by Processing when running via the PDE
            *
        -   * --external             set when the applet is being used by the PDE
        +   * --external             set when the sketch is being used by the PDE
            *
            * --editor-location=x,y  position of the upper left-hand corner of the
        -   *                        editor window, for placement of applet window
        +   *                        editor window, for placement of sketch window
            *
            * All parameters *after* the sketch class name are passed to the sketch
            * itself and available from its 'args' array while the sketch is running.
        @@ -10172,89 +9952,125 @@ public class PApplet implements PConstants {
             boolean hideStop = false;
         
             int displayNum = -1;  // use default
        -//    boolean fullScreen = false;
             boolean present = false;
        -//    boolean spanDisplays = false;
        -    int density = -1;
        +    boolean fullScreen = false;
        +    float uiScale = 0;
         
             String param, value;
             String folder = calcSketchPath();
         
             int argIndex = 0;
        +    label:
             while (argIndex < args.length) {
               int equals = args[argIndex].indexOf('=');
               if (equals != -1) {
                 param = args[argIndex].substring(0, equals);
                 value = args[argIndex].substring(equals + 1);
         
        -        if (param.equals(ARGS_EDITOR_LOCATION)) {
        -          external = true;
        -          editorLocation = parseInt(split(value, ','));
        +        switch (param) {
        +          case ARGS_EDITOR_LOCATION:
        +            external = true;
        +            editorLocation = parseInt(split(value, ','));
        +            break;
         
        -        } else if (param.equals(ARGS_DISPLAY)) {
        -          displayNum = parseInt(value, -2);
        -          if (displayNum == -2) {
        -            // this means the display value couldn't be parsed properly
        -            System.err.println(value + " is not a valid choice for " + ARGS_DISPLAY);
        -            displayNum = -1;  // use the default
        -          }
        +          case ARGS_DISPLAY:
        +            displayNum = parseInt(value, -2);
        +            if (displayNum == -2) {
        +              // this means the display value couldn't be parsed properly
        +              System.err.println(value + " is not a valid choice for " + ARGS_DISPLAY);
        +              displayNum = -1;  // use the default
        +            }
        +            break;
         
        -        } else if (param.equals(ARGS_DISABLE_AWT)) {
        -          disableAWT = true;
        +          case ARGS_DISABLE_AWT:
        +            disableAWT = true;
        +            break;
         
        -        } else if (param.equals(ARGS_WINDOW_COLOR)) {
        -          if (value.charAt(0) == '#' && value.length() == 7) {
        -            value = value.substring(1);
        -            windowColor = 0xff000000 | Integer.parseInt(value, 16);
        -          } else {
        -            System.err.println(ARGS_WINDOW_COLOR + " should be a # followed by six digits");
        -          }
        +          case ARGS_WINDOW_COLOR:
        +            if (value.charAt(0) == '#' && value.length() == 7) {
        +              value = value.substring(1);
        +              windowColor = 0xff000000 | Integer.parseInt(value, 16);
        +            } else {
        +              System.err.println(ARGS_WINDOW_COLOR + " should be a # followed by six digits");
        +            }
        +            break;
         
        -        } else if (param.equals(ARGS_STOP_COLOR)) {
        -          if (value.charAt(0) == '#' && value.length() == 7) {
        -            value = value.substring(1);
        -            stopColor = 0xff000000 | Integer.parseInt(value, 16);
        -          } else {
        -            System.err.println(ARGS_STOP_COLOR + " should be a # followed by six digits");
        -          }
        +          case ARGS_STOP_COLOR:
        +            if (value.charAt(0) == '#' && value.length() == 7) {
        +              value = value.substring(1);
        +              stopColor = 0xff000000 | Integer.parseInt(value, 16);
        +            } else {
        +              System.err.println(ARGS_STOP_COLOR + " should be a # followed by six digits");
        +            }
        +            break;
         
        -        } else if (param.equals(ARGS_SKETCH_FOLDER)) {
        -          folder = value;
        +          case ARGS_SKETCH_FOLDER:
        +            folder = value;
        +            break;
         
        -        } else if (param.equals(ARGS_LOCATION)) {
        -          location = parseInt(split(value, ','));
        +          case ARGS_LOCATION:
        +            location = parseInt(split(value, ','));
        +            break;
         
        -        } else if (param.equals(ARGS_DENSITY)) {
        -          density = parseInt(value, -1);
        -          if (density == -1) {
        -            System.err.println("Could not parse " + value + " for " + ARGS_DENSITY);
        -          } else if (density != 1 && density != 2) {
        -            density = -1;
        -            System.err.println(ARGS_DENSITY + " should be 1 or 2");
        -          }
        +          case ARGS_UI_SCALE:
        +            uiScale = parseFloat(value, 0);
        +            if (uiScale == 0) {
        +              System.err.println("Could not parse " + value + " for " + ARGS_UI_SCALE);
        +            }
        +            break;
                 }
        -
               } else {
        -        if (args[argIndex].equals(ARGS_PRESENT)) {
        -          present = true;
        +        switch (args[argIndex]) {
        +          case ARGS_PRESENT:
        +            present = true;
        +            break;
         
        -//        } else if (args[argIndex].equals(ARGS_SPAN_DISPLAYS)) {
        -//          spanDisplays = true;
        +          case ARGS_HIDE_STOP:
        +            hideStop = true;
        +            break;
         
        -        } else if (args[argIndex].equals(ARGS_HIDE_STOP)) {
        -          hideStop = true;
        +          case ARGS_EXTERNAL:
        +            external = true;
        +            break;
         
        -        } else if (args[argIndex].equals(ARGS_EXTERNAL)) {
        -          external = true;
        +          case ARGS_FULL_SCREEN:
        +            fullScreen = true;
        +            break;
         
        -        } else {
        -          name = args[argIndex];
        -          break;  // because of break, argIndex won't increment again
        +          default:
        +            name = args[argIndex];
        +            break label;  // because of break, argIndex won't increment again
                 }
               }
               argIndex++;
             }
         
        +    if (platform == WINDOWS) {
        +      // Set DPI scaling to either 1 or 2, but avoid fractional
        +      // settings such as 125% and 250% that make things look gross.
        +      // Also applies to 300% since that is not even a thing.
        +
        +      // no longer possible to set prop after this line initializes AWT
        +      //int dpi = java.awt.Toolkit.getDefaultToolkit().getScreenResolution();
        +
        +      // Attempt to get the resolution using a helper app. This code is
        +      // fairly conservative: if there is trouble, we go with the default.
        +      if (uiScale == 0) {
        +        int dpi = getWindowsDPI();
        +        if (dpi != 0) {
        +          //uiScale = constrain(dpi / 96, 1, 2);
        +          // If larger than 150% set scale to 2. Using scale 1 at 175% feels
        +          // reeaally small. 150% is more of a tossup; it could also use 2.
        +          uiScale = (dpi > 144) ? 2 : 1;
        +        }
        +      }
        +      if (uiScale != 0) {
        +        System.setProperty("sun.java2d.uiScale", String.valueOf(uiScale));
        +      //} else {
        +        //System.err.println("Could not identify Windows DPI, not setting sun.java2d.uiScale");
        +      }
        +    }
        +
             if (!disableAWT) {
               ShimAWT.initRun();
             }
        @@ -10277,7 +10093,7 @@ public class PApplet implements PConstants {
             }
         
             // TODO When disabling AWT for LWJGL or others, we need to figure out
        -    // how to make Cmd-Q and the rest of this still work properly.
        +    //      how to make Cmd-Q and the rest of this still work properly.
             if (platform == MACOS && !disableAWT) {
               try {
                 final String td = "processing.core.ThinkDifferent";
        @@ -10295,19 +10111,12 @@ public class PApplet implements PConstants {
             // (and most likely, from the PDE's preference setting).
             sketch.display = displayNum;
         
        -    // Set the suggested density that is coming from command line
        -    // (most likely set from the PDE based on a system DPI scaling)
        -    sketch.suggestedDensity = density;
        -
             sketch.present = present;
        +    sketch.fullScreen = fullScreen;
         
             // For 3.0.1, moved this above handleSettings() so that loadImage() can be
             // used inside settings(). Sets a terrible precedent, but the alternative
             // of not being able to size a sketch to an image is driving people loopy.
        -    // A handful of things that need to be set before init/start.
        -//    if (folder == null) {
        -//      folder = calcSketchPath();
        -//    }
             sketch.sketchPath = folder;
         
             // Don't set 'args' to a zero-length array if it should be null [3.0a8]
        @@ -10337,11 +10146,13 @@ public class PApplet implements PConstants {
               surface.placeWindow(location, editorLocation);
             }
         
        +    /*
             // not always running externally when in present mode
             // moved above setVisible() in 3.0 alpha 11
             if (sketch.external) {
               surface.setupExternalMessages();
             }
        +    */
         
             sketch.showSurface();
             sketch.startSurface();
        @@ -10458,17 +10269,63 @@ public class PApplet implements PConstants {
         
           /** Convenience method, should only be called by PSurface subclasses. */
           static public void hideMenuBar() {
        -    if (PApplet.platform == PConstants.MACOS) {
        -      // Call some native code to remove the menu bar on OS X. Not necessary
        +    if (platform == MACOS) {
        +      // Call some native code to remove the menu bar on macOS. Not necessary
               // on Linux and Windows, who are happy to make full screen windows.
        -      japplemenubar.JAppleMenuBar.hide();
        +      try {
        +        final String td = "processing.core.ThinkDifferent";
        +        final Class thinkDifferent = PApplet.class.getClassLoader().loadClass(td);
        +        thinkDifferent.getMethod("hideMenuBar").invoke(null);
        +      } catch (Exception e) {
        +        e.printStackTrace();
        +      }
             }
           }
         
         
        +  /**
        +   * Find the location of fenster.exe by walking through java.library.path.
        +   * (It will be on the path because it's part of core/library/windows-amd64)
        +   */
        +  static private String findFenster() {
        +    String libraryPath = System.getProperty("java.library.path");
        +    // Should not be null, but cannot assume
        +    if (libraryPath != null) {
        +      String[] folders = split(libraryPath, ';');
        +      // Usually, the most relevant paths will be at the front of the list,
        +      // so hopefully this will not walk several entries.
        +      for (String folder : folders) {
        +        File file = new File(folder, "fenster.exe");
        +        if (file.exists()) {
        +          return file.getAbsolutePath();
        +        }
        +      }
        +    }
        +    return null;
        +  }
        +
        +
        +  /**
        +   * Get the display scaling for Windows by calling out to a helper app.
        +   * https://github.com/processing/processing4/tree/master/build/windows/fenster
        +   */
        +  static private int getWindowsDPI() {
        +    String fensterPath = findFenster();
        +    if (fensterPath != null) {
        +      StringList stdout = new StringList();
        +      StringList stderr = new StringList();
        +      int result = exec(stdout, stderr, fensterPath);
        +      if (result == 0) {
        +        return parseInt(stdout.join(""), 0);
        +      }
        +    }
        +    return 0;
        +  }
        +
        +
           /**
            * Convenience method for Python Mode to run an already-constructed sketch.
        -   * This makes it makes it easy to launch a sketch in Jython:
        +   * This makes it easy to launch a sketch in Jython:
            *
            * 
        class MySketch(PApplet):
            *     pass
        @@ -10510,7 +10367,6 @@ public class PApplet implements PConstants {
            * 
        * beginRecord() works only with the PDF and SVG renderers. * - * * @webref output:files * @webBrief Opens a new file and all subsequent drawing functions are echoed * to this file as well as the display window @@ -10575,7 +10431,7 @@ public class PApplet implements PConstants { *

        * If you want a background to show up in your files, use rect(0, 0, * width, height) after setting the fill() to the background - * color. Otherwise the background will not be rendered to the file because + * color. Otherwise, the background will not be rendered to the file because * the background is not shape. *

        * Using hint(ENABLE_DEPTH_SORT) can improve the appearance of 3D @@ -10585,7 +10441,6 @@ public class PApplet implements PConstants { * See examples in the reference for the PDF and DXF * libraries for more information. * - * * @webref output:files * @webBrief To create vectors from 3D data, use the beginRaw() and * endRaw() commands @@ -10606,12 +10461,12 @@ public class PApplet implements PConstants { /** * @nowebref * Begin recording raw shape data to the specified renderer. - * + *

        * This simply echoes to g.beginRaw(), but since is placed here (rather than * generated by preproc.pl) for clarity and so that it doesn't echo the * command should beginRecord() be in use. * - * @param rawGraphics ??? + * @param rawGraphics PGraphics context that raw shapes will be written to */ public void beginRaw(PGraphics rawGraphics) { g.beginRaw(rawGraphics); @@ -10623,7 +10478,6 @@ public class PApplet implements PConstants { * Complement to beginRaw(); they must always be used together. See * the beginRaw() reference for details. * - * * @webref output:files * @webBrief Complement to beginRaw(); they must always be used together * @see PApplet#beginRaw(String, String) @@ -10785,12 +10639,12 @@ public class PApplet implements PConstants { /** * - * Sets the current normal vector. Used for drawing three dimensional shapes and - * surfaces, normal() specifies a vector perpendicular to a shape's - * surface which, in turn, determines how lighting affects it. Processing - * attempts to automatically assign normals to shapes, but since that's - * imperfect, this is a better option when you want more control. This function - * is identical to glNormal3f() in OpenGL. + * Sets the current normal vector. Used for drawing three-dimensional + * shapes and surfaces, normal() specifies a vector perpendicular + * to a shape's surface which, in turn, determines how lighting affects it. + * Processing attempts to automatically assign normals to shapes, but since + * that's imperfect, this is a better option when you want more control. + * This function is identical to glNormal3f() in OpenGL. * * @webref lights_camera:lights * @webBrief Sets the current normal vector @@ -10867,12 +10721,10 @@ public class PApplet implements PConstants { /** - * * Defines if textures repeat or draw once within a texture map. * The two parameters are CLAMP (the default behavior) and REPEAT. * This function only works with the P2D and P3D renderers. * - * * @webref image:textures * @webBrief Defines if textures repeat or draw once within a texture map * @param wrap Either CLAMP (default) or REPEAT @@ -10886,12 +10738,11 @@ public class PApplet implements PConstants { /** - * * Sets a texture to be applied to vertex points. The texture() function * must be called between beginShape() and endShape() and before * any calls to vertex(). This function only works with the P2D and P3D - * renderers.
        - *
        + * renderers. + *

        * When textures are in use, the fill color is ignored. Instead, use * tint() to specify the color of the texture as it is applied to the * shape. @@ -10992,7 +10843,7 @@ public class PApplet implements PConstants { /** * Use the beginContour() and endContour() function to * create negative shapes within shapes such as the center of the - * letter 'O'. beginContour() begins recording vertices for the + * letter "O". beginContour() begins recording vertices for the * shape and endContour() stops recording. The vertices that * define a negative shape must "wind" in the opposite direction from * the exterior shape. First draw vertices for the exterior shape in @@ -11016,7 +10867,7 @@ public class PApplet implements PConstants { /** * Use the beginContour() and endContour() function to * create negative shapes within shapes such as the center of the - * letter 'O'. beginContour() begins recording vertices for + * letter "O". beginContour() begins recording vertices for * the shape and endContour() stops recording. The vertices * that define a negative shape must "wind" in the opposite direction * from the exterior shape. First draw vertices for the exterior shape @@ -11047,7 +10898,7 @@ public class PApplet implements PConstants { * * The endShape() function is the companion to beginShape() * and may only be called after beginShape(). When endshape() - * is called, all of image data defined since the previous call to + * is called, all the image data defined since the previous call to * beginShape() is written into the image buffer. The constant CLOSE * as the value for the MODE parameter to close the shape (to connect the * beginning and the end). @@ -11194,7 +11045,6 @@ public class PApplet implements PConstants { * Applies the shader specified by the parameters. It's compatible with * the P2D and P3D renderers, but not with the default renderer. * - * * @webref rendering:shaders * @webBrief Applies the shader specified by the parameters * @param shader name of shader file @@ -11215,11 +11065,9 @@ public class PApplet implements PConstants { /** - * * Restores the default shaders. Code that runs after resetShader() * will not be affected by previously defined shaders. * - * * @webref rendering:shaders * @webBrief Restores the default shaders */ @@ -11253,7 +11101,6 @@ public class PApplet implements PConstants { * by the parameters. The boundaries are drawn based on the state * of the imageMode() function, either CORNER, CORNERS, or CENTER. * - * * @webref rendering * @webBrief Limits the rendering to the boundaries of a rectangle defined * by the parameters @@ -11269,10 +11116,8 @@ public class PApplet implements PConstants { /** - * * Disables the clipping previously started by the clip() function. * - * * @webref rendering * @webBrief Disables the clipping previously started by the clip() function */ @@ -11317,7 +11162,6 @@ public class PApplet implements PConstants { * BURN. On older hardware, the LIGHTEST, DARKEST, and DIFFERENCE modes might * not be available as well. * - * * @webref rendering * @webBrief Blends the pixels in the display window according to a defined mode * @param mode the blending mode to use @@ -11338,9 +11182,9 @@ public class PApplet implements PConstants { /** * - * Specifies vertex coordinates for Bezier curves. Each call to + * Specifies vertex coordinates for Bézier curves. Each call to * bezierVertex() defines the position of two control points and one - * anchor point of a Bezier curve, adding a new segment to a line or shape. + * anchor point of a Bézier curve, adding a new segment to a line or shape. * The first time bezierVertex() is used within a * beginShape() call, it must be prefaced with a call to * vertex() to set the first anchor point. This function must be @@ -11374,9 +11218,9 @@ public class PApplet implements PConstants { /** - * Specifies vertex coordinates for quadratic Bezier curves. Each call + * Specifies vertex coordinates for quadratic Bézier curves. Each call * to quadraticVertex() defines the position of one control - * point and one anchor point of a Bezier curve, adding a new segment + * point and one anchor point of a Bézier curve, adding a new segment * to a line or shape. The first time quadraticVertex() is used * within a beginShape() call, it must be prefaced with a call * to vertex() to set the first anchor point. This function must @@ -11415,19 +11259,17 @@ public class PApplet implements PConstants { /** - * - * Specifies vertex coordinates for curves. This function may only be used - * between beginShape() and endShape() and only when there is - * no MODE parameter specified to beginShape(). The first and last - * points in a series of curveVertex() lines will be used to guide - * the beginning and end of a the curve. A minimum of four points is - * required to draw a tiny curve between the second and third points. - * Adding a fifth point with curveVertex() will draw the curve - * between the second, third, and fourth points. The curveVertex() - * function is an implementation of Catmull-Rom splines. Using the 3D - * version requires rendering with P3D (see the Environment reference for - * more information). - * + * Specifies vertex coordinates for curves. This function may only be used + * between beginShape() and endShape() and only when there is + * no MODE parameter specified to beginShape(). The first and last + * points in a series of curveVertex() lines will be used to guide + * the beginning and end of the curve. A minimum of four points is + * required to draw a tiny curve between the second and third points. + * Adding a fifth point with curveVertex() will draw the curve + * between the second, third, and fourth points. The curveVertex() + * function is an implementation of Catmull-Rom splines. Using the 3D + * version requires rendering with P3D (see the Environment reference for + * more information). * * @webref shape:vertex * @webBrief Specifies vertex coordinates for curves @@ -11475,7 +11317,6 @@ public class PApplet implements PConstants { * setting the pixel using set() or drawing the point using either * circle() or square(). * - * * @webref shape:2d primitives * @webBrief Draws a point, a coordinate in space at the dimension of one pixel * @param x x-coordinate of the point @@ -11637,7 +11478,6 @@ public class PApplet implements PConstants { * each corner separately, starting with the top-left corner and moving * clockwise around the rectangle. * - * * @webref shape:2d primitives * @webBrief Draws a rectangle to the screen * @param a x-coordinate of the rectangle by default @@ -11684,7 +11524,6 @@ public class PApplet implements PConstants { * these parameters are interpreted, however, may be changed with the * rectMode() function. * - * * @webref shape:2d primitives * @webBrief Draws a square to the screen * @param x x-coordinate of the rectangle by default @@ -11830,10 +11669,8 @@ public class PApplet implements PConstants { /** - * - * A box is an extruded rectangle. A box with equal dimension on all sides - * is a cube. - * + * A box is an extruded rectangle. A box with equal dimension + * on all sides is a cube. * * @webref shape:3d primitives * @webBrief A box is an extruded rectangle @@ -11872,7 +11709,6 @@ public class PApplet implements PConstants { * and vertical resolution independently, use the version of the functions * with two parameters. * - * *

        Advanced

        * Code for sphereDetail() submitted by toxi [031031]. * Code for enhanced u/v version from davbol [080801]. @@ -11900,10 +11736,8 @@ public class PApplet implements PConstants { /** - * * A sphere is a hollow ball made from tessellated triangles. * - * *

        Advanced

        *

        * Implementation notes: @@ -11913,7 +11747,7 @@ public class PApplet implements PConstants { * in the center point *

        * sphere is a series of concentric circles who radii vary - * along the shape, based on, er.. cos or something + * along the shape, based on, err... cos or something *

            * [toxi 031031] new sphere code. removed all multiplies with
            * radius, as scale() will take care of that anyway
        @@ -11941,10 +11775,9 @@ public class PApplet implements PConstants {
            * Evaluates the Bezier at point t for points a, b, c, d. The parameter t
            * varies between 0 and 1, a and d are points on the curve, and b and c are
            * the control points. This can be done once with the x coordinates and a
        -   * second time with the y coordinates to get the location of a bezier curve
        +   * second time with the y coordinates to get the location of a Bézier curve
            * at t.
            *
        -   *
            * 

        Advanced

        * For instance, to convert the following example:
            * stroke(255, 102, 0);
        @@ -11983,17 +11816,15 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
        -   * Calculates the tangent of a point on a Bezier curve. There is a good
        +   * Calculates the tangent of a point on a Bézier curve. There is a good
            * definition of tangent on Wikipedia.
            *
        -   *
            * 

        Advanced

        * Code submitted by Dave Bollinger (davbol) for release 0136. * * @webref shape:curves - * @webBrief Calculates the tangent of a point on a Bezier curve + * @webBrief Calculates the tangent of a point on a Bézier curve * @param a coordinate of first point on the curve * @param b coordinate of first control point * @param c coordinate of second control point @@ -12009,14 +11840,12 @@ public class PApplet implements PConstants { /** - * - * Sets the resolution at which Beziers display. The default value is 20. This - * function is only useful when using the P3D renderer; the default - * P2D renderer does not use this information. - * + * Sets the resolution at which Bézier curves display. The default value is 20. + * This function is only useful when using the P2D or P3D renderer; + * the default (JAVA2D) renderer does not use this information. * * @webref shape:curves - * @webBrief Sets the resolution at which Beziers display + * @webBrief Sets the resolution at which Bézier curves display * @param detail resolution of the curves * @see PGraphics#curve(float, float, float, float, float, float, float, float, * float, float, float, float) @@ -12040,17 +11869,16 @@ public class PApplet implements PConstants { /** * - * Draws a Bezier curve on the screen. These curves are defined by a series + * Draws a Bézier curve on the screen. These curves are defined by a series * of anchor and control points. The first two parameters specify the first * anchor point and the last two parameters specify the other anchor point. * The middle parameters specify the control points which define the shape - * of the curve. Bezier curves were developed by French engineer Pierre + * of the curve. The curves were developed by French engineer Pierre * Bezier. Using the 3D version requires rendering with P3D (see the * Environment reference for more information). * - * *

        Advanced

        - * Draw a cubic bezier curve. The first and last points are + * Draw a cubic Bézier curve. The first and last points are * the on-curve points. The middle two are the 'control' points, * or 'handles' in an application like Illustrator. *

        @@ -12073,7 +11901,7 @@ public class PApplet implements PConstants { *

        bezier(x1, y1, cx, cy, cx, cy, x2, y2);
        * * @webref shape:curves - * @webBrief Draws a Bezier curve on the screen + * @webBrief Draws a Bézier curve on the screen * @param x1 coordinates for the first anchor point * @param y1 coordinates for the first anchor point * @param z1 coordinates for the first anchor point @@ -12100,7 +11928,6 @@ public class PApplet implements PConstants { /** - * * Evaluates the curve at point t for points a, b, * c, d. The parameter t may range from 0 (the start of the * curve) and 1 (the end of the curve). a and d are the control @@ -12109,7 +11936,6 @@ public class PApplet implements PConstants { * second time with the y coordinates to get the location of a curve at * t. * - * * @webref shape:curves * @webBrief Evaluates the curve at point t for points a, b, c, d * @param a coordinate of first control point @@ -12128,12 +11954,10 @@ public class PApplet implements PConstants { /** - * * Calculates the tangent of a point on a curve. There's a good definition * of tangent on Wikipedia. * - * *

        Advanced

        * Code thanks to Dave Bollinger (Bug #715) * @@ -12155,12 +11979,10 @@ public class PApplet implements PConstants { /** - * * Sets the resolution at which curves display. The default value is 20. * This function is only useful when using the P3D renderer as the default * P2D renderer does not use this information. * - * * @webref shape:curves * @webBrief Sets the resolution at which curves display * @param detail resolution of the curves @@ -12211,12 +12033,11 @@ public class PApplet implements PConstants { * implementation of Catmull-Rom splines. Using the 3D version requires * rendering with P3D (see the Environment reference for more information). * - * *

        Advanced

        * As of revision 0070, this function no longer doubles the first * and last points. The curves are a bit more boring, but it's more * mathematically correct, and properly mirrored in curvePoint(). - *

        + *

        * Identical to typing out:

            * beginShape();
            * curveVertex(x1, y1);
        @@ -12285,7 +12106,6 @@ public class PApplet implements PConstants {
            * The parameter must be written in ALL CAPS because Processing is a
            * case-sensitive language.
            *
        -   *
            * @webref image:loading & displaying
            * @webBrief Modifies the location from which images draw
            * @param mode either CORNER, CORNERS, or CENTER
        @@ -12375,8 +12195,7 @@ public class PApplet implements PConstants {
            * shapeMode(CENTER) draws the shape from its center point and uses
            * the third and forth parameters of shape() to specify the width
            * and height. The parameter must be written in "ALL CAPS" because
        -   * Processing is a case sensitive language.
        -   *
        +   * Processing is a case-sensitive language.
            *
            * @webref shape:loading & displaying
            * @webBrief Modifies the location from which shapes draw
        @@ -12398,7 +12217,6 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Draws shapes to the display window. Shapes must be in the sketch's "data"
            * directory to load correctly. Select "Add file..." from the "Sketch" menu to
            * add the shape. Processing currently works with SVG, OBJ, and custom-created
        @@ -12408,7 +12226,6 @@ public class PApplet implements PConstants {
            * d parameters specify a different size. The shapeMode() function
            * can be used to change the way these parameters are interpreted.
            *
        -   *
            * @webref shape:loading & displaying
            * @webBrief Displays shapes to the screen
            * @param shape the shape to display
        @@ -12417,8 +12234,6 @@ public class PApplet implements PConstants {
            * @see PShape
            * @see PApplet#loadShape(String)
            * @see PGraphics#shapeMode(int)
        -   *
        -   *      Convenience method to draw at a particular location.
            */
           public void shape(PShape shape, float x, float y) {
             if (recorder != null) recorder.shape(shape, x, y);
        @@ -12445,7 +12260,6 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Sets the current alignment for drawing text. The parameters LEFT, CENTER, and
            * RIGHT set the display characteristics of the letters in relation to the
            * values for the x and y parameters of the text()
        @@ -12470,8 +12284,6 @@ public class PApplet implements PConstants {
            * textDescent() so that the hack works even if you change the size of
            * the font.
            *
        -   *
        -   *
            * @webref typography:attributes
            * @webBrief Sets the current alignment for drawing text
            * @param alignX horizontal alignment, either LEFT, CENTER, or RIGHT
        @@ -12490,11 +12302,9 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Returns ascent of the current font at its current size. This information is
            * useful for determining the height of the font above the baseline.
            *
        -   *
            * @webref typography:metrics
            * @webBrief Returns ascent of the current font at its current size
            * @see PGraphics#textDescent()
        @@ -12505,7 +12315,6 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Returns descent of the current font at its current size. This information is
            * useful for determining the height of the font below the baseline.
            *
        @@ -12563,7 +12372,6 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Sets the spacing between lines of text in units of pixels. This setting will
            * be used in all subsequent calls to the text() function. Note, however,
            * that the leading is reset by textSize(). For example, if the leading
        @@ -12571,7 +12379,6 @@ public class PApplet implements PConstants {
            * at a later point, the leading will be reset to the default for the text size
            * of 48.
            *
        -   *
            * @webref typography:attributes
            * @webBrief Sets the spacing between lines of text in units of pixels
            * @param leading the size in pixels for spacing between lines
        @@ -12588,7 +12395,6 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Sets the way text draws to the screen, either as texture maps or as vector
            * geometry. The default textMode(MODEL), uses textures to render the
            * fonts. The textMode(SHAPE) mode draws text using the glyph outlines of
        @@ -12622,11 +12428,9 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Sets the current font size. This size will be used in all subsequent
            * calls to the text() function. Font size is measured in units of pixels.
            *
        -   *
            * @webref typography:attributes
            * @webBrief Sets the current font size
            * @param size the size of the letters in units of pixels
        @@ -12650,10 +12454,8 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Calculates and returns the width of any character or text string.
            *
        -   *
            * @webref typography:attributes
            * @webBrief Calculates and returns the width of any character or text string
            * @param str the String of characters to measure
        @@ -12677,7 +12479,6 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Draws text to the screen. Displays the information specified in the first
            * parameter on the screen in the position specified by the additional
            * parameters. A default font will be used unless a font is set with the
        @@ -12697,7 +12498,6 @@ public class PApplet implements PConstants {
            * a PFont with textFont(). In that case, a generic sans-serif font will
            * be used instead. (See the third example above.)
            *
        -   *
            * @webref typography:loading & displaying
            * @webBrief Draws text to the screen
            * @param c the alphanumeric character to be displayed
        @@ -12858,7 +12658,6 @@ public class PApplet implements PConstants {
            * transformations (rotate, scale, translate) and the drawing styles
            * at the same time.
            *
        -   *
            * @webref structure
            * @webBrief The push() function saves the current drawing style
            * settings and transformations, while pop() restores these
        @@ -12897,7 +12696,6 @@ public class PApplet implements PConstants {
            * transformations (rotate, scale, translate) and the drawing styles
            * at the same time.
            *
        -   *
            * @webref structure
            * @webBrief The pop() function restores the previous drawing style
            * settings and transformations after push() has changed them
        @@ -12910,7 +12708,6 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Pushes the current transformation matrix onto the matrix stack.
            * Understanding pushMatrix() and popMatrix() requires
            * understanding the concept of a matrix stack. The pushMatrix()
        @@ -12920,7 +12717,6 @@ public class PApplet implements PConstants {
            * the other transformation functions and may be embedded to control the
            * scope of the transformations.
            *
        -   *
            * @webref transform
            * @webBrief Pushes the current transformation matrix onto the matrix stack
            * @see PGraphics#popMatrix()
        @@ -12938,7 +12734,6 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Pops the current transformation matrix off the matrix stack.
            * Understanding pushing and popping requires understanding the concept of
            * a matrix stack. The pushMatrix() function saves the current
        @@ -12947,7 +12742,6 @@ public class PApplet implements PConstants {
            * in conjunction with the other transformation functions and may be
            * embedded to control the scope of the transformations.
            *
        -   *
            * @webref transform
            * @webBrief Pops the current transformation matrix off the matrix stack
            * @see PGraphics#pushMatrix()
        @@ -12959,14 +12753,13 @@ public class PApplet implements PConstants {
         
         
           /**
        -   *
            * Specifies an amount to displace objects within the display window. The
            * x parameter specifies left/right translation, the y parameter
            * specifies up/down translation, and the z parameter specifies
            * translations toward/away from the screen. Using this function with the
            * z parameter requires using P3D as a parameter in combination with size
        -   * as shown in the above example. 
        - *
        + * as shown in the above example. + *

        * Transformations are cumulative and apply to everything that happens after and * subsequent calls to the function accumulates the effect. For example, calling * translate(50, 0) and then translate(20, 0) is the same as @@ -12975,7 +12768,6 @@ public class PApplet implements PConstants { * function can be further controlled by using pushMatrix() and * popMatrix(). * - * * @webref transform * @webBrief Specifies an amount to displace objects within the display window * @param x left/right translation @@ -13021,7 +12813,6 @@ public class PApplet implements PConstants { * matrix by a rotation matrix. This function can be further controlled by * the pushMatrix() and popMatrix(). * - * * @webref transform * @webBrief Rotates a shape the amount specified by the angle parameter * @param angle angle of rotation specified in radians @@ -13054,7 +12845,6 @@ public class PApplet implements PConstants { * This function requires using P3D as a third parameter to size() * as shown in the example above. * - * * @webref transform * @webBrief Rotates a shape around the x-axis the amount specified by the * angle parameter @@ -13088,7 +12878,6 @@ public class PApplet implements PConstants { * This function requires using P3D as a third parameter to size() * as shown in the examples above. * - * * @webref transform * @webBrief Rotates a shape around the y-axis the amount specified by the * angle parameter @@ -13122,7 +12911,6 @@ public class PApplet implements PConstants { * This function requires using P3D as a third parameter to size() * as shown in the examples above. * - * * @webref transform * @webBrief Rotates a shape around the z-axis the amount specified by the * angle parameter @@ -13169,7 +12957,6 @@ public class PApplet implements PConstants { * example above. This function can be further controlled with * pushMatrix() and popMatrix(). * - * * @webref transform * @webBrief Increases or decreases the size of a shape by expanding and * contracting vertices @@ -13191,7 +12978,7 @@ public class PApplet implements PConstants { /** *

        Advanced

        * Scale in X and Y. Equivalent to scale(sx, sy, 1). - * + *

        * Not recommended for use in 3D, because the z-dimension is just * scaled by 1, since there's no way to know what else to scale it by. * @@ -13230,7 +13017,6 @@ public class PApplet implements PConstants { * matrix by a rotation matrix. This function can be further controlled by * the pushMatrix() and popMatrix() functions. * - * * @webref transform * @webBrief Shears a shape around the x-axis the amount specified by the * angle parameter @@ -13265,7 +13051,6 @@ public class PApplet implements PConstants { * matrix by a rotation matrix. This function can be further controlled by * the pushMatrix() and popMatrix() functions. * - * * @webref transform * @webBrief Shears a shape around the y-axis the amount specified by the * angle parameter @@ -13284,10 +13069,8 @@ public class PApplet implements PConstants { /** - * - * Replaces the current matrix with the identity matrix. The equivalent function - * in OpenGL is glLoadIdentity(). - * + * Replaces the current matrix with the identity matrix. + * The equivalent function in OpenGL is glLoadIdentity(). * * @webref transform * @webBrief Replaces the current matrix with the identity matrix @@ -13303,16 +13086,14 @@ public class PApplet implements PConstants { /** - * * Multiplies the current matrix by the one specified through the * parameters. This is very slow because it will try to calculate the * inverse of the transform, so avoid it whenever possible. The equivalent * function in OpenGL is glMultMatrix(). * - * * @webref transform - * @webBrief Multiplies the current matrix by the one specified through the - * parameters + * @webBrief Multiplies the current matrix by the one specified in the + * parameter * @see PGraphics#pushMatrix() * @see PGraphics#popMatrix() * @see PGraphics#resetMatrix() @@ -13423,11 +13204,9 @@ public class PApplet implements PConstants { /** - * * Prints the current matrix to the Console (the text window at the bottom * of Processing). * - * * @webref transform * @webBrief Prints the current matrix to the Console (the text window at the bottom * of Processing) @@ -13443,7 +13222,6 @@ public class PApplet implements PConstants { /** - * * The beginCamera() and endCamera() functions enable * advanced customization of the camera space. The functions are useful if * you want to more control over camera movement, however for most users, @@ -13461,7 +13239,6 @@ public class PApplet implements PConstants { * following endCamera() and pairs of beginCamera() and * endCamera() cannot be nested. * - * * @webref lights_camera:camera * @webBrief The beginCamera() and endCamera() functions enable * advanced customization of the camera space @@ -13479,12 +13256,10 @@ public class PApplet implements PConstants { /** - * * The beginCamera() and endCamera() functions enable * advanced customization of the camera space. Please see the reference for * beginCamera() for a description of how the functions are used. * - * * @webref lights_camera:camera * @webBrief The beginCamera() and endCamera() functions enable * advanced customization of the camera space @@ -13498,7 +13273,6 @@ public class PApplet implements PConstants { /** - * * Sets the position of the camera through setting the eye position, the * center of the scene, and which axis is facing upward. Moving the eye * position and the direction it is pointing (the center of the scene) @@ -13509,7 +13283,6 @@ public class PApplet implements PConstants { * 180.0), width/2.0, height/2.0, 0, 0, 1, 0). This function is similar * to gluLookAt() in OpenGL, but it first clears the current camera settings. * - * * @webref lights_camera:camera * @webBrief Sets the position of the camera * @see PGraphics#beginCamera() @@ -13567,7 +13340,6 @@ public class PApplet implements PConstants { * are the minimum and maximum z values. If no parameters are given, the default * is used: ortho(-width/2, width/2, -height/2, height/2). * - * * @webref lights_camera:camera * @webBrief Sets an orthographic projection and defines a parallel clipping * volume @@ -13616,7 +13388,6 @@ public class PApplet implements PConstants { * default values are: perspective(PI/3.0, width/height, cameraZ/10.0, * cameraZ*10.0) where cameraZ is ((height/2.0) / tan(PI*60.0/360.0)) * - * * @webref lights_camera:camera * @webBrief Sets a perspective projection applying foreshortening, making distant * objects appear smaller than closer ones @@ -13631,7 +13402,7 @@ public class PApplet implements PConstants { * @param fovy field-of-view angle (in radians) for vertical direction * @param aspect ratio of width to height * @param zNear z-position of nearest clipping plane - * @param zFar z-position of farthest clipping plane + * @param zFar z-position of the farthest clipping plane */ public void perspective(float fovy, float aspect, float zNear, float zFar) { if (recorder != null) recorder.perspective(fovy, aspect, zNear, zFar); @@ -13661,7 +13432,6 @@ public class PApplet implements PConstants { * Works like glFrustum, except it wipes out the current perspective matrix * rather than multiplying itself with it. * - * * @webref lights_camera:camera * @webBrief Sets a perspective matrix defined through the parameters * @param left left coordinate of the clipping plane @@ -13686,11 +13456,9 @@ public class PApplet implements PConstants { /** - * * Prints the current projection matrix to the Console (the text window at * the bottom of Processing). * - * * @webref lights_camera:camera * @webBrief Prints the current projection matrix to the Console * @see PGraphics#camera(float, float, float, float, float, float, float, float, float) @@ -13702,11 +13470,9 @@ public class PApplet implements PConstants { /** - * * Takes a three-dimensional X, Y, Z position and returns the X value for * where it will appear on a (two-dimensional) screen. * - * * @webref lights_camera:coordinates * @webBrief Takes a three-dimensional X, Y, Z position and returns the X value for * where it will appear on a (two-dimensional) screen @@ -13721,11 +13487,9 @@ public class PApplet implements PConstants { /** - * * Takes a three-dimensional X, Y, Z position and returns the Y value for * where it will appear on a (two-dimensional) screen. * - * * @webref lights_camera:coordinates * @webBrief Takes a three-dimensional X, Y, Z position and returns the Y value for * where it will appear on a (two-dimensional) screen @@ -13756,11 +13520,9 @@ public class PApplet implements PConstants { /** - * * Takes a three-dimensional X, Y, Z position and returns the Z value for * where it will appear on a (two-dimensional) screen. * - * * @webref lights_camera:coordinates * @webBrief Takes a three-dimensional X, Y, Z position and returns the Z value for * where it will appear on a (two-dimensional) screen @@ -13790,7 +13552,6 @@ public class PApplet implements PConstants { * (x, y, z) coordinate returned by the model functions is used to place * another box in the same location. * - * * @webref lights_camera:coordinates * @webBrief Returns the three-dimensional X, Y, Z position in model space * @param x 3D x-coordinate to be mapped @@ -13805,7 +13566,6 @@ public class PApplet implements PConstants { /** - * * Returns the three-dimensional X, Y, Z position in model space. This * returns the Y value for a given coordinate based on the current set of * transformations (scale, rotate, translate, etc.) The Y value can be used @@ -13819,7 +13579,6 @@ public class PApplet implements PConstants { * (x, y, z) coordinate returned by the model functions is used to place * another box in the same location. * - * * @webref lights_camera:coordinates * @webBrief Returns the three-dimensional X, Y, Z position in model space * @param x 3D x-coordinate to be mapped @@ -13848,7 +13607,6 @@ public class PApplet implements PConstants { * (x, y, z) coordinate returned by the model functions is used to place * another box in the same location. * - * * @webref lights_camera:coordinates * @webBrief Returns the three-dimensional X, Y, Z position in model space * @param x 3D x-coordinate to be mapped @@ -13879,7 +13637,6 @@ public class PApplet implements PConstants { * textAlign(), textFont(), textMode(), textSize(), textLeading(), * emissive(), specular(), shininess(), ambient() * - * * @webref structure * @webBrief Saves the current style settings and popStyle() restores the prior settings * @see PGraphics#popStyle() @@ -13891,7 +13648,6 @@ public class PApplet implements PConstants { /** - * * The pushStyle() function saves the current style settings and * popStyle() restores the prior settings; these functions are * always used together. They allow you to change the style settings and @@ -13900,7 +13656,6 @@ public class PApplet implements PConstants { * pushStyle() and popStyle() functions can be embedded to * provide more control (see the second example above for a demonstration.) * - * * @webref structure * @webBrief Saves the current style settings and popStyle() restores the prior settings * @see PGraphics#pushStyle() @@ -13918,7 +13673,6 @@ public class PApplet implements PConstants { /** - * * Sets the width of the stroke used for lines, points, and the border around * shapes. All widths are set in units of pixels.
        *
        @@ -13927,7 +13681,6 @@ public class PApplet implements PConstants { * setting the pixel using set() or drawing the point using either * circle() or square(). * - * * @webref shape:attributes * @webBrief Sets the width of the stroke used for lines, points, and the border * around shapes @@ -13943,12 +13696,10 @@ public class PApplet implements PConstants { /** - * * Sets the style of the joints which connect line segments. These joints are * either mitered, beveled, or rounded and specified with the corresponding * parameters MITER, BEVEL, and ROUND. The default joint is MITER. * - * * @webref shape:attributes * @webBrief Sets the style of the joints which connect line segments * @param join either MITER, BEVEL, ROUND @@ -13963,7 +13714,6 @@ public class PApplet implements PConstants { /** - * * Sets the style for rendering line endings. These ends are either squared, * extended, or rounded, each of which specified with the corresponding * parameters: SQUARE, PROJECT, and ROUND. The default cap is ROUND.
        @@ -13986,11 +13736,9 @@ public class PApplet implements PConstants { /** - * * Disables drawing the stroke (outline). If both noStroke() and * noFill() are called, nothing will be drawn to the screen. * - * * @webref color:setting * @webBrief Disables drawing the stroke (outline) * @see PGraphics#stroke(int, float) @@ -14091,7 +13839,6 @@ public class PApplet implements PConstants { * Removes the current fill value for displaying images and reverts to * displaying images with their original hues. * - * * @webref image:loading & displaying * @webBrief Removes the current fill value for displaying images and reverts to * displaying images with their original hues @@ -14130,7 +13877,6 @@ public class PApplet implements PConstants { * The tint() function is also used to control the coloring of textures * in 3D. * - * * @webref image:loading & displaying * @webBrief Sets the fill value for displaying images * @usage web_application @@ -14190,7 +13936,6 @@ public class PApplet implements PConstants { * Disables filling geometry. If both noStroke() and noFill() * are called, nothing will be drawn to the screen. * - * * @webref color:setting * @webBrief Disables filling geometry * @usage web_application @@ -14226,7 +13971,6 @@ public class PApplet implements PConstants { *

        * To change the color of an image (or a texture), use tint(). * - * * @webref color:setting * @webBrief Sets the color used to fill shapes * @usage web_application @@ -14295,7 +14039,6 @@ public class PApplet implements PConstants { * reflect. Used in combination with emissive(), specular(), * and shininess() in setting the material properties of shapes. * - * * @webref lights_camera:material properties * @webBrief Sets the ambient reflectance for shapes drawn to the screen * @usage web_application @@ -14338,7 +14081,6 @@ public class PApplet implements PConstants { * with emissive(), ambient(), and shininess() in * setting the material properties of shapes. * - * * @webref lights_camera:material properties * @webBrief Sets the specular color of the materials used for shapes drawn to the * screen, which sets the color of highlights @@ -14383,7 +14125,6 @@ public class PApplet implements PConstants { * with ambient(), specular(), and emissive() in * setting the material properties of shapes. * - * * @webref lights_camera:material properties * @webBrief Sets the amount of gloss in the surface of shapes * @usage web_application @@ -14405,7 +14146,6 @@ public class PApplet implements PConstants { * specular(), and shininess() in setting the material * properties of shapes. * - * * @webref lights_camera:material properties * @webBrief Sets the emissive color of the material used for drawing shapes drawn to * the screen @@ -14453,7 +14193,6 @@ public class PApplet implements PConstants { * looping program will cause them to only have an effect the first time * through the loop. * - * * @webref lights_camera:lights * @webBrief Sets the default ambient light, directional light, falloff, and specular * values @@ -14477,7 +14216,6 @@ public class PApplet implements PConstants { * lighting so that 2D geometry (which does not require lighting) can be * drawn after a set of lighted 3D geometry. * - * * @webref lights_camera:lights * @webBrief Disable all lighting * @usage web_application @@ -14500,7 +14238,6 @@ public class PApplet implements PConstants { * through the loop. The v1, v2, and v3 parameters are * interpreted as either RGB or HSB values, depending on the current color mode. * - * * @webref lights_camera:lights * @webBrief Adds an ambient light * @usage web_application @@ -14534,7 +14271,7 @@ public class PApplet implements PConstants { /** * * Adds a directional light. Directional light comes from one direction and - * is stronger when hitting a surface squarely and weaker if it hits at a a + * is stronger when hitting a surface squarely and weaker if it hits at a * gentle angle. After hitting a surface, a directional lights scatters in * all directions. Lights need to be included in the draw() to * remain persistent in a looping program. Placing them in the @@ -14545,7 +14282,6 @@ public class PApplet implements PConstants { * direction the light is facing. For example, setting ny to -1 will * cause the geometry to be lit from below (the light is facing directly upward). * - * * @webref lights_camera:lights * @webBrief Adds a directional light * @usage web_application @@ -14576,7 +14312,6 @@ public class PApplet implements PConstants { * as either RGB or HSB values, depending on the current color mode. The * x, y, and z parameters set the position of the light. * - * * @webref lights_camera:lights * @webBrief Adds a point light * @usage web_application @@ -14601,7 +14336,7 @@ public class PApplet implements PConstants { /** * - * Adds a spot light. Lights need to be included in the draw() to remain + * Adds a spotlight. Lights need to be included in the draw() to remain * persistent in a looping program. Placing them in the setup() of a * looping program will cause them to only have an effect the first time through * the loop. The v1, v2, and v3 parameters are interpreted @@ -14613,7 +14348,7 @@ public class PApplet implements PConstants { * that cone. * * @webref lights_camera:lights - * @webBrief Adds a spot light + * @webBrief Adds a spotlight * @usage web_application * @param v1 red or hue value (depending on current color mode) * @param v2 green or saturation value (depending on current color @@ -14623,9 +14358,9 @@ public class PApplet implements PConstants { * @param x x-coordinate of the light * @param y y-coordinate of the light * @param z z-coordinate of the light - * @param nx direction along the x axis - * @param ny direction along the y axis - * @param nz direction along the z axis + * @param nx direction along the x-axis + * @param ny direction along the y-axis + * @param nz direction along the z-axis * @param angle angle of the spotlight cone * @param concentration exponent determining the center bias of the cone * @see PGraphics#lights() @@ -14644,7 +14379,7 @@ public class PApplet implements PConstants { /** * - * Sets the falloff rates for point lights, spot lights, and ambient lights. + * Sets the falloff rates for point lights, spotlights, and ambient lights. * Like fill(), it affects only the elements which are created after it * in the code. The default value is lightFalloff(1.0, 0.0, 0.0), and the * parameters are used to calculate the falloff with the following @@ -14659,9 +14394,8 @@ public class PApplet implements PConstants { * location and falloff. You can think of it as a point light that doesn't care * which direction a surface is facing. * - * * @webref lights_camera:lights - * @webBrief Sets the falloff rates for point lights, spot lights, and ambient + * @webBrief Sets the falloff rates for point lights, spotlights, and ambient * lights * @usage web_application * @param constant constant value or determining falloff @@ -14690,7 +14424,6 @@ public class PApplet implements PConstants { * specular material qualities set through the specular() and * shininess() functions. * - * * @webref lights_camera:lights * @webBrief Sets the specular color for lights * @usage web_application @@ -14728,7 +14461,6 @@ public class PApplet implements PConstants { * background colors on the main drawing surface. It can only be used along with * a PGraphics object and createGraphics(). * - * *

        Advanced

        *

        * Clear the background with a color that includes an alpha value. This can only @@ -14805,7 +14537,7 @@ public class PApplet implements PConstants { * function. Unlike the main graphics context (the display window), * pixels in additional graphics areas created with createGraphics() * can be entirely or partially transparent. This function clears - * everything in a PGraphics object to make all of the pixels + * everything in a PGraphics object to make all the pixels * 100% transparent. * * @webref color:setting @@ -14857,7 +14589,6 @@ public class PApplet implements PConstants { * instance, instead of colorMode(RGB), write colorMode(RGB, 255, 255, * 255). * - * * @webref color:setting * @webBrief Changes the way Processing interprets color data * @usage web_application @@ -15078,10 +14809,8 @@ public class PApplet implements PConstants { /** - * * Extracts the brightness value from a color. * - * * @webref color:creating & reading * @webBrief Extracts the brightness value from a color * @usage web_application @@ -15174,8 +14903,8 @@ public class PApplet implements PConstants { * y parameters define the coordinates for the upper-left corner of * the image, regardless of the current imageMode().
        *
        - * If the pixel requested is outside of the image window, black is - * returned. The numbers returned are scaled according to the current color + * If the pixel requested is outside the image window, black is returned. + * The numbers returned are scaled according to the current color * ranges, but only RGB values are returned by this function. For example, * even though you may have drawn a shape with colorMode(HSB), the * numbers returned will be in RGB format.
        @@ -15185,9 +14914,8 @@ public class PApplet implements PConstants { * equivalent statement to get(x, y) using pixels[] is * pixels[y*width+x]. See the reference for pixels[] for more information. * - * *

        Advanced

        - * Returns an ARGB "color" type (a packed 32 bit int with the color. + * Returns an ARGB "color" type (a packed 32-bit int) with the color. * If the coordinate is outside the image, zero is returned * (black, but completely transparent). *

        @@ -15259,7 +14987,6 @@ public class PApplet implements PConstants { * is pixels[y*width+x] = #000000. See the reference for * pixels[] for more information. * - * * @webref image:pixels * @webBrief Writes a color to any pixel or writes an image into another * @usage web_application @@ -15303,7 +15030,6 @@ public class PApplet implements PConstants { * same length as the target image's pixels array and should contain only * grayscale data of values between 0-255. * - * *

        Advanced

        * * Set alpha channel for an image. Black colors in the source @@ -15336,14 +15062,13 @@ public class PApplet implements PConstants { /** - * * Filters the image as defined by one of the following modes:
        *
        * THRESHOLD
        - * Converts the image to black and white pixels depending if they are above or - * below the threshold defined by the level parameter. The parameter must be - * between 0.0 (black) and 1.0 (white). If no level is specified, 0.5 is - * used.
        + * Converts the image to black and white pixels depending on if they + * are above or below the threshold defined by the level parameter. + * The parameter must be between 0.0 (black) and 1.0 (white). + * If no level is specified, 0.5 is used.
        *
        * GRAY
        * Converts any colors in the image to grayscale equivalents. No parameter is @@ -15371,7 +15096,6 @@ public class PApplet implements PConstants { * DILATE
        * Increases the light areas. No parameter is used. * - * *

        Advanced

        Method to apply a variety of basic filters to this image. *

        *

          @@ -15403,7 +15127,6 @@ public class PApplet implements PConstants { /** - * * Copies a region of pixels from one image into another. If the source and * destination regions aren't the same size, it will automatically resize * source pixels to fit the specified target region. No alpha information @@ -15412,7 +15135,6 @@ public class PApplet implements PConstants { *

          * As of release 0149, this function ignores imageMode(). * - * * @webref image:pixels * @webBrief Copies the entire image * @usage web_application @@ -15492,7 +15214,7 @@ public class PApplet implements PConstants { * BURN - Darker areas are applied, increasing contrast, ignores lights. * Called "Color Burn" in Illustrator and Photoshop.
          *
          - * All modes use the alpha information (highest byte) of source image + * All modes use the alpha information (the highest byte) of source image * pixels as the blending factor. If the source and destination regions are * different sizes, the image will be automatically resized to match the * destination size. If the srcImg parameter is not used, the @@ -15500,7 +15222,6 @@ public class PApplet implements PConstants { *
          * As of release 0149, this function ignores imageMode(). * - * * @webref image:pixels * @webBrief Copies a pixel or rectangle of pixels using different blending modes * @param src an image variable referring to the source image diff --git a/core/src/processing/core/PConstants.java b/core/src/processing/core/PConstants.java index cc93f5f1c..af17c1fbf 100644 --- a/core/src/processing/core/PConstants.java +++ b/core/src/processing/core/PConstants.java @@ -88,7 +88,7 @@ public interface PConstants { int MACOSX = 2; String[] platformNames = { - "other", "windows", "macosx", "linux" + "other", "windows", "macos", "linux" }; diff --git a/core/src/processing/core/PFont.java b/core/src/processing/core/PFont.java index dcc7924a0..1b54396c5 100644 --- a/core/src/processing/core/PFont.java +++ b/core/src/processing/core/PFont.java @@ -181,6 +181,9 @@ public class PFont implements PConstants { protected FontMetrics lazyMetrics; protected int[] lazySamples; + // Debugging for https://github.com/processing/processing4/issues/278 + private final boolean DEBUG_P4_0278 = false; + /** * @nowebref @@ -249,12 +252,20 @@ public class PFont implements PConstants { smooth ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF); + // adding this for post-1.0.9 lazyGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, smooth ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + // Trying to track down https://github.com/processing/processing4/issues/278 + // But at least on macOS, it's still smooth when this is called from Create Font. + lazyGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + smooth ? + RenderingHints.VALUE_INTERPOLATION_BICUBIC : + RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + lazyGraphics.setFont(font); lazyMetrics = lazyGraphics.getFontMetrics(); lazySamples = new int[mbox3 * mbox3]; @@ -270,7 +281,7 @@ public class PFont implements PConstants { } else { // The charset needs to be sorted to make index lookup run quickly - // http://dev.processing.org/bugs/show_bug.cgi?id=494 + // https://download.processing.org/bugzilla/494.html // First make copy of charset[] so the user's array is not modified // https://github.com/processing/processing4/issues/197 char[] sortedCharset = Arrays.copyOf(charset, charset.length); @@ -320,7 +331,7 @@ public class PFont implements PConstants { /** - * Adds an additional parameter that indicates the font came from a file, + * Adds a parameter that indicates the font came from a file, * not a built-in OS font. * * @nowebref @@ -728,7 +739,7 @@ public class PFont implements PConstants { // six element array received from the Java2D path iterator float[] iterPoints = new float[6]; - // array passed to createGylphVector + // array passed to createGlyphVector char[] textArray = new char[] { ch }; //Graphics2D graphics = (Graphics2D) this.getGraphics(); @@ -905,7 +916,7 @@ public class PFont implements PConstants { /** * Starting with Java 1.5, Apple broke the ability to specify most fonts. * This bug was filed years ago as #4769141 at bugreporter.apple.com. More: - * Bug 407. + * Bug 407. *
          * This function displays a warning when the font is not found * and Java's system font is used. @@ -1034,6 +1045,12 @@ public class PFont implements PConstants { protected Glyph(char c) { + if (DEBUG_P4_0278 && c == 'd') { + System.out.println(lazyGraphics.getRenderingHint(RenderingHints.KEY_ANTIALIASING)); + System.out.println(lazyGraphics.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); + System.out.println(lazyGraphics.getRenderingHint(RenderingHints.KEY_INTERPOLATION)); + } + int mbox3 = size * 3; lazyGraphics.setColor(Color.white); lazyGraphics.fillRect(0, 0, mbox3, mbox3); @@ -1060,6 +1077,16 @@ public class PFont implements PConstants { } } + if (DEBUG_P4_0278 && c == 'd') { + for (int y = minY; y <= maxY; y++) { + for (int x = minX; x <= maxX; x++) { + int sample = lazySamples[y * mbox3 + x] & 0xff; + System.out.format("%3d ", sample); + } + System.out.println(); + } + } + if (!pixelFound) { minX = minY = 0; maxX = maxY = 0; diff --git a/core/src/processing/core/PGraphics.java b/core/src/processing/core/PGraphics.java index 350c5b1f1..4252c609c 100644 --- a/core/src/processing/core/PGraphics.java +++ b/core/src/processing/core/PGraphics.java @@ -81,7 +81,7 @@ import processing.opengl.PShader; * renderer to complete its initialization routine. *
        * The functions were broken out because of the growing number of parameters - * such as these that might be used by a renderer, yet with the exception of + * such as these that might be used by a renderer, but except for * setSize(), it's not clear which will be necessary. So while the size could be * passed in to the constructor instead of a setSize() function, a function * would still be needed that would notify the renderer that it was time to @@ -140,7 +140,7 @@ import processing.opengl.PShader; * the time. That is, it's not something that to be done once—it's a * matter of keeping the multiple references synchronized (to say nothing of the * translation issues), while targeting them for their separate audiences. Ouch. - * + *

        * We're working right now on synchronizing the two references, so the website * reference is generated from the javadoc comments. Yay. * @@ -153,39 +153,23 @@ import processing.opengl.PShader; */ public class PGraphics extends PImage implements PConstants { -// /// Canvas object that covers rendering this graphics on screen. -// public Canvas canvas; - - // ........................................................ - - // width and height are already inherited from PImage - - -// /// width minus one (useful for many calculations) -// protected int width1; -// -// /// height minus one (useful for many calculations) -// protected int height1; - - /// width * height (useful for many calculations) + /** width * height (useful for many calculations) */ + @SuppressWarnings("unused") public int pixelCount; -// /// true if smoothing is enabled (read-only) -// public boolean smooth; - - /// the anti-aliasing level for renderers that support it + /** the anti-aliasing level for renderers that support it */ public int smooth; // ........................................................ - /// true if defaults() has been called a first time + /** true if defaults() has been called a first time */ protected boolean settingsInited; - /// true if settings should be re-applied on next beginDraw() + /** true if settings should be re-applied on next beginDraw() */ protected boolean reapplySettings; - /// set to a PGraphics object being used inside a beginRaw/endRaw() block + /** set to a PGraphics object being used inside a beginRaw/endRaw() block */ protected PGraphics raw; // ........................................................ @@ -199,9 +183,6 @@ public class PGraphics extends PImage implements PConstants { */ protected boolean primaryGraphics; -// // TODO nervous about leaving this here since it seems likely to create -// // back-references where we don't want them -// protected PSurface surface; // ........................................................ @@ -266,13 +247,20 @@ public class PGraphics extends PImage implements PConstants { // transformations (2D and 3D) + @SuppressWarnings("unused") static public final int TX = 18; // transformed xyzw + @SuppressWarnings("unused") static public final int TY = 19; + @SuppressWarnings("unused") static public final int TZ = 20; + @SuppressWarnings("unused") static public final int VX = 21; // view space coords + @SuppressWarnings("unused") static public final int VY = 22; + @SuppressWarnings("unused") static public final int VZ = 23; + @SuppressWarnings("unused") static public final int VW = 24; @@ -285,9 +273,13 @@ public class PGraphics extends PImage implements PConstants { static public final int AB = 27; // Diffuse is shared with fill. + @SuppressWarnings("unused") static public final int DR = 3; // TODO needs to not be shared, this is a material property + @SuppressWarnings("unused") static public final int DG = 4; + @SuppressWarnings("unused") static public final int DB = 5; + @SuppressWarnings("unused") static public final int DA = 6; // specular (by default kept white) @@ -347,7 +339,7 @@ public class PGraphics extends PImage implements PConstants { /** * True if tint() is enabled (read-only). - * + *

        * Using tint/tintColor seems a better option for naming than * tintEnabled/tint because the latter seems ugly, even though * g.tint as the actual color seems a little more intuitive, @@ -460,6 +452,9 @@ public class PGraphics extends PImage implements PConstants { /** The current text leading (read-only) */ public float textLeading; + /** Used internally to check whether still using the default font */ + protected String defaultFontName; + static final protected String ERROR_TEXTFONT_NULL_PFONT = "A null PFont was passed to textFont()"; @@ -661,9 +656,6 @@ public class PGraphics extends PImage implements PConstants { /// Current mode for normals, one of AUTO, SHAPE, or VERTEX protected int normalMode; - /// Keep track of how many calls to normal, to determine the mode. - //protected int normalCount; - protected boolean autoNormal; /** Current normal vector. */ @@ -761,7 +753,7 @@ public class PGraphics extends PImage implements PConstants { * The final step in setting up a renderer, set its size of this renderer. * This was formerly handled by the constructor, but instead it's been broken * out so that setParent/setPrimary/setPath can be handled differently. - * + *

        * Important: this is ignored by the Methods task because otherwise it will * override setSize() in PApplet/Applet/Component, which will 1) not call * super.setSize(), and 2) will cause the renderer to be resized from the @@ -930,7 +922,7 @@ public class PGraphics extends PImage implements PConstants { * graphics buffer, meaning that for subclasses like OpenGL, there * needs to be a valid graphics context to mess with otherwise * you'll get some good crashing action. - * + *

        * This is currently called by checkSettings(), during beginDraw(). */ protected void defaultSettings() { // ignore @@ -968,7 +960,7 @@ public class PGraphics extends PImage implements PConstants { textAlign = LEFT; textMode = MODEL; - // if this fella is associated with an applet, then clear its background. + // if this fella is associated with a component, then clear its background. // if it's been created by someone else through createGraphics, // they have to call background() themselves, otherwise everything gets // a gray background (when just a transparent surface or an empty pdf @@ -992,7 +984,7 @@ public class PGraphics extends PImage implements PConstants { * their methods be called (rather than simply setting the textFont variable) * because they affect the graphics context, or they require parameters from * the context (e.g. getting native fonts for text). - * + *

        * This will only be called from an allocate(), which is only called from * size(), which is safely called from inside beginDraw(). And it cannot be * called before defaultSettings(), so we should be safe. @@ -1220,12 +1212,12 @@ public class PGraphics extends PImage implements PConstants { /** * - * Sets the current normal vector. Used for drawing three dimensional shapes and - * surfaces, normal() specifies a vector perpendicular to a shape's - * surface which, in turn, determines how lighting affects it. Processing - * attempts to automatically assign normals to shapes, but since that's - * imperfect, this is a better option when you want more control. This function - * is identical to glNormal3f() in OpenGL. + * Sets the current normal vector. Used for drawing three-dimensional + * shapes and surfaces, normal() specifies a vector perpendicular + * to a shape's surface which, in turn, determines how lighting affects it. + * Processing attempts to automatically assign normals to shapes, but since + * that's imperfect, this is a better option when you want more control. + * This function is identical to glNormal3f() in OpenGL. * * @webref lights_camera:lights * @webBrief Sets the current normal vector @@ -1310,12 +1302,10 @@ public class PGraphics extends PImage implements PConstants { } /** - * * Defines if textures repeat or draw once within a texture map. * The two parameters are CLAMP (the default behavior) and REPEAT. * This function only works with the P2D and P3D renderers. * - * * @webref image:textures * @webBrief Defines if textures repeat or draw once within a texture map * @param wrap Either CLAMP (default) or REPEAT @@ -1328,12 +1318,11 @@ public class PGraphics extends PImage implements PConstants { /** - * * Sets a texture to be applied to vertex points. The texture() function * must be called between beginShape() and endShape() and before * any calls to vertex(). This function only works with the P2D and P3D - * renderers.
        - *
        + * renderers. + *

        * When textures are in use, the fill color is ignored. Instead, use * tint() to specify the color of the texture as it is applied to the * shape. @@ -1451,9 +1440,9 @@ public class PGraphics extends PImage implements PConstants { float[] vertex = vertices[vertexCount]; // only do this if we're using an irregular (POLYGON) shape that - // will go through the triangulator. otherwise it'll do thinks like + // will go through the triangulator. otherwise it'll do things like // disappear in mathematically odd ways - // http://dev.processing.org/bugs/show_bug.cgi?id=444 + // https://download.processing.org/bugzilla/444.html if (shape == POLYGON) { if (vertexCount > 0) { float[] pvertex = vertices[vertexCount-1]; @@ -1666,7 +1655,7 @@ public class PGraphics extends PImage implements PConstants { /** * Use the beginContour() and endContour() function to * create negative shapes within shapes such as the center of the - * letter 'O'. beginContour() begins recording vertices for the + * letter "O". beginContour() begins recording vertices for the * shape and endContour() stops recording. The vertices that * define a negative shape must "wind" in the opposite direction from * the exterior shape. First draw vertices for the exterior shape in @@ -1689,7 +1678,7 @@ public class PGraphics extends PImage implements PConstants { /** * Use the beginContour() and endContour() function to * create negative shapes within shapes such as the center of the - * letter 'O'. beginContour() begins recording vertices for + * letter "O". beginContour() begins recording vertices for * the shape and endContour() stops recording. The vertices * that define a negative shape must "wind" in the opposite direction * from the exterior shape. First draw vertices for the exterior shape @@ -1718,7 +1707,7 @@ public class PGraphics extends PImage implements PConstants { * * The endShape() function is the companion to beginShape() * and may only be called after beginShape(). When endshape() - * is called, all of image data defined since the previous call to + * is called, all the image data defined since the previous call to * beginShape() is written into the image buffer. The constant CLOSE * as the value for the MODE parameter to close the shape (to connect the * beginning and the end). @@ -1974,7 +1963,6 @@ public class PGraphics extends PImage implements PConstants { * Applies the shader specified by the parameters. It's compatible with * the P2D and P3D renderers, but not with the default renderer. * - * * @webref rendering:shaders * @webBrief Applies the shader specified by the parameters * @param shader name of shader file @@ -1993,11 +1981,9 @@ public class PGraphics extends PImage implements PConstants { /** - * * Restores the default shaders. Code that runs after resetShader() * will not be affected by previously defined shaders. * - * * @webref rendering:shaders * @webBrief Restores the default shaders */ @@ -2033,7 +2019,6 @@ public class PGraphics extends PImage implements PConstants { * by the parameters. The boundaries are drawn based on the state * of the imageMode() function, either CORNER, CORNERS, or CENTER. * - * * @webref rendering * @webBrief Limits the rendering to the boundaries of a rectangle defined * by the parameters @@ -2081,10 +2066,8 @@ public class PGraphics extends PImage implements PConstants { /** - * * Disables the clipping previously started by the clip() function. * - * * @webref rendering * @webBrief Disables the clipping previously started by the clip() function */ @@ -2133,7 +2116,6 @@ public class PGraphics extends PImage implements PConstants { * BURN. On older hardware, the LIGHTEST, DARKEST, and DIFFERENCE modes might * not be available as well. * - * * @webref rendering * @webBrief Blends the pixels in the display window according to a defined mode * @param mode the blending mode to use @@ -2203,9 +2185,9 @@ public class PGraphics extends PImage implements PConstants { /** * - * Specifies vertex coordinates for Bezier curves. Each call to + * Specifies vertex coordinates for Bézier curves. Each call to * bezierVertex() defines the position of two control points and one - * anchor point of a Bezier curve, adding a new segment to a line or shape. + * anchor point of a Bézier curve, adding a new segment to a line or shape. * The first time bezierVertex() is used within a * beginShape() call, it must be prefaced with a call to * vertex() to set the first anchor point. This function must be @@ -2264,9 +2246,9 @@ public class PGraphics extends PImage implements PConstants { /** - * Specifies vertex coordinates for quadratic Bezier curves. Each call + * Specifies vertex coordinates for quadratic Bézier curves. Each call * to quadraticVertex() defines the position of one control - * point and one anchor point of a Bezier curve, adding a new segment + * point and one anchor point of a Bézier curve, adding a new segment * to a line or shape. The first time quadraticVertex() is used * within a beginShape() call, it must be prefaced with a call * to vertex() to set the first anchor point. This function must @@ -2345,19 +2327,17 @@ public class PGraphics extends PImage implements PConstants { /** - * - * Specifies vertex coordinates for curves. This function may only be used - * between beginShape() and endShape() and only when there is - * no MODE parameter specified to beginShape(). The first and last - * points in a series of curveVertex() lines will be used to guide - * the beginning and end of a the curve. A minimum of four points is - * required to draw a tiny curve between the second and third points. - * Adding a fifth point with curveVertex() will draw the curve - * between the second, third, and fourth points. The curveVertex() - * function is an implementation of Catmull-Rom splines. Using the 3D - * version requires rendering with P3D (see the Environment reference for - * more information). - * + * Specifies vertex coordinates for curves. This function may only be used + * between beginShape() and endShape() and only when there is + * no MODE parameter specified to beginShape(). The first and last + * points in a series of curveVertex() lines will be used to guide + * the beginning and end of the curve. A minimum of four points is + * required to draw a tiny curve between the second and third points. + * Adding a fifth point with curveVertex() will draw the curve + * between the second, third, and fourth points. The curveVertex() + * function is an implementation of Catmull-Rom splines. Using the 3D + * version requires rendering with P3D (see the Environment reference for + * more information). * * @webref shape:vertex * @webBrief Specifies vertex coordinates for curves @@ -2519,7 +2499,6 @@ public class PGraphics extends PImage implements PConstants { * setting the pixel using set() or drawing the point using either * circle() or square(). * - * * @webref shape:2d primitives * @webBrief Draws a point, a coordinate in space at the dimension of one pixel * @param x x-coordinate of the point @@ -2694,7 +2673,6 @@ public class PGraphics extends PImage implements PConstants { * each corner separately, starting with the top-left corner and moving * clockwise around the rectangle. * - * * @webref shape:2d primitives * @webBrief Draws a rectangle to the screen * @param a x-coordinate of the rectangle by default @@ -2749,7 +2727,7 @@ public class PGraphics extends PImage implements PConstants { // Still need to do a lot of work here to make it behave across renderers // (e.g. not all renderers use the vertices array) // Also seems to be some issues on quality here (too dense) - // http://code.google.com/p/processing/issues/detail?id=265 + // https://github.com/processing/processing/issues/304 // private void quadraticVertex(float cpx, float cpy, float x, float y) { // float[] prev = vertices[vertexCount - 1]; // float prevX = prev[X]; @@ -2859,7 +2837,6 @@ public class PGraphics extends PImage implements PConstants { * these parameters are interpreted, however, may be changed with the * rectMode() function. * - * * @webref shape:2d primitives * @webBrief Draws a square to the screen * @param x x-coordinate of the rectangle by default @@ -3094,10 +3071,8 @@ public class PGraphics extends PImage implements PConstants { // BOX /** - * - * A box is an extruded rectangle. A box with equal dimension on all sides - * is a cube. - * + * A box is an extruded rectangle. A box with equal dimension + * on all sides is a cube. * * @webref shape:3d primitives * @webBrief A box is an extruded rectangle @@ -3190,7 +3165,6 @@ public class PGraphics extends PImage implements PConstants { * and vertical resolution independently, use the version of the functions * with two parameters. * - * *

        Advanced

        * Code for sphereDetail() submitted by toxi [031031]. * Code for enhanced u/v version from davbol [080801]. @@ -3223,8 +3197,8 @@ public class PGraphics extends PImage implements PConstants { cx[i] = cosLUT[(int) (i*delta) % SINCOS_LENGTH]; cz[i] = sinLUT[(int) (i*delta) % SINCOS_LENGTH]; } - // computing vertexlist - // vertexlist starts at south pole + // Computing vertex list. + // The vertex list starts at southern pole. int vertCount = ures * (vres-1) + 2; int currVert = 0; @@ -3253,10 +3227,8 @@ public class PGraphics extends PImage implements PConstants { /** - * * A sphere is a hollow ball made from tessellated triangles. * - * *

        Advanced

        *

        * Implementation notes: @@ -3266,7 +3238,7 @@ public class PGraphics extends PImage implements PConstants { * in the center point *

        * sphere is a series of concentric circles who radii vary - * along the shape, based on, er.. cos or something + * along the shape, based on, err... cos or something *

            * [toxi 031031] new sphere code. removed all multiplies with
            * radius, as scale() will take care of that anyway
        @@ -3291,7 +3263,7 @@ public class PGraphics extends PImage implements PConstants {
             edge(false);
         
         
        -    // 1st ring from south pole
        +    // 1st ring from southern pole
             beginShape(TRIANGLE_STRIP);
             for (int i = 0; i < sphereDetailU; i++) {
               normal(0, -1, 0);
        @@ -3359,10 +3331,9 @@ public class PGraphics extends PImage implements PConstants {
            * Evaluates the Bezier at point t for points a, b, c, d. The parameter t
            * varies between 0 and 1, a and d are points on the curve, and b and c are
            * the control points. This can be done once with the x coordinates and a
        -   * second time with the y coordinates to get the location of a bezier curve
        +   * second time with the y coordinates to get the location of a Bézier curve
            * at t.
            *
        -   *
            * 

        Advanced

        * For instance, to convert the following example:
            * stroke(255, 102, 0);
        @@ -3400,17 +3371,15 @@ public class PGraphics extends PImage implements PConstants {
             return (a*t1 + 3*b*t)*t1*t1 + (3*c*t1 + d*t)*t*t;
           }
           /**
        -   *
        -   * Calculates the tangent of a point on a Bezier curve. There is a good
        +   * Calculates the tangent of a point on a Bézier curve. There is a good
            * definition of tangent on Wikipedia.
            *
        -   *
            * 

        Advanced

        * Code submitted by Dave Bollinger (davbol) for release 0136. * * @webref shape:curves - * @webBrief Calculates the tangent of a point on a Bezier curve + * @webBrief Calculates the tangent of a point on a Bézier curve * @param a coordinate of first point on the curve * @param b coordinate of first control point * @param c coordinate of second control point @@ -3442,14 +3411,12 @@ public class PGraphics extends PImage implements PConstants { /** - * - * Sets the resolution at which Beziers display. The default value is 20. This - * function is only useful when using the P3D renderer; the default - * P2D renderer does not use this information. - * + * Sets the resolution at which Bézier curves display. The default value is 20. + * This function is only useful when using the P2D or P3D renderer; + * the default (JAVA2D) renderer does not use this information. * * @webref shape:curves - * @webBrief Sets the resolution at which Beziers display + * @webBrief Sets the resolution at which Bézier curves display * @param detail resolution of the curves * @see PGraphics#curve(float, float, float, float, float, float, float, float, * float, float, float, float) @@ -3487,17 +3454,16 @@ public class PGraphics extends PImage implements PConstants { /** * - * Draws a Bezier curve on the screen. These curves are defined by a series + * Draws a Bézier curve on the screen. These curves are defined by a series * of anchor and control points. The first two parameters specify the first * anchor point and the last two parameters specify the other anchor point. * The middle parameters specify the control points which define the shape - * of the curve. Bezier curves were developed by French engineer Pierre + * of the curve. The curves were developed by French engineer Pierre * Bezier. Using the 3D version requires rendering with P3D (see the * Environment reference for more information). * - * *

        Advanced

        - * Draw a cubic bezier curve. The first and last points are + * Draw a cubic Bézier curve. The first and last points are * the on-curve points. The middle two are the 'control' points, * or 'handles' in an application like Illustrator. *

        @@ -3520,7 +3486,7 @@ public class PGraphics extends PImage implements PConstants { *

        bezier(x1, y1, cx, cy, cx, cy, x2, y2);
        * * @webref shape:curves - * @webBrief Draws a Bezier curve on the screen + * @webBrief Draws a Bézier curve on the screen * @param x1 coordinates for the first anchor point * @param y1 coordinates for the first anchor point * @param z1 coordinates for the first anchor point @@ -3556,7 +3522,6 @@ public class PGraphics extends PImage implements PConstants { // CATMULL-ROM CURVE /** - * * Evaluates the curve at point t for points a, b, * c, d. The parameter t may range from 0 (the start of the * curve) and 1 (the end of the curve). a and d are the control @@ -3565,7 +3530,6 @@ public class PGraphics extends PImage implements PConstants { * second time with the y coordinates to get the location of a curve at * t. * - * * @webref shape:curves * @webBrief Evaluates the curve at point t for points a, b, c, d * @param a coordinate of first control point @@ -3594,12 +3558,10 @@ public class PGraphics extends PImage implements PConstants { /** - * * Calculates the tangent of a point on a curve. There's a good definition * of tangent on Wikipedia. * - * *

        Advanced

        * Code thanks to Dave Bollinger (Bug #715) * @@ -3631,12 +3593,10 @@ public class PGraphics extends PImage implements PConstants { /** - * * Sets the resolution at which curves display. The default value is 20. * This function is only useful when using the P3D renderer as the default * P2D renderer does not use this information. * - * * @webref shape:curves * @webBrief Sets the resolution at which curves display * @param detail resolution of the curves @@ -3740,12 +3700,11 @@ public class PGraphics extends PImage implements PConstants { * implementation of Catmull-Rom splines. Using the 3D version requires * rendering with P3D (see the Environment reference for more information). * - * *

        Advanced

        * As of revision 0070, this function no longer doubles the first * and last points. The curves are a bit more boring, but it's more * mathematically correct, and properly mirrored in curvePoint(). - *

        + *

        * Identical to typing out:

            * beginShape();
            * curveVertex(x1, y1);
        @@ -3892,7 +3851,6 @@ public class PGraphics extends PImage implements PConstants {
            * The parameter must be written in ALL CAPS because Processing is a
            * case-sensitive language.
            *
        -   *
            * @webref image:loading & displaying
            * @webBrief Modifies the location from which images draw
            * @param mode either CORNER, CORNERS, or CENTER
        @@ -4102,8 +4060,7 @@ public class PGraphics extends PImage implements PConstants {
            * shapeMode(CENTER) draws the shape from its center point and uses
            * the third and forth parameters of shape() to specify the width
            * and height. The parameter must be written in "ALL CAPS" because
        -   * Processing is a case sensitive language.
        -   *
        +   * Processing is a case-sensitive language.
            *
            * @webref shape:loading & displaying
            * @webBrief Modifies the location from which shapes draw
        @@ -4138,7 +4095,6 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Draws shapes to the display window. Shapes must be in the sketch's "data"
            * directory to load correctly. Select "Add file..." from the "Sketch" menu to
            * add the shape. Processing currently works with SVG, OBJ, and custom-created
        @@ -4148,7 +4104,6 @@ public class PGraphics extends PImage implements PConstants {
            * d parameters specify a different size. The shapeMode() function
            * can be used to change the way these parameters are interpreted.
            *
        -   *
            * @webref shape:loading & displaying
            * @webBrief Displays shapes to the screen
            * @param shape the shape to display
        @@ -4157,8 +4112,6 @@ public class PGraphics extends PImage implements PConstants {
            * @see PShape
            * @see PApplet#loadShape(String)
            * @see PGraphics#shapeMode(int)
        -   *
        -   *      Convenience method to draw at a particular location.
            */
           public void shape(PShape shape, float x, float y) {
             if (shape.isVisible()) {  // don't do expensive matrix ops if invisible
        @@ -4243,13 +4196,13 @@ public class PGraphics extends PImage implements PConstants {
               InputStream input = getClass().getResourceAsStream("/font/ProcessingSansPro-Regular.ttf");
               if (input != null) {
                 baseFont = Font.createFont(Font.TRUETYPE_FONT, input);
        +        defaultFontName = baseFont.getName();
               }
        -
             } catch (Exception e) {
        -      // Fall back to how this was handled in 3.x, ugly!
               e.printStackTrace(); // dammit
             }
         
        +    // Fall back to how this was handled in 3.x, ugly!
             if (baseFont == null) {
               baseFont = new Font("Lucida Sans", Font.PLAIN, 1);
             }
        @@ -4303,7 +4256,6 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Sets the current alignment for drawing text. The parameters LEFT, CENTER, and
            * RIGHT set the display characteristics of the letters in relation to the
            * values for the x and y parameters of the text()
        @@ -4328,8 +4280,6 @@ public class PGraphics extends PImage implements PConstants {
            * textDescent() so that the hack works even if you change the size of
            * the font.
            *
        -   *
        -   *
            * @webref typography:attributes
            * @webBrief Sets the current alignment for drawing text
            * @param alignX horizontal alignment, either LEFT, CENTER, or RIGHT
        @@ -4348,11 +4298,9 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Returns ascent of the current font at its current size. This information is
            * useful for determining the height of the font above the baseline.
            *
        -   *
            * @webref typography:metrics
            * @webBrief Returns ascent of the current font at its current size
            * @see PGraphics#textDescent()
        @@ -4366,7 +4314,6 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Returns descent of the current font at its current size. This information is
            * useful for determining the height of the font below the baseline.
            *
        @@ -4483,7 +4430,6 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Sets the spacing between lines of text in units of pixels. This setting will
            * be used in all subsequent calls to the text() function. Note, however,
            * that the leading is reset by textSize(). For example, if the leading
        @@ -4491,7 +4437,6 @@ public class PGraphics extends PImage implements PConstants {
            * at a later point, the leading will be reset to the default for the text size
            * of 48.
            *
        -   *
            * @webref typography:attributes
            * @webBrief Sets the spacing between lines of text in units of pixels
            * @param leading the size in pixels for spacing between lines
        @@ -4507,7 +4452,6 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Sets the way text draws to the screen, either as texture maps or as vector
            * geometry. The default textMode(MODEL), uses textures to render the
            * fonts. The textMode(SHAPE) mode draws text using the glyph outlines of
        @@ -4548,11 +4492,11 @@ public class PGraphics extends PImage implements PConstants {
             if (textModeCheck(mode)) {
               textMode = mode;
             } else {
        -      String modeStr = String.valueOf(mode);
        -      switch (mode) {
        -        case MODEL: modeStr = "MODEL"; break;
        -        case SHAPE: modeStr = "SHAPE"; break;
        -      }
        +      String modeStr = switch (mode) {
        +        case MODEL -> "MODEL";
        +        case SHAPE -> "SHAPE";
        +        default -> String.valueOf(mode);
        +      };
               showWarning("textMode(" + modeStr + ") is not supported by this renderer.");
             }
           }
        @@ -4564,11 +4508,9 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Sets the current font size. This size will be used in all subsequent
            * calls to the text() function. Font size is measured in units of pixels.
            *
        -   *
            * @webref typography:attributes
            * @webBrief Sets the current font size
            * @param size the size of the letters in units of pixels
        @@ -4628,10 +4570,8 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Calculates and returns the width of any character or text string.
            *
        -   *
            * @webref typography:attributes
            * @webBrief Calculates and returns the width of any character or text string
            * @param str the String of characters to measure
        @@ -4680,7 +4620,7 @@ public class PGraphics extends PImage implements PConstants {
         
           /**
            * Implementation of returning the text width of
        -   * the chars [start, stop) in the buffer.
        +   * the chars [start, stop) in the buffer.
            * Unlike the previous version that was inside PFont, this will
            * return the size not of a 1 pixel font, but the actual current size.
            */
        @@ -4698,7 +4638,6 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Draws text to the screen. Displays the information specified in the first
            * parameter on the screen in the position specified by the additional
            * parameters. A default font will be used unless a font is set with the
        @@ -4718,7 +4657,6 @@ public class PGraphics extends PImage implements PConstants {
            * a PFont with textFont(). In that case, a generic sans-serif font will
            * be used instead. (See the third example above.)
            *
        -   *
            * @webref typography:loading & displaying
            * @webBrief Draws text to the screen
            * @param c the alphanumeric character to be displayed
        @@ -4895,24 +4833,26 @@ public class PGraphics extends PImage implements PConstants {
         
             float hradius, vradius;
             switch (rectMode) {
        -    case CORNER:
        -      x2 += x1; y2 += y1;
        -      break;
        -    case RADIUS:
        -      hradius = x2;
        -      vradius = y2;
        -      x2 = x1 + hradius;
        -      y2 = y1 + vradius;
        -      x1 -= hradius;
        -      y1 -= vradius;
        -      break;
        -    case CENTER:
        -      hradius = x2 / 2.0f;
        -      vradius = y2 / 2.0f;
        -      x2 = x1 + hradius;
        -      y2 = y1 + vradius;
        -      x1 -= hradius;
        -      y1 -= vradius;
        +      case CORNER -> {
        +        x2 += x1;
        +        y2 += y1;
        +      }
        +      case RADIUS -> {
        +        hradius = x2;
        +        vradius = y2;
        +        x2 = x1 + hradius;
        +        y2 = y1 + vradius;
        +        x1 -= hradius;
        +        y1 -= vradius;
        +      }
        +      case CENTER -> {
        +        hradius = x2 / 2.0f;
        +        vradius = y2 / 2.0f;
        +        x2 = x1 + hradius;
        +        y2 = y1 + vradius;
        +        x1 -= hradius;
        +        y1 -= vradius;
        +      }
             }
             if (x2 < x1) {
               float temp = x1; x1 = x2; x2 = temp;
        @@ -5005,7 +4945,7 @@ public class PGraphics extends PImage implements PConstants {
           /**
            * Emit a sentence of text, defined as a chunk of text without any newlines.
            * @param stop non-inclusive, the end of the text in question
        -   * @return false if cannot fit
        +   * @return false if it cannot fit
            */
           protected boolean textSentence(char[] buffer, int start, int stop,
                                          float boxWidth) {
        @@ -5320,7 +5260,6 @@ public class PGraphics extends PImage implements PConstants {
            * transformations (rotate, scale, translate) and the drawing styles
            * at the same time.
            *
        -   *
            * @webref structure
            * @webBrief The push() function saves the current drawing style
            * settings and transformations, while pop() restores these
        @@ -5358,7 +5297,6 @@ public class PGraphics extends PImage implements PConstants {
            * transformations (rotate, scale, translate) and the drawing styles
            * at the same time.
            *
        -   *
            * @webref structure
            * @webBrief The pop() function restores the previous drawing style
            * settings and transformations after push() has changed them
        @@ -5377,7 +5315,6 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Pushes the current transformation matrix onto the matrix stack.
            * Understanding pushMatrix() and popMatrix() requires
            * understanding the concept of a matrix stack. The pushMatrix()
        @@ -5387,7 +5324,6 @@ public class PGraphics extends PImage implements PConstants {
            * the other transformation functions and may be embedded to control the
            * scope of the transformations.
            *
        -   *
            * @webref transform
            * @webBrief Pushes the current transformation matrix onto the matrix stack
            * @see PGraphics#popMatrix()
        @@ -5404,7 +5340,6 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Pops the current transformation matrix off the matrix stack.
            * Understanding pushing and popping requires understanding the concept of
            * a matrix stack. The pushMatrix() function saves the current
        @@ -5413,7 +5348,6 @@ public class PGraphics extends PImage implements PConstants {
            * in conjunction with the other transformation functions and may be
            * embedded to control the scope of the transformations.
            *
        -   *
            * @webref transform
            * @webBrief Pops the current transformation matrix off the matrix stack
            * @see PGraphics#pushMatrix()
        @@ -5430,14 +5364,13 @@ public class PGraphics extends PImage implements PConstants {
         
         
           /**
        -   *
            * Specifies an amount to displace objects within the display window. The
            * x parameter specifies left/right translation, the y parameter
            * specifies up/down translation, and the z parameter specifies
            * translations toward/away from the screen. Using this function with the
            * z parameter requires using P3D as a parameter in combination with size
        -   * as shown in the above example. 
        - *
        + * as shown in the above example. + *

        * Transformations are cumulative and apply to everything that happens after and * subsequent calls to the function accumulates the effect. For example, calling * translate(50, 0) and then translate(20, 0) is the same as @@ -5446,7 +5379,6 @@ public class PGraphics extends PImage implements PConstants { * function can be further controlled by using pushMatrix() and * popMatrix(). * - * * @webref transform * @webBrief Specifies an amount to displace objects within the display window * @param x left/right translation @@ -5490,7 +5422,6 @@ public class PGraphics extends PImage implements PConstants { * matrix by a rotation matrix. This function can be further controlled by * the pushMatrix() and popMatrix(). * - * * @webref transform * @webBrief Rotates a shape the amount specified by the angle parameter * @param angle angle of rotation specified in radians @@ -5522,7 +5453,6 @@ public class PGraphics extends PImage implements PConstants { * This function requires using P3D as a third parameter to size() * as shown in the example above. * - * * @webref transform * @webBrief Rotates a shape around the x-axis the amount specified by the * angle parameter @@ -5555,7 +5485,6 @@ public class PGraphics extends PImage implements PConstants { * This function requires using P3D as a third parameter to size() * as shown in the examples above. * - * * @webref transform * @webBrief Rotates a shape around the y-axis the amount specified by the * angle parameter @@ -5588,7 +5517,6 @@ public class PGraphics extends PImage implements PConstants { * This function requires using P3D as a third parameter to size() * as shown in the examples above. * - * * @webref transform * @webBrief Rotates a shape around the z-axis the amount specified by the * angle parameter @@ -5633,7 +5561,6 @@ public class PGraphics extends PImage implements PConstants { * example above. This function can be further controlled with * pushMatrix() and popMatrix(). * - * * @webref transform * @webBrief Increases or decreases the size of a shape by expanding and * contracting vertices @@ -5654,7 +5581,7 @@ public class PGraphics extends PImage implements PConstants { /** *

        Advanced

        * Scale in X and Y. Equivalent to scale(sx, sy, 1). - * + *

        * Not recommended for use in 3D, because the z-dimension is just * scaled by 1, since there's no way to know what else to scale it by. * @@ -5691,7 +5618,6 @@ public class PGraphics extends PImage implements PConstants { * matrix by a rotation matrix. This function can be further controlled by * the pushMatrix() and popMatrix() functions. * - * * @webref transform * @webBrief Shears a shape around the x-axis the amount specified by the * angle parameter @@ -5725,7 +5651,6 @@ public class PGraphics extends PImage implements PConstants { * matrix by a rotation matrix. This function can be further controlled by * the pushMatrix() and popMatrix() functions. * - * * @webref transform * @webBrief Shears a shape around the y-axis the amount specified by the * angle parameter @@ -5748,10 +5673,8 @@ public class PGraphics extends PImage implements PConstants { /** - * - * Replaces the current matrix with the identity matrix. The equivalent function - * in OpenGL is glLoadIdentity(). - * + * Replaces the current matrix with the identity matrix. + * The equivalent function in OpenGL is glLoadIdentity(). * * @webref transform * @webBrief Replaces the current matrix with the identity matrix @@ -5765,16 +5688,14 @@ public class PGraphics extends PImage implements PConstants { } /** - * * Multiplies the current matrix by the one specified through the * parameters. This is very slow because it will try to calculate the * inverse of the transform, so avoid it whenever possible. The equivalent * function in OpenGL is glMultMatrix(). * - * * @webref transform - * @webBrief Multiplies the current matrix by the one specified through the - * parameters + * @webBrief Multiplies the current matrix by the one specified in the + * parameter * @see PGraphics#pushMatrix() * @see PGraphics#popMatrix() * @see PGraphics#resetMatrix() @@ -5897,11 +5818,9 @@ public class PGraphics extends PImage implements PConstants { /** - * * Prints the current matrix to the Console (the text window at the bottom * of Processing). * - * * @webref transform * @webBrief Prints the current matrix to the Console (the text window at the bottom * of Processing) @@ -5920,7 +5839,6 @@ public class PGraphics extends PImage implements PConstants { // CAMERA /** - * * The beginCamera() and endCamera() functions enable * advanced customization of the camera space. The functions are useful if * you want to more control over camera movement, however for most users, @@ -5938,7 +5856,6 @@ public class PGraphics extends PImage implements PConstants { * following endCamera() and pairs of beginCamera() and * endCamera() cannot be nested. * - * * @webref lights_camera:camera * @webBrief The beginCamera() and endCamera() functions enable * advanced customization of the camera space @@ -5954,12 +5871,10 @@ public class PGraphics extends PImage implements PConstants { } /** - * * The beginCamera() and endCamera() functions enable * advanced customization of the camera space. Please see the reference for * beginCamera() for a description of how the functions are used. * - * * @webref lights_camera:camera * @webBrief The beginCamera() and endCamera() functions enable * advanced customization of the camera space @@ -5971,7 +5886,6 @@ public class PGraphics extends PImage implements PConstants { } /** - * * Sets the position of the camera through setting the eye position, the * center of the scene, and which axis is facing upward. Moving the eye * position and the direction it is pointing (the center of the scene) @@ -5982,7 +5896,6 @@ public class PGraphics extends PImage implements PConstants { * 180.0), width/2.0, height/2.0, 0, 0, 1, 0). This function is similar * to gluLookAt() in OpenGL, but it first clears the current camera settings. * - * * @webref lights_camera:camera * @webBrief Sets the position of the camera * @see PGraphics#beginCamera() @@ -6040,7 +5953,6 @@ public class PGraphics extends PImage implements PConstants { * are the minimum and maximum z values. If no parameters are given, the default * is used: ortho(-width/2, width/2, -height/2, height/2). * - * * @webref lights_camera:camera * @webBrief Sets an orthographic projection and defines a parallel clipping * volume @@ -6083,7 +5995,6 @@ public class PGraphics extends PImage implements PConstants { * default values are: perspective(PI/3.0, width/height, cameraZ/10.0, * cameraZ*10.0) where cameraZ is ((height/2.0) / tan(PI*60.0/360.0)) * - * * @webref lights_camera:camera * @webBrief Sets a perspective projection applying foreshortening, making distant * objects appear smaller than closer ones @@ -6096,7 +6007,7 @@ public class PGraphics extends PImage implements PConstants { * @param fovy field-of-view angle (in radians) for vertical direction * @param aspect ratio of width to height * @param zNear z-position of nearest clipping plane - * @param zFar z-position of farthest clipping plane + * @param zFar z-position of the farthest clipping plane */ public void perspective(float fovy, float aspect, float zNear, float zFar) { showMissingWarning("perspective"); @@ -6124,7 +6035,6 @@ public class PGraphics extends PImage implements PConstants { * Works like glFrustum, except it wipes out the current perspective matrix * rather than multiplying itself with it. * - * * @webref lights_camera:camera * @webBrief Sets a perspective matrix defined through the parameters * @param left left coordinate of the clipping plane @@ -6147,11 +6057,9 @@ public class PGraphics extends PImage implements PConstants { } /** - * * Prints the current projection matrix to the Console (the text window at * the bottom of Processing). * - * * @webref lights_camera:camera * @webBrief Prints the current projection matrix to the Console * @see PGraphics#camera(float, float, float, float, float, float, float, float, float) @@ -6168,11 +6076,9 @@ public class PGraphics extends PImage implements PConstants { /** - * * Takes a three-dimensional X, Y, Z position and returns the X value for * where it will appear on a (two-dimensional) screen. * - * * @webref lights_camera:coordinates * @webBrief Takes a three-dimensional X, Y, Z position and returns the X value for * where it will appear on a (two-dimensional) screen @@ -6188,11 +6094,9 @@ public class PGraphics extends PImage implements PConstants { /** - * * Takes a three-dimensional X, Y, Z position and returns the Y value for * where it will appear on a (two-dimensional) screen. * - * * @webref lights_camera:coordinates * @webBrief Takes a three-dimensional X, Y, Z position and returns the Y value for * where it will appear on a (two-dimensional) screen @@ -6227,11 +6131,9 @@ public class PGraphics extends PImage implements PConstants { /** - * * Takes a three-dimensional X, Y, Z position and returns the Z value for * where it will appear on a (two-dimensional) screen. * - * * @webref lights_camera:coordinates * @webBrief Takes a three-dimensional X, Y, Z position and returns the Z value for * where it will appear on a (two-dimensional) screen @@ -6262,7 +6164,6 @@ public class PGraphics extends PImage implements PConstants { * (x, y, z) coordinate returned by the model functions is used to place * another box in the same location. * - * * @webref lights_camera:coordinates * @webBrief Returns the three-dimensional X, Y, Z position in model space * @param x 3D x-coordinate to be mapped @@ -6278,7 +6179,6 @@ public class PGraphics extends PImage implements PConstants { /** - * * Returns the three-dimensional X, Y, Z position in model space. This * returns the Y value for a given coordinate based on the current set of * transformations (scale, rotate, translate, etc.) The Y value can be used @@ -6292,7 +6192,6 @@ public class PGraphics extends PImage implements PConstants { * (x, y, z) coordinate returned by the model functions is used to place * another box in the same location. * - * * @webref lights_camera:coordinates * @webBrief Returns the three-dimensional X, Y, Z position in model space * @param x 3D x-coordinate to be mapped @@ -6322,7 +6221,6 @@ public class PGraphics extends PImage implements PConstants { * (x, y, z) coordinate returned by the model functions is used to place * another box in the same location. * - * * @webref lights_camera:coordinates * @webBrief Returns the three-dimensional X, Y, Z position in model space * @param x 3D x-coordinate to be mapped @@ -6359,7 +6257,6 @@ public class PGraphics extends PImage implements PConstants { * textAlign(), textFont(), textMode(), textSize(), textLeading(), * emissive(), specular(), shininess(), ambient() * - * * @webref structure * @webBrief Saves the current style settings and popStyle() restores the prior settings * @see PGraphics#popStyle() @@ -6376,7 +6273,6 @@ public class PGraphics extends PImage implements PConstants { } /** - * * The pushStyle() function saves the current style settings and * popStyle() restores the prior settings; these functions are * always used together. They allow you to change the style settings and @@ -6385,7 +6281,6 @@ public class PGraphics extends PImage implements PConstants { * pushStyle() and popStyle() functions can be embedded to * provide more control (see the second example above for a demonstration.) * - * * @webref structure * @webBrief Saves the current style settings and popStyle() restores the prior settings * @see PGraphics#pushStyle() @@ -6539,7 +6434,6 @@ public class PGraphics extends PImage implements PConstants { // STROKE CAP/JOIN/WEIGHT /** - * * Sets the width of the stroke used for lines, points, and the border around * shapes. All widths are set in units of pixels.
        *
        @@ -6548,7 +6442,6 @@ public class PGraphics extends PImage implements PConstants { * setting the pixel using set() or drawing the point using either * circle() or square(). * - * * @webref shape:attributes * @webBrief Sets the width of the stroke used for lines, points, and the border * around shapes @@ -6562,12 +6455,10 @@ public class PGraphics extends PImage implements PConstants { } /** - * * Sets the style of the joints which connect line segments. These joints are * either mitered, beveled, or rounded and specified with the corresponding * parameters MITER, BEVEL, and ROUND. The default joint is MITER. * - * * @webref shape:attributes * @webBrief Sets the style of the joints which connect line segments * @param join either MITER, BEVEL, ROUND @@ -6580,7 +6471,6 @@ public class PGraphics extends PImage implements PConstants { } /** - * * Sets the style for rendering line endings. These ends are either squared, * extended, or rounded, each of which specified with the corresponding * parameters: SQUARE, PROJECT, and ROUND. The default cap is ROUND.
        @@ -6608,11 +6498,9 @@ public class PGraphics extends PImage implements PConstants { /** - * * Disables drawing the stroke (outline). If both noStroke() and * noFill() are called, nothing will be drawn to the screen. * - * * @webref color:setting * @webBrief Disables drawing the stroke (outline) * @see PGraphics#stroke(int, float) @@ -6733,7 +6621,6 @@ public class PGraphics extends PImage implements PConstants { * Removes the current fill value for displaying images and reverts to * displaying images with their original hues. * - * * @webref image:loading & displaying * @webBrief Removes the current fill value for displaying images and reverts to * displaying images with their original hues @@ -6771,7 +6658,6 @@ public class PGraphics extends PImage implements PConstants { * The tint() function is also used to control the coloring of textures * in 3D. * - * * @webref image:loading & displaying * @webBrief Sets the fill value for displaying images * @usage web_application @@ -6851,7 +6737,6 @@ public class PGraphics extends PImage implements PConstants { * Disables filling geometry. If both noStroke() and noFill() * are called, nothing will be drawn to the screen. * - * * @webref color:setting * @webBrief Disables filling geometry * @usage web_application @@ -6886,7 +6771,6 @@ public class PGraphics extends PImage implements PConstants { *

        * To change the color of an image (or a texture), use tint(). * - * * @webref color:setting * @webBrief Sets the color used to fill shapes * @usage web_application @@ -6974,7 +6858,6 @@ public class PGraphics extends PImage implements PConstants { * reflect. Used in combination with emissive(), specular(), * and shininess() in setting the material properties of shapes. * - * * @webref lights_camera:material properties * @webBrief Sets the ambient reflectance for shapes drawn to the screen * @usage web_application @@ -7030,7 +6913,6 @@ public class PGraphics extends PImage implements PConstants { * with emissive(), ambient(), and shininess() in * setting the material properties of shapes. * - * * @webref lights_camera:material properties * @webBrief Sets the specular color of the materials used for shapes drawn to the * screen, which sets the color of highlights @@ -7090,7 +6972,6 @@ public class PGraphics extends PImage implements PConstants { * with ambient(), specular(), and emissive() in * setting the material properties of shapes. * - * * @webref lights_camera:material properties * @webBrief Sets the amount of gloss in the surface of shapes * @usage web_application @@ -7110,7 +6991,6 @@ public class PGraphics extends PImage implements PConstants { * specular(), and shininess() in setting the material * properties of shapes. * - * * @webref lights_camera:material properties * @webBrief Sets the emissive color of the material used for drawing shapes drawn to * the screen @@ -7180,7 +7060,6 @@ public class PGraphics extends PImage implements PConstants { * looping program will cause them to only have an effect the first time * through the loop. * - * * @webref lights_camera:lights * @webBrief Sets the default ambient light, directional light, falloff, and specular * values @@ -7202,7 +7081,6 @@ public class PGraphics extends PImage implements PConstants { * lighting so that 2D geometry (which does not require lighting) can be * drawn after a set of lighted 3D geometry. * - * * @webref lights_camera:lights * @webBrief Disable all lighting * @usage web_application @@ -7223,7 +7101,6 @@ public class PGraphics extends PImage implements PConstants { * through the loop. The v1, v2, and v3 parameters are * interpreted as either RGB or HSB values, depending on the current color mode. * - * * @webref lights_camera:lights * @webBrief Adds an ambient light * @usage web_application @@ -7253,7 +7130,7 @@ public class PGraphics extends PImage implements PConstants { /** * * Adds a directional light. Directional light comes from one direction and - * is stronger when hitting a surface squarely and weaker if it hits at a a + * is stronger when hitting a surface squarely and weaker if it hits at a * gentle angle. After hitting a surface, a directional lights scatters in * all directions. Lights need to be included in the draw() to * remain persistent in a looping program. Placing them in the @@ -7264,7 +7141,6 @@ public class PGraphics extends PImage implements PConstants { * direction the light is facing. For example, setting ny to -1 will * cause the geometry to be lit from below (the light is facing directly upward). * - * * @webref lights_camera:lights * @webBrief Adds a directional light * @usage web_application @@ -7293,7 +7169,6 @@ public class PGraphics extends PImage implements PConstants { * as either RGB or HSB values, depending on the current color mode. The * x, y, and z parameters set the position of the light. * - * * @webref lights_camera:lights * @webBrief Adds a point light * @usage web_application @@ -7316,7 +7191,7 @@ public class PGraphics extends PImage implements PConstants { /** * - * Adds a spot light. Lights need to be included in the draw() to remain + * Adds a spotlight. Lights need to be included in the draw() to remain * persistent in a looping program. Placing them in the setup() of a * looping program will cause them to only have an effect the first time through * the loop. The v1, v2, and v3 parameters are interpreted @@ -7328,7 +7203,7 @@ public class PGraphics extends PImage implements PConstants { * that cone. * * @webref lights_camera:lights - * @webBrief Adds a spot light + * @webBrief Adds a spotlight * @usage web_application * @param v1 red or hue value (depending on current color mode) * @param v2 green or saturation value (depending on current color @@ -7338,9 +7213,9 @@ public class PGraphics extends PImage implements PConstants { * @param x x-coordinate of the light * @param y y-coordinate of the light * @param z z-coordinate of the light - * @param nx direction along the x axis - * @param ny direction along the y axis - * @param nz direction along the z axis + * @param nx direction along the x-axis + * @param ny direction along the y-axis + * @param nz direction along the z-axis * @param angle angle of the spotlight cone * @param concentration exponent determining the center bias of the cone * @see PGraphics#lights() @@ -7357,7 +7232,7 @@ public class PGraphics extends PImage implements PConstants { /** * - * Sets the falloff rates for point lights, spot lights, and ambient lights. + * Sets the falloff rates for point lights, spotlights, and ambient lights. * Like fill(), it affects only the elements which are created after it * in the code. The default value is lightFalloff(1.0, 0.0, 0.0), and the * parameters are used to calculate the falloff with the following @@ -7372,9 +7247,8 @@ public class PGraphics extends PImage implements PConstants { * location and falloff. You can think of it as a point light that doesn't care * which direction a surface is facing. * - * * @webref lights_camera:lights - * @webBrief Sets the falloff rates for point lights, spot lights, and ambient + * @webBrief Sets the falloff rates for point lights, spotlights, and ambient * lights * @usage web_application * @param constant constant value or determining falloff @@ -7401,7 +7275,6 @@ public class PGraphics extends PImage implements PConstants { * specular material qualities set through the specular() and * shininess() functions. * - * * @webref lights_camera:lights * @webBrief Sets the specular color for lights * @usage web_application @@ -7444,7 +7317,6 @@ public class PGraphics extends PImage implements PConstants { * background colors on the main drawing surface. It can only be used along with * a PGraphics object and createGraphics(). * - * *

        Advanced

        *

        * Clear the background with a color that includes an alpha value. This can only @@ -7552,7 +7424,7 @@ public class PGraphics extends PImage implements PConstants { * function. Unlike the main graphics context (the display window), * pixels in additional graphics areas created with createGraphics() * can be entirely or partially transparent. This function clears - * everything in a PGraphics object to make all of the pixels + * everything in a PGraphics object to make all the pixels * 100% transparent. * * @webref color:setting @@ -7660,7 +7532,6 @@ public class PGraphics extends PImage implements PConstants { * instance, instead of colorMode(RGB), write colorMode(RGB, 255, 255, * 255). * - * * @webref color:setting * @webBrief Changes the way Processing interprets color data * @usage web_application @@ -7741,7 +7612,7 @@ public class PGraphics extends PImage implements PConstants { * (the alpha for 0xAA000000) is set, and if not, whether the color value * that follows is less than colorModeX (first param passed to colorMode). *

        - * This auto-detect would break in the following situation: + * This auto-detection would break in the following situation: *

        size(256, 256);
            * for (int i = 0; i < 256; i++) {
            *   color c = color(0, 0, 0, i);
        @@ -7869,7 +7740,7 @@ public class PGraphics extends PImage implements PConstants {
            * Strangely the old version of this code ignored the alpha
            * value. not sure if that was a bug or what.
            * 

        - * Note, no need for a bounds check for 'argb' since it's a 32 bit number. + * Note, no need for a bounds check for 'argb' since it's a 32-bit number. * Bounds now checked on alpha, however (rev 0225). */ protected void colorCalcARGB(int argb, float alpha) { @@ -7902,13 +7773,13 @@ public class PGraphics extends PImage implements PConstants { // These functions are really slow (because they take the current colorMode // into account), but they're easy to use. Advanced users can write their - // own bit shifting operations to setup 'color' data types. + // own bit shifting operations to set up 'color' data types. public final int color(int c) { // ignore // if (((c & 0xff000000) == 0) && (c <= colorModeX)) { // if (colorModeDefault) { -// // bounds checking to make sure the numbers aren't to high or low +// // bounds checking to make sure the numbers aren't too high or low // if (c > 255) c = 255; else if (c < 0) c = 0; // return 0xff000000 | (c << 16) | (c << 8) | c; // } else { @@ -7933,7 +7804,7 @@ public class PGraphics extends PImage implements PConstants { */ public final int color(int c, int alpha) { // ignore // if (colorModeDefault) { -// // bounds checking to make sure the numbers aren't to high or low +// // bounds checking to make sure the numbers aren't too high or low // if (c > 255) c = 255; else if (c < 0) c = 0; // if (alpha > 255) alpha = 255; else if (alpha < 0) alpha = 0; // @@ -8187,10 +8058,8 @@ public class PGraphics extends PImage implements PConstants { /** - * * Extracts the brightness value from a color. * - * * @webref color:creating & reading * @webBrief Extracts the brightness value from a color * @usage web_application @@ -8231,7 +8100,6 @@ public class PGraphics extends PImage implements PConstants { * because otherwise numbers outside the range will produce strange and * unexpected colors. * - * * @webref color:creating & reading * @webBrief Calculates a color or colors between two colors at a specific * increment @@ -8295,7 +8163,7 @@ public class PGraphics extends PImage implements PConstants { * cycling through ROYGBIV. */ // Disabling rollover (wasn't working anyway) for 0126. - // Otherwise it makes full spectrum scale impossible for + // Otherwise, it makes full spectrum scale impossible for // those who might want it...in spite of how despicable // a full spectrum scale might be. // roll around when 0.9 to 0.1 @@ -8447,7 +8315,7 @@ public class PGraphics extends PImage implements PConstants { /** - * Show an renderer-related exception that halts the program. Currently just + * Show a renderer-related exception that halts the program. Currently just * wraps the message as a RuntimeException and throws it, but might do * something more specific might be used in the future. */ @@ -8471,7 +8339,9 @@ public class PGraphics extends PImage implements PConstants { */ protected void defaultFontOrDeath(String method, float size) { if (parent != null) { - textFont = createDefaultFont(size); + // Call textFont() so that subclasses can do necessary setup + // https://github.com/processing/processing4/issues/303 + textFont(createDefaultFont(size)); } else { throw new RuntimeException("Use textFont() before " + method + "()"); } @@ -8514,7 +8384,7 @@ public class PGraphics extends PImage implements PConstants { /** - * Return true if this renderer does rendering through OpenGL. Defaults to false. + * Return true if this renderer uses OpenGL. Defaults to false. */ public boolean isGL() { // ignore return false; @@ -8533,7 +8403,6 @@ public class PGraphics extends PImage implements PConstants { @Override public boolean save(String filename) { // ignore - if (hints[DISABLE_ASYNC_SAVEFRAME]) { return super.save(filename); } @@ -8543,14 +8412,16 @@ public class PGraphics extends PImage implements PConstants { } if (!loaded) loadPixels(); - PImage target = asyncImageSaver.getAvailableTarget(pixelWidth, pixelHeight, - format); - if (target == null) return false; - int count = PApplet.min(pixels.length, target.pixels.length); - System.arraycopy(pixels, 0, target.pixels, 0, count); - asyncImageSaver.saveTargetAsync(this, target, parent.sketchFile(filename)); - return true; + PImage target = + asyncImageSaver.getAvailableTarget(pixelWidth, pixelHeight, format); + if (target != null) { + int count = PApplet.min(pixels.length, target.pixels.length); + System.arraycopy(pixels, 0, target.pixels, 0, count); + asyncImageSaver.saveTargetAsync(this, target, parent.sketchFile(filename)); + return true; + } + return false; } protected void processImageBeforeAsyncSave(PImage image) { } diff --git a/core/src/processing/core/PImage.java b/core/src/processing/core/PImage.java index 7115905e8..d2ac4e3b9 100644 --- a/core/src/processing/core/PImage.java +++ b/core/src/processing/core/PImage.java @@ -31,6 +31,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Arrays; import processing.awt.ShimAWT; @@ -53,7 +54,6 @@ import processing.awt.ShimAWT; * To create a new image, use the createImage() function. Do not use the * syntax new PImage(). * - * * @webref image * @webBrief Datatype for storing images * @usage Web & Application @@ -62,19 +62,9 @@ import processing.awt.ShimAWT; * @see PApplet#imageMode(int) * @see PApplet#createImage(int, int, int) */ +@SuppressWarnings("ManualMinMaxCalculation") public class PImage implements PConstants, Cloneable { - private static final byte[] TIFF_HEADER = { - 77, 77, 0, 42, 0, 0, 0, 8, 0, 9, 0, -2, 0, 4, 0, 0, 0, 1, 0, 0, - 0, 0, 1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 3, 0, 0, 0, 1, - 0, 0, 0, 0, 1, 2, 0, 3, 0, 0, 0, 3, 0, 0, 0, 122, 1, 6, 0, 3, 0, - 0, 0, 1, 0, 2, 0, 0, 1, 17, 0, 4, 0, 0, 0, 1, 0, 0, 3, 0, 1, 21, - 0, 3, 0, 0, 0, 1, 0, 3, 0, 0, 1, 22, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, - 1, 23, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 8, 0, 8 - }; - - private static final String TIFF_ERROR = "Error: Processing can only read its own TIFF files."; - /** * Format for this image, one of RGB, ARGB or ALPHA. * note that RGB images still require 0xff in the high byte @@ -89,13 +79,12 @@ public class PImage implements PConstants, Cloneable { * meaning if the image is 100 x 100 pixels, there will be 10,000 values and if * the window is 200 x 300 pixels, there will be 60,000 values.
        *
        - * Before accessing this array, the data must loaded with the + * Before accessing this array, the data must be loaded with the * loadPixels() method. Failure to do so may result in a * NullPointerException. After the array data has been modified, the * updatePixels() method must be run to update the content of the display * window. * - * * @webref image:pixels * @webBrief Array containing the color of every pixel in the image * @usage web_application @@ -178,7 +167,6 @@ public class PImage implements PConstants, Cloneable { /** * ( begin auto-generated from PImage.xml ) - * * Datatype for storing images. Processing can display .gif, * .jpg, .tga, and .png images. Images may be * displayed in 2D and 3D space. Before an image is used, it must be loaded @@ -249,7 +237,7 @@ public class PImage implements PConstants, Cloneable { /** * Function to be used by subclasses of PImage to init later than - * at the constructor, or re-init later when things changes. + * at the constructor, or re-init later when things change. * Used by Capture and Movie classes (and perhaps others), * because the width/height will not be known when super() is called. * (Leave this public so that other libraries can do the same.) @@ -264,6 +252,12 @@ public class PImage implements PConstants, Cloneable { pixelHeight = height * pixelDensity; pixels = new int[pixelWidth * pixelHeight]; + + if (format == RGB) { + // Initialize the pixels as opaque, because Java2D gets quirky otherwise. + // https://github.com/processing/processing4/issues/388 + Arrays.fill(pixels, 0xFF000000); + } } @@ -288,6 +282,7 @@ public class PImage implements PConstants, Cloneable { public void checkAlpha() { if (pixels == null) return; + //noinspection ForLoopReplaceableByForEach for (int i = 0; i < pixels.length; i++) { // since transparency is often at corners, hopefully this // will find a non-transparent pixel quickly and exit @@ -392,13 +387,11 @@ public class PImage implements PConstants, Cloneable { /** - * * Loads the pixel data of the current display window into the pixels[] * array. This function must always be called before reading from or writing to * pixels[]. Subsequent changes to the display window will not be * reflected in pixels until loadPixels() is called again. * - * *

        Advanced

        Call this when you want to mess with the pixels[] array. *

        * For subclasses where the pixels[] buffer isn't set by default, this should @@ -547,8 +540,8 @@ public class PImage implements PConstants, Cloneable { * y parameters define the coordinates for the upper-left corner of * the image, regardless of the current imageMode().
        *
        - * If the pixel requested is outside of the image window, black is - * returned. The numbers returned are scaled according to the current color + * If the pixel requested is outside the image window, black is returned. + * The numbers returned are scaled according to the current color * ranges, but only RGB values are returned by this function. For example, * even though you may have drawn a shape with colorMode(HSB), the * numbers returned will be in RGB format.
        @@ -558,9 +551,8 @@ public class PImage implements PConstants, Cloneable { * equivalent statement to get(x, y) using pixels[] is * pixels[y*width+x]. See the reference for pixels[] for more information. * - * *

        Advanced

        - * Returns an ARGB "color" type (a packed 32 bit int with the color. + * Returns an ARGB "color" type (a packed 32-bit int) with the color. * If the coordinate is outside the image, zero is returned * (black, but completely transparent). *

        @@ -589,17 +581,12 @@ public class PImage implements PConstants, Cloneable { public int get(int x, int y) { if ((x < 0) || (y < 0) || (x >= pixelWidth) || (y >= pixelHeight)) return 0; - switch (format) { - case RGB: - return pixels[y*pixelWidth + x] | 0xff000000; - - case ARGB: - return pixels[y*pixelWidth + x]; - - case ALPHA: - return (pixels[y*pixelWidth + x] << 24) | 0xffffff; - } - return 0; + return switch (format) { + case RGB -> pixels[y * pixelWidth + x] | 0xff000000; + case ARGB -> pixels[y * pixelWidth + x]; + case ALPHA -> (pixels[y * pixelWidth + x] << 24) | 0xffffff; + default -> 0; + }; } @@ -665,7 +652,7 @@ public class PImage implements PConstants, Cloneable { */ public PImage get() { // Formerly this used clone(), which caused memory problems. - // http://code.google.com/p/processing/issues/detail?id=42 + // https://github.com/processing/processing/issues/81 return get(0, 0, pixelWidth, pixelHeight); } @@ -712,7 +699,6 @@ public class PImage implements PConstants, Cloneable { * is pixels[y*width+x] = #000000. See the reference for * pixels[] for more information. * - * * @webref image:pixels * @webBrief Writes a color to any pixel or writes an image into another * @usage web_application @@ -725,7 +711,13 @@ public class PImage implements PConstants, Cloneable { */ public void set(int x, int y, int c) { if ((x < 0) || (y < 0) || (x >= pixelWidth) || (y >= pixelHeight)) return; - pixels[y*pixelWidth + x] = c; + + switch (format) { + case RGB -> pixels[y * pixelWidth + x] = 0xff000000 | c; + case ARGB -> pixels[y * pixelWidth + x] = c; + case ALPHA -> pixels[y * pixelWidth + x] = ((c & 0xff) << 24) | 0xffffff; + } + updatePixels(x, y, 1, 1); // slow... } @@ -827,7 +819,6 @@ public class PImage implements PConstants, Cloneable { * same length as the target image's pixels array and should contain only * grayscale data of values between 0-255. * - * *

        Advanced

        * * Set alpha channel for an image. Black colors in the source @@ -931,14 +922,13 @@ public class PImage implements PConstants, Cloneable { /** - * * Filters the image as defined by one of the following modes:
        *
        * THRESHOLD
        - * Converts the image to black and white pixels depending if they are above or - * below the threshold defined by the level parameter. The parameter must be - * between 0.0 (black) and 1.0 (white). If no level is specified, 0.5 is - * used.
        + * Converts the image to black and white pixels depending on if they + * are above or below the threshold defined by the level parameter. + * The parameter must be between 0.0 (black) and 1.0 (white). + * If no level is specified, 0.5 is used.
        *
        * GRAY
        * Converts any colors in the image to grayscale equivalents. No parameter is @@ -966,7 +956,6 @@ public class PImage implements PConstants, Cloneable { * DILATE
        * Increases the light areas. No parameter is used. * - * *

        Advanced

        Method to apply a variety of basic filters to this image. *

        *

          @@ -1484,7 +1473,6 @@ public class PImage implements PConstants, Cloneable { /** - * * Copies a region of pixels from one image into another. If the source and * destination regions aren't the same size, it will automatically resize * source pixels to fit the specified target region. No alpha information @@ -1493,7 +1481,6 @@ public class PImage implements PConstants, Cloneable { *

          * As of release 0149, this function ignores imageMode(). * - * * @webref image:pixels * @webBrief Copies the entire image * @usage web_application @@ -1610,30 +1597,24 @@ public class PImage implements PConstants, Cloneable { * @see PApplet#color(float, float, float, float) */ static public int blendColor(int c1, int c2, int mode) { // ignore - switch (mode) { - case REPLACE: return c2; - case BLEND: return blend_blend(c1, c2); - - case ADD: return blend_add_pin(c1, c2); - case SUBTRACT: return blend_sub_pin(c1, c2); - - case LIGHTEST: return blend_lightest(c1, c2); - case DARKEST: return blend_darkest(c1, c2); - - case DIFFERENCE: return blend_difference(c1, c2); - case EXCLUSION: return blend_exclusion(c1, c2); - - case MULTIPLY: return blend_multiply(c1, c2); - case SCREEN: return blend_screen(c1, c2); - - case HARD_LIGHT: return blend_hard_light(c1, c2); - case SOFT_LIGHT: return blend_soft_light(c1, c2); - case OVERLAY: return blend_overlay(c1, c2); - - case DODGE: return blend_dodge(c1, c2); - case BURN: return blend_burn(c1, c2); - } - return 0; + return switch (mode) { + case REPLACE -> c2; + case BLEND -> blend_blend(c1, c2); + case ADD -> blend_add_pin(c1, c2); + case SUBTRACT -> blend_sub_pin(c1, c2); + case LIGHTEST -> blend_lightest(c1, c2); + case DARKEST -> blend_darkest(c1, c2); + case DIFFERENCE -> blend_difference(c1, c2); + case EXCLUSION -> blend_exclusion(c1, c2); + case MULTIPLY -> blend_multiply(c1, c2); + case SCREEN -> blend_screen(c1, c2); + case HARD_LIGHT -> blend_hard_light(c1, c2); + case SOFT_LIGHT -> blend_soft_light(c1, c2); + case OVERLAY -> blend_overlay(c1, c2); + case DODGE -> blend_dodge(c1, c2); + case BURN -> blend_burn(c1, c2); + default -> 0; + }; } @@ -1683,7 +1664,7 @@ public class PImage implements PConstants, Cloneable { * BURN - Darker areas are applied, increasing contrast, ignores lights. * Called "Color Burn" in Illustrator and Photoshop.
          *
          - * All modes use the alpha information (highest byte) of source image + * All modes use the alpha information (the highest byte) of source image * pixels as the blending factor. If the source and destination regions are * different sizes, the image will be automatically resized to match the * destination size. If the srcImg parameter is not used, the @@ -1691,7 +1672,6 @@ public class PImage implements PConstants, Cloneable { *
          * As of release 0149, this function ignores imageMode(). * - * * @webref image:pixels * @webBrief Copies a pixel or rectangle of pixels using different blending modes * @param src an image variable referring to the source image @@ -2380,7 +2360,7 @@ public class PImage implements PConstants, Cloneable { * channel in the upper 16 bits and BLUE channel in the lower 16 bits. This * decreases the number of operations per pixel and thus makes things faster. * - * Some of the modes are hand tweaked (various +1s etc.) to be more accurate + * Some modes are hand tweaked (various +1s etc.) to be more accurate * and to produce correct values in extremes. Below is a sketch you can use * to check any blending function for * @@ -2692,10 +2672,11 @@ int testFunction(int dst, int src) { /** * Hard Light - * O = OVERLAY(S, D) + *
          O = OVERLAY(S, D)
              *
              * O = 2 * MULTIPLY(D, S) = 2DS                   for S < 0.5
              * O = 2 * SCREEN(D, S) - 1 = 2(S + D - DS) - 1   otherwise
          +   * 
          */ private static int blend_hard_light(int dst, int src) { int a = src >>> 24; @@ -2822,99 +2803,6 @@ int testFunction(int dst, int src) { // FILE I/O - static public PImage loadTIFF(InputStream input) { // ignore - byte[] tiff = PApplet.loadBytes(input); - if (tiff == null) { - return null; - } - - if ((tiff[42] != tiff[102]) || // width/height in both places - (tiff[43] != tiff[103])) { - System.err.println(TIFF_ERROR); - return null; - } - - int width = - ((tiff[30] & 0xff) << 8) | (tiff[31] & 0xff); - int height = - ((tiff[42] & 0xff) << 8) | (tiff[43] & 0xff); - - int count = - ((tiff[114] & 0xff) << 24) | - ((tiff[115] & 0xff) << 16) | - ((tiff[116] & 0xff) << 8) | - (tiff[117] & 0xff); - if (count != width * height * 3) { - System.err.println(TIFF_ERROR + " (" + width + ", " + height +")"); - return null; - } - - // check the rest of the header - for (int i = 0; i < TIFF_HEADER.length; i++) { - if ((i == 30) || (i == 31) || (i == 42) || (i == 43) || - (i == 102) || (i == 103) || - (i == 114) || (i == 115) || (i == 116) || (i == 117)) continue; - - if (tiff[i] != TIFF_HEADER[i]) { - System.err.println(TIFF_ERROR + " (" + i + ")"); - return null; - } - } - - PImage outgoing = new PImage(width, height, RGB); - int index = 768; - count /= 3; - for (int i = 0; i < count; i++) { - outgoing.pixels[i] = - 0xFF000000 | - (tiff[index++] & 0xff) << 16 | - (tiff[index++] & 0xff) << 8 | - (tiff[index++] & 0xff); - } - return outgoing; - } - - protected boolean saveTIFF(OutputStream output) { - /* - // shutting off this warning, people can figure this out themselves - if (format != RGB) { - System.err.println("Warning: only RGB information is saved with " + - ".tif files. Use .tga or .png for ARGB images and others."); - } - */ - try { - byte[] tiff = new byte[768]; - System.arraycopy(TIFF_HEADER, 0, tiff, 0, TIFF_HEADER.length); - - tiff[30] = (byte) ((pixelWidth >> 8) & 0xff); - tiff[31] = (byte) ((pixelWidth) & 0xff); - tiff[42] = tiff[102] = (byte) ((pixelHeight >> 8) & 0xff); - tiff[43] = tiff[103] = (byte) ((pixelHeight) & 0xff); - - int count = pixelWidth*pixelHeight*3; - tiff[114] = (byte) ((count >> 24) & 0xff); - tiff[115] = (byte) ((count >> 16) & 0xff); - tiff[116] = (byte) ((count >> 8) & 0xff); - tiff[117] = (byte) ((count) & 0xff); - - // spew the header to the disk - output.write(tiff); - - for (int i = 0; i < pixels.length; i++) { - output.write((pixels[i] >> 16) & 0xff); - output.write((pixels[i] >> 8) & 0xff); - output.write(pixels[i] & 0xff); - } - output.flush(); - return true; - - } catch (IOException e) { - e.printStackTrace(); - } - return false; - } - - /** * Targa image loader for RLE-compressed TGA files. *

          @@ -3054,21 +2942,14 @@ int testFunction(int dst, int src) { boolean isRLE = (num & 0x80) != 0; if (isRLE) { num -= 127; // (num & 0x7F) + 1 - int pixel = 0; - switch (format) { - case ALPHA: - pixel = input.read(); - break; - case RGB: - pixel = 0xFF000000 | + int pixel = switch (format) { + case ALPHA -> input.read(); + case RGB -> 0xFF000000 | input.read() | (input.read() << 8) | (input.read() << 16); - //(is.read() << 16) | (is.read() << 8) | is.read(); - break; - case ARGB: - pixel = input.read() | + case ARGB -> input.read() | (input.read() << 8) | (input.read() << 16) | (input.read() << 24); - break; - } + default -> 0; + }; for (int i = 0; i < num; i++) { px[index++] = pixel; if (index == px.length) break; @@ -3278,7 +3159,7 @@ int testFunction(int dst, int src) { * may be opened by selecting "Show sketch folder" from the "Sketch" menu. *

          To save an image created within the code, rather * than through loading, it's necessary to make the image with the - * createImage() function so it is aware of the location of the + * createImage() function, so it is aware of the location of the * program and can therefore save the file to the right place. See the * createImage() reference for more information. * @@ -3299,13 +3180,12 @@ int testFunction(int dst, int src) { * To get a list of the supported formats for writing, use:
          * println(javax.imageio.ImageIO.getReaderFormatNames()) *

          - * To use the original built-in image writers, use .tga or .tif as the - * extension, or don't include an extension. When no extension is used, - * the extension .tif will be added to the file name. + * In Processing 4.0 beta 5, the old (and sometimes buggy) TIFF + * reader/writer was removed, so ImageIO is used for TIFF files. *

          - * The ImageIO API claims to support wbmp files, however they probably - * require a black and white image. Basic testing produced a zero-length - * file with no error. + * Also, files must have an extension: we're no longer adding .tif to + * files with no extension, because that can lead to confusing results, + * and the behavior is inconsistent with the rest of the API. * * @webref pimage:method * @webBrief Saves the image to a TIFF, TARGA, PNG, or JPEG file @@ -3313,58 +3193,27 @@ int testFunction(int dst, int src) { * @param filename a sequence of letters and numbers */ public boolean save(String filename) { // ignore - boolean success; + String path; if (parent != null) { // use savePath(), so that the intermediate directories are created - filename = parent.savePath(filename); + path = parent.savePath(filename); } else { File file = new File(filename); if (file.isAbsolute()) { // make sure that the intermediate folders have been created PApplet.createPath(file); + path = file.getAbsolutePath(); } else { String msg = - "PImage.save() requires an absolute path. " + - "Use createImage(), or pass savePath() to save()."; + "PImage.save() requires an absolute path. " + + "Use createImage(), or pass savePath() to save()."; PGraphics.showException(msg); + return false; } } - - // Make sure the pixel data is ready to go - loadPixels(); - - try { - final String lower = filename.toLowerCase(); - - if (saveImpl(filename)) { - return true; - } - - if (lower.endsWith(".tga")) { - OutputStream os = new BufferedOutputStream(new FileOutputStream(filename), 32768); - success = saveTGA(os); //, pixels, width, height, format); - os.close(); - - } else { // fall-through case is TIFF - // add a default extension and save uncompressed - // TODO this is the only place in the api that we mess w/ file names, - // and while arguably useful, seems like a weird precedent [fry 200816] - if (!lower.endsWith(".tif") && !lower.endsWith(".tiff")) { - filename += ".tif"; - } - OutputStream os = new BufferedOutputStream(new FileOutputStream(filename), 32768); - success = saveTIFF(os); //, pixels, width, height); - os.close(); - } - - } catch (IOException e) { - System.err.println("Error while saving image."); - e.printStackTrace(); - success = false; - } - return success; + return saveImpl(path); } @@ -3376,11 +3225,28 @@ 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 current 4.x releases - // https://github.com/processing/processing4/wiki/Exorcising-AWT - //if (!PApplet.disableAWT) { // TODO necessary? will this trigger NEWT? - return ShimAWT.saveImage(this, path); - //} - //return false; + // Make sure the pixel data is ready to go + loadPixels(); + boolean success; + + try { + final String lower = path.toLowerCase(); + + if (lower.endsWith(".tga")) { + OutputStream os = new BufferedOutputStream(new FileOutputStream(path), 32768); + success = saveTGA(os); //, pixels, width, height, format); + os.close(); + + } else { + // TODO Imperfect, possibly temporary solution for 4.x releases + // https://github.com/processing/processing4/wiki/Exorcising-AWT + success = ShimAWT.saveImage(this, path); + } + } catch (IOException e) { + System.err.println("Error while saving " + path); + e.printStackTrace(); + success = false; + } + return success; } } diff --git a/core/src/processing/core/PShape.java b/core/src/processing/core/PShape.java index 483df966f..0448a2ef8 100644 --- a/core/src/processing/core/PShape.java +++ b/core/src/processing/core/PShape.java @@ -623,19 +623,19 @@ public class PShape implements PConstants { /** - * The beginContour() and endContour() methods make it - * possible to define shapes with other shapes cut out of them. For - * example, the inside of a letter 'O'. These two functions are always - * used together, you'll never use one without the other. Between them, - * define the geometry you want to create. As you'll see when you run - * the example above, the second smaller shape is cut out of the first + * The beginContour() and endContour() methods make it + * possible to define shapes with other shapes cut out of them. For + * example, the inside of a letter 'O'. These two functions are always + * used together, you'll never use one without the other. Between them, + * define the geometry you want to create. As you'll see when you run + * the example above, the second smaller shape is cut out of the first * larger shape.
          *
          - * The exterior shape and the interior contour must wind in - * opposite directions. This means that if the points of the geometry - * for the exterior shape are described in a clockwise order, the points - * on the interior shape are defined in a counterclockwise order. - * + * The exterior shape and the interior contour must wind in + * opposite directions. This means that if the points of the geometry + * for the exterior shape are described in a clockwise order, the points + * on the interior shape are defined in a counterclockwise order. + * * @webref shape:vertex * @webBrief Starts a new contour * @see PShape#endContour() @@ -671,18 +671,18 @@ public class PShape implements PConstants { /** - * The beginContour() and endContour() methods make - * it possible to define shapes with other shapes cut out of them. - * For example, the inside of a letter 'O'. These two functions are - * always used together, you'll never use one without the other. - * Between them, define the geometry you want to create. As you'll - * see when you run the example above, the second smaller shape is + * The beginContour() and endContour() methods make + * it possible to define shapes with other shapes cut out of them. + * For example, the inside of a letter 'O'. These two functions are + * always used together, you'll never use one without the other. + * Between them, define the geometry you want to create. As you'll + * see when you run the example above, the second smaller shape is * cut out of the first larger shape.
          *
          - * The exterior shape and the interior contour must wind - * in opposite directions. This means that if the points of the - * geometry for the exterior shape are described in a clockwise order, - * the points on the interior shape are defined in a counterclockwise order. + * The exterior shape and the interior contour must wind + * in opposite directions. This means that if the points of the + * geometry for the exterior shape are described in a clockwise order, + * the points on the interior shape are defined in a counterclockwise order. * * @webref shape:vertex * @webBrief Ends a contour @@ -756,6 +756,7 @@ public class PShape implements PConstants { public void attribPosition(String name, float x, float y, float z) { } + public void attribNormal(String name, float nx, float ny, float nz) { } @@ -777,9 +778,9 @@ public class PShape implements PConstants { /** - * This method is used to start a custom shape created with the createShape() - * function. It's always and only used with createShape(). - * + * This method is used to start a custom shape created with the createShape() + * function. It's always and only used with createShape(). + * * @webref pshape:method * @webBrief Starts the creation of a new PShape * @see PApplet#endShape() @@ -795,8 +796,8 @@ public class PShape implements PConstants { } /** - * This method is used to complete a custom shape created with the createShape() - * function. It's always and only used with createShape(). + * This method is used to complete a custom shape created with the createShape() + * function. It's always and only used with createShape(). * * @webref pshape:method * @webBrief Finishes the creation of a new PShape @@ -1363,17 +1364,39 @@ public class PShape implements PConstants { public void curveDetail(int detail) { } + public void curveTightness(float tightness) { } + public void curveVertex(float x, float y) { + if (vertices == null) { + vertices = new float[10][]; + } else if (vertexCount >= vertices.length) { + vertices = (float[][]) PApplet.expand(vertices); + } + vertices[vertexCount++] = new float[] { x, y }; + + if (vertexCodes == null) { + vertexCodes = new int[10]; + } else if (vertexCodes.length == vertexCodeCount) { + vertexCodes = PApplet.expand(vertexCodes); + } + vertexCodes[vertexCodeCount++] = CURVE_VERTEX; + + if (x > width) { + width = x; + } + if (y > height) { + height = y; + } } + public void curveVertex(float x, float y, float z) { } - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -1756,7 +1779,7 @@ public class PShape implements PConstants { } else { for (int i = 0; i < vertexCount; i++) { float[] vert = vertices[i]; - if (vert[Z] == 0) { + if (vert.length < 3 || vert[Z] == 0) { g.vertex(vert[X], vert[Y]); } else { g.vertex(vert[X], vert[Y], vert[Z]); @@ -1823,7 +1846,7 @@ public class PShape implements PConstants { protected void drawPath(PGraphics g) { // Paths might be empty (go figure) - // http://dev.processing.org/bugs/show_bug.cgi?id=982 + // https://download.processing.org/bugzilla/982.html if (vertices == null) return; boolean insideContour = false; @@ -2004,7 +2027,7 @@ public class PShape implements PConstants { /** * Returns the number of children within the PShape. - * + * * @webref * @webBrief Returns the number of children */ @@ -2079,9 +2102,9 @@ public class PShape implements PConstants { // can't be just 'add' because that suggests additive geometry /** - * Adds a child PShape to a parent PShape that is defined as a GROUP. - * In the example, the three shapes path, rectangle, - * and circle are added to a parent PShape variable named + * Adds a child PShape to a parent PShape that is defined as a GROUP. + * In the example, the three shapes path, rectangle, + * and circle are added to a parent PShape variable named * house that is a GROUP. * @webref pshape:method * @webBrief Adds a new child @@ -2179,11 +2202,28 @@ public class PShape implements PConstants { } + /** + * Returns a PShape holding the tessellated geometry of this shape, + * composed entirely of triangles. + */ public PShape getTessellation() { return null; } + public void beginTessellation() { + beginTessellation(TRIANGLES); + } + + + public void beginTessellation(int kind) { + } + + + public void endTessellation() { + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -2253,11 +2293,11 @@ public class PShape implements PConstants { } /** - * The getVertexCount() method returns the number of vertices that - * make up a PShape. In the above example, the value 4 is returned by the - * getVertexCount() method because 4 vertices are defined in + * The getVertexCount() method returns the number of vertices that + * make up a PShape. In the above example, the value 4 is returned by the + * getVertexCount() method because 4 vertices are defined in * setup(). - * + * * @webref pshape:method * @webBrief Returns the total number of vertices as an int * @see PShape#getVertex(int) @@ -2272,12 +2312,12 @@ public class PShape implements PConstants { /** - * The getVertex() method returns a PVector with the coordinates of - * the vertex point located at the position defined by the index - * parameter. This method works when shapes are created as shown in the - * example above, but won't work properly when a shape is defined explicitly + * The getVertex() method returns a PVector with the coordinates of + * the vertex point located at the position defined by the index + * parameter. This method works when shapes are created as shown in the + * example above, but won't work properly when a shape is defined explicitly * (e.g. createShape(RECT, 20, 20, 80, 80). - * + * * @webref pshape:method * @webBrief Returns the vertex at the index position * @param index the location of the vertex @@ -2293,9 +2333,7 @@ public class PShape implements PConstants { * @param vec PVector to assign the data to */ public PVector getVertex(int index, PVector vec) { - if (vec == null) { - vec = new PVector(); - } + if (vec == null) vec = new PVector(); float[] vert = vertices[index]; vec.x = vert[X]; vec.y = vert[Y]; @@ -2324,11 +2362,11 @@ public class PShape implements PConstants { /** - * The setVertex() method defines the coordinates of the vertex point - * located at the position defined by the index parameter. This method - * works when shapes are created as shown in the example above, but won't work + * The setVertex() method defines the coordinates of the vertex point + * located at the position defined by the index parameter. This method + * works when shapes are created as shown in the example above, but won't work * properly when a shape is defined explicitly (e.g. createShape(RECT, 20, 20, 80, 80). - * + * * @webref pshape:method * @webBrief Sets the vertex at the index position * @param index the location of the vertex @@ -2389,9 +2427,7 @@ public class PShape implements PConstants { public PVector getNormal(int index, PVector vec) { - if (vec == null) { - vec = new PVector(); - } + if (vec == null) vec = new PVector(); vec.x = vertices[index][PGraphics.NX]; vec.y = vertices[index][PGraphics.NY]; vec.z = vertices[index][PGraphics.NZ]; @@ -2426,19 +2462,6 @@ public class PShape implements PConstants { } - - public void setAttrib(String name, int index, float... values) { - } - - - public void setAttrib(String name, int index, int... values) { - } - - - public void setAttrib(String name, int index, boolean... values) { - } - - public float getTextureU(int index) { return vertices[index][PGraphics.U]; } @@ -3023,9 +3046,11 @@ public class PShape implements PConstants { /** * Return true if this x, y coordinate is part of this shape. Only works * with PATH shapes or GROUP shapes that contain other GROUPs or PATHs. + * This method is not imperfect and doesn't account for all cases + * (not all complex shapes: concave shapes or holes may have issues). */ public boolean contains(float x, float y) { - if (family == PATH) { + if (family == PATH || family == GEOMETRY) { PVector p = new PVector(x, y); if (matrix != null) { // apply the inverse transformation matrix to the point coordinates diff --git a/core/src/processing/core/PShapeSVG.java b/core/src/processing/core/PShapeSVG.java index f572406eb..39cbf0c11 100644 --- a/core/src/processing/core/PShapeSVG.java +++ b/core/src/processing/core/PShapeSVG.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2006-12 Ben Fry and Casey Reas Copyright (c) 2004-06 Michael Chang @@ -121,7 +121,7 @@ public class PShapeSVG extends PShape { this(null, svg, true); if (!svg.getName().equals("svg")) { - if (svg.getName().toLowerCase().equals("html")) { + if (svg.getName().equalsIgnoreCase("html")) { // Common case is that files aren't downloaded properly throw new RuntimeException("This appears to be a web page, not an SVG file."); } else { @@ -541,7 +541,7 @@ public class PShapeSVG extends PShape { } if (c == '-' && !lastSeparate) { // allow for 'e' notation in numbers, e.g. 2.10e-9 - // http://dev.processing.org/bugs/show_bug.cgi?id=1408 + // https://download.processing.org/bugzilla/1408.html if (i == 0 || pathDataChars[i-1] != 'e') { pathBuffer.append("|"); } @@ -566,7 +566,6 @@ public class PShapeSVG extends PShape { int i = 0; char implicitCommand = '\0'; -// char prevCommand = '\0'; boolean prevCurve = false; float ctrlX, ctrlY; // store values for closepath so that relative coords work properly @@ -575,7 +574,7 @@ public class PShapeSVG extends PShape { while (i < pathTokens.length) { char c = pathTokens[i].charAt(0); - if (((c >= '0' && c <= '9') || (c == '-')) && implicitCommand != '\0') { + if (((c >= '0' && c <= '9') || (c == '-') || (c == '.')) && implicitCommand != '\0') { c = implicitCommand; i--; } else { @@ -864,7 +863,7 @@ public class PShapeSVG extends PShape { case 'z': // since closing the path, the 'current' point needs // to return back to the last moveto location. - // http://code.google.com/p/processing/issues/detail?id=1058 + // https://github.com/processing/processing/issues/1096 cx = movetoX; cy = movetoY; close = true; diff --git a/core/src/processing/core/PSurface.java b/core/src/processing/core/PSurface.java index 512286825..4a74c2754 100644 --- a/core/src/processing/core/PSurface.java +++ b/core/src/processing/core/PSurface.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2014-15 The Processing Foundation + Copyright (c) 2014-22 The Processing Foundation This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -26,7 +26,7 @@ import java.io.File; public interface PSurface { /** - * Minimum dimensions for the window holding an applet. This varies between + * Minimum dimensions for the window holding a sketch. This varies between * platforms, Mac OS X 10.3 (confirmed with 10.7 and Java 6) can do any * height but requires at least 128 pixels width. Windows XP has another * set of limitations. And for all I know, Linux probably allows window @@ -55,21 +55,6 @@ public interface PSurface { // - public PImage loadImage(String path, Object... args); - - // - - public void selectInput(String prompt, String callback, - File file, Object callbackObject); - - public void selectOutput(String prompt, String callback, - File file, Object callbackObject); - - public void selectFolder(String prompt, String callback, - File file, Object callbackObject); - - // - /** * Get the native window object associated with this drawing surface. * For Java2D, this will be an AWT Frame object. For OpenGL, the window. @@ -110,7 +95,7 @@ public interface PSurface { public void placePresent(int stopColor); // Sketch is running from the PDE, set up messaging back to the PDE - public void setupExternalMessages(); + //public void setupExternalMessages(); // @@ -163,12 +148,23 @@ public interface PSurface { // + public PImage loadImage(String path, Object... args); + /** * @param url the link to open * @return false if unable to find a viable way to open */ public boolean openLink(String url); + public void selectInput(String prompt, String callback, + File file, Object callbackObject); + + public void selectOutput(String prompt, String callback, + File file, Object callbackObject); + + public void selectFolder(String prompt, String callback, + File file, Object callbackObject); + // /** Start the animation thread */ diff --git a/core/src/processing/core/PSurfaceNone.java b/core/src/processing/core/PSurfaceNone.java index ad2903ec7..ca401cfb5 100644 --- a/core/src/processing/core/PSurfaceNone.java +++ b/core/src/processing/core/PSurfaceNone.java @@ -137,8 +137,10 @@ public class PSurfaceNone implements PSurface { public void placePresent(int stopColor) { } + /* @Override public void setupExternalMessages() { } + */ @Override diff --git a/core/src/processing/core/PVector.java b/core/src/processing/core/PVector.java index c34623470..a2bdd4718 100644 --- a/core/src/processing/core/PVector.java +++ b/core/src/processing/core/PVector.java @@ -979,13 +979,13 @@ public class PVector implements Serializable { // This should be a number between -1 and 1, since it's "normalized" double amt = dot / (v1mag * v2mag); // But if it's not due to rounding error, then we need to fix it - // http://code.google.com/p/processing/issues/detail?id=340 + // https://github.com/processing/processing/issues/379 // Otherwise if outside the range, acos() will return NaN // http://www.cppreference.com/wiki/c/math/acos if (amt <= -1) { return PConstants.PI; } else if (amt >= 1) { - // http://code.google.com/p/processing/issues/detail?id=435 + // https://github.com/processing/processing/issues/474 return 0; } return (float) Math.acos(amt); diff --git a/core/src/processing/core/ThinkDifferent.java b/core/src/processing/core/ThinkDifferent.java index 4c6de03ed..862da283f 100644 --- a/core/src/processing/core/ThinkDifferent.java +++ b/core/src/processing/core/ThinkDifferent.java @@ -25,6 +25,9 @@ package processing.core; import java.awt.Desktop; import java.awt.Image; import java.awt.Taskbar; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; /** @@ -32,8 +35,8 @@ import java.awt.Taskbar; * * We have to register a quit handler to safely shut down the sketch, * otherwise OS X will just kill the sketch when a user hits Cmd-Q. - * In addition, we have a method to set the dock icon image so we look more - * like a native application. + * In addition, we have a method to set the dock icon image, + * so we look more like a native application. * * This is a stripped-down version of what's in processing.app.platform to fix * 3301. @@ -112,4 +115,40 @@ public class ThinkDifferent { } return desktop; } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static native public void hideMenuBar(); + + static native public void showMenuBar(); + + static { + final String NATIVE_FILENAME = "libDifferent.jnilib"; + try { + File temp = File.createTempFile("processing", "different"); + if (temp.delete() && temp.mkdirs()) { + temp.deleteOnExit(); + + File jnilibFile = new File(temp, NATIVE_FILENAME); + InputStream input = ThinkDifferent.class.getResourceAsStream(NATIVE_FILENAME); + if (input != null) { + if (PApplet.saveStream(jnilibFile, input)) { + System.load(jnilibFile.getAbsolutePath()); + + } else { + System.err.println("Full screen disabled: could not save library"); + } + } else { + System.err.println("Full screen disabled: could not load " + NATIVE_FILENAME + " from core.jar"); + } + } else { + System.err.println("Full screen disabled: could not create temporary folder"); + } + } catch (IOException e) { + System.err.println("Full screen disabled"); + e.printStackTrace(); + } + } } diff --git a/core/src/processing/core/libDifferent.jnilib b/core/src/processing/core/libDifferent.jnilib new file mode 100755 index 000000000..5030a755f Binary files /dev/null and b/core/src/processing/core/libDifferent.jnilib differ diff --git a/core/src/processing/data/DoubleList.java b/core/src/processing/data/DoubleList.java index 6c364130c..f33f024cc 100644 --- a/core/src/processing/data/DoubleList.java +++ b/core/src/processing/data/DoubleList.java @@ -10,12 +10,11 @@ import processing.core.PApplet; /** - * Helper class for a list of floats. Lists are designed to have some of the - * features of ArrayLists, but to maintain the simplicity and efficiency of - * working with arrays. - * - * Functions like sort() and shuffle() always act on the list itself. To get - * a sorted copy, use list.copy().sort(). + * Helper class for a list of double values. Lists are designed + * to have some features of ArrayList, but to maintain the + * simplicity and efficiency of working with arrays. + * Functions such as sort() and shuffle() always act on + * the list itself. To get a sorted copy, use list.copy().sort(). * * @nowebref * @see IntList @@ -26,6 +25,7 @@ public class DoubleList implements Iterable { double[] data; + @SuppressWarnings("unused") public DoubleList() { data = new double[10]; } @@ -51,13 +51,14 @@ public class DoubleList implements Iterable { /** * Construct an FloatList from an iterable pile of objects. - * For instance, a double array, an array of strings, who knows). - * Un-parseable or null values will be set to NaN. + * For instance, a double array, an array of strings, who knows. + * Un-parsable or null values will be set to NaN. * @nowebref */ - public DoubleList(Iterable iter) { + @SuppressWarnings("unused") + public DoubleList(Iterable iterator) { this(10); - for (Object o : iter) { + for (Object o : iterator) { if (o == null) { append(Double.NaN); } else if (o instanceof Number) { @@ -74,6 +75,7 @@ public class DoubleList implements Iterable { * Construct an FloatList from a random pile of objects. * Un-parseable or null values will be set to NaN. */ + @SuppressWarnings("unused") public DoubleList(Object... items) { // nuts, no good way to pass missingValue to this fn (varargs must be last) final double missingValue = Double.NaN; @@ -89,9 +91,7 @@ public class DoubleList implements Iterable { } else { try { value = Double.parseDouble(o.toString().trim()); - } catch (NumberFormatException nfe) { - value = missingValue; - } + } catch (NumberFormatException ignored) { } } } data[index++] = value; @@ -225,6 +225,7 @@ public class DoubleList implements Iterable { // Remove the first instance of a particular value, // and return the index at which it was found. + @SuppressWarnings("unused") public int removeValue(int value) { int index = index(value); if (index != -1) { @@ -237,7 +238,8 @@ public class DoubleList implements Iterable { // Remove all instances of a particular value, // and return the number of values found and removed - public int removeValues(int value) { + @SuppressWarnings("unused") + public int removeValues(double value) { int ii = 0; if (Double.isNaN(value)) { for (int i = 0; i < count; i++) { @@ -259,6 +261,7 @@ public class DoubleList implements Iterable { /** Replace the first instance of a particular value */ + @SuppressWarnings("unused") public boolean replaceValue(double value, double newValue) { if (Double.isNaN(value)) { for (int i = 0; i < count; i++) { @@ -279,6 +282,7 @@ public class DoubleList implements Iterable { /** Replace all instances of a particular value */ + @SuppressWarnings("unused") public boolean replaceValues(double value, double newValue) { boolean changed = false; if (Double.isNaN(value)) { @@ -330,6 +334,7 @@ public class DoubleList implements Iterable { /** Add this value, but only if it's not already in the list. */ + @SuppressWarnings("unused") public void appendUnique(double value) { if (!hasValue(value)) { append(value); @@ -337,36 +342,6 @@ public class DoubleList implements Iterable { } -// public void insert(int index, int value) { -// if (index+1 > count) { -// if (index+1 < data.length) { -// } -// } -// if (index >= data.length) { -// data = PApplet.expand(data, index+1); -// data[index] = value; -// count = index+1; -// -// } else if (count == data.length) { -// if (index >= count) { -// //int[] temp = new int[count << 1]; -// System.arraycopy(data, 0, temp, 0, index); -// temp[index] = value; -// System.arraycopy(data, index, temp, index+1, count - index); -// data = temp; -// -// } else { -// // data[] has room to grow -// // for() loop believed to be faster than System.arraycopy over itself -// for (int i = count; i > index; --i) { -// data[i] = data[i-1]; -// } -// data[index] = value; -// count++; -// } -// } - - public void insert(int index, double value) { insert(index, new double[] { value }); } @@ -406,60 +381,8 @@ public class DoubleList implements Iterable { } - // below are aborted attempts at more optimized versions of the code - // that are harder to read and debug... - -// if (index + values.length >= count) { -// // We're past the current 'count', check to see if we're still allocated -// // index 9, data.length = 10, values.length = 1 -// if (index + values.length < data.length) { -// // There's still room for these entries, even though it's past 'count'. -// // First clear out the entries leading up to it, however. -// for (int i = count; i < index; i++) { -// data[i] = 0; -// } -// data[index] = -// } -// if (index >= data.length) { -// int length = index + values.length; -// int[] temp = new int[length]; -// System.arraycopy(data, 0, temp, 0, count); -// System.arraycopy(values, 0, temp, index, values.length); -// data = temp; -// count = data.length; -// } else { -// -// } -// -// } else if (count == data.length) { -// int[] temp = new int[count << 1]; -// System.arraycopy(data, 0, temp, 0, index); -// temp[index] = value; -// System.arraycopy(data, index, temp, index+1, count - index); -// data = temp; -// -// } else { -// // data[] has room to grow -// // for() loop believed to be faster than System.arraycopy over itself -// for (int i = count; i > index; --i) { -// data[i] = data[i-1]; -// } -// data[index] = value; -// count++; -// } - - /** Return the first index of a particular value. */ public int index(double what) { - /* - if (indexCache != null) { - try { - return indexCache.get(what); - } catch (Exception e) { // not there - return -1; - } - } - */ for (int i = 0; i < count; i++) { if (data[i] == what) { return i; @@ -573,7 +496,7 @@ public class DoubleList implements Iterable { public int minIndex() { checkMinMax("minIndex"); - double m = Double.NaN; + double m; int mi = -1; for (int i = 0; i < count; i++) { // find one good value to start @@ -609,7 +532,7 @@ public class DoubleList implements Iterable { public int maxIndex() { checkMinMax("maxIndex"); - double m = Double.NaN; + double m; int mi = -1; for (int i = 0; i < count; i++) { // find one good value to start @@ -701,24 +624,6 @@ public class DoubleList implements Iterable { } - // use insert() -// public void splice(int index, int value) { -// } - - -// public void subset(int start) { -// subset(start, count - start); -// } - - -// public void subset(int start, int num) { -// for (int i = 0; i < num; i++) { -// data[i] = data[i+start]; -// } -// count = num; -// } - - /** * @webref doublelist:method * @brief Reverse the order of the list elements @@ -741,6 +646,7 @@ public class DoubleList implements Iterable { * @webref doublelist:method * @brief Randomize the order of the list elements */ + @SuppressWarnings("unused") public void shuffle() { Random r = new Random(); int num = count; @@ -758,6 +664,7 @@ public class DoubleList implements Iterable { * Randomize the list order using the random() function from the specified * sketch, allowing shuffle() to use its current randomSeed() setting. */ + @SuppressWarnings("unused") public void shuffle(PApplet sketch) { int num = count; while (num > 1) { @@ -770,6 +677,23 @@ public class DoubleList implements Iterable { } + /** + * Return a random value from the list. + */ + public double random() { + return data[(int) (Math.random() * count)]; + } + + + /** + * Return a random value from the list, using the + * randomSeed() from the specified sketch object. + */ + public double random(PApplet sketch) { + return data[(int) sketch.random(count)]; + } + + public DoubleList copy() { DoubleList outgoing = new DoubleList(data); outgoing.count = count; @@ -791,11 +715,7 @@ public class DoubleList implements Iterable { /** Implemented this way so that we can use a FloatList in a for loop. */ @Override public Iterator iterator() { -// } -// -// -// public Iterator valueIterator() { - return new Iterator() { + return new Iterator<>() { int index = -1; public void remove() { @@ -814,23 +734,34 @@ public class DoubleList implements Iterable { } + @Deprecated + public double[] array() { + return toArray(); + } + + /** * Create a new array with a copy of all the values. * @return an array sized by the length of the list with each of the values. * @webref doublelist:method * @brief Create a new array with a copy of all the values */ - public double[] array() { - return array(null); + public double[] toArray() { + return toArray(null); + } + + + @Deprecated + public double[] array(double[] array) { + return toArray(array); } /** - * Copy values into the specified array. If the specified array is null or - * not the same size, a new array will be allocated. - * @param array + * Copy values into the specified array. If the specified array is + * null or not the same size, a new array will be allocated. */ - public double[] array(double[] array) { + public double[] toArray(double[] array) { if (array == null || array.length != count) { array = new double[count]; } @@ -842,9 +773,10 @@ public class DoubleList implements Iterable { /** * Returns a normalized version of this array. Called getPercent() for * consistency with the Dict classes. It's a getter method because it needs - * to returns a new list (because IntList/Dict can't do percentages or + * to return a new list (because IntList/Dict can't do percentages or * normalization in place on int values). */ + @SuppressWarnings("unused") public DoubleList getPercent() { double sum = 0; for (double value : array()) { @@ -859,6 +791,7 @@ public class DoubleList implements Iterable { } + @SuppressWarnings("unused") public DoubleList getSubset(int start) { return getSubset(start, count - start); } diff --git a/core/src/processing/data/FloatList.java b/core/src/processing/data/FloatList.java index ab8300c20..670b52a16 100644 --- a/core/src/processing/data/FloatList.java +++ b/core/src/processing/data/FloatList.java @@ -10,12 +10,11 @@ import processing.core.PApplet; /** - * Helper class for a list of floats. Lists are designed to have some of the - * features of ArrayLists, but to maintain the simplicity and efficiency of - * working with arrays. - * - * Functions like sort() and shuffle() always act on the list itself. To get - * a sorted copy, use list.copy().sort(). + * Helper class for a list of float values. Lists are designed + * to have some features of ArrayList, but to maintain the + * simplicity and efficiency of working with arrays. + * Functions such as sort() and shuffle() always act on + * the list itself. To get a sorted copy, use list.copy().sort(). * * @webref data:composite * @webBrief Helper class for a list of floats @@ -52,13 +51,13 @@ public class FloatList implements Iterable { /** * Construct an FloatList from an iterable pile of objects. - * For instance, a float array, an array of strings, who knows). - * Un-parseable or null values will be set to NaN. + * For instance, a float array, an array of strings, who knows. + * Un-parsable or null values will be set to NaN. * @nowebref */ - public FloatList(Iterable iter) { + public FloatList(Iterable iterator) { this(10); - for (Object o : iter) { + for (Object o : iterator) { if (o == null) { append(Float.NaN); } else if (o instanceof Number) { @@ -73,7 +72,7 @@ public class FloatList implements Iterable { /** * Construct an FloatList from a random pile of objects. - * Un-parseable or null values will be set to NaN. + * Un-parsable or null values will be set to NaN. */ public FloatList(Object... items) { // nuts, no good way to pass missingValue to this fn (varargs must be last) @@ -220,6 +219,7 @@ public class FloatList implements Iterable { // Remove the first instance of a particular value, // and return the index at which it was found. + @SuppressWarnings("unused") public int removeValue(int value) { int index = index(value); if (index != -1) { @@ -232,7 +232,8 @@ public class FloatList implements Iterable { // Remove all instances of a particular value, // and return the number of values found and removed - public int removeValues(int value) { + @SuppressWarnings("unused") + public int removeValues(float value) { int ii = 0; if (Float.isNaN(value)) { for (int i = 0; i < count; i++) { @@ -254,6 +255,7 @@ public class FloatList implements Iterable { /** Replace the first instance of a particular value */ + @SuppressWarnings("unused") public boolean replaceValue(float value, float newValue) { if (Float.isNaN(value)) { for (int i = 0; i < count; i++) { @@ -274,6 +276,7 @@ public class FloatList implements Iterable { /** Replace all instances of a particular value */ + @SuppressWarnings("unused") public boolean replaceValues(float value, float newValue) { boolean changed = false; if (Float.isNaN(value)) { @@ -325,6 +328,7 @@ public class FloatList implements Iterable { /** Add this value, but only if it's not already in the list. */ + @SuppressWarnings("unused") public void appendUnique(float value) { if (!hasValue(value)) { append(value); @@ -332,36 +336,6 @@ public class FloatList implements Iterable { } -// public void insert(int index, int value) { -// if (index+1 > count) { -// if (index+1 < data.length) { -// } -// } -// if (index >= data.length) { -// data = PApplet.expand(data, index+1); -// data[index] = value; -// count = index+1; -// -// } else if (count == data.length) { -// if (index >= count) { -// //int[] temp = new int[count << 1]; -// System.arraycopy(data, 0, temp, 0, index); -// temp[index] = value; -// System.arraycopy(data, index, temp, index+1, count - index); -// data = temp; -// -// } else { -// // data[] has room to grow -// // for() loop believed to be faster than System.arraycopy over itself -// for (int i = count; i > index; --i) { -// data[i] = data[i-1]; -// } -// data[index] = value; -// count++; -// } -// } - - public void insert(int index, float value) { insert(index, new float[] { value }); } @@ -401,60 +375,8 @@ public class FloatList implements Iterable { } - // below are aborted attempts at more optimized versions of the code - // that are harder to read and debug... - -// if (index + values.length >= count) { -// // We're past the current 'count', check to see if we're still allocated -// // index 9, data.length = 10, values.length = 1 -// if (index + values.length < data.length) { -// // There's still room for these entries, even though it's past 'count'. -// // First clear out the entries leading up to it, however. -// for (int i = count; i < index; i++) { -// data[i] = 0; -// } -// data[index] = -// } -// if (index >= data.length) { -// int length = index + values.length; -// int[] temp = new int[length]; -// System.arraycopy(data, 0, temp, 0, count); -// System.arraycopy(values, 0, temp, index, values.length); -// data = temp; -// count = data.length; -// } else { -// -// } -// -// } else if (count == data.length) { -// int[] temp = new int[count << 1]; -// System.arraycopy(data, 0, temp, 0, index); -// temp[index] = value; -// System.arraycopy(data, index, temp, index+1, count - index); -// data = temp; -// -// } else { -// // data[] has room to grow -// // for() loop believed to be faster than System.arraycopy over itself -// for (int i = count; i > index; --i) { -// data[i] = data[i-1]; -// } -// data[index] = value; -// count++; -// } - - /** Return the first index of a particular value. */ public int index(float what) { - /* - if (indexCache != null) { - try { - return indexCache.get(what); - } catch (Exception e) { // not there - return -1; - } - } - */ for (int i = 0; i < count; i++) { if (data[i] == what) { return i; @@ -580,7 +502,7 @@ public class FloatList implements Iterable { public int minIndex() { checkMinMax("minIndex"); - float m = Float.NaN; + float m; int mi = -1; for (int i = 0; i < count; i++) { // find one good value to start @@ -618,7 +540,7 @@ public class FloatList implements Iterable { public int maxIndex() { checkMinMax("maxIndex"); - float m = Float.NaN; + float m; int mi = -1; for (int i = 0; i < count; i++) { // find one good value to start @@ -723,24 +645,6 @@ public class FloatList implements Iterable { } - // use insert() -// public void splice(int index, int value) { -// } - - -// public void subset(int start) { -// subset(start, count - start); -// } - - -// public void subset(int start, int num) { -// for (int i = 0; i < num; i++) { -// data[i] = data[i+start]; -// } -// count = num; -// } - - /** * Reverse the order of the list * @@ -764,6 +668,7 @@ public class FloatList implements Iterable { * @webref floatlist:method * @webBrief Randomize the order of the list elements */ + @SuppressWarnings("unused") public void shuffle() { Random r = new Random(); int num = count; @@ -781,6 +686,7 @@ public class FloatList implements Iterable { * Randomize the list order using the random() function from the specified * sketch, allowing shuffle() to use its current randomSeed() setting. */ + @SuppressWarnings("unused") public void shuffle(PApplet sketch) { int num = count; while (num > 1) { @@ -793,6 +699,23 @@ public class FloatList implements Iterable { } + /** + * Return a random value from the list. + */ + public float random() { + return data[(int) (Math.random() * count)]; + } + + + /** + * Return a random value from the list, using the + * randomSeed() from the specified sketch object. + */ + public float random(PApplet sketch) { + return data[(int) sketch.random(count)]; + } + + public FloatList copy() { FloatList outgoing = new FloatList(data); outgoing.count = count; @@ -814,11 +737,7 @@ public class FloatList implements Iterable { /** Implemented this way so that we can use a FloatList in a for loop. */ @Override public Iterator iterator() { -// } -// -// -// public Iterator valueIterator() { - return new Iterator() { + return new Iterator<>() { int index = -1; public void remove() { @@ -837,23 +756,34 @@ public class FloatList implements Iterable { } + @Deprecated + public float[] array() { + return toArray(); + } + + /** * Create a new array with a copy of all the values. * @return an array sized by the length of the list with each of the values. * @webref floatlist:method * @webBrief Create a new array with a copy of all the values */ - public float[] array() { - return array(null); + public float[] toArray() { + return toArray(null); + } + + + @Deprecated + public float[] array(float[] array) { + return toArray(array); } /** - * Copy values into the specified array. If the specified array is null or - * not the same size, a new array will be allocated. - * @param array + * Copy values into the specified array. If the specified array is + * null or not the same size, a new array will be allocated. */ - public float[] array(float[] array) { + public float[] toArray(float[] array) { if (array == null || array.length != count) { array = new float[count]; } @@ -863,11 +793,12 @@ public class FloatList implements Iterable { /** - * Returns a normalized version of this array. Called getPercent() for - * consistency with the Dict classes. It's a getter method because it needs - * to returns a new list (because IntList/Dict can't do percentages or - * normalization in place on int values). + * Returns a normalized version of this array. Called getPercent() + * for consistency with the Dict classes. It's a getter method + * because it needs to return a new list (because IntList/Dict + * can't do percentages or normalization in place on int values). */ + @SuppressWarnings("unused") public FloatList getPercent() { double sum = 0; for (float value : array()) { @@ -882,6 +813,7 @@ public class FloatList implements Iterable { } + @SuppressWarnings("unused") public FloatList getSubset(int start) { return getSubset(start, count - start); } diff --git a/core/src/processing/data/IntList.java b/core/src/processing/data/IntList.java index 3ce6f0538..6ef0b3f8b 100644 --- a/core/src/processing/data/IntList.java +++ b/core/src/processing/data/IntList.java @@ -9,18 +9,12 @@ import java.util.Random; import processing.core.PApplet; -// splice, slice, subset, concat, reverse - -// trim, join for String versions - - /** - * Helper class for a list of ints. Lists are designed to have some of the - * features of ArrayLists, but to maintain the simplicity and efficiency of - * working with arrays. - * - * Functions like sort() and shuffle() always act on the list itself. To get - * a sorted copy, use list.copy().sort(). + * Helper class for a list of int values. Lists are designed + * to have some features of ArrayList, but to maintain the + * simplicity and efficiency of working with arrays. + * Functions such as sort() and shuffle() always act on + * the list itself. To get a sorted copy, use list.copy().sort(). * * @webref data:composite * @webBrief Helper class for a list of ints @@ -57,13 +51,13 @@ public class IntList implements Iterable { /** * Construct an IntList from an iterable pile of objects. - * For instance, a float array, an array of strings, who knows). - * Un-parseable or null values will be set to 0. + * For instance, a float array, an array of strings, who knows. + * Un-parsable or null values will be set to 0. * @nowebref */ - public IntList(Iterable iter) { + public IntList(Iterable iterable) { this(10); - for (Object o : iter) { + for (Object o : iterable) { if (o == null) { append(0); // missing value default } else if (o instanceof Number) { @@ -78,7 +72,7 @@ public class IntList implements Iterable { /** * Construct an IntList from a random pile of objects. - * Un-parseable or null values will be set to zero. + * Un-parsable or null values will be set to zero. */ public IntList(Object... items) { final int missingValue = 0; // nuts, can't be last/final/second arg @@ -239,6 +233,7 @@ public class IntList implements Iterable { // Remove the first instance of a particular value, // and return the index at which it was found. + @SuppressWarnings("unused") public int removeValue(int value) { int index = index(value); if (index != -1) { @@ -251,6 +246,7 @@ public class IntList implements Iterable { // Remove all instances of a particular value, // and return the number of values found and removed + @SuppressWarnings("unused") public int removeValues(int value) { int ii = 0; for (int i = 0; i < count; i++) { @@ -293,6 +289,7 @@ public class IntList implements Iterable { /** Add this value, but only if it's not already in the list. */ + @SuppressWarnings("unused") public void appendUnique(int value) { if (!hasValue(value)) { append(value); @@ -300,36 +297,6 @@ public class IntList implements Iterable { } -// public void insert(int index, int value) { -// if (index+1 > count) { -// if (index+1 < data.length) { -// } -// } -// if (index >= data.length) { -// data = PApplet.expand(data, index+1); -// data[index] = value; -// count = index+1; -// -// } else if (count == data.length) { -// if (index >= count) { -// //int[] temp = new int[count << 1]; -// System.arraycopy(data, 0, temp, 0, index); -// temp[index] = value; -// System.arraycopy(data, index, temp, index+1, count - index); -// data = temp; -// -// } else { -// // data[] has room to grow -// // for() loop believed to be faster than System.arraycopy over itself -// for (int i = count; i > index; --i) { -// data[i] = data[i-1]; -// } -// data[index] = value; -// count++; -// } -// } - - public void insert(int index, int value) { insert(index, new int[] { value }); } @@ -414,15 +381,6 @@ public class IntList implements Iterable { /** Return the first index of a particular value. */ public int index(int what) { - /* - if (indexCache != null) { - try { - return indexCache.get(what); - } catch (Exception e) { // not there - return -1; - } - } - */ for (int i = 0; i < count; i++) { if (data[i] == what) { return i; @@ -432,15 +390,6 @@ public class IntList implements Iterable { } - // !!! TODO this is not yet correct, because it's not being reset when - // the rest of the entries are changed -// protected void cacheIndices() { -// indexCache = new HashMap(); -// for (int i = 0; i < count; i++) { -// indexCache.put(data[i], i); -// } -// } - /** * Check if a number is a part of the data structure. * @@ -448,10 +397,6 @@ public class IntList implements Iterable { * @webBrief Check if a number is a part of the list */ public boolean hasValue(int value) { -// if (indexCache == null) { -// cacheIndices(); -// } -// return index(what) != -1; for (int i = 0; i < count; i++) { if (data[i] == value) { return true; @@ -460,6 +405,7 @@ public class IntList implements Iterable { return false; } + /** * Add one to a value. * @@ -566,6 +512,7 @@ public class IntList implements Iterable { // returns the index of the minimum value. // if there are ties, it returns the first one found. + @SuppressWarnings("unused") public int minIndex() { checkMinMax("minIndex"); int value = data[0]; @@ -673,23 +620,6 @@ public class IntList implements Iterable { } - // use insert() -// public void splice(int index, int value) { -// } - - -// public void subset(int start) { -// subset(start, count - start); -// } -// -// -// public void subset(int start, int num) { -// for (int i = 0; i < num; i++) { -// data[i] = data[i+start]; -// } -// count = num; -// } - /** * Reverse the order of the list. * @@ -713,6 +643,7 @@ public class IntList implements Iterable { * @webref intlist:method * @webBrief Randomize the order of the list elements */ + @SuppressWarnings("unused") public void shuffle() { Random r = new Random(); int num = count; @@ -730,6 +661,7 @@ public class IntList implements Iterable { * Randomize the list order using the random() function from the specified * sketch, allowing shuffle() to use its current randomSeed() setting. */ + @SuppressWarnings("unused") public void shuffle(PApplet sketch) { int num = count; while (num > 1) { @@ -742,6 +674,23 @@ public class IntList implements Iterable { } + /** + * Return a random value from the list. + */ + public int random() { + return data[(int) (Math.random() * count)]; + } + + + /** + * Return a random value from the list, using the + * randomSeed() from the specified sketch object. + */ + public int random(PApplet sketch) { + return data[(int) sketch.random(count)]; + } + + public IntList copy() { IntList outgoing = new IntList(data); outgoing.count = count; @@ -762,8 +711,7 @@ public class IntList implements Iterable { @Override public Iterator iterator() { -// public Iterator valueIterator() { - return new Iterator() { + return new Iterator<>() { int index = -1; public void remove() { @@ -782,6 +730,12 @@ public class IntList implements Iterable { } + @Deprecated + public int[] array() { + return toArray(); + } + + /** * Create a new array with a copy of all the values. * @@ -789,17 +743,22 @@ public class IntList implements Iterable { * @webref intlist:method * @webBrief Create a new array with a copy of all the values */ - public int[] array() { - return array(null); + public int[] toArray() { + return toArray(null); + } + + + @Deprecated + public int[] array(int[] array) { + return toArray(array); } /** - * Copy values into the specified array. If the specified array is null or - * not the same size, a new array will be allocated. - * @param array + * Copy values into the specified array. If the specified array is + * null or not the same size, a new array will be allocated. */ - public int[] array(int[] array) { + public int[] toArray(int[] array) { if (array == null || array.length != count) { array = new int[count]; } @@ -808,55 +767,13 @@ public class IntList implements Iterable { } -// public int[] toIntArray() { -// int[] outgoing = new int[count]; -// for (int i = 0; i < count; i++) { -// outgoing[i] = (int) data[i]; -// } -// return outgoing; -// } - - -// public long[] toLongArray() { -// long[] outgoing = new long[count]; -// for (int i = 0; i < count; i++) { -// outgoing[i] = (long) data[i]; -// } -// return outgoing; -// } - - -// public float[] toFloatArray() { -// float[] outgoing = new float[count]; -// System.arraycopy(data, 0, outgoing, 0, count); -// return outgoing; -// } - - -// public double[] toDoubleArray() { -// double[] outgoing = new double[count]; -// for (int i = 0; i < count; i++) { -// outgoing[i] = data[i]; -// } -// return outgoing; -// } - - -// public String[] toStringArray() { -// String[] outgoing = new String[count]; -// for (int i = 0; i < count; i++) { -// outgoing[i] = String.valueOf(data[i]); -// } -// return outgoing; -// } - - /** - * Returns a normalized version of this array. Called getPercent() for - * consistency with the Dict classes. It's a getter method because it needs - * to returns a new list (because IntList/Dict can't do percentages or - * normalization in place on int values). + * Returns a normalized version of this array. Called getPercent() + * for consistency with the Dict classes. It's a getter method because + * it needs to return a new list (because IntList/Dict can't do + * percentages or normalization in place on int values). */ + @SuppressWarnings("unused") public FloatList getPercent() { double sum = 0; for (float value : array()) { @@ -871,24 +788,13 @@ public class IntList implements Iterable { } -// /** -// * Count the number of times each entry is found in this list. -// * Converts each entry to a String so it can be used as a key. -// */ -// public IntDict getTally() { -// IntDict outgoing = new IntDict(); -// for (int i = 0; i < count; i++) { -// outgoing.increment(String.valueOf(data[i])); -// } -// return outgoing; -// } - - + @SuppressWarnings("unused") public IntList getSubset(int start) { return getSubset(start, count - start); } + @SuppressWarnings("unused") public IntList getSubset(int start, int num) { int[] subset = new int[num]; System.arraycopy(data, start, subset, 0, num); diff --git a/core/src/processing/data/JSONArray.java b/core/src/processing/data/JSONArray.java index d81e4db60..2823dcdf3 100644 --- a/core/src/processing/data/JSONArray.java +++ b/core/src/processing/data/JSONArray.java @@ -561,6 +561,13 @@ public class JSONArray { } + /** Use toStringArray() instead. */ + @Deprecated + public String[] getStringArray() { + return toStringArray(); + } + + /** * Returns the entire JSONArray as an array of Strings. * (All values in the array must be of the String type.) @@ -569,7 +576,7 @@ public class JSONArray { * @webBrief Returns the entire JSONArray as an array of Strings * @see JSONArray#getIntArray() */ - public String[] getStringArray() { + public String[] toStringArray() { String[] outgoing = new String[size()]; for (int i = 0; i < size(); i++) { outgoing[i] = getString(i); @@ -578,6 +585,18 @@ public class JSONArray { } + public StringList toStringList() { + return new StringList(getStringArray()); + } + + + /** Use toIntArray() instead. */ + @Deprecated + public int[] getIntArray() { + return toIntArray(); + } + + /** * Returns the entire JSONArray as an array of ints. * (All values in the array must be of the int type.) @@ -586,7 +605,7 @@ public class JSONArray { * @webBrief Returns the entire JSONArray as an array of ints * @see JSONArray#getStringArray() */ - public int[] getIntArray() { + public int[] toIntArray() { int[] outgoing = new int[size()]; for (int i = 0; i < size(); i++) { outgoing[i] = getInt(i); @@ -595,8 +614,20 @@ public class JSONArray { } - /** Get this entire array as a long array. Everything must be an long. */ + public IntList toIntList() { + return new IntList(toIntArray()); + } + + + /** Use toLongArray() instead. */ + @Deprecated public long[] getLongArray() { + return toLongArray(); + } + + + /** Get this entire array as a long array. Everything must be an long. */ + public long[] toLongArray() { long[] outgoing = new long[size()]; for (int i = 0; i < size(); i++) { outgoing[i] = getLong(i); @@ -605,8 +636,20 @@ public class JSONArray { } - /** Get this entire array as a float array. Everything must be an float. */ + public LongList toLongList() { + return new LongList(toLongArray()); + } + + + /** Use toFloatArray() instead. */ + @Deprecated public float[] getFloatArray() { + return toFloatArray(); + } + + + /** Get this entire array as a float array. Everything must be an float. */ + public float[] toFloatArray() { float[] outgoing = new float[size()]; for (int i = 0; i < size(); i++) { outgoing[i] = getFloat(i); @@ -615,8 +658,20 @@ public class JSONArray { } - /** Get this entire array as a double array. Everything must be an double. */ + public FloatList toFloatList() { + return new FloatList(toFloatArray()); + } + + + /** Use toDoubleArray() instead. */ + @Deprecated public double[] getDoubleArray() { + return toDoubleArray(); + } + + + /** Get this entire array as a double array. Everything must be an double. */ + public double[] toDoubleArray() { double[] outgoing = new double[size()]; for (int i = 0; i < size(); i++) { outgoing[i] = getDouble(i); @@ -625,8 +680,19 @@ public class JSONArray { } - /** Get this entire array as a boolean array. Everything must be a boolean. */ + public DoubleList toDoubleList() { + return new DoubleList(toDoubleArray()); + } + + + /** Use toBooleanArray() instead. */ public boolean[] getBooleanArray() { + return toBooleanArray(); + } + + + /** Get this entire array as a boolean array. Everything must be a boolean. */ + public boolean[] toBooleanArray() { boolean[] outgoing = new boolean[size()]; for (int i = 0; i < size(); i++) { outgoing[i] = getBoolean(i); diff --git a/core/src/processing/data/JSONObject.java b/core/src/processing/data/JSONObject.java index 7f7a113ed..2b9885702 100644 --- a/core/src/processing/data/JSONObject.java +++ b/core/src/processing/data/JSONObject.java @@ -821,6 +821,31 @@ public class JSONObject { } + public IntList getIntList(String key) { + return getJSONArray(key).toIntList(); + } + + + public LongList getLongList(String key) { + return getJSONArray(key).toLongList(); + } + + + public FloatList getFloatList(String key) { + return getJSONArray(key).toFloatList(); + } + + + public DoubleList getDoubleList(String key) { + return getJSONArray(key).toDoubleList(); + } + + + public StringList getStringList(String key) { + return getJSONArray(key).toStringList(); + } + + /** * Given a key value, retrieves the associated JSONObject. * diff --git a/core/src/processing/data/LongList.java b/core/src/processing/data/LongList.java index 2c818526c..f78edf9e5 100644 --- a/core/src/processing/data/LongList.java +++ b/core/src/processing/data/LongList.java @@ -15,15 +15,14 @@ import processing.core.PApplet; /** - * Helper class for a list of ints. Lists are designed to have some of the - * features of ArrayLists, but to maintain the simplicity and efficiency of - * working with arrays. - * - * Functions like sort() and shuffle() always act on the list itself. To get - * a sorted copy, use list.copy().sort(). + * Helper class for a list of long values. Lists are designed + * to have some features of ArrayList, but to maintain the + * simplicity and efficiency of working with arrays. + * Functions such as sort() and shuffle() always act on + * the list itself. To get a sorted copy, use list.copy().sort(). * * @nowebref - * @see FloatList + * @see IntList * @see StringList */ public class LongList implements Iterable { @@ -31,6 +30,7 @@ public class LongList implements Iterable { protected long[] data; + @SuppressWarnings("unused") public LongList() { data = new long[10]; } @@ -47,7 +47,7 @@ public class LongList implements Iterable { /** * @nowebref */ - public LongList(int[] source) { + public LongList(long[] source) { count = source.length; data = new long[count]; System.arraycopy(source, 0, data, 0, count); @@ -56,13 +56,14 @@ public class LongList implements Iterable { /** * Construct an IntList from an iterable pile of objects. - * For instance, a float array, an array of strings, who knows). - * Un-parseable or null values will be set to 0. + * For instance, a float array, an array of strings, who knows. + * Un-parsable or null values will be set to 0. * @nowebref */ - public LongList(Iterable iter) { + @SuppressWarnings("unused") + public LongList(Iterable iterable) { this(10); - for (Object o : iter) { + for (Object o : iterable) { if (o == null) { append(0); // missing value default } else if (o instanceof Number) { @@ -77,21 +78,24 @@ public class LongList implements Iterable { /** * Construct an IntList from a random pile of objects. - * Un-parseable or null values will be set to zero. + * Un-parsable or null values will be set to zero. */ + @SuppressWarnings("unused") public LongList(Object... items) { - final int missingValue = 0; // nuts, can't be last/final/second arg + final long missingValue = 0; // nuts, can't be last/final/second arg count = items.length; data = new long[count]; int index = 0; for (Object o : items) { - int value = missingValue; + long value = missingValue; if (o != null) { if (o instanceof Number) { - value = ((Number) o).intValue(); + value = ((Number) o).longValue(); } else { - value = PApplet.parseInt(o.toString().trim(), missingValue); + try { + value = Long.parseLong(o.toString().trim()); + } catch (NumberFormatException ignored) { } } } data[index++] = value; @@ -99,6 +103,7 @@ public class LongList implements Iterable { } + @SuppressWarnings("unused") static public LongList fromRange(int stop) { return fromRange(0, stop); } @@ -240,6 +245,7 @@ public class LongList implements Iterable { // Remove the first instance of a particular value, // and return the index at which it was found. + @SuppressWarnings("unused") public int removeValue(int value) { int index = index(value); if (index != -1) { @@ -252,6 +258,7 @@ public class LongList implements Iterable { // Remove all instances of a particular value, // and return the number of values found and removed + @SuppressWarnings("unused") public int removeValues(int value) { int ii = 0; for (int i = 0; i < count; i++) { @@ -294,6 +301,7 @@ public class LongList implements Iterable { /** Add this value, but only if it's not already in the list. */ + @SuppressWarnings("unused") public void appendUnique(int value) { if (!hasValue(value)) { append(value); @@ -370,60 +378,8 @@ public class LongList implements Iterable { } - // below are aborted attempts at more optimized versions of the code - // that are harder to read and debug... - -// if (index + values.length >= count) { -// // We're past the current 'count', check to see if we're still allocated -// // index 9, data.length = 10, values.length = 1 -// if (index + values.length < data.length) { -// // There's still room for these entries, even though it's past 'count'. -// // First clear out the entries leading up to it, however. -// for (int i = count; i < index; i++) { -// data[i] = 0; -// } -// data[index] = -// } -// if (index >= data.length) { -// int length = index + values.length; -// int[] temp = new int[length]; -// System.arraycopy(data, 0, temp, 0, count); -// System.arraycopy(values, 0, temp, index, values.length); -// data = temp; -// count = data.length; -// } else { -// -// } -// -// } else if (count == data.length) { -// int[] temp = new int[count << 1]; -// System.arraycopy(data, 0, temp, 0, index); -// temp[index] = value; -// System.arraycopy(data, index, temp, index+1, count - index); -// data = temp; -// -// } else { -// // data[] has room to grow -// // for() loop believed to be faster than System.arraycopy over itself -// for (int i = count; i > index; --i) { -// data[i] = data[i-1]; -// } -// data[index] = value; -// count++; -// } - - /** Return the first index of a particular value. */ public int index(int what) { - /* - if (indexCache != null) { - try { - return indexCache.get(what); - } catch (Exception e) { // not there - return -1; - } - } - */ for (int i = 0; i < count; i++) { if (data[i] == what) { return i; @@ -433,15 +389,6 @@ public class LongList implements Iterable { } - // !!! TODO this is not yet correct, because it's not being reset when - // the rest of the entries are changed -// protected void cacheIndices() { -// indexCache = new HashMap(); -// for (int i = 0; i < count; i++) { -// indexCache.put(data[i], i); -// } -// } - /** * Check if a number is a part of the list * @@ -449,10 +396,6 @@ public class LongList implements Iterable { * @webBrief Check if a number is a part of the list */ public boolean hasValue(int value) { -// if (indexCache == null) { -// cacheIndices(); -// } -// return index(what) != -1; for (int i = 0; i < count; i++) { if (data[i] == value) { return true; @@ -567,6 +510,7 @@ public class LongList implements Iterable { // returns the index of the minimum value. // if there are ties, it returns the first one found. + @SuppressWarnings("unused") public int minIndex() { checkMinMax("minIndex"); long value = data[0]; @@ -674,23 +618,6 @@ public class LongList implements Iterable { } - // use insert() -// public void splice(int index, int value) { -// } - - -// public void subset(int start) { -// subset(start, count - start); -// } -// -// -// public void subset(int start, int num) { -// for (int i = 0; i < num; i++) { -// data[i] = data[i+start]; -// } -// count = num; -// } - /** * Reverse the order of the list elements * @@ -715,6 +642,7 @@ public class LongList implements Iterable { * @webref intlist:method * @webBrief Randomize the order of the list elements */ + @SuppressWarnings("unused") public void shuffle() { Random r = new Random(); int num = count; @@ -732,6 +660,7 @@ public class LongList implements Iterable { * Randomize the list order using the random() function from the specified * sketch, allowing shuffle() to use its current randomSeed() setting. */ + @SuppressWarnings("unused") public void shuffle(PApplet sketch) { int num = count; while (num > 1) { @@ -744,6 +673,23 @@ public class LongList implements Iterable { } + /** + * Return a random value from the list. + */ + public long random() { + return data[(int) (Math.random() * count)]; + } + + + /** + * Return a random value from the list, using the + * randomSeed() from the specified sketch object. + */ + public long random(PApplet sketch) { + return data[(int) sketch.random(count)]; + } + + public LongList copy() { LongList outgoing = new LongList(data); outgoing.count = count; @@ -764,8 +710,7 @@ public class LongList implements Iterable { @Override public Iterator iterator() { -// public Iterator valueIterator() { - return new Iterator() { + return new Iterator<>() { int index = -1; public void remove() { @@ -791,78 +736,35 @@ public class LongList implements Iterable { * @webref intlist:method * @webBrief Create a new array with a copy of all the values */ - public int[] array() { - return array(null); + public long[] toArray() { + return toArray(null); } /** - * Copy values into the specified array. If the specified array is null or - * not the same size, a new array will be allocated. - * @param array + * Copy values into the specified array. If the specified array is + * null or not the same size, a new array will be allocated. */ - public int[] array(int[] array) { + public long[] toArray(long[] array) { if (array == null || array.length != count) { - array = new int[count]; + array = new long[count]; } System.arraycopy(data, 0, array, 0, count); return array; } -// public int[] toIntArray() { -// int[] outgoing = new int[count]; -// for (int i = 0; i < count; i++) { -// outgoing[i] = (int) data[i]; -// } -// return outgoing; -// } - - -// public long[] toLongArray() { -// long[] outgoing = new long[count]; -// for (int i = 0; i < count; i++) { -// outgoing[i] = (long) data[i]; -// } -// return outgoing; -// } - - -// public float[] toFloatArray() { -// float[] outgoing = new float[count]; -// System.arraycopy(data, 0, outgoing, 0, count); -// return outgoing; -// } - - -// public double[] toDoubleArray() { -// double[] outgoing = new double[count]; -// for (int i = 0; i < count; i++) { -// outgoing[i] = data[i]; -// } -// return outgoing; -// } - - -// public String[] toStringArray() { -// String[] outgoing = new String[count]; -// for (int i = 0; i < count; i++) { -// outgoing[i] = String.valueOf(data[i]); -// } -// return outgoing; -// } - - /** - * Returns a normalized version of this array. Called getPercent() for - * consistency with the Dict classes. It's a getter method because it needs - * to returns a new list (because IntList/Dict can't do percentages or - * normalization in place on int values). + * Returns a normalized version of this array. Called getPercent() + * for consistency with the Dict classes. It's a get method because + * it needs to return a new list (because IntList/Dict can't do + * percentages or normalization in place on int values). */ + @SuppressWarnings("unused") public FloatList getPercent() { double sum = 0; - for (float value : array()) { - sum += value; + for (int i = 0; i < count; i++) { + sum += data[i]; } FloatList outgoing = new FloatList(count); for (int i = 0; i < count; i++) { @@ -873,26 +775,14 @@ public class LongList implements Iterable { } -// /** -// * Count the number of times each entry is found in this list. -// * Converts each entry to a String so it can be used as a key. -// */ -// public IntDict getTally() { -// IntDict outgoing = new IntDict(); -// for (int i = 0; i < count; i++) { -// outgoing.increment(String.valueOf(data[i])); -// } -// return outgoing; -// } - - + @SuppressWarnings("unused") public LongList getSubset(int start) { return getSubset(start, count - start); } public LongList getSubset(int start, int num) { - int[] subset = new int[num]; + long[] subset = new long[num]; System.arraycopy(data, start, subset, 0, num); return new LongList(subset); } diff --git a/core/src/processing/data/StringList.java b/core/src/processing/data/StringList.java index 8438bfdb9..f7b6e6b4d 100644 --- a/core/src/processing/data/StringList.java +++ b/core/src/processing/data/StringList.java @@ -9,12 +9,11 @@ import java.util.Random; import processing.core.PApplet; /** - * Helper class for a list of Strings. Lists are designed to have some of the - * features of ArrayLists, but to maintain the simplicity and efficiency of - * working with arrays. - * - * Functions like sort() and shuffle() always act on the list itself. To get - * a sorted copy, use list.copy().sort(). + * Helper class for a list of String objects. Lists are designed + * to have some features of ArrayList, but to maintain the + * simplicity and efficiency of working with arrays. + * Functions such as sort() and shuffle() always act on + * the list itself. To get a sorted copy, use list.copy().sort(). * * @webref data:composite * @webBrief Helper class for a list of Strings @@ -58,9 +57,6 @@ public class StringList implements Iterable { data = new String[count]; int index = 0; for (Object o : items) { -// // Not gonna go with null values staying that way because perhaps -// // the most common case here is to immediately call join() or similar. -// data[index++] = String.valueOf(o); // Keep null values null (because join() will make non-null anyway) if (o != null) { // leave null values null data[index] = o.toString(); @@ -76,9 +72,9 @@ public class StringList implements Iterable { * * @nowebref */ - public StringList(Iterable iter) { + public StringList(Iterable iterable) { this(10); - for (String s : iter) { + for (String s : iterable) { append(s); } } @@ -115,7 +111,7 @@ public class StringList implements Iterable { data = temp; } else if (length > count) { - Arrays.fill(data, count, length, 0); + Arrays.fill(data, count, length, null); } count = length; } @@ -193,11 +189,6 @@ public class StringList implements Iterable { throw new ArrayIndexOutOfBoundsException(index); } String entry = data[index]; -// int[] outgoing = new int[count - 1]; -// System.arraycopy(data, 0, outgoing, 0, index); -// count--; -// System.arraycopy(data, index + 1, outgoing, 0, count - index); -// data = outgoing; for (int i = index; i < count-1; i++) { data[i] = data[i+1]; } @@ -207,6 +198,7 @@ public class StringList implements Iterable { // Remove the first instance of a particular value and return its index. + @SuppressWarnings("unused") public int removeValue(String value) { if (value == null) { for (int i = 0; i < count; i++) { @@ -227,6 +219,7 @@ public class StringList implements Iterable { // Remove all instances of a particular value and return the count removed. + @SuppressWarnings("unused") public int removeValues(String value) { int ii = 0; if (value == null) { @@ -249,6 +242,7 @@ public class StringList implements Iterable { // replace the first value that matches, return the index that was replaced + @SuppressWarnings("unused") public int replaceValue(String value, String newValue) { if (value == null) { for (int i = 0; i < count; i++) { @@ -270,6 +264,7 @@ public class StringList implements Iterable { // replace all values that match, return the count of those replaced + @SuppressWarnings("unused") public int replaceValues(String value, String newValue) { int changed = 0; if (value == null) { @@ -458,15 +453,6 @@ public class StringList implements Iterable { } - // !!! TODO this is not yet correct, because it's not being reset when - // the rest of the entries are changed -// protected void cacheIndices() { -// indexCache = new HashMap(); -// for (int i = 0; i < count; i++) { -// indexCache.put(data[i], i); -// } -// } - /** * Check if a value is a part of the list * @@ -537,23 +523,6 @@ public class StringList implements Iterable { } - // use insert() -// public void splice(int index, int value) { -// } - - -// public void subset(int start) { -// subset(start, count - start); -// } -// -// -// public void subset(int start, int num) { -// for (int i = 0; i < num; i++) { -// data[i] = data[i+start]; -// } -// count = num; -// } - /** * Reverse the order of the list * @@ -577,6 +546,7 @@ public class StringList implements Iterable { * @webref stringlist:method * @webBrief Randomize the order of the list elements */ + @SuppressWarnings("unused") public void shuffle() { Random r = new Random(); int num = count; @@ -594,6 +564,7 @@ public class StringList implements Iterable { * Randomize the list order using the random() function from the specified * sketch, allowing shuffle() to use its current randomSeed() setting. */ + @SuppressWarnings("unused") public void shuffle(PApplet sketch) { int num = count; while (num > 1) { @@ -606,6 +577,23 @@ public class StringList implements Iterable { } + /** + * Return a random value from the list. + */ + public String random() { + return data[(int) (Math.random() * count)]; + } + + + /** + * Return a random value from the list, using the + * randomSeed() from the specified sketch object. + */ + public String random(PApplet sketch) { + return data[(int) sketch.random(count)]; + } + + /** * Make the entire list lower case. * @@ -656,12 +644,7 @@ public class StringList implements Iterable { @Override public Iterator iterator() { -// return valueIterator(); -// } -// -// -// public Iterator valueIterator() { - return new Iterator() { + return new Iterator<>() { int index = -1; public void remove() { @@ -680,6 +663,12 @@ public class StringList implements Iterable { } + @Deprecated + public String[] array() { + return toArray(); + } + + /** * Create a new array with a copy of all the values. * @@ -687,17 +676,22 @@ public class StringList implements Iterable { * @webref stringlist:method * @webBrief Create a new array with a copy of all the values */ - public String[] array() { - return array(null); + public String[] toArray() { + return toArray(null); + } + + + @Deprecated + public String[] array(String[] array) { + return toArray(array); } /** * Copy values into the specified array. If the specified array is null or * not the same size, a new array will be allocated. - * @param array */ - public String[] array(String[] array) { + public String[] toArray(String[] array) { if (array == null || array.length != count) { array = new String[count]; } @@ -706,6 +700,7 @@ public class StringList implements Iterable { } + @SuppressWarnings("unused") public StringList getSubset(int start) { return getSubset(start, count - start); } diff --git a/core/src/processing/opengl/FontTexture.java b/core/src/processing/opengl/FontTexture.java index af28ad9f3..bec0ab098 100644 --- a/core/src/processing/opengl/FontTexture.java +++ b/core/src/processing/opengl/FontTexture.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -104,7 +104,7 @@ class FontTexture implements PConstants { offsetY = 0; lineHeight = 0; - texinfoMap = new HashMap(); + texinfoMap = new HashMap<>(); glyphTexinfos = new TextureInfo[font.getGlyphCount()]; addAllGlyphsToTexture(pg, font); } diff --git a/core/src/processing/opengl/FrameBuffer.java b/core/src/processing/opengl/FrameBuffer.java index e2992be68..a5426c52d 100644 --- a/core/src/processing/opengl/FrameBuffer.java +++ b/core/src/processing/opengl/FrameBuffer.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 diff --git a/core/src/processing/opengl/PGL.java b/core/src/processing/opengl/PGL.java index a87d248fe..c415790f1 100644 --- a/core/src/processing/opengl/PGL.java +++ b/core/src/processing/opengl/PGL.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -129,6 +129,36 @@ public abstract class PGL { // ........................................................ + // These parameters are left public so advanced users can experiment with different + // configurations of buffer object streaming, buffer usage modes and access policies. + + /** Controls the use of buffer object streaming: + * https://www.khronos.org/opengl/wiki/Buffer_Object_Streaming + * In combination with use direct buffers, + * the only advantage of enabling it in immediate mode would be to reduce memory footprint + * since the direct vertex buffers would not be allocated, simply mapped from the OpenGL + * objects and thus only the vertex arrays would be created. + * In the case of the retained mode (PShape), memory footprint would be reduced (for the same + * reason) but it may enable some speed-ups when editing a geometry in within a being/end + * tessellation update block. */ + static public boolean bufferStreamingImmediate = false; + static public boolean bufferStreamingRetained = true; + + /** Controls the usage of the buffer data store: + * https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBufferData.xhtml + * Supported options include STATIC_DRAW, DYNAMIC_DRAW, STREAM_DRAW, and STREAM_READ. + */ + static public int bufferUsageRetained; + static public int bufferUsageImmediate; + + /** Controls the access to the mapped buffer object's data store (when using buffer streaming). + * https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glMapBuffer.xhtml + * Supported options are READ_ONLY, WRITE_ONLY, and READ_WRITE. + */ + static public int bufferMapAccess; + + // ........................................................ + // Variables to handle single-buffered situations (i.e.: Android) protected IntBuffer firstFrame; @@ -340,7 +370,7 @@ public abstract class PGL { protected static int INDEX_TYPE = 0x1403; // GL_UNSIGNED_SHORT /** Machine Epsilon for float precision. */ - protected static float FLOAT_EPS = Float.MIN_VALUE; + protected static float FLOAT_EPS; // Calculation of the Machine Epsilon for float precision. From: // http://en.wikipedia.org/wiki/Machine_epsilon#Approximation_using_Java static { @@ -526,14 +556,14 @@ public abstract class PGL { protected boolean getDepthTest() { intBuffer.rewind(); getBooleanv(DEPTH_TEST, intBuffer); - return intBuffer.get(0) == 0 ? false : true; + return intBuffer.get(0) != 0; } protected boolean getDepthWriteMask() { intBuffer.rewind(); getBooleanv(DEPTH_WRITEMASK, intBuffer); - return intBuffer.get(0) == 0 ? false : true; + return intBuffer.get(0) != 0; } @@ -809,14 +839,14 @@ public abstract class PGL { // Multiply the texture by the button color float ba = ((stopButtonColor >> 24) & 0xFF) / 255f; float br = ((stopButtonColor >> 16) & 0xFF) / 255f; - float bg = ((stopButtonColor >> 8) & 0xFF) / 255f; - float bb = ((stopButtonColor >> 0) & 0xFF) / 255f; + float bg = ((stopButtonColor >> 8) & 0xFF) / 255f; + float bb = (stopButtonColor & 0xFF) / 255f; for (int i = 0; i < color.length; i++) { int c = closeButtonPix[i]; int a = (int)(ba * ((c >> 24) & 0xFF)); int r = (int)(br * ((c >> 16) & 0xFF)); - int g = (int)(bg * ((c >> 8) & 0xFF)); - int b = (int)(bb * ((c >> 0) & 0xFF)); + int g = (int)(bg * ((c >> 8) & 0xFF)); + int b = (int)(bb * (c & 0xFF)); color[i] = javaToNativeARGB((a << 24) | (r << 16) | (g << 8) | b); } IntBuffer buf = allocateIntBuffer(color); @@ -1092,8 +1122,8 @@ public abstract class PGL { depthComponent = DEPTH_COMPONENT32; } else if (depthBits == 24) { depthComponent = DEPTH_COMPONENT24; - } else if (depthBits == 16) { - depthComponent = DEPTH_COMPONENT16; + //} else if (depthBits == 16) { + //depthComponent = DEPTH_COMPONENT16; } IntBuffer depthBuf = multisample ? glMultiDepth : glDepth; @@ -1116,8 +1146,8 @@ public abstract class PGL { stencilIndex = STENCIL_INDEX8; } else if (stencilBits == 4) { stencilIndex = STENCIL_INDEX4; - } else if (stencilBits == 1) { - stencilIndex = STENCIL_INDEX1; + //} else if (stencilBits == 1) { + //stencilIndex = STENCIL_INDEX1; } IntBuffer stencilBuf = multisample ? glMultiStencil : glStencil; @@ -1602,7 +1632,9 @@ public abstract class PGL { // bit shifting this might be more efficient protected static int nextPowerOfTwo(int val) { int ret = 1; - while (ret < val) ret <<= 1; + while (ret < val) { + ret <<= 1; + } return ret; } @@ -1854,8 +1886,7 @@ public abstract class PGL { return 1; } else { // Number of samples is always an even number: - int n = 2 * (quality / 2); - return n; + return 2 * (quality / 2); } } @@ -1968,31 +1999,29 @@ public abstract class PGL { String[] vertSrc; + Pattern[] search; + String[] replace; + int offset = 1; if (version < 130) { - Pattern[] search = { }; - String[] replace = { }; - int offset = 1; + search = new Pattern[] { }; + replace = new String[] { }; - vertSrc = preprocessShaderSource(vertSrc0, search, replace, offset); - vertSrc[0] = "#version " + version + versionSuffix; } else { // We need to replace 'texture' uniform by 'texMap' uniform and // 'textureXXX()' functions by 'texture()' functions. Order of these // replacements is important to prevent collisions between these two. - Pattern[] search = new Pattern[] { - Pattern.compile(String.format(GLSL_ID_REGEX, "varying")), - Pattern.compile(String.format(GLSL_ID_REGEX, "attribute")), - Pattern.compile(String.format(GLSL_ID_REGEX, "texture")), - Pattern.compile(String.format(GLSL_FN_REGEX, "texture2DRect|texture2D|texture3D|textureCube")) + search = new Pattern[] { + Pattern.compile(String.format(GLSL_ID_REGEX, "varying")), + Pattern.compile(String.format(GLSL_ID_REGEX, "attribute")), + Pattern.compile(String.format(GLSL_ID_REGEX, "texture")), + Pattern.compile(String.format(GLSL_FN_REGEX, "texture2DRect|texture2D|texture3D|textureCube")) }; - String[] replace = new String[] { - "out", "in", "texMap", "texture", + replace = new String[]{ + "out", "in", "texMap", "texture", }; - int offset = 1; - - vertSrc = preprocessShaderSource(vertSrc0, search, replace, offset); - vertSrc[0] = "#version " + version + versionSuffix; } + vertSrc = preprocessShaderSource(vertSrc0, search, replace, offset); + vertSrc[0] = "#version " + version + versionSuffix; return vertSrc; } @@ -2022,8 +2051,7 @@ public abstract class PGL { } protected static boolean containsVersionDirective(String[] shSrc) { - for (int i = 0; i < shSrc.length; i++) { - String line = shSrc[i]; + for (String line : shSrc) { int versionIndex = line.indexOf("#version"); if (versionIndex >= 0) { int commentIndex = line.indexOf("//"); @@ -2071,14 +2099,14 @@ public abstract class PGL { protected boolean compiled(int shader) { intBuffer.rewind(); getShaderiv(shader, COMPILE_STATUS, intBuffer); - return intBuffer.get(0) == 0 ? false : true; + return intBuffer.get(0) != 0; } protected boolean linked(int program) { intBuffer.rewind(); getProgramiv(program, LINK_STATUS, intBuffer); - return intBuffer.get(0) == 0 ? false : true; + return intBuffer.get(0) != 0; } @@ -2087,38 +2115,27 @@ public abstract class PGL { if (status == FRAMEBUFFER_COMPLETE) { return 0; } else if (status == FRAMEBUFFER_UNDEFINED) { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "framebuffer undefined")); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "framebuffer undefined"); } else if (status == FRAMEBUFFER_INCOMPLETE_ATTACHMENT) { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "incomplete attachment")); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "incomplete attachment"); } else if (status == FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "incomplete missing attachment")); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "incomplete missing attachment"); } else if (status == FRAMEBUFFER_INCOMPLETE_DIMENSIONS) { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "incomplete dimensions")); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "incomplete dimensions"); } else if (status == FRAMEBUFFER_INCOMPLETE_FORMATS) { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "incomplete formats")); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "incomplete formats"); } else if (status == FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER) { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "incomplete draw buffer")); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "incomplete draw buffer"); } else if (status == FRAMEBUFFER_INCOMPLETE_READ_BUFFER) { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "incomplete read buffer")); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "incomplete read buffer"); } else if (status == FRAMEBUFFER_UNSUPPORTED) { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "framebuffer unsupported")); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "framebuffer unsupported"); } else if (status == FRAMEBUFFER_INCOMPLETE_MULTISAMPLE) { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "incomplete multisample buffer")); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "incomplete multisample buffer"); } else if (status == FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS) { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "incomplete layer targets")); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "incomplete layer targets"); } else { - System.err.println(String.format(FRAMEBUFFER_ERROR, - "unknown error " + status)); + System.err.printf((FRAMEBUFFER_ERROR) + "%n", "unknown error " + status); } return status; } @@ -2138,21 +2155,27 @@ public abstract class PGL { int[] res = {0, 0, 0}; String[] parts = version.split(" "); - for (int i = 0; i < parts.length; i++) { - if (0 < parts[i].indexOf(".")) { - String[] nums = parts[i].split("\\."); + for (String part : parts) { + if (0 < part.indexOf(".")) { + String[] nums = part.split("\\."); try { res[0] = Integer.parseInt(nums[0]); - } catch (NumberFormatException e) { } + } catch (NumberFormatException e) { + // ignored + } if (1 < nums.length) { try { res[1] = Integer.parseInt(nums[1]); - } catch (NumberFormatException e) { } + } catch (NumberFormatException e) { + // ignored + } } if (2 < nums.length) { try { res[2] = Integer.parseInt(nums[2]); - } catch (NumberFormatException e) { } + } catch (NumberFormatException e) { + // ignored + } } break; } @@ -2166,10 +2189,10 @@ public abstract class PGL { int major = getGLVersion()[0]; if (major < 2) { String ext = getString(EXTENSIONS); - return ext.indexOf("_framebuffer_object") != -1 && - ext.indexOf("_vertex_shader") != -1 && - ext.indexOf("_shader_objects") != -1 && - ext.indexOf("_shading_language") != -1; + return (ext.contains("_framebuffer_object") && + ext.contains("_vertex_shader") && + ext.contains("_shader_objects") && + ext.contains("_shading_language")); } else { return true; } @@ -2183,10 +2206,10 @@ public abstract class PGL { int major = getGLVersion()[0]; if (major < 2) { String ext = getString(EXTENSIONS); - return ext.indexOf("_fragment_shader") != -1 && - ext.indexOf("_vertex_shader") != -1 && - ext.indexOf("_shader_objects") != -1 && - ext.indexOf("_shading_language") != -1; + return (ext.contains("_fragment_shader") && + ext.contains("_vertex_shader") && + ext.contains("_shader_objects") && + ext.contains("_shading_language")); } else { return true; } @@ -2198,9 +2221,9 @@ public abstract class PGL { if (major < 3) { String ext = getString(EXTENSIONS); if (isES()) { - return -1 < ext.indexOf("_texture_npot"); + return ext.contains("_texture_npot"); } else { - return -1 < ext.indexOf("_texture_non_power_of_two"); + return ext.contains("_texture_non_power_of_two"); } } else { return true; @@ -2216,7 +2239,7 @@ public abstract class PGL { return true; } else { String ext = getString(EXTENSIONS); - return -1 < ext.indexOf("_generate_mipmap"); + return ext.contains("_generate_mipmap"); } } @@ -2225,7 +2248,7 @@ public abstract class PGL { int major = getGLVersion()[0]; if (major < 3) { String ext = getString(EXTENSIONS); - return -1 < ext.indexOf("_framebuffer_multisample"); + return ext.contains("_framebuffer_multisample"); } else { return true; } @@ -2236,7 +2259,7 @@ public abstract class PGL { int major = getGLVersion()[0]; if (major < 3) { String ext = getString(EXTENSIONS); - return -1 < ext.indexOf("_packed_depth_stencil"); + return ext.contains("_packed_depth_stencil"); } else { return true; } @@ -2247,7 +2270,7 @@ public abstract class PGL { int major = getGLVersion()[0]; if (isES() || major < 3) { String ext = getString(EXTENSIONS); - return -1 < ext.indexOf("_texture_filter_anisotropic"); + return ext.contains("_texture_filter_anisotropic"); } else { return true; } @@ -2355,7 +2378,7 @@ public abstract class PGL { protected static ByteBuffer updateByteBuffer(ByteBuffer buf, byte[] arr, boolean wrap) { - if (USE_DIRECT_BUFFERS) { + if (USE_DIRECT_BUFFERS || (buf != null && buf.isDirect())) { if (buf == null || buf.capacity() < arr.length) { buf = allocateDirectByteBuffer(arr.length); } @@ -2380,7 +2403,7 @@ public abstract class PGL { protected static void updateByteBuffer(ByteBuffer buf, byte[] arr, int offset, int size) { - if (USE_DIRECT_BUFFERS || (buf.hasArray() && buf.array() != arr)) { + if (buf.isDirect() || (buf.hasArray() && buf.array() != arr)) { buf.position(offset); buf.put(arr, offset, size); buf.rewind(); @@ -2447,7 +2470,7 @@ public abstract class PGL { protected static ShortBuffer updateShortBuffer(ShortBuffer buf, short[] arr, boolean wrap) { - if (USE_DIRECT_BUFFERS) { + if (USE_DIRECT_BUFFERS || (buf != null && buf.isDirect())) { if (buf == null || buf.capacity() < arr.length) { buf = allocateDirectShortBuffer(arr.length); } @@ -2472,7 +2495,7 @@ public abstract class PGL { protected static void updateShortBuffer(ShortBuffer buf, short[] arr, int offset, int size) { - if (USE_DIRECT_BUFFERS || (buf.hasArray() && buf.array() != arr)) { + if (buf.isDirect() || (buf.hasArray() && buf.array() != arr)) { buf.position(offset); buf.put(arr, offset, size); buf.rewind(); @@ -2539,7 +2562,7 @@ public abstract class PGL { protected static IntBuffer updateIntBuffer(IntBuffer buf, int[] arr, boolean wrap) { - if (USE_DIRECT_BUFFERS) { + if (USE_DIRECT_BUFFERS || (buf != null && buf.isDirect())) { if (buf == null || buf.capacity() < arr.length) { buf = allocateDirectIntBuffer(arr.length); } @@ -2564,7 +2587,7 @@ public abstract class PGL { protected static void updateIntBuffer(IntBuffer buf, int[] arr, int offset, int size) { - if (USE_DIRECT_BUFFERS || (buf.hasArray() && buf.array() != arr)) { + if (buf.isDirect() || (buf.hasArray() && buf.array() != arr)) { buf.position(offset); buf.put(arr, offset, size); buf.rewind(); @@ -2602,8 +2625,7 @@ public abstract class PGL { protected static FloatBuffer allocateDirectFloatBuffer(int size) { int bytes = PApplet.max(MIN_DIRECT_BUFFER_SIZE, size) * SIZEOF_FLOAT; - return ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder()). - asFloatBuffer(); + return ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder()).asFloatBuffer(); } @@ -2630,7 +2652,7 @@ public abstract class PGL { protected static FloatBuffer updateFloatBuffer(FloatBuffer buf, float[] arr, boolean wrap) { - if (USE_DIRECT_BUFFERS) { + if (USE_DIRECT_BUFFERS || (buf != null && buf.isDirect())) { if (buf == null || buf.capacity() < arr.length) { buf = allocateDirectFloatBuffer(arr.length); } @@ -2655,7 +2677,7 @@ public abstract class PGL { protected static void updateFloatBuffer(FloatBuffer buf, float[] arr, int offset, int size) { - if (USE_DIRECT_BUFFERS || (buf.hasArray() && buf.array() != arr)) { + if (buf.isDirect() || (buf.hasArray() && buf.array() != arr)) { buf.position(offset); buf.put(arr, offset, size); buf.rewind(); @@ -2716,27 +2738,27 @@ public abstract class PGL { protected interface Tessellator { - public void setCallback(int flag); - public void setWindingRule(int rule); - public void setProperty(int property, int value); + void setCallback(int flag); + void setWindingRule(int rule); + void setProperty(int property, int value); - public void beginPolygon(); - public void beginPolygon(Object data); - public void endPolygon(); - public void beginContour(); - public void endContour(); - public void addVertex(double[] v); - public void addVertex(double[] v, int n, Object data); + void beginPolygon(); + void beginPolygon(Object data); + void endPolygon(); + void beginContour(); + void endContour(); + void addVertex(double[] v); + void addVertex(double[] v, int n, Object data); } protected interface TessellatorCallback { - public void begin(int type); - public void end(); - public void vertex(Object data); - public void combine(double[] coords, Object[] data, - float[] weight, Object[] outData); - public void error(int errnum); + void begin(int type); + void end(); + void vertex(Object data); + void combine(double[] coords, Object[] data, + float[] weight, Object[] outData); + void error(int errnum); } @@ -2762,9 +2784,9 @@ public abstract class PGL { protected interface FontOutline { - public boolean isDone(); - public int currentSegment(float coords[]); - public void next(); + boolean isDone(); + int currentSegment(float[] coords); + void next(); } diff --git a/core/src/processing/opengl/PGraphics2D.java b/core/src/processing/opengl/PGraphics2D.java index 8fbdd5eff..fc9dd7af7 100644 --- a/core/src/processing/opengl/PGraphics2D.java +++ b/core/src/processing/opengl/PGraphics2D.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 diff --git a/core/src/processing/opengl/PGraphics3D.java b/core/src/processing/opengl/PGraphics3D.java index 50b18b167..5a5e1c3b3 100644 --- a/core/src/processing/opengl/PGraphics3D.java +++ b/core/src/processing/opengl/PGraphics3D.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 diff --git a/core/src/processing/opengl/PGraphicsOpenGL.java b/core/src/processing/opengl/PGraphicsOpenGL.java index dc224e40a..4470f01a4 100644 --- a/core/src/processing/opengl/PGraphicsOpenGL.java +++ b/core/src/processing/opengl/PGraphicsOpenGL.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -239,7 +239,7 @@ public class PGraphicsOpenGL extends PGraphics { protected AsyncPixelReader asyncPixelReader; protected boolean asyncPixelReaderInitialized; - // Keeps track of ongoing transfers so they can be finished. + // Keeps track of ongoing transfers so that they can be finished. // Set is copied to the List when we need to iterate it // so that readers can remove themselves from the Set during // iteration if they don't have any ongoing transfers. @@ -575,9 +575,14 @@ public class PGraphicsOpenGL extends PGraphics { viewport = PGL.allocateIntBuffer(4); + PGL.bufferUsageRetained = PGL.DYNAMIC_DRAW; + PGL.bufferUsageImmediate = PGL.STATIC_DRAW; + PGL.bufferMapAccess = PGL.READ_WRITE; + polyAttribs = newAttributeMap(); inGeo = newInGeometry(this, polyAttribs, IMMEDIATE); - tessGeo = newTessGeometry(this, polyAttribs, IMMEDIATE); + tessGeo = newTessGeometry(this, polyAttribs, IMMEDIATE, + PGL.bufferStreamingImmediate); texCache = newTexCache(this); projection = new PMatrix3D(); @@ -756,16 +761,29 @@ public class PGraphicsOpenGL extends PGraphics { // ASYNC save frame using PBOs not yet available on Android //return super.save(filename); + // In 4.0 beta 5, the loadPixels() call is moved into saveImpl(), + // otherwise it undermines part of the point to having optimized + // image writing methods in subclasses (which presumably might be + // able to write directly from frame buffer to file). + loadPixels(); + if (getHint(DISABLE_ASYNC_SAVEFRAME)) { - // Act as an opaque surface for the purposes of saving. if (primaryGraphics) { + // Act as an opaque surface while saving int prevFormat = format; format = RGB; + if (pixels == null) { + // Workaround for an NPE caused by resize events: + // https://github.com/processing/processing4/issues/162 + // But there's a larger problem at play here: + // https://github.com/processing/processing4/issues/385 + System.err.println("Not saving image because pixels not ready."); + return false; + } boolean result = super.saveImpl(filename); format = prevFormat; return result; } - return super.saveImpl(filename); } @@ -794,6 +812,7 @@ public class PGraphicsOpenGL extends PGraphics { asyncPixelReader.readAndSaveAsync(parent.sketchFile(filename)); if (needEndDraw) endDraw(); + } else { // async transfer is not supported or // pixels are already in memory, just do async save @@ -871,12 +890,11 @@ public class PGraphicsOpenGL extends PGraphics { int glName; private PGL pgl; - private int context; + private final int context; public GLResourceTexture(Texture tex) { super(tex); - pgl = tex.pg.getPrimaryPGL(); pgl.genTextures(1, intBuffer); tex.glName = intBuffer.get(0); @@ -921,7 +939,7 @@ public class PGraphicsOpenGL extends PGraphics { int glId; private PGL pgl; - private int context; + final private int context; public GLResourceVertexBuffer(VertexBuffer vbo) { super(vbo); @@ -972,7 +990,7 @@ public class PGraphicsOpenGL extends PGraphics { int glFragment; private PGL pgl; - private int context; + final private int context; public GLResourceShader(PShader sh) { super(sh); @@ -1040,7 +1058,7 @@ public class PGraphicsOpenGL extends PGraphics { int glMultisample; private PGL pgl; - private int context; + final private int context; public GLResourceFrameBuffer(FrameBuffer fb) { super(fb); @@ -1188,20 +1206,20 @@ public class PGraphicsOpenGL extends PGraphics { // FRAME RENDERING - protected void createPolyBuffers() { + protected void createPolyBuffers(int usage) { if (!polyBuffersCreated || polyBuffersContextIsOutdated()) { polyBuffersContext = pgl.getCurrentContext(); - bufPolyVertex = new VertexBuffer(this, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); - bufPolyColor = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); - bufPolyNormal = new VertexBuffer(this, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT); - bufPolyTexcoord = new VertexBuffer(this, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT); - bufPolyAmbient = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); - bufPolySpecular = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); - bufPolyEmissive = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); - bufPolyShininess = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_FLOAT); + bufPolyVertex = new VertexBuffer(this, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT, usage); + bufPolyColor = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, usage); + bufPolyNormal = new VertexBuffer(this, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT, usage); + bufPolyTexcoord = new VertexBuffer(this, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT, usage); + bufPolyAmbient = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, usage); + bufPolySpecular = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, usage); + bufPolyEmissive = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, usage); + bufPolyShininess = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_FLOAT, usage); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - bufPolyIndex = new VertexBuffer(this, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); + bufPolyIndex = new VertexBuffer(this, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, usage, true); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); polyBuffersCreated = true; @@ -1221,71 +1239,46 @@ public class PGraphicsOpenGL extends PGraphics { protected void updatePolyBuffers(boolean lit, boolean tex, boolean needNormals, boolean needTexCoords) { - createPolyBuffers(); + createPolyBuffers(PGL.bufferUsageImmediate); - int size = tessGeo.polyVertexCount; - int sizef = size * PGL.SIZEOF_FLOAT; - int sizei = size * PGL.SIZEOF_INT; - - tessGeo.updatePolyVerticesBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyVertex.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.polyVerticesBuffer, PGL.STATIC_DRAW); + tessGeo.copyPolyVertices(PGL.bufferUsageImmediate); - tessGeo.updatePolyColorsBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyColor.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polyColorsBuffer, PGL.STATIC_DRAW); + tessGeo.copyPolyColors(PGL.bufferUsageImmediate); if (lit) { - tessGeo.updatePolyAmbientBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyAmbient.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polyAmbientBuffer, PGL.STATIC_DRAW); + tessGeo.copyPolyAmbient(PGL.bufferUsageImmediate); - tessGeo.updatePolySpecularBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolySpecular.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polySpecularBuffer, PGL.STATIC_DRAW); + tessGeo.copyPolySpecular(PGL.bufferUsageImmediate); - tessGeo.updatePolyEmissiveBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyEmissive.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polyEmissiveBuffer, PGL.STATIC_DRAW); + tessGeo.copyPolyEmissive(PGL.bufferUsageImmediate); - tessGeo.updatePolyShininessBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyShininess.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizef, - tessGeo.polyShininessBuffer, PGL.STATIC_DRAW); + tessGeo.copyPolyShininess(PGL.bufferUsageImmediate); } if (lit || needNormals) { - tessGeo.updatePolyNormalsBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyNormal.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, - tessGeo.polyNormalsBuffer, PGL.STATIC_DRAW); + tessGeo.copyPolyNormals(PGL.bufferUsageImmediate); } if (tex || needTexCoords) { - tessGeo.updatePolyTexCoordsBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyTexcoord.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, - tessGeo.polyTexCoordsBuffer, PGL.STATIC_DRAW); + tessGeo.copyPolyTexCoords(PGL.bufferUsageImmediate); } for (String name: polyAttribs.keySet()) { VertexAttribute attrib = polyAttribs.get(name); - tessGeo.updateAttribBuffer(name); pgl.bindBuffer(PGL.ARRAY_BUFFER, attrib.buf.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, attrib.sizeInBytes(size), - tessGeo.polyAttribBuffers.get(name), PGL.STATIC_DRAW); + tessGeo.copyPolyAttribs(attrib, PGL.bufferUsageImmediate); } - tessGeo.updatePolyIndicesBuffer(); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufPolyIndex.glId); - pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, - tessGeo.polyIndexCount * PGL.SIZEOF_INDEX, tessGeo.polyIndicesBuffer, - PGL.STATIC_DRAW); + tessGeo.copyPolyIndices(PGL.bufferUsageImmediate); } @@ -1300,15 +1293,15 @@ public class PGraphicsOpenGL extends PGraphics { } - protected void createLineBuffers() { + protected void createLineBuffers(int usage) { if (!lineBuffersCreated || lineBufferContextIsOutdated()) { lineBuffersContext = pgl.getCurrentContext(); - bufLineVertex = new VertexBuffer(this, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT); - bufLineColor = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); - bufLineAttrib = new VertexBuffer(this, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); + bufLineVertex = new VertexBuffer(this, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT, usage); + bufLineColor = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, usage); + bufLineAttrib = new VertexBuffer(this, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT, usage); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - bufLineIndex = new VertexBuffer(this, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); + bufLineIndex = new VertexBuffer(this, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, usage, true); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); lineBuffersCreated = true; @@ -1317,34 +1310,19 @@ public class PGraphicsOpenGL extends PGraphics { protected void updateLineBuffers() { - createLineBuffers(); + createLineBuffers(PGL.bufferUsageImmediate); - int size = tessGeo.lineVertexCount; - int sizef = size * PGL.SIZEOF_FLOAT; - int sizei = size * PGL.SIZEOF_INT; - - - - tessGeo.updateLineVerticesBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineVertex.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, tessGeo.lineVerticesBuffer, - PGL.STATIC_DRAW); + tessGeo.copyLineVertices(PGL.bufferUsageImmediate); - tessGeo.updateLineColorsBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineColor.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.lineColorsBuffer, PGL.STATIC_DRAW); + tessGeo.copyLineColors(PGL.bufferUsageImmediate); - tessGeo.updateLineDirectionsBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineAttrib.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.lineDirectionsBuffer, PGL.STATIC_DRAW); + tessGeo.copyLineDirections(PGL.bufferUsageImmediate); - tessGeo.updateLineIndicesBuffer(); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufLineIndex.glId); - pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, - tessGeo.lineIndexCount * PGL.SIZEOF_INDEX, - tessGeo.lineIndicesBuffer, PGL.STATIC_DRAW); + tessGeo.copyLineIndices(PGL.bufferUsageImmediate); } @@ -1359,15 +1337,15 @@ public class PGraphicsOpenGL extends PGraphics { } - protected void createPointBuffers() { + protected void createPointBuffers(int usage) { if (!pointBuffersCreated || pointBuffersContextIsOutdated()) { pointBuffersContext = pgl.getCurrentContext(); - bufPointVertex = new VertexBuffer(this, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT); - bufPointColor = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); - bufPointAttrib = new VertexBuffer(this, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT); + bufPointVertex = new VertexBuffer(this, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT, usage); + bufPointColor = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, usage); + bufPointAttrib = new VertexBuffer(this, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT, usage); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - bufPointIndex = new VertexBuffer(this, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); + bufPointIndex = new VertexBuffer(this, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, usage, true); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); pointBuffersCreated = true; @@ -1376,32 +1354,19 @@ public class PGraphicsOpenGL extends PGraphics { protected void updatePointBuffers() { - createPointBuffers(); + createPointBuffers(PGL.bufferUsageImmediate); - int size = tessGeo.pointVertexCount; - int sizef = size * PGL.SIZEOF_FLOAT; - int sizei = size * PGL.SIZEOF_INT; - - tessGeo.updatePointVerticesBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointVertex.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.pointVerticesBuffer, PGL.STATIC_DRAW); + tessGeo.copyPointVertices(PGL.bufferUsageImmediate); - tessGeo.updatePointColorsBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointColor.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.pointColorsBuffer, PGL.STATIC_DRAW); + tessGeo.copyPointColors(PGL.bufferUsageImmediate); - tessGeo.updatePointOffsetsBuffer(); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointAttrib.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, - tessGeo.pointOffsetsBuffer, PGL.STATIC_DRAW); + tessGeo.copyPointOffsets(PGL.bufferUsageImmediate); - tessGeo.updatePointIndicesBuffer(); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufPointIndex.glId); - pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, - tessGeo.pointIndexCount * PGL.SIZEOF_INDEX, - tessGeo.pointIndicesBuffer, PGL.STATIC_DRAW); + tessGeo.copyPointIndices(PGL.bufferUsageImmediate); } @@ -1567,7 +1532,7 @@ public class PGraphicsOpenGL extends PGraphics { // neither GL_MULTISAMPLE nor GL_POLYGON_SMOOTH are part of GLES2 or GLES3 } else if (smooth < 1) { pgl.disable(PGL.MULTISAMPLE); - } else if (1 <= smooth) { + } else { pgl.enable(PGL.MULTISAMPLE); } if (!pgl.isES()) { @@ -1588,16 +1553,14 @@ public class PGraphicsOpenGL extends PGraphics { pgl.activeTexture(PGL.TEXTURE0); - if (hints[DISABLE_DEPTH_MASK]) { - pgl.depthMask(false); - } else { - pgl.depthMask(true); - } + pgl.depthMask(!hints[DISABLE_DEPTH_MASK]); FrameBuffer fb = getCurrentFB(); if (fb != null) { fb.bind(); - if (drawBufferSupported) pgl.drawBuffer(fb.getDefaultDrawBuffer()); + if (drawBufferSupported) { + pgl.drawBuffer(fb.getDefaultDrawBuffer()); + } } } @@ -2109,7 +2072,7 @@ public class PGraphicsOpenGL extends PGraphics { } VertexAttribute attrib = polyAttribs.get(name); if (attrib == null) { - attrib = new VertexAttribute(this, name, kind, type, size); + attrib = new VertexAttribute(this, name, kind, type, size, PGL.bufferUsageImmediate); polyAttribs.put(name, attrib); inGeo.initAttrib(attrib); tessGeo.initAttrib(attrib); @@ -2337,7 +2300,7 @@ public class PGraphicsOpenGL extends PGraphics { } if (hasPolys && isDepthSortingEnabled) { - // We flush after lines so they are visible + // Flush after lines so that they are visible // under transparent polygons flushSortedPolys(); if (raw != null) { @@ -2783,12 +2746,11 @@ public class PGraphicsOpenGL extends PGraphics { int voffset = cache.vertexOffset[n]; for (int ln = ioffset / 6; ln < (ioffset + icount) / 6; ln++) { - // Each line segment is defined by six indices since its - // formed by two triangles. We only need the first and last - // vertices. + // Each line segment is defined by six indices since it is + // formed by two triangles. Only need the first and last verts. // This bunch of vertices could also be the bevel triangles, // with we detect this situation by looking at the line weight. - int i0 = voffset + indices[6 * ln + 0]; + int i0 = voffset + indices[6 * ln]; int i1 = voffset + indices[6 * ln + 5]; float sw0 = 2 * attribs[4 * i0 + 3]; float sw1 = 2 * attribs[4 * i1 + 3]; @@ -2888,7 +2850,7 @@ public class PGraphicsOpenGL extends PGraphics { float weight; int perim; if (0 < size) { // round point - weight = +size / 0.5f; + weight = size / 0.5f; perim = PApplet.min(MAX_POINT_ACCURACY, PApplet.max(MIN_POINT_ACCURACY, (int) (TWO_PI * weight / POINT_ACCURACY_FACTOR))) + 1; } else { // Square point @@ -3926,7 +3888,7 @@ public class PGraphicsOpenGL extends PGraphics { static protected void invScale(PMatrix2D matrix, float x, float y) { - matrix.preApply(1/x, 0, 0, 1/y, 0, 0); + matrix.preApply(1/x, 0, 0, 0, 1/y, 0); } @@ -4506,8 +4468,8 @@ public class PGraphicsOpenGL extends PGraphics { // Flushing geometry with a different perspective configuration. flush(); - float x = +2.0f / w; - float y = +2.0f / h; + float x = 2.0f / w; + float y = 2.0f / h; float z = -2.0f / d; float tx = -(right + left) / w; @@ -4665,8 +4627,7 @@ public class PGraphicsOpenGL extends PGraphics { if (nonZero(ow)) { ox /= ow; } - float sx = width * (1 + ox) / 2.0f; - return sx; + return width * (1 + ox) / 2.0f; } @@ -4721,8 +4682,7 @@ public class PGraphicsOpenGL extends PGraphics { if (nonZero(ow)) { oz /= ow; } - float sz = (oz + 1) / 2.0f; - return sz; + return (oz + 1) / 2.0f; } @@ -5443,6 +5403,7 @@ public class PGraphicsOpenGL extends PGraphics { PGL.getIntArray(pixelBuffer, pixels); PGL.nativeToJavaARGB(pixels, pixelWidth, pixelHeight); } catch (ArrayIndexOutOfBoundsException e) { + // ignored } } @@ -5472,6 +5433,7 @@ public class PGraphicsOpenGL extends PGraphics { } PGL.javaToNativeARGB(nativePixels, w, h); } catch (ArrayIndexOutOfBoundsException e) { + // ignored } PGL.putIntArray(nativePixelBuffer, nativePixels); // Copying pixel buffer to screen texture... @@ -5854,8 +5816,7 @@ public class PGraphicsOpenGL extends PGraphics { // LOAD/UPDATE TEXTURE - // Loads the current contents of the renderer's drawing surface into the - // its texture. + // Load the current contents of the drawing surface into a texture. public void loadTexture() { boolean needEndDraw = false; if (!drawing) { @@ -5889,6 +5850,7 @@ public class PGraphicsOpenGL extends PGraphics { pgl.readPixelsImpl(0, 0, pixelWidth, pixelHeight, PGL.RGBA, PGL.UNSIGNED_BYTE, nativePixelBuffer); } catch (IndexOutOfBoundsException e) { + // ignored } endPixelsOp(); @@ -6192,25 +6154,25 @@ public class PGraphicsOpenGL extends PGraphics { int scrX0, scrX1; int scrY0, scrY1; if (invX) { - scrX0 = dx + dw; - scrX1 = dx; + scrX0 = (dx + dw) / src.pixelDensity; + scrX1 = dx / src.pixelDensity; } else { - scrX0 = dx; - scrX1 = dx + dw; + scrX0 = dx / src.pixelDensity; + scrX1 = (dx + dw) / src.pixelDensity; } int texX0 = sx; int texX1 = sx + sw; int texY0, texY1; if (invY) { - scrY0 = height - (dy + dh); - scrY1 = height - dy; + scrY0 = height - (dy + dh) / src.pixelDensity; + scrY1 = height - dy / src.pixelDensity; texY0 = tex.height - (sy + sh); texY1 = tex.height - sy; } else { // Because drawTexture uses bottom-to-top orientation of Y axis. - scrY0 = height - dy; - scrY1 = height - (dy + dh); + scrY0 = height - dy / src.pixelDensity; + scrY1 = height - (dy + dh) / src.pixelDensity; texY0 = sy; texY1 = sy + sh; } @@ -6220,7 +6182,6 @@ public class PGraphicsOpenGL extends PGraphics { texX0, texY0, texX1, texY1, scrX0, scrY0, scrX1, scrY1); - if (needEndDraw) { endDraw(); } @@ -6502,20 +6463,12 @@ public class PGraphicsOpenGL extends PGraphics { if (!tex.colorBuffer() && (tex.usingMipmaps == hints[DISABLE_TEXTURE_MIPMAPS] || tex.currentSampling() != textureSampling)) { - if (hints[DISABLE_TEXTURE_MIPMAPS]) { - tex.usingMipmaps(false, textureSampling); - } else { - tex.usingMipmaps(true, textureSampling); - } + tex.usingMipmaps(!hints[DISABLE_TEXTURE_MIPMAPS], textureSampling); } if ((tex.usingRepeat && textureWrap == CLAMP) || (!tex.usingRepeat && textureWrap == REPEAT)) { - if (textureWrap == CLAMP) { - tex.usingRepeat(false); - } else { - tex.usingRepeat(true); - } + tex.usingRepeat(textureWrap != CLAMP); } } @@ -6809,7 +6762,7 @@ public class PGraphicsOpenGL extends PGraphics { // neither GL_MULTISAMPLE nor GL_POLYGON_SMOOTH are part of GLES2 or GLES3 } else if (smooth < 1) { pgl.disable(PGL.MULTISAMPLE); - } else if (1 <= smooth) { + } else { pgl.enable(PGL.MULTISAMPLE); } if (!pgl.isES()) { @@ -6825,7 +6778,7 @@ public class PGraphicsOpenGL extends PGraphics { background(backgroundColor); } else { // offscreen surfaces are transparent by default. - background(0x00 << 24 | (backgroundColor & 0xFFFFFF)); + background(backgroundColor & 0xFFFFFF); // Recreate offscreen FBOs restartPGL(); @@ -7213,6 +7166,7 @@ public class PGraphicsOpenGL extends PGraphics { int elementSize; VertexBuffer buf; int glLoc; + int glUsage; float[] fvalues; int[] ivalues; @@ -7224,7 +7178,7 @@ public class PGraphicsOpenGL extends PGraphics { int lastModified; boolean active; - VertexAttribute(PGraphicsOpenGL pg, String name, int kind, int type, int size) { + VertexAttribute(PGraphicsOpenGL pg, String name, int kind, int type, int size, int usage) { this.pg = pg; this.name = name; this.kind = kind; @@ -7250,6 +7204,7 @@ public class PGraphicsOpenGL extends PGraphics { buf = null; glLoc = -1; + glUsage = usage; modified = false; firstModified = PConstants.MAX_INT; @@ -7300,7 +7255,7 @@ public class PGraphicsOpenGL extends PGraphics { } void createBuffer(PGL pgl) { - buf = new VertexBuffer(pg, PGL.ARRAY_BUFFER, size, elementSize, false); + buf = new VertexBuffer(pg, PGL.ARRAY_BUFFER, size, elementSize, glUsage, false); } void deleteBuffer(PGL pgl) { @@ -7382,8 +7337,8 @@ public class PGraphicsOpenGL extends PGraphics { static protected TessGeometry newTessGeometry(PGraphicsOpenGL pg, - AttributeMap attr, int mode) { - return new TessGeometry(pg, attr, mode); + AttributeMap attr, int mode, boolean stream) { + return new TessGeometry(pg, attr, mode, stream); } @@ -7875,8 +7830,8 @@ public class PGraphicsOpenGL extends PGraphics { int col = iarray[aidx]; vector[vidx++] = (col >> 24) & 0xFF; vector[vidx++] = (col >> 16) & 0xFF; - vector[vidx++] = (col >> 8) & 0xFF; - vector[vidx++] = (col >> 0) & 0xFF; + vector[vidx++] = (col >> 8) & 0xFF; + vector[vidx++] = col & 0xFF; } else { if (attrib.isFloat()) { float[] farray = fattribs.get(name); @@ -9123,6 +9078,7 @@ public class PGraphicsOpenGL extends PGraphics { // Holds tessellated data for polygon, line and point geometry. static protected class TessGeometry { int renderMode; + boolean bufObjStreaming; PGraphicsOpenGL pg; AttributeMap polyAttribs; @@ -9202,10 +9158,11 @@ public class PGraphicsOpenGL extends PGraphics { HashMap ipolyAttribs = new HashMap<>(); HashMap bpolyAttribs = new HashMap<>(); - TessGeometry(PGraphicsOpenGL pg, AttributeMap attr, int mode) { + TessGeometry(PGraphicsOpenGL pg, AttributeMap attr, int mode, boolean stream) { this.pg = pg; this.polyAttribs = attr; renderMode = mode; + bufObjStreaming = stream; allocate(); } @@ -9234,25 +9191,28 @@ public class PGraphicsOpenGL extends PGraphics { pointOffsets = new float[2 * PGL.DEFAULT_TESS_VERTICES]; pointIndices = new short[PGL.DEFAULT_TESS_VERTICES]; - polyVerticesBuffer = PGL.allocateFloatBuffer(polyVertices); - polyColorsBuffer = PGL.allocateIntBuffer(polyColors); - polyNormalsBuffer = PGL.allocateFloatBuffer(polyNormals); - polyTexCoordsBuffer = PGL.allocateFloatBuffer(polyTexCoords); - polyAmbientBuffer = PGL.allocateIntBuffer(polyAmbient); - polySpecularBuffer = PGL.allocateIntBuffer(polySpecular); - polyEmissiveBuffer = PGL.allocateIntBuffer(polyEmissive); - polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); - polyIndicesBuffer = PGL.allocateShortBuffer(polyIndices); + if (!bufObjStreaming) { + polyVerticesBuffer = PGL.allocateFloatBuffer(polyVertices); - lineVerticesBuffer = PGL.allocateFloatBuffer(lineVertices); - lineColorsBuffer = PGL.allocateIntBuffer(lineColors); - lineDirectionsBuffer = PGL.allocateFloatBuffer(lineDirections); - lineIndicesBuffer = PGL.allocateShortBuffer(lineIndices); + polyColorsBuffer = PGL.allocateIntBuffer(polyColors); + polyNormalsBuffer = PGL.allocateFloatBuffer(polyNormals); + polyTexCoordsBuffer = PGL.allocateFloatBuffer(polyTexCoords); + polyAmbientBuffer = PGL.allocateIntBuffer(polyAmbient); + polySpecularBuffer = PGL.allocateIntBuffer(polySpecular); + polyEmissiveBuffer = PGL.allocateIntBuffer(polyEmissive); + polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); + polyIndicesBuffer = PGL.allocateShortBuffer(polyIndices); - pointVerticesBuffer = PGL.allocateFloatBuffer(pointVertices); - pointColorsBuffer = PGL.allocateIntBuffer(pointColors); - pointOffsetsBuffer = PGL.allocateFloatBuffer(pointOffsets); - pointIndicesBuffer = PGL.allocateShortBuffer(pointIndices); + lineVerticesBuffer = PGL.allocateFloatBuffer(lineVertices); + lineColorsBuffer = PGL.allocateIntBuffer(lineColors); + lineDirectionsBuffer = PGL.allocateFloatBuffer(lineDirections); + lineIndicesBuffer = PGL.allocateShortBuffer(lineIndices); + + pointVerticesBuffer = PGL.allocateFloatBuffer(pointVertices); + pointColorsBuffer = PGL.allocateIntBuffer(pointColors); + pointOffsetsBuffer = PGL.allocateFloatBuffer(pointOffsets); + pointIndicesBuffer = PGL.allocateShortBuffer(pointIndices); + } clear(); } @@ -9261,15 +9221,27 @@ public class PGraphicsOpenGL extends PGraphics { if (attrib.type == PGL.FLOAT && !fpolyAttribs.containsKey(attrib.name)) { float[] temp = new float[attrib.tessSize * PGL.DEFAULT_TESS_VERTICES]; fpolyAttribs.put(attrib.name, temp); - polyAttribBuffers.put(attrib.name, PGL.allocateFloatBuffer(temp)); + if (!bufObjStreaming) { + polyAttribBuffers.put(attrib.name, PGL.allocateFloatBuffer(temp)); + } else { + polyAttribBuffers.put(attrib.name, null); + } } else if (attrib.type == PGL.INT && !ipolyAttribs.containsKey(attrib.name)) { int[] temp = new int[attrib.tessSize * PGL.DEFAULT_TESS_VERTICES]; ipolyAttribs.put(attrib.name, temp); - polyAttribBuffers.put(attrib.name, PGL.allocateIntBuffer(temp)); + if (!bufObjStreaming) { + polyAttribBuffers.put(attrib.name, PGL.allocateIntBuffer(temp)); + } else { + polyAttribBuffers.put(attrib.name, null); + } } else if (attrib.type == PGL.BOOL && !bpolyAttribs.containsKey(attrib.name)) { byte[] temp = new byte[attrib.tessSize * PGL.DEFAULT_TESS_VERTICES]; bpolyAttribs.put(attrib.name, temp); - polyAttribBuffers.put(attrib.name, PGL.allocateByteBuffer(temp)); + if (!bufObjStreaming) { + polyAttribBuffers.put(attrib.name, PGL.allocateByteBuffer(temp)); + } else { + polyAttribBuffers.put(attrib.name, null); + } } } @@ -9504,6 +9476,952 @@ public class PGraphicsOpenGL extends PGraphics { return last - first + 1; } + + // ----------------------------------------------------------------- + // + // Buffer mapping methods + + protected void mapPolyVerticesBuffer() { + polyVerticesBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer(); + } + + protected void initPolyVerticesBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizef = polyVertexCount * PGL.SIZEOF_FLOAT; + if (bufObjStreaming) { + if (onlymap) { + mapPolyVerticesBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, null, usage); + mapPolyVerticesBuffer(); + updatePolyVerticesBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, polyVerticesBuffer, usage); + } + } + + protected void finalPolyVerticesBuffer(int first, int last) { + if (0 <= first && first <= last) updatePolyVerticesBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPolyVertices(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * polyVertexCount * PGL.SIZEOF_FLOAT, null, usage); + mapPolyVerticesBuffer(); + updatePolyVerticesBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyVerticesBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * polyVertexCount * PGL.SIZEOF_FLOAT, polyVerticesBuffer, usage); + } + } + + protected void copyPolyVertices(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPolyVerticesBuffer(); + updatePolyVerticesBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyVerticesBuffer(offset, size); + polyVerticesBuffer.position(4 * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, 4 * size * PGL.SIZEOF_FLOAT, polyVerticesBuffer); + polyVerticesBuffer.rewind(); + } + } + + protected void mapPolyColorsBuffer() { + polyColorsBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + } + + protected void initPolyColorsBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizei = polyVertexCount * PGL.SIZEOF_INT; + if (bufObjStreaming) { + if (onlymap) { + polyColorsBuffer = pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, usage); + polyColorsBuffer = pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + updatePolyColorsBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, polyColorsBuffer, usage); + } + } + + protected void finalPolyColorsBuffer(int first, int last) { + if (0 <= first && first <= last) updatePolyColorsBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPolyColors(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, polyVertexCount * PGL.SIZEOF_INT, null, usage); + mapPolyColorsBuffer(); + updatePolyColorsBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyColorsBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, polyVertexCount * PGL.SIZEOF_INT, polyColorsBuffer, usage); + } + } + + protected void copyPolyColors(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPolyColorsBuffer(); + updatePolyColorsBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyColorsBuffer(offset, size); + polyColorsBuffer.position(offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, size * PGL.SIZEOF_INT, polyColorsBuffer); + polyColorsBuffer.rewind(); + } + } + + protected void mapPolyNormalsBuffer() { + polyNormalsBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer(); + } + + protected void initPolyNormalsBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizef = polyVertexCount * PGL.SIZEOF_FLOAT; + if (bufObjStreaming) { + if (onlymap) { + mapPolyNormalsBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, null, usage); + mapPolyNormalsBuffer(); + updatePolyNormalsBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, polyNormalsBuffer, usage); + } + } + + protected void finalPolyNormalsBuffer(int first, int last) { + if (0 <= first && first <= last) updatePolyNormalsBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPolyNormals(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, 3 * polyVertexCount * PGL.SIZEOF_FLOAT, null, usage); + mapPolyNormalsBuffer(); + updatePolyNormalsBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyNormalsBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, 3 * polyVertexCount * PGL.SIZEOF_FLOAT, polyNormalsBuffer, usage); + } + } + + protected void copyPolyNormals(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPolyNormalsBuffer(); + updatePolyNormalsBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyNormalsBuffer(offset, size); + polyNormalsBuffer.position(3 * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, 3 * offset * PGL.SIZEOF_FLOAT, 3 * size * PGL.SIZEOF_FLOAT, polyNormalsBuffer); + polyNormalsBuffer.rewind(); + } + } + + protected void mapPolyTexCoordsBuffer() { + polyTexCoordsBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer(); + } + + protected void initPolyTexCoordsBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizef = polyVertexCount * PGL.SIZEOF_FLOAT; + if (bufObjStreaming) { + if (onlymap) { + mapPolyTexCoordsBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, null, usage); + mapPolyTexCoordsBuffer(); + updatePolyTexCoordsBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, polyTexCoordsBuffer, usage); + } + } + + protected void finalPolyTexCoordsBuffer(int first, int last) { + if (0 <= first && first <= last) updatePolyTexCoordsBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPolyTexCoords(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * polyVertexCount * PGL.SIZEOF_FLOAT, null, usage); + mapPolyTexCoordsBuffer(); + updatePolyTexCoordsBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyTexCoordsBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * polyVertexCount * PGL.SIZEOF_FLOAT, polyTexCoordsBuffer, usage); + } + } + + protected void copyPolyTexCoords(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPolyTexCoordsBuffer(); + updatePolyTexCoordsBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyTexCoordsBuffer(offset, size); + polyTexCoordsBuffer.position(2 * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, 2 * offset * PGL.SIZEOF_FLOAT, 2 * size * PGL.SIZEOF_FLOAT, polyTexCoordsBuffer); + polyTexCoordsBuffer.rewind(); + } + } + + protected void mapPolyAmbientBuffer() { + polyAmbientBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + } + + protected void initPolyAmbientBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizei = polyVertexCount * PGL.SIZEOF_INT; + if (bufObjStreaming) { + if (onlymap) { + polyAmbientBuffer = pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, usage); + polyAmbientBuffer = pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + updatePolyAmbientBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, polyAmbientBuffer, usage); + } + } + + protected void finalPolyAmbientBuffer(int first, int last) { + if (0 <= first && first <= last) updatePolyAmbientBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPolyAmbient(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, polyVertexCount * PGL.SIZEOF_INT, null, usage); + mapPolyAmbientBuffer(); + updatePolyAmbientBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyAmbientBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, polyVertexCount * PGL.SIZEOF_INT, polyAmbientBuffer, usage); + } + } + + protected void copyPolyAmbient(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPolyAmbientBuffer(); + updatePolyAmbientBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyAmbientBuffer(offset, size); + polyAmbientBuffer.position(offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, size * PGL.SIZEOF_INT, polyAmbientBuffer); + polyAmbientBuffer.rewind(); + } + } + + protected void mapPolySpecularBuffer() { + polySpecularBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + } + + protected void initPolySpecularBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizei = polyVertexCount * PGL.SIZEOF_INT; + if (bufObjStreaming) { + if (onlymap) { + polySpecularBuffer = pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, usage); + polySpecularBuffer = pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + updatePolySpecularBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, polySpecularBuffer, usage); + } + } + + protected void finalPolySpecularBuffer(int first, int last) { + if (0 <= first && first <= last) updatePolySpecularBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPolySpecular(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, polyVertexCount * PGL.SIZEOF_INT, null, usage); + mapPolySpecularBuffer(); + updatePolySpecularBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolySpecularBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, polyVertexCount * PGL.SIZEOF_INT, polySpecularBuffer, usage); + } + } + + protected void copyPolySpecular(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPolySpecularBuffer(); + updatePolySpecularBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolySpecularBuffer(offset, size); + polySpecularBuffer.position(offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, size * PGL.SIZEOF_INT, polySpecularBuffer); + polySpecularBuffer.rewind(); + } + } + + protected void mapPolyEmissiveBuffer() { + polyEmissiveBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + } + + protected void initPolyEmissiveBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizei = polyVertexCount * PGL.SIZEOF_INT; + if (bufObjStreaming) { + if (onlymap) { + polyEmissiveBuffer = pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, usage); + polyEmissiveBuffer = pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + updatePolyEmissiveBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, polyEmissiveBuffer, usage); + } + } + + protected void finalPolyEmissiveBuffer(int first, int last) { + if (0 <= first && first <= last) updatePolyEmissiveBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPolyEmissive(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, polyVertexCount * PGL.SIZEOF_INT, null, usage); + mapPolyEmissiveBuffer(); + updatePolyEmissiveBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyEmissiveBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, polyVertexCount * PGL.SIZEOF_INT, polyEmissiveBuffer, usage); + } + } + + protected void copyPolyEmissive(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPolyEmissiveBuffer(); + updatePolyEmissiveBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyEmissiveBuffer(offset, size); + polyEmissiveBuffer.position(offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, size * PGL.SIZEOF_INT, polyEmissiveBuffer); + polyEmissiveBuffer.rewind(); + } + } + + protected void mapPolyShininessBuffer() { + polyShininessBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer(); + } + + protected void initPolyShininessBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizei = polyVertexCount * PGL.SIZEOF_FLOAT; + if (bufObjStreaming) { + if (onlymap) { + polyShininessBuffer = pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, usage); + polyShininessBuffer = pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer(); + updatePolyShininessBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, polyShininessBuffer, usage); + } + } + + protected void finalPolyShininessBuffer(int first, int last) { + if (0 <= first && first <= last) updatePolyShininessBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPolyShininess(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, polyVertexCount * PGL.SIZEOF_FLOAT, null, usage); + mapPolyShininessBuffer(); + updatePolyShininessBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyShininessBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, polyVertexCount * PGL.SIZEOF_FLOAT, polyShininessBuffer, usage); + } + } + + protected void copyPolyShininess(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPolyShininessBuffer(); + updatePolyShininessBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePolyShininessBuffer(offset, size); + polyShininessBuffer.position(offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_FLOAT, size * PGL.SIZEOF_FLOAT, polyShininessBuffer); + polyShininessBuffer.rewind(); + } + } + + protected void mapPolyAttribBuffer(VertexAttribute attrib) { + if (attrib.type == PGL.FLOAT) { + polyAttribBuffers.put(attrib.name, pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer()); + } else if (attrib.type == PGL.INT) { + polyAttribBuffers.put(attrib.name, pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer()); + } else if (attrib.type == PGL.BOOL) { + polyAttribBuffers.put(attrib.name, pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess)); + } + } + + protected void initPolyAttribsBuffer(VertexAttribute attrib, boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int size = attrib.sizeInBytes(polyVertexCount); + if (bufObjStreaming) { + if (onlymap) { + mapPolyAttribBuffer(attrib); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, size, null, usage); + mapPolyAttribBuffer(attrib); + updateAttribBuffer(attrib.name); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, size, polyAttribBuffers.get(attrib.name), usage); + } + } + + protected void finalPolyAttribsBuffer(VertexAttribute attrib, int first, int last) { + if (0 <= first && first <= last) updateAttribBuffer(attrib.name, first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPolyAttribs(VertexAttribute attrib, int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, attrib.sizeInBytes(polyVertexCount), null, usage); + mapPolyAttribBuffer(attrib); + updateAttribBuffer(attrib.name); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updateAttribBuffer(attrib.name); + Buffer buf = polyAttribBuffers.get(attrib.name); + pgl.bufferData(PGL.ARRAY_BUFFER, attrib.sizeInBytes(polyVertexCount), buf, usage); + } + } + + protected void copyPolyAttribs(VertexAttribute attrib, int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPolyAttribBuffer(attrib); + updateAttribBuffer(attrib.name, offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updateAttribBuffer(attrib.name, offset, size); + Buffer buf = polyAttribBuffers.get(attrib.name); + buf.position(attrib.size * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, attrib.sizeInBytes(offset), attrib.sizeInBytes(size), buf); + buf.rewind(); + } + } + + protected void mapPolyIndicesBuffer() { + polyIndicesBuffer = pg.pgl.mapBuffer(PGL.ELEMENT_ARRAY_BUFFER, PGL.bufferMapAccess).asShortBuffer(); + } + + protected void initPolyIndicesBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizei = polyIndexCount * PGL.SIZEOF_INDEX; + if (bufObjStreaming) { + if (onlymap) { + mapPolyIndicesBuffer(); + } else { + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizei, null, usage); + mapPolyIndicesBuffer(); + updatePolyIndicesBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ELEMENT_ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizei, polyIndicesBuffer, usage); + } + } + + protected void copyPolyIndices(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, polyIndexCount * PGL.SIZEOF_INDEX, null, usage); + mapPolyIndicesBuffer(); + updatePolyIndicesBuffer(); + pgl.unmapBuffer(PGL.ELEMENT_ARRAY_BUFFER); + } else { + updatePolyIndicesBuffer(); + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, polyIndexCount * PGL.SIZEOF_INDEX, polyIndicesBuffer, usage); + } + } + + protected void mapLineVerticesBuffer() { + lineVerticesBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer(); + } + + protected void initLineVerticesBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizef = lineVertexCount * PGL.SIZEOF_FLOAT; + if (bufObjStreaming) { + if (onlymap) { + mapLineVerticesBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, null, usage); + mapLineVerticesBuffer(); + updateLineVerticesBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, lineVerticesBuffer, usage); + } + } + + protected void finalLineVerticesBuffer(int first, int last) { + if (0 <= first && first <= last) updateLineVerticesBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyLineVertices(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * lineVertexCount * PGL.SIZEOF_FLOAT, null, usage); + mapLineVerticesBuffer(); + updateLineVerticesBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updateLineVerticesBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * lineVertexCount * PGL.SIZEOF_FLOAT, lineVerticesBuffer, usage); + } + } + + protected void copyLineVertices(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapLineVerticesBuffer(); + updateLineVerticesBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updateLineVerticesBuffer(offset, size); + lineVerticesBuffer.position(4 * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, 4 * size * PGL.SIZEOF_FLOAT, lineVerticesBuffer); + lineVerticesBuffer.rewind(); + } + } + + protected void mapLineColorsBuffer() { + lineColorsBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + } + + protected void initLineColorsBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizei = lineVertexCount * PGL.SIZEOF_INT; + if (bufObjStreaming) { + if (onlymap) { + mapLineColorsBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, usage); + mapLineColorsBuffer(); + updateLineColorsBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, lineColorsBuffer, usage); + } + } + + protected void finalLineColorsBuffer(int first, int last) { + if (0 <= first && first <= last) updateLineColorsBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyLineColors(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, lineVertexCount * PGL.SIZEOF_INT, null, usage); + mapLineColorsBuffer(); + updateLineColorsBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updateLineColorsBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, lineVertexCount * PGL.SIZEOF_INT, lineColorsBuffer, usage); + } + } + + protected void copyLineColors(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapLineColorsBuffer(); + updateLineColorsBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updateLineColorsBuffer(offset, size); + lineColorsBuffer.position(4 * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, size * PGL.SIZEOF_INT, lineColorsBuffer); + lineColorsBuffer.rewind(); + } + } + + protected void mapLineDirectionsBuffer() { + lineDirectionsBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer(); + } + + protected void initLineDirectionsBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizef = lineVertexCount * PGL.SIZEOF_FLOAT; + if (bufObjStreaming) { + if (onlymap) { + mapLineDirectionsBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, null, usage); + mapLineDirectionsBuffer(); + updateLineDirectionsBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, lineDirectionsBuffer, usage); + } + } + + protected void finalLineDirectionsBuffer(int first, int last) { + if (0 <= first && first <= last) updateLineDirectionsBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyLineDirections(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * lineVertexCount * PGL.SIZEOF_FLOAT, null, usage); + mapLineDirectionsBuffer(); + updateLineDirectionsBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updateLineDirectionsBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * lineVertexCount * PGL.SIZEOF_FLOAT, lineDirectionsBuffer, usage); + } + } + + protected void copyLineDirections(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapLineDirectionsBuffer(); + updateLineDirectionsBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updateLineDirectionsBuffer(offset, size); + lineDirectionsBuffer.position(4 * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, 4 * size * PGL.SIZEOF_FLOAT, lineDirectionsBuffer); + lineDirectionsBuffer.rewind(); + } + } + + protected void mapLineIndicesBuffer() { + lineIndicesBuffer = pg.pgl.mapBuffer(PGL.ELEMENT_ARRAY_BUFFER, PGL.bufferMapAccess).asShortBuffer(); + } + + protected void initLineIndicesBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizei = lineIndexCount * PGL.SIZEOF_INDEX; + if (bufObjStreaming) { + if (onlymap) { + mapLineIndicesBuffer(); + } else { + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizei, null, usage); + mapLineIndicesBuffer(); + updateLineIndicesBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ELEMENT_ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizei, lineIndicesBuffer, usage); + } + } + + protected void copyLineIndices(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, lineIndexCount * PGL.SIZEOF_INDEX, null, usage); + mapLineIndicesBuffer(); + updateLineIndicesBuffer(); + pgl.unmapBuffer(PGL.ELEMENT_ARRAY_BUFFER); + } else { + updateLineIndicesBuffer(); + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, lineIndexCount * PGL.SIZEOF_INDEX, lineIndicesBuffer, usage); + } + } + + protected void mapPointVerticesBuffer() { + pointVerticesBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer(); + } + + protected void initPointVerticesBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizef = pointVertexCount * PGL.SIZEOF_FLOAT; + if (bufObjStreaming) { + if (onlymap) { + mapPointVerticesBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, null, usage); + mapPointVerticesBuffer(); + updatePointVerticesBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, pointVerticesBuffer, usage); + } + } + + protected void finalPointVerticesBuffer(int first, int last) { + if (0 <= first && first <= last) updatePointVerticesBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPointVertices(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * pointVertexCount * PGL.SIZEOF_FLOAT, null, usage); + mapPointVerticesBuffer(); + updatePointVerticesBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePointVerticesBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, 4 * pointVertexCount * PGL.SIZEOF_FLOAT, pointVerticesBuffer, usage); + } + } + + protected void copyPointVertices(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPointVerticesBuffer(); + updatePointVerticesBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePointVerticesBuffer(offset, size); + pointVerticesBuffer.position(4 * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, 4 * size * PGL.SIZEOF_FLOAT, pointVerticesBuffer); + pointVerticesBuffer.rewind(); + } + } + + protected void mapPointColorsBuffer() { + pointColorsBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asIntBuffer(); + } + + protected void initPointColorsBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizei = pointVertexCount * PGL.SIZEOF_INT; + if (bufObjStreaming) { + if (onlymap) { + mapPointColorsBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, usage); + mapPointColorsBuffer(); + updatePointColorsBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, sizei, pointColorsBuffer, usage); + } + } + + protected void finalPointColorsBuffer(int first, int last) { + if (0 <= first && first <= last) updatePointColorsBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPointColors(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, pointVertexCount * PGL.SIZEOF_INT, null, usage); + mapPointColorsBuffer(); + updatePointColorsBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePointColorsBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, pointVertexCount * PGL.SIZEOF_INT, pointColorsBuffer, usage); + } + } + + protected void copyPointColors(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPointColorsBuffer(); + updatePointColorsBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePointColorsBuffer(offset, size); + pointColorsBuffer.position(4 * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, size * PGL.SIZEOF_INT, pointColorsBuffer); + pointColorsBuffer.rewind(); + } + } + + protected void mapPointOffsetsBuffer() { + pointOffsetsBuffer = pg.pgl.mapBuffer(PGL.ARRAY_BUFFER, PGL.bufferMapAccess).asFloatBuffer(); + } + + protected void initPointOffsetsBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizef = pointVertexCount * PGL.SIZEOF_FLOAT; + if (bufObjStreaming) { + if (onlymap) { + mapPointOffsetsBuffer(); + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, null, usage); + mapPointOffsetsBuffer(); + updatePointOffsetsBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, pointOffsetsBuffer, usage); + } + } + + protected void finalPointOffsetsBuffer(int first, int last) { + if (0 <= first && first <= last) updatePointOffsetsBuffer(first, last - first + 1); + pg.pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } + + protected void copyPointOffsets(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * pointVertexCount * PGL.SIZEOF_FLOAT, null, usage); + mapPointOffsetsBuffer(); + updatePointOffsetsBuffer(); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePointOffsetsBuffer(); + pgl.bufferData(PGL.ARRAY_BUFFER, 2 * pointVertexCount * PGL.SIZEOF_FLOAT, pointOffsetsBuffer, usage); + } + } + + protected void copyPointOffsets(int offset, int size) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + mapPointOffsetsBuffer(); + updatePointOffsetsBuffer(offset, size); + pgl.unmapBuffer(PGL.ARRAY_BUFFER); + } else { + updatePointOffsetsBuffer(offset, size); + pointOffsetsBuffer.position(2 * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, 2 * offset * PGL.SIZEOF_FLOAT, 2 * size * PGL.SIZEOF_FLOAT, pointOffsetsBuffer); + pointOffsetsBuffer.rewind(); + } + } + + protected void mapPointIndicesBuffer() { + pointIndicesBuffer = pg.pgl.mapBuffer(PGL.ELEMENT_ARRAY_BUFFER, PGL.bufferMapAccess).asShortBuffer(); + } + + protected void initPointIndicesBuffer(boolean onlymap, boolean unmap, int usage) { + PGL pgl = pg.pgl; + int sizei = pointIndexCount * PGL.SIZEOF_INDEX; + if (bufObjStreaming) { + if (onlymap) { + mapPointIndicesBuffer(); + } else { + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizei, null, usage); + mapPointIndicesBuffer(); + updatePointIndicesBuffer(); + } + if (unmap) { + pgl.unmapBuffer(PGL.ELEMENT_ARRAY_BUFFER); + } + } else { + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizei, pointIndicesBuffer, usage); + } + } + + protected void copyPointIndices(int usage) { + PGL pgl = pg.pgl; + if (bufObjStreaming) { + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, pointIndexCount * PGL.SIZEOF_INDEX, null, usage); + mapPointIndicesBuffer(); + updatePointIndicesBuffer(); + pgl.unmapBuffer(PGL.ELEMENT_ARRAY_BUFFER); + } else { + updatePointIndicesBuffer(); + pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, pointIndexCount * PGL.SIZEOF_INDEX, pointIndicesBuffer, usage); + } + } + // ----------------------------------------------------------------- // // Methods to prepare buffers for relative read/write operations @@ -9513,8 +10431,7 @@ public class PGraphicsOpenGL extends PGraphics { } protected void updatePolyVerticesBuffer(int offset, int size) { - PGL.updateFloatBuffer(polyVerticesBuffer, polyVertices, - 4 * offset, 4 * size); + PGL.updateFloatBuffer(polyVerticesBuffer, polyVertices, 4 * offset, 4 * size); } protected void updatePolyColorsBuffer() { @@ -9530,8 +10447,7 @@ public class PGraphicsOpenGL extends PGraphics { } protected void updatePolyNormalsBuffer(int offset, int size) { - PGL.updateFloatBuffer(polyNormalsBuffer, polyNormals, - 3 * offset, 3 * size); + PGL.updateFloatBuffer(polyNormalsBuffer, polyNormals, 3 * offset, 3 * size); } protected void updatePolyTexCoordsBuffer() { @@ -9539,8 +10455,7 @@ public class PGraphicsOpenGL extends PGraphics { } protected void updatePolyTexCoordsBuffer(int offset, int size) { - PGL.updateFloatBuffer(polyTexCoordsBuffer, polyTexCoords, - 2 * offset, 2 * size); + PGL.updateFloatBuffer(polyTexCoordsBuffer, polyTexCoords, 2 * offset, 2 * size); } protected void updatePolyAmbientBuffer() { @@ -9612,8 +10527,7 @@ public class PGraphicsOpenGL extends PGraphics { } protected void updateLineVerticesBuffer(int offset, int size) { - PGL.updateFloatBuffer(lineVerticesBuffer, lineVertices, - 4 * offset, 4 * size); + PGL.updateFloatBuffer(lineVerticesBuffer, lineVertices, 4 * offset, 4 * size); } protected void updateLineColorsBuffer() { @@ -9629,8 +10543,7 @@ public class PGraphicsOpenGL extends PGraphics { } protected void updateLineDirectionsBuffer(int offset, int size) { - PGL.updateFloatBuffer(lineDirectionsBuffer, lineDirections, - 4 * offset, 4 * size); + PGL.updateFloatBuffer(lineDirectionsBuffer, lineDirections, 4 * offset, 4 * size); } protected void updateLineIndicesBuffer() { @@ -9646,8 +10559,7 @@ public class PGraphicsOpenGL extends PGraphics { } protected void updatePointVerticesBuffer(int offset, int size) { - PGL.updateFloatBuffer(pointVerticesBuffer, pointVertices, - 4 * offset, 4 * size); + PGL.updateFloatBuffer(pointVerticesBuffer, pointVertices, 4 * offset, 4 * size); } protected void updatePointColorsBuffer() { @@ -9663,8 +10575,7 @@ public class PGraphicsOpenGL extends PGraphics { } protected void updatePointOffsetsBuffer(int offset, int size) { - PGL.updateFloatBuffer(pointOffsetsBuffer, pointOffsets, - 2 * offset, 2 * size); + PGL.updateFloatBuffer(pointOffsetsBuffer, pointOffsets, 2 * offset, 2 * size); } protected void updatePointIndicesBuffer() { @@ -9683,56 +10594,56 @@ public class PGraphicsOpenGL extends PGraphics { float[] temp = new float[4 * n]; PApplet.arrayCopy(polyVertices, 0, temp, 0, 4 * polyVertexCount); polyVertices = temp; - polyVerticesBuffer = PGL.allocateFloatBuffer(polyVertices); + if (!bufObjStreaming) polyVerticesBuffer = PGL.allocateFloatBuffer(polyVertices); } void expandPolyColors(int n) { int[] temp = new int[n]; PApplet.arrayCopy(polyColors, 0, temp, 0, polyVertexCount); polyColors = temp; - polyColorsBuffer = PGL.allocateIntBuffer(polyColors); + if (!bufObjStreaming) polyColorsBuffer = PGL.allocateIntBuffer(polyColors); } void expandPolyNormals(int n) { float[] temp = new float[3 * n]; PApplet.arrayCopy(polyNormals, 0, temp, 0, 3 * polyVertexCount); polyNormals = temp; - polyNormalsBuffer = PGL.allocateFloatBuffer(polyNormals); + if (!bufObjStreaming) polyNormalsBuffer = PGL.allocateFloatBuffer(polyNormals); } void expandPolyTexCoords(int n) { float[] temp = new float[2 * n]; PApplet.arrayCopy(polyTexCoords, 0, temp, 0, 2 * polyVertexCount); polyTexCoords = temp; - polyTexCoordsBuffer = PGL.allocateFloatBuffer(polyTexCoords); + if (!bufObjStreaming) polyTexCoordsBuffer = PGL.allocateFloatBuffer(polyTexCoords); } void expandPolyAmbient(int n) { int[] temp = new int[n]; PApplet.arrayCopy(polyAmbient, 0, temp, 0, polyVertexCount); polyAmbient = temp; - polyAmbientBuffer = PGL.allocateIntBuffer(polyAmbient); + if (!bufObjStreaming) polyAmbientBuffer = PGL.allocateIntBuffer(polyAmbient); } void expandPolySpecular(int n) { int[] temp = new int[n]; PApplet.arrayCopy(polySpecular, 0, temp, 0, polyVertexCount); polySpecular = temp; - polySpecularBuffer = PGL.allocateIntBuffer(polySpecular); + if (!bufObjStreaming) polySpecularBuffer = PGL.allocateIntBuffer(polySpecular); } void expandPolyEmissive(int n) { int[] temp = new int[n]; PApplet.arrayCopy(polyEmissive, 0, temp, 0, polyVertexCount); polyEmissive = temp; - polyEmissiveBuffer = PGL.allocateIntBuffer(polyEmissive); + if (!bufObjStreaming) polyEmissiveBuffer = PGL.allocateIntBuffer(polyEmissive); } void expandPolyShininess(int n) { float[] temp = new float[n]; PApplet.arrayCopy(polyShininess, 0, temp, 0, polyVertexCount); polyShininess = temp; - polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); + if (!bufObjStreaming) polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); } void expandAttributes(int n) { @@ -9753,7 +10664,7 @@ public class PGraphicsOpenGL extends PGraphics { float[] temp = new float[attrib.tessSize * n]; PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); fpolyAttribs.put(attrib.name, temp); - polyAttribBuffers.put(attrib.name, PGL.allocateFloatBuffer(temp)); + if (!bufObjStreaming) polyAttribBuffers.put(attrib.name, PGL.allocateFloatBuffer(temp)); } void expandIntAttribute(VertexAttribute attrib, int n) { @@ -9761,7 +10672,7 @@ public class PGraphicsOpenGL extends PGraphics { int[] temp = new int[attrib.tessSize * n]; PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); ipolyAttribs.put(attrib.name, temp); - polyAttribBuffers.put(attrib.name, PGL.allocateIntBuffer(temp)); + if (!bufObjStreaming) polyAttribBuffers.put(attrib.name, PGL.allocateIntBuffer(temp)); } void expandBoolAttribute(VertexAttribute attrib, int n) { @@ -9769,70 +10680,70 @@ public class PGraphicsOpenGL extends PGraphics { byte[] temp = new byte[attrib.tessSize * n]; PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); bpolyAttribs.put(attrib.name, temp); - polyAttribBuffers.put(attrib.name, PGL.allocateByteBuffer(temp)); + if (!bufObjStreaming) polyAttribBuffers.put(attrib.name, PGL.allocateByteBuffer(temp)); } void expandPolyIndices(int n) { short[] temp = new short[n]; PApplet.arrayCopy(polyIndices, 0, temp, 0, polyIndexCount); polyIndices = temp; - polyIndicesBuffer = PGL.allocateShortBuffer(polyIndices); + if (!bufObjStreaming) polyIndicesBuffer = PGL.allocateShortBuffer(polyIndices); } void expandLineVertices(int n) { float[] temp = new float[4 * n]; PApplet.arrayCopy(lineVertices, 0, temp, 0, 4 * lineVertexCount); lineVertices = temp; - lineVerticesBuffer = PGL.allocateFloatBuffer(lineVertices); + if (!bufObjStreaming) lineVerticesBuffer = PGL.allocateFloatBuffer(lineVertices); } void expandLineColors(int n) { int[] temp = new int[n]; PApplet.arrayCopy(lineColors, 0, temp, 0, lineVertexCount); lineColors = temp; - lineColorsBuffer = PGL.allocateIntBuffer(lineColors); + if (!bufObjStreaming) lineColorsBuffer = PGL.allocateIntBuffer(lineColors); } void expandLineDirections(int n) { float[] temp = new float[4 * n]; PApplet.arrayCopy(lineDirections, 0, temp, 0, 4 * lineVertexCount); lineDirections = temp; - lineDirectionsBuffer = PGL.allocateFloatBuffer(lineDirections); + if (!bufObjStreaming) lineDirectionsBuffer = PGL.allocateFloatBuffer(lineDirections); } void expandLineIndices(int n) { short[] temp = new short[n]; PApplet.arrayCopy(lineIndices, 0, temp, 0, lineIndexCount); lineIndices = temp; - lineIndicesBuffer = PGL.allocateShortBuffer(lineIndices); + if (!bufObjStreaming) lineIndicesBuffer = PGL.allocateShortBuffer(lineIndices); } void expandPointVertices(int n) { float[] temp = new float[4 * n]; PApplet.arrayCopy(pointVertices, 0, temp, 0, 4 * pointVertexCount); pointVertices = temp; - pointVerticesBuffer = PGL.allocateFloatBuffer(pointVertices); + if (!bufObjStreaming) pointVerticesBuffer = PGL.allocateFloatBuffer(pointVertices); } void expandPointColors(int n) { int[] temp = new int[n]; PApplet.arrayCopy(pointColors, 0, temp, 0, pointVertexCount); pointColors = temp; - pointColorsBuffer = PGL.allocateIntBuffer(pointColors); + if (!bufObjStreaming) pointColorsBuffer = PGL.allocateIntBuffer(pointColors); } void expandPointOffsets(int n) { float[] temp = new float[2 * n]; PApplet.arrayCopy(pointOffsets, 0, temp, 0, 2 * pointVertexCount); pointOffsets = temp; - pointOffsetsBuffer = PGL.allocateFloatBuffer(pointOffsets); + if (!bufObjStreaming) pointOffsetsBuffer = PGL.allocateFloatBuffer(pointOffsets); } void expandPointIndices(int n) { short[] temp = new short[n]; PApplet.arrayCopy(pointIndices, 0, temp, 0, pointIndexCount); pointIndices = temp; - pointIndicesBuffer = PGL.allocateShortBuffer(pointIndices); + if (!bufObjStreaming) pointIndicesBuffer = PGL.allocateShortBuffer(pointIndices); } // ----------------------------------------------------------------- @@ -9881,56 +10792,56 @@ public class PGraphicsOpenGL extends PGraphics { float[] temp = new float[4 * polyVertexCount]; PApplet.arrayCopy(polyVertices, 0, temp, 0, 4 * polyVertexCount); polyVertices = temp; - polyVerticesBuffer = PGL.allocateFloatBuffer(polyVertices); + if (!bufObjStreaming) polyVerticesBuffer = PGL.allocateFloatBuffer(polyVertices); } void trimPolyColors() { int[] temp = new int[polyVertexCount]; PApplet.arrayCopy(polyColors, 0, temp, 0, polyVertexCount); polyColors = temp; - polyColorsBuffer = PGL.allocateIntBuffer(polyColors); + if (!bufObjStreaming) polyColorsBuffer = PGL.allocateIntBuffer(polyColors); } void trimPolyNormals() { float[] temp = new float[3 * polyVertexCount]; PApplet.arrayCopy(polyNormals, 0, temp, 0, 3 * polyVertexCount); polyNormals = temp; - polyNormalsBuffer = PGL.allocateFloatBuffer(polyNormals); + if (!bufObjStreaming) polyNormalsBuffer = PGL.allocateFloatBuffer(polyNormals); } void trimPolyTexCoords() { float[] temp = new float[2 * polyVertexCount]; PApplet.arrayCopy(polyTexCoords, 0, temp, 0, 2 * polyVertexCount); polyTexCoords = temp; - polyTexCoordsBuffer = PGL.allocateFloatBuffer(polyTexCoords); + if (!bufObjStreaming) polyTexCoordsBuffer = PGL.allocateFloatBuffer(polyTexCoords); } void trimPolyAmbient() { int[] temp = new int[polyVertexCount]; PApplet.arrayCopy(polyAmbient, 0, temp, 0, polyVertexCount); polyAmbient = temp; - polyAmbientBuffer = PGL.allocateIntBuffer(polyAmbient); + if (!bufObjStreaming) polyAmbientBuffer = PGL.allocateIntBuffer(polyAmbient); } void trimPolySpecular() { int[] temp = new int[polyVertexCount]; PApplet.arrayCopy(polySpecular, 0, temp, 0, polyVertexCount); polySpecular = temp; - polySpecularBuffer = PGL.allocateIntBuffer(polySpecular); + if (!bufObjStreaming) polySpecularBuffer = PGL.allocateIntBuffer(polySpecular); } void trimPolyEmissive() { int[] temp = new int[polyVertexCount]; PApplet.arrayCopy(polyEmissive, 0, temp, 0, polyVertexCount); polyEmissive = temp; - polyEmissiveBuffer = PGL.allocateIntBuffer(polyEmissive); + if (!bufObjStreaming) polyEmissiveBuffer = PGL.allocateIntBuffer(polyEmissive); } void trimPolyShininess() { float[] temp = new float[polyVertexCount]; PApplet.arrayCopy(polyShininess, 0, temp, 0, polyVertexCount); polyShininess = temp; - polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); + if (!bufObjStreaming) polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); } void trimPolyAttributes() { @@ -9951,7 +10862,7 @@ public class PGraphicsOpenGL extends PGraphics { float[] temp = new float[attrib.tessSize * polyVertexCount]; PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); fpolyAttribs.put(attrib.name, temp); - polyAttribBuffers.put(attrib.name, PGL.allocateFloatBuffer(temp)); + if (!bufObjStreaming) polyAttribBuffers.put(attrib.name, PGL.allocateFloatBuffer(temp)); } void trimIntAttribute(VertexAttribute attrib) { @@ -9959,7 +10870,7 @@ public class PGraphicsOpenGL extends PGraphics { int[] temp = new int[attrib.tessSize * polyVertexCount]; PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); ipolyAttribs.put(attrib.name, temp); - polyAttribBuffers.put(attrib.name, PGL.allocateIntBuffer(temp)); + if (!bufObjStreaming) polyAttribBuffers.put(attrib.name, PGL.allocateIntBuffer(temp)); } void trimBoolAttribute(VertexAttribute attrib) { @@ -9967,70 +10878,70 @@ public class PGraphicsOpenGL extends PGraphics { byte[] temp = new byte[attrib.tessSize * polyVertexCount]; PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); bpolyAttribs.put(attrib.name, temp); - polyAttribBuffers.put(attrib.name, PGL.allocateByteBuffer(temp)); + if (!bufObjStreaming) polyAttribBuffers.put(attrib.name, PGL.allocateByteBuffer(temp)); } void trimPolyIndices() { short[] temp = new short[polyIndexCount]; PApplet.arrayCopy(polyIndices, 0, temp, 0, polyIndexCount); polyIndices = temp; - polyIndicesBuffer = PGL.allocateShortBuffer(polyIndices); + if (!bufObjStreaming) polyIndicesBuffer = PGL.allocateShortBuffer(polyIndices); } void trimLineVertices() { float[] temp = new float[4 * lineVertexCount]; PApplet.arrayCopy(lineVertices, 0, temp, 0, 4 * lineVertexCount); lineVertices = temp; - lineVerticesBuffer = PGL.allocateFloatBuffer(lineVertices); + if (!bufObjStreaming) lineVerticesBuffer = PGL.allocateFloatBuffer(lineVertices); } void trimLineColors() { int[] temp = new int[lineVertexCount]; PApplet.arrayCopy(lineColors, 0, temp, 0, lineVertexCount); lineColors = temp; - lineColorsBuffer = PGL.allocateIntBuffer(lineColors); + if (!bufObjStreaming) lineColorsBuffer = PGL.allocateIntBuffer(lineColors); } void trimLineDirections() { float[] temp = new float[4 * lineVertexCount]; PApplet.arrayCopy(lineDirections, 0, temp, 0, 4 * lineVertexCount); lineDirections = temp; - lineDirectionsBuffer = PGL.allocateFloatBuffer(lineDirections); + if (!bufObjStreaming) lineDirectionsBuffer = PGL.allocateFloatBuffer(lineDirections); } void trimLineIndices() { short[] temp = new short[lineIndexCount]; PApplet.arrayCopy(lineIndices, 0, temp, 0, lineIndexCount); lineIndices = temp; - lineIndicesBuffer = PGL.allocateShortBuffer(lineIndices); + if (!bufObjStreaming) lineIndicesBuffer = PGL.allocateShortBuffer(lineIndices); } void trimPointVertices() { float[] temp = new float[4 * pointVertexCount]; PApplet.arrayCopy(pointVertices, 0, temp, 0, 4 * pointVertexCount); pointVertices = temp; - pointVerticesBuffer = PGL.allocateFloatBuffer(pointVertices); + if (!bufObjStreaming) pointVerticesBuffer = PGL.allocateFloatBuffer(pointVertices); } void trimPointColors() { int[] temp = new int[pointVertexCount]; PApplet.arrayCopy(pointColors, 0, temp, 0, pointVertexCount); pointColors = temp; - pointColorsBuffer = PGL.allocateIntBuffer(pointColors); + if (!bufObjStreaming) pointColorsBuffer = PGL.allocateIntBuffer(pointColors); } void trimPointOffsets() { float[] temp = new float[2 * pointVertexCount]; PApplet.arrayCopy(pointOffsets, 0, temp, 0, 2 * pointVertexCount); pointOffsets = temp; - pointOffsetsBuffer = PGL.allocateFloatBuffer(pointOffsets); + if (!bufObjStreaming) pointOffsetsBuffer = PGL.allocateFloatBuffer(pointOffsets); } void trimPointIndices() { short[] temp = new short[pointIndexCount]; PApplet.arrayCopy(pointIndices, 0, temp, 0, pointIndexCount); pointIndices = temp; - pointIndicesBuffer = PGL.allocateShortBuffer(pointIndices); + if (!bufObjStreaming) pointIndicesBuffer = PGL.allocateShortBuffer(pointIndices); } // ----------------------------------------------------------------- @@ -11614,7 +12525,7 @@ public class PGraphicsOpenGL extends PGraphics { path.moveTo(in.vertices[0], in.vertices[1], in.strokeColors[0]); for (int ln = 0; ln < lineCount - 1; ln++) { int i1 = ln + 1; - path.lineTo(in.vertices[3 * i1 + 0], in.vertices[3 * i1 + 1], + path.lineTo(in.vertices[3 * i1], in.vertices[3 * i1 + 1], in.strokeColors[i1]); } path.closePath(); @@ -11732,19 +12643,19 @@ public class PGraphicsOpenGL extends PGraphics { int i1 = edge[1]; switch (edge[2]) { case EDGE_MIDDLE: - path.lineTo(strokeVertices[3 * i1 + 0], strokeVertices[3 * i1 + 1], + path.lineTo(strokeVertices[3 * i1], strokeVertices[3 * i1 + 1], strokeColors[i1]); break; case EDGE_START: - path.moveTo(strokeVertices[3 * i0 + 0], strokeVertices[3 * i0 + 1], + path.moveTo(strokeVertices[3 * i0], strokeVertices[3 * i0 + 1], strokeColors[i0]); - path.lineTo(strokeVertices[3 * i1 + 0], strokeVertices[3 * i1 + 1], + path.lineTo(strokeVertices[3 * i1], strokeVertices[3 * i1 + 1], strokeColors[i1]); break; case EDGE_STOP: - path.lineTo(strokeVertices[3 * i1 + 0], strokeVertices[3 * i1 + 1], + path.lineTo(strokeVertices[3 * i1], strokeVertices[3 * i1 + 1], strokeColors[i1]); - path.moveTo(strokeVertices[3 * i1 + 0], strokeVertices[3 * i1 + 1], + path.moveTo(strokeVertices[3 * i1], strokeVertices[3 * i1 + 1], strokeColors[i1]); break; case EDGE_SINGLE: @@ -11802,8 +12713,8 @@ public class PGraphicsOpenGL extends PGraphics { weight = constStroke ? strokeWeight : strokeWeights[i0]; weight *= transformScale(); - tess.setLineVertex(vidx++, strokeVertices, i0, i1, color, +weight/2); - tess.lineIndices[iidx++] = (short) (count + 0); + tess.setLineVertex(vidx++, strokeVertices, i0, i1, color, weight/2); + tess.lineIndices[iidx++] = (short) (count); tess.setLineVertex(vidx++, strokeVertices, i0, i1, color, -weight/2); tess.lineIndices[iidx++] = (short) (count + 1); @@ -11841,7 +12752,7 @@ public class PGraphicsOpenGL extends PGraphics { tess.lineIndices[iidx++] = (short) (count + 4); tess.lineIndices[iidx++] = (short) (count + 5); - tess.lineIndices[iidx++] = (short) (count + 0); + tess.lineIndices[iidx++] = (short) (count); tess.lineIndices[iidx++] = (short) (count + 4); tess.lineIndices[iidx++] = (short) (count + 6); @@ -11854,7 +12765,7 @@ public class PGraphicsOpenGL extends PGraphics { tess.lineIndices[iidx++] = (short) (count + 4); tess.lineIndices[iidx++] = lastInd[0]; - tess.lineIndices[iidx++] = (short) (count + 0); + tess.lineIndices[iidx++] = (short) (count); tess.lineIndices[iidx++] = (short) (count + 4); tess.lineIndices[iidx++] = lastInd[1]; @@ -12051,7 +12962,7 @@ public class PGraphicsOpenGL extends PGraphics { } boolean noCapsJoins() { - // The stroke weight is scaled so it corresponds to the current + // The stroke weight is scaled to correspond to the current // "zoom level" being applied on the geometry due to scaling: return tess.renderMode == IMMEDIATE && transformScale() * strokeWeight < PGL.MIN_CAPS_JOINS_WEIGHT; @@ -12063,7 +12974,7 @@ public class PGraphicsOpenGL extends PGraphics { } boolean segmentIsAxisAligned(int i0, int i1) { - return zero(in.vertices[3 * i0 + 0] - in.vertices[3 * i1 + 0]) || + return zero(in.vertices[3 * i0] - in.vertices[3 * i1]) || zero(in.vertices[3 * i0 + 1] - in.vertices[3 * i1 + 1]); } diff --git a/core/src/processing/opengl/PJOGL.java b/core/src/processing/opengl/PJOGL.java index debe59665..cfe58dc9f 100644 --- a/core/src/processing/opengl/PJOGL.java +++ b/core/src/processing/opengl/PJOGL.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -51,6 +51,7 @@ import com.jogamp.opengl.GLCapabilitiesImmutable; import com.jogamp.opengl.GLContext; import com.jogamp.opengl.GLDrawable; import com.jogamp.opengl.fixedfunc.GLMatrixFunc; +import com.jogamp.opengl.GLRendererQuirks; import com.jogamp.opengl.glu.GLU; import com.jogamp.opengl.glu.GLUtessellator; import com.jogamp.opengl.glu.GLUtessellatorCallbackAdapter; @@ -87,15 +88,6 @@ public class PJOGL extends PGL { // ........................................................ - // Additional parameters - - /** Time that the Processing's animation thread will wait for JOGL's rendering - * thread to be done with a single frame. - */ - protected static int DRAW_TIMEOUT_MILLIS = 500; - - // ........................................................ - // Protected JOGL-specific objects needed to access the GL profiles /** The capabilities of the OpenGL rendering surface */ @@ -110,16 +102,15 @@ public class PJOGL extends PGL { /** GL3 interface */ protected GL2GL3 gl3; - /** GL2 desktop functionality (blit framebuffer, map buffer range, - * multisampled renderbuffers) */ + /** + * GL2 desktop functionality (blit framebuffer, map buffer range, + * multi-sampled render buffers) + */ protected GL2 gl2x; /** GL3ES3 interface */ protected GL3ES3 gl3es3; - /** Stores exceptions that ocurred during drawing */ - protected Exception drawException; - // ........................................................ // Utility arrays to copy projection/modelview matrices to GL @@ -138,6 +129,8 @@ public class PJOGL extends PGL { } + private static boolean forceSharedObjectSync = true; + /////////////////////////////////////////////////////////////// // Initialization, finalization @@ -193,6 +186,11 @@ public class PJOGL extends PGL { } + public boolean needSharedObjectSync() { + return forceSharedObjectSync || gl.getContext().hasRendererQuirk(GLRendererQuirks.NeedSharedObjectSync); + } + + public void setFps(float fps) { if (!setFps || targetFps != fps) { if (60 < fps) { @@ -992,7 +990,6 @@ public class PJOGL extends PGL { READ_FRAMEBUFFER = GL.GL_READ_FRAMEBUFFER; DRAW_FRAMEBUFFER = GL.GL_DRAW_FRAMEBUFFER; - RGBA8 = GL.GL_RGBA8; DEPTH24_STENCIL8 = GL.GL_DEPTH24_STENCIL8; DEPTH_COMPONENT = GL2ES2.GL_DEPTH_COMPONENT; @@ -1529,8 +1526,7 @@ public class PJOGL extends PGL { gl2.glGetActiveAttrib(program, index, 1024, tmp, 0, tmp, 1, tmp, 2, namebuf, 0); size.put(tmp[1]); type.put(tmp[2]); - String name = new String(namebuf, 0, tmp[0]); - return name; + return new String(namebuf, 0, tmp[0]); } @Override @@ -1550,13 +1546,12 @@ public class PJOGL extends PGL { @Override public String getActiveUniform(int program, int index, IntBuffer size, IntBuffer type) { - int[] tmp= {0, 0, 0}; - byte[] namebuf = new byte[1024]; + final int[] tmp = { 0, 0, 0 }; + final byte[] namebuf = new byte[1024]; gl2.glGetActiveUniform(program, index, 1024, tmp, 0, tmp, 1, tmp, 2, namebuf, 0); size.put(tmp[1]); type.put(tmp[2]); - String name = new String(namebuf, 0, tmp[0]); - return name; + return new String(namebuf, 0, tmp[0]); } @Override @@ -1898,8 +1893,8 @@ public class PJOGL extends PGL { } @Override - public void framebufferRenderbuffer(int target, int attachment, int rendbuferfTarget, int renderbuffer) { - gl.glFramebufferRenderbuffer(target, attachment, rendbuferfTarget, renderbuffer); + public void framebufferRenderbuffer(int target, int attachment, int rbt, int renderbuffer) { + gl.glFramebufferRenderbuffer(target, attachment, rbt, renderbuffer); } @Override @@ -1918,8 +1913,8 @@ public class PJOGL extends PGL { } @Override - public void getFramebufferAttachmentParameteriv(int target, int attachment, int pname, IntBuffer params) { - gl2.glGetFramebufferAttachmentParameteriv(target, attachment, pname, params); + public void getFramebufferAttachmentParameteriv(int target, int attachment, int name, IntBuffer params) { + gl2.glGetFramebufferAttachmentParameteriv(target, attachment, name, params); } @Override @@ -1928,8 +1923,8 @@ public class PJOGL extends PGL { } @Override - public void getRenderbufferParameteriv(int target, int pname, IntBuffer params) { - gl2.glGetRenderbufferParameteriv(target, pname, params); + public void getRenderbufferParameteriv(int target, int name, IntBuffer params) { + gl2.glGetRenderbufferParameteriv(target, name, params); } @Override diff --git a/core/src/processing/opengl/PShader.java b/core/src/processing/opengl/PShader.java index 42a1ebfcf..f3c25e216 100644 --- a/core/src/processing/opengl/PShader.java +++ b/core/src/processing/opengl/PShader.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -33,16 +33,18 @@ import java.nio.IntBuffer; import java.util.HashMap; /** - * This class encapsulates a GLSL shader program, including a vertex and a - * fragment shader. It's compatible with the P2D and P3D renderers, but not with - * the default renderer. Use the loadShader() function to load your - * shader code. [Note: It's strongly encouraged to use loadShader() to - * create a PShader object, rather than calling the PShader constructor - * manually.] + * This class encapsulates a GLSL shader program, including a vertex + * and a fragment shader. It is compatible with P2D and P3D, but not + * with the default renderer. + * + * Use the loadShader() function to load your shader code. + * Note: It's strongly encouraged to use loadShader() to create + * a PShader object, rather than calling the PShader + * constructor manually. * * @webref rendering:shaders - * @webBrief This class encapsulates a GLSL shader program, including a vertex - * and a fragment shader + * @webBrief This class encapsulates a GLSL shader program, + * including a vertex and a fragment shader */ public class PShader implements PConstants { static protected final int POINT = 0; @@ -386,7 +388,7 @@ public class PShader implements PConstants { } /** - * Sets the uniform variables inside the shader to modify the effect while the + * Sets the uniform variables inside the shader to modify the effect while the * program is running. * * @webref rendering:shaders @@ -743,7 +745,7 @@ public class PShader implements PConstants { protected void setUniformImpl(String name, int type, Object value) { if (uniformValues == null) { - uniformValues = new HashMap(); + uniformValues = new HashMap<>(); } uniformValues.put(name, new UniformValue(type, value)); } @@ -833,10 +835,10 @@ public class PShader implements PConstants { PImage img = (PImage)val.value; Texture tex = currentPG.getTexture(img); - if (textures == null) textures = new HashMap(); + if (textures == null) textures = new HashMap<>(); textures.put(loc, tex); - if (texUnits == null) texUnits = new HashMap(); + if (texUnits == null) texUnits = new HashMap<>(); if (texUnits.containsKey(loc)) { unit = texUnits.get(loc); pgl.uniform1i(loc, unit); @@ -939,7 +941,7 @@ public class PShader implements PConstants { protected void validate() { pgl.getProgramiv(glProgram, PGL.LINK_STATUS, intBuffer); - boolean linked = intBuffer.get(0) == 0 ? false : true; + boolean linked = intBuffer.get(0) != 0; if (!linked) { PGraphics.showException("Cannot link shader program:\n" + pgl.getProgramInfoLog(glProgram)); @@ -947,7 +949,7 @@ public class PShader implements PConstants { pgl.validateProgram(glProgram); pgl.getProgramiv(glProgram, PGL.VALIDATE_STATUS, intBuffer); - boolean validated = intBuffer.get(0) == 0 ? false : true; + boolean validated = intBuffer.get(0) != 0; if (!validated) { PGraphics.showException("Cannot validate shader program:\n" + pgl.getProgramInfoLog(glProgram)); @@ -975,15 +977,12 @@ public class PShader implements PConstants { } - /** - * @param shaderSource a string containing the shader's code - */ protected boolean compileVertexShader() { pgl.shaderSource(glVertex, PApplet.join(vertexShaderSource, "\n")); pgl.compileShader(glVertex); pgl.getShaderiv(glVertex, PGL.COMPILE_STATUS, intBuffer); - boolean compiled = intBuffer.get(0) == 0 ? false : true; + boolean compiled = intBuffer.get(0) != 0; if (!compiled) { PGraphics.showException("Cannot compile vertex shader:\n" + pgl.getShaderInfoLog(glVertex)); @@ -994,15 +993,12 @@ public class PShader implements PConstants { } - /** - * @param shaderSource a string containing the shader's code - */ protected boolean compileFragmentShader() { pgl.shaderSource(glFragment, PApplet.join(fragmentShaderSource, "\n")); pgl.compileShader(glFragment); pgl.getShaderiv(glFragment, PGL.COMPILE_STATUS, intBuffer); - boolean compiled = intBuffer.get(0) == 0 ? false : true; + boolean compiled = intBuffer.get(0) != 0; if (!compiled) { PGraphics.showException("Cannot compile fragment shader:\n" + pgl.getShaderInfoLog(glFragment)); @@ -1025,8 +1021,8 @@ public class PShader implements PConstants { static protected int getShaderType(String[] source, int defaultType) { - for (int i = 0; i < source.length; i++) { - String line = source[i].trim(); + for (String s : source) { + String line = s.trim(); if (PApplet.match(line, colorShaderDefRegexp) != null) return PShader.COLOR; diff --git a/core/src/processing/opengl/PShapeOpenGL.java b/core/src/processing/opengl/PShapeOpenGL.java index 1f4e8be6f..e53a2c943 100644 --- a/core/src/processing/opengl/PShapeOpenGL.java +++ b/core/src/processing/opengl/PShapeOpenGL.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -40,7 +40,6 @@ import processing.opengl.PGraphicsOpenGL.TessGeometry; import processing.opengl.PGraphicsOpenGL.Tessellator; import processing.opengl.PGraphicsOpenGL.VertexAttribute; -import java.nio.Buffer; import java.util.Arrays; import java.util.HashSet; @@ -105,7 +104,7 @@ public class PShapeOpenGL extends PShape { protected VertexBuffer bufPolyVertex; protected VertexBuffer bufPolyColor; protected VertexBuffer bufPolyNormal; - protected VertexBuffer bufPolyTexcoord; + protected VertexBuffer bufPolyTexCoord; protected VertexBuffer bufPolyAmbient; protected VertexBuffer bufPolySpecular; protected VertexBuffer bufPolyEmissive; @@ -122,9 +121,6 @@ public class PShapeOpenGL extends PShape { protected VertexBuffer bufPointAttrib; protected VertexBuffer bufPointIndex; - // Testing this field, not use as it might go away... - public int glUsage = PGL.STATIC_DRAW; - // ........................................................ // Offsets for geometry aggregation and update. @@ -254,8 +250,8 @@ public class PShapeOpenGL extends PShape { protected int lastModifiedPolyColor; protected int firstModifiedPolyNormal; protected int lastModifiedPolyNormal; - protected int firstModifiedPolyTexcoord; - protected int lastModifiedPolyTexcoord; + protected int firstModifiedPolyTexCoord; + protected int lastModifiedPolyTexCoord; protected int firstModifiedPolyAmbient; protected int lastModifiedPolyAmbient; protected int firstModifiedPolySpecular; @@ -303,6 +299,15 @@ public class PShapeOpenGL extends PShape { protected int savedTextureMode; + // ........................................................ + + // variables controlling tessellation update + + private boolean tessUpdate = false; + private int tessKind; + + // Temporary array for performance + private float[] selVertices; PShapeOpenGL() { } @@ -318,7 +323,7 @@ public class PShapeOpenGL extends PShape { bufPolyVertex = null; bufPolyColor = null; bufPolyNormal = null; - bufPolyTexcoord = null; + bufPolyTexCoord = null; bufPolyAmbient = null; bufPolySpecular = null; bufPolyEmissive = null; @@ -988,20 +993,32 @@ public class PShapeOpenGL extends PShape { @Override protected void beginContourImpl() { + if (family == PShape.PATH) { + super.beginContourImpl(); + return; + } breakShape = true; } @Override protected void endContourImpl() { + if (family == PShape.PATH) { + super.endContourImpl(); + } } @Override public void vertex(float x, float y) { + if (family == PShape.PATH) { + super.vertex(x, y); + return; + } vertexImpl(x, y, 0, 0, 0); - if (image != null) + if (image != null) { PGraphics.showWarning(PGraphicsOpenGL.MISSING_UV_TEXCOORDS_ERROR); + } } @@ -1013,9 +1030,14 @@ public class PShapeOpenGL extends PShape { @Override public void vertex(float x, float y, float z) { + if (family == PShape.PATH) { + super.vertex(x, y); + return; + } vertexImpl(x, y, z, 0, 0); - if (image != null) + if (image != null) { PGraphics.showWarning(PGraphicsOpenGL.MISSING_UV_TEXCOORDS_ERROR); + } } @@ -1113,16 +1135,14 @@ public class PShapeOpenGL extends PShape { @Override public void attribPosition(String name, float x, float y, float z) { - VertexAttribute attrib = attribImpl(name, VertexAttribute.POSITION, - PGL.FLOAT, 3); + VertexAttribute attrib = attribImpl(name, VertexAttribute.POSITION, PGL.FLOAT, 3); if (attrib != null) attrib.set(x, y, z); } @Override public void attribNormal(String name, float nx, float ny, float nz) { - VertexAttribute attrib = attribImpl(name, VertexAttribute.NORMAL, - PGL.FLOAT, 3); + VertexAttribute attrib = attribImpl(name, VertexAttribute.NORMAL, PGL.FLOAT, 3); if (attrib != null) attrib.set(nx, ny, nz); } @@ -1136,24 +1156,21 @@ public class PShapeOpenGL extends PShape { @Override public void attrib(String name, float... values) { - VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.FLOAT, - values.length); + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.FLOAT, values.length); if (attrib != null) attrib.set(values); } @Override public void attrib(String name, int... values) { - VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.INT, - values.length); + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.INT, values.length); if (attrib != null) attrib.set(values); } @Override public void attrib(String name, boolean... values) { - VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.BOOL, - values.length); + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.BOOL, values.length); if (attrib != null) attrib.set(values); } @@ -1165,7 +1182,7 @@ public class PShapeOpenGL extends PShape { } VertexAttribute attrib = polyAttribs.get(name); if (attrib == null) { - attrib = new VertexAttribute(pg, name, kind, type, size); + attrib = new VertexAttribute(pg, name, kind, type, size, PGL.bufferUsageRetained); polyAttribs.put(name, attrib); inGeo.initAttrib(attrib); } @@ -1251,7 +1268,11 @@ public class PShapeOpenGL extends PShape { @Override public void rotate(float angle) { - transform(ROTATE, angle); + if (is3D) { + transform(ROTATE, angle, 0, 0, 1); + } else { + transform(ROTATE, angle); + } } @@ -1397,7 +1418,6 @@ public class PShapeOpenGL extends PShape { } else { transform.scale(args[0], args[1]); PGraphicsOpenGL.invScale((PMatrix2D)transformInv, args[0], args[1]); - tessellated = false; // Quick fix for https://github.com/processing/processing4/issues/217 } break; case MATRIX: @@ -1493,6 +1513,10 @@ public class PShapeOpenGL extends PShape { public void bezierVertex(float x2, float y2, float x3, float y3, float x4, float y4) { + if (family == PShape.PATH) { + super.bezierVertex(x2, y2, x3, y3, x4, y4); + return; + } bezierVertexImpl(x2, y2, 0, x3, y3, 0, x4, y4, 0); @@ -1524,6 +1548,10 @@ public class PShapeOpenGL extends PShape { @Override public void quadraticVertex(float cx, float cy, float x3, float y3) { + if (family == PShape.PATH) { + super.quadraticVertex(cx, cy, x3, y3); + return; + } quadraticVertexImpl(cx, cy, 0, x3, y3, 0); } @@ -1576,6 +1604,10 @@ public class PShapeOpenGL extends PShape { @Override public void curveVertex(float x, float y) { + if (family == PShape.PATH) { + super.curveVertex(x, y); + return; + } curveVertexImpl(x, y, 0); } @@ -1605,43 +1637,75 @@ public class PShapeOpenGL extends PShape { public int getVertexCount() { if (family == GROUP) return 0; // Group shapes don't have vertices else { - if (family == PRIMITIVE || family == PATH) { - // the input geometry of primitive and path shapes is built during - // tessellation - updateTessellation(); + if (root.tessUpdate) { + if (root.tessKind == TRIANGLES) { + return lastPolyVertex - firstPolyVertex + 1; + } else if (root.tessKind == LINES) { + return lastLineVertex - firstLineVertex + 1; + } else if (root.tessKind == POINTS) { + return lastPointVertex - firstPointVertex + 1; + } else { + return 0; + } + } else { + if (family == PRIMITIVE || family == PATH) { + // the input geometry of primitive and path shapes is built during + // tessellation + updateTessellation(); + } + return inGeo.vertexCount; } - return inGeo.vertexCount; } } @Override public PVector getVertex(int index, PVector vec) { - if (vec == null) { - vec = new PVector(); + if (vec == null) vec = new PVector(); + if (root.tessUpdate) { + int tessIdx = getFirstTessVertex() + index; + vec.x = root.selVertices[4 * tessIdx + 0]; + vec.y = root.selVertices[4 * tessIdx + 1]; + vec.z = root.selVertices[4 * tessIdx + 2]; + } else { + vec.x = inGeo.vertices[3 * index + 0]; + vec.y = inGeo.vertices[3 * index + 1]; + vec.z = inGeo.vertices[3 * index + 2]; } - vec.x = inGeo.vertices[3 * index + 0]; - vec.y = inGeo.vertices[3 * index + 1]; - vec.z = inGeo.vertices[3 * index + 2]; return vec; } @Override public float getVertexX(int index) { - return inGeo.vertices[3 * index + 0]; + if (root.tessUpdate) { + int tessIdx = getFirstTessVertex() + index; + return root.selVertices[4 * tessIdx + 0]; + } else { + return inGeo.vertices[3 * index + 0]; + } } @Override public float getVertexY(int index) { - return inGeo.vertices[3 * index + 1]; + if (root.tessUpdate) { + int tessIdx = getFirstTessVertex() + index; + return root.selVertices[4 * tessIdx + 1]; + } else { + return inGeo.vertices[3 * index + 1]; + } } @Override public float getVertexZ(int index) { - return inGeo.vertices[3 * index + 2]; + if (root.tessUpdate) { + int tessIdx = getFirstTessVertex() + index; + return root.selVertices[4 * tessIdx + 2]; + } else { + return inGeo.vertices[3 * index + 2]; + } } @@ -1658,32 +1722,34 @@ public class PShapeOpenGL extends PShape { return; } - // TODO: in certain cases (kind = TRIANGLE, etc) the correspondence between - // input and tessellated vertices is 1-1, so in those cases re-tessellation - // wouldn't be necessary. But in order to reasonable take care of that - // situation, we would need a complete rethinking of the rendering architecture - // in Processing :-) - if (family == PATH) { - if (vertexCodes != null && vertexCodeCount > 0 && - vertexCodes[index] != VERTEX) { - PGraphics.showWarning(NOT_A_SIMPLE_VERTEX, "setVertex()"); - return; - } - vertices[index][X] = x; - vertices[index][Y] = y; - if (is3D && vertices[index].length > 2) { - // P3D allows to modify 2D shapes, ignoring the Z coordinate. - vertices[index][Z] = z; - } + if (root.tessUpdate) { + int tessIdx = getFirstTessVertex() + index; + root.selVertices[4 * tessIdx + 0] = x; + root.selVertices[4 * tessIdx + 1] = y; + root.selVertices[4 * tessIdx + 2] = z; + root.setModifiedTessVertex(tessIdx, tessIdx); } else { - inGeo.vertices[3 * index + 0] = x; - inGeo.vertices[3 * index + 1] = y; - inGeo.vertices[3 * index + 2] = z; + if (family == PATH) { + if (vertexCodes != null && vertexCodeCount > 0 && + vertexCodes[index] != VERTEX) { + PGraphics.showWarning(NOT_A_SIMPLE_VERTEX, "setVertex()"); + return; + } + vertices[index][X] = x; + vertices[index][Y] = y; + if (is3D && vertices[index].length > 2) { + // P3D allows to modify 2D shapes, ignoring the Z coordinate. + vertices[index][Z] = z; + } + } else { + inGeo.vertices[3 * index + 0] = x; + inGeo.vertices[3 * index + 1] = y; + inGeo.vertices[3 * index + 2] = z; + } + markForTessellation(); } - markForTessellation(); } - @Override public void setVertex(int index, PVector vec) { if (openShape) { @@ -1691,52 +1757,93 @@ public class PShapeOpenGL extends PShape { return; } - if (family == PATH) { - if (vertexCodes != null && vertexCodeCount > 0 && - vertexCodes[index] != VERTEX) { - PGraphics.showWarning(NOT_A_SIMPLE_VERTEX, "setVertex()"); - return; - } - vertices[index][X] = vec.x; - vertices[index][Y] = vec.y; - if (is3D && vertices[index].length > 2) { - vertices[index][Z] = vec.z; - } + if (root.tessUpdate) { + int tessIdx = getFirstTessVertex() + index; + root.selVertices[4 * tessIdx + 0] = vec.x; + root.selVertices[4 * tessIdx + 1] = vec.y; + root.selVertices[4 * tessIdx + 2] = vec.z; + root.setModifiedTessVertex(tessIdx, tessIdx); } else { - inGeo.vertices[3 * index + 0] = vec.x; - inGeo.vertices[3 * index + 1] = vec.y; - inGeo.vertices[3 * index + 2] = vec.z; + if (family == PATH) { + if (vertexCodes != null && vertexCodeCount > 0 && + vertexCodes[index] != VERTEX) { + PGraphics.showWarning(NOT_A_SIMPLE_VERTEX, "setVertex()"); + return; + } + vertices[index][X] = vec.x; + vertices[index][Y] = vec.y; + if (is3D && vertices[index].length > 2) { + vertices[index][Z] = vec.z; + } + } else { + inGeo.vertices[3 * index + 0] = vec.x; + inGeo.vertices[3 * index + 1] = vec.y; + inGeo.vertices[3 * index + 2] = vec.z; + } + markForTessellation(); } - markForTessellation(); } + private int getFirstTessVertex() { + if (root.tessKind == TRIANGLES) { + return firstPolyVertex; + } else if (root.tessKind == LINES) { + return firstLineVertex; + } else { + return firstPointVertex; + } + } + + private void setModifiedTessVertex(int first, int last) { + if (root.tessKind == TRIANGLES) { + root.setModifiedPolyVertices(first, last); + } else if (root.tessKind == LINES) { + root.setModifiedLineVertices(first, last); + } else { + root.setModifiedPointVertices(first, last); + } + } @Override public PVector getNormal(int index, PVector vec) { - if (vec == null) { - vec = new PVector(); + if (vec == null) vec = new PVector(); + if (root.tessUpdate) { + int tessIdx = firstPolyVertex + index; + vec.x = tessGeo.polyNormals[3 * tessIdx + 0]; + vec.y = tessGeo.polyNormals[3 * tessIdx + 1]; + vec.z = tessGeo.polyNormals[3 * tessIdx + 2]; + } else { + vec.x = inGeo.normals[3 * index + 0]; + vec.y = inGeo.normals[3 * index + 1]; + vec.z = inGeo.normals[3 * index + 2]; } - vec.x = inGeo.normals[3 * index + 0]; - vec.y = inGeo.normals[3 * index + 1]; - vec.z = inGeo.normals[3 * index + 2]; return vec; } @Override public float getNormalX(int index) { + if (root.tessUpdate) { + return tessGeo.polyNormals[3 * (firstPolyVertex + index) + 0]; + } return inGeo.normals[3 * index + 0]; } @Override public float getNormalY(int index) { + if (root.tessUpdate) { + return tessGeo.polyNormals[3 * (firstPolyVertex + index) + 1]; + } return inGeo.normals[3 * index + 1]; } @Override public float getNormalZ(int index) { + if (root.tessUpdate) { + return tessGeo.polyNormals[3 * (firstPolyVertex + index) + 2]; + } return inGeo.normals[3 * index + 2]; } @@ -1748,59 +1855,16 @@ public class PShapeOpenGL extends PShape { return; } - inGeo.normals[3 * index + 0] = nx; - inGeo.normals[3 * index + 1] = ny; - inGeo.normals[3 * index + 2] = nz; - markForTessellation(); - } - - - @Override - public void setAttrib(String name, int index, float... values) { - if (openShape) { - PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setAttrib()"); - return; - } - - VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.FLOAT, - values.length); - float[] array = inGeo.fattribs.get(name); - for (int i = 0; i < values.length; i++) { - array[attrib.size * index + i] = values[i]; - } - markForTessellation(); - } - - - @Override - public void setAttrib(String name, int index, int... values) { - if (openShape) { - PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setAttrib()"); - return; - } - - VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.INT, - values.length); - int[] array = inGeo.iattribs.get(name); - for (int i = 0; i < values.length; i++) { - array[attrib.size * index + i] = values[i]; - } - markForTessellation(); - } - - - @Override - public void setAttrib(String name, int index, boolean... values) { - if (openShape) { - PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setAttrib()"); - return; - } - - VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.BOOL, - values.length); - byte[] array = inGeo.battribs.get(name); - for (int i = 0; i < values.length; i++) { - array[attrib.size * index + i] = (byte)(values[i]?1:0); + if (root.tessUpdate) { + int tessIdx = firstPolyVertex + index; + tessGeo.polyNormals[3 * tessIdx + 0] = nx; + tessGeo.polyNormals[3 * tessIdx + 1] = ny; + tessGeo.polyNormals[3 * tessIdx + 2] = nz; + root.setModifiedPolyNormals(tessIdx, tessIdx); + } else { + inGeo.normals[3 * index + 0] = nx; + inGeo.normals[3 * index + 1] = ny; + inGeo.normals[3 * index + 2] = nz; } markForTessellation(); } @@ -1808,12 +1872,18 @@ public class PShapeOpenGL extends PShape { @Override public float getTextureU(int index) { + if (root.tessUpdate) { + return tessGeo.polyTexCoords[2 * (firstPolyVertex + index) + 0]; + } return inGeo.texcoords[2 * index + 0]; } @Override public float getTextureV(int index) { + if (root.tessUpdate) { + return tessGeo.polyTexCoords[2 * (firstPolyVertex + index) + 1]; + } return inGeo.texcoords[2 * index + 1]; } @@ -1829,17 +1899,27 @@ public class PShapeOpenGL extends PShape { u /= image.width; v /= image.height; } - inGeo.texcoords[2 * index + 0] = u; - inGeo.texcoords[2 * index + 1] = v; - - markForTessellation(); + if (root.tessUpdate) { + int tessIdx = firstPolyVertex + index; + tessGeo.polyTexCoords[2 * tessIdx + 0] = u; + tessGeo.polyTexCoords[2 * tessIdx + 1] = v; + root.setModifiedPolyTexCoords(tessIdx, tessIdx); + } else { + inGeo.texcoords[2 * index + 0] = u; + inGeo.texcoords[2 * index + 1] = v; + markForTessellation(); + } } @Override public int getFill(int index) { if (family != GROUP && image == null) { - return PGL.nativeToJavaARGB(inGeo.colors[index]); + if (root.tessUpdate) { + return PGL.nativeToJavaARGB(tessGeo.polyColors[firstPolyVertex + index]); + } else { + return PGL.nativeToJavaARGB(inGeo.colors[index]); + } } else { return 0; } @@ -1925,8 +2005,14 @@ public class PShapeOpenGL extends PShape { } if (image == null) { - inGeo.colors[index] = PGL.javaToNativeARGB(fill); - markForTessellation(); + if (root.tessUpdate) { + int tessIdx = firstPolyVertex + index; + tessGeo.polyColors[tessIdx] = PGL.javaToNativeARGB(fill); + root.setModifiedPolyColors(tessIdx, tessIdx); + } else { + inGeo.colors[index] = PGL.javaToNativeARGB(fill); + markForTessellation(); + } } } @@ -1934,7 +2020,11 @@ public class PShapeOpenGL extends PShape { @Override public int getTint(int index) { if (family != GROUP && image != null) { - return PGL.nativeToJavaARGB(inGeo.colors[index]); + if (root.tessUpdate) { + return PGL.nativeToJavaARGB(tessGeo.polyColors[firstPolyVertex + index]); + } else { + return PGL.nativeToJavaARGB(inGeo.colors[index]); + } } else { return 0; } @@ -2011,8 +2101,14 @@ public class PShapeOpenGL extends PShape { } if (image != null) { - inGeo.colors[index] = PGL.javaToNativeARGB(tint); - markForTessellation(); + if (root.tessUpdate) { + int tessIdx = firstPolyVertex + index; + tessGeo.polyColors[tessIdx] = PGL.javaToNativeARGB(tint); + root.setModifiedPolyColors(tessIdx, tessIdx); + } else { + inGeo.colors[index] = PGL.javaToNativeARGB(tint); + markForTessellation(); + } } } @@ -2125,8 +2221,33 @@ public class PShapeOpenGL extends PShape { return; } - inGeo.strokeColors[index] = PGL.javaToNativeARGB(stroke); - markForTessellation(); + if (root.tessUpdate) { + if (hasLines) { + if (is3D()) { + int tessIdx = firstLineVertex + index; + tessGeo.lineColors[tessIdx] = PGL.javaToNativeARGB(stroke); + root.setModifiedLineColors(tessIdx, tessIdx); + } else if (is2D()) { + int tessIdx = firstLineVertex + index; + tessGeo.polyColors[tessIdx] = PGL.javaToNativeARGB(stroke); + root.setModifiedPolyColors(tessIdx, tessIdx); + } + } + if (hasPoints) { + if (is3D()) { + int tessIdx = firstPointVertex + index; + tessGeo.lineColors[tessIdx] = PGL.javaToNativeARGB(stroke); + root.setModifiedPointColors(tessIdx, tessIdx); + } else if (is2D()) { + int tessIdx = firstPointVertex + index; + tessGeo.polyColors[tessIdx] = PGL.javaToNativeARGB(stroke); + root.setModifiedPolyColors(tessIdx, tessIdx); + } + } + } else { + inGeo.strokeColors[index] = PGL.javaToNativeARGB(stroke); + markForTessellation(); + } } @@ -2260,7 +2381,11 @@ public class PShapeOpenGL extends PShape { @Override public int getAmbient(int index) { if (family != GROUP) { - return PGL.nativeToJavaARGB(inGeo.ambient[index]); + if (root.tessUpdate) { + return PGL.nativeToJavaARGB(tessGeo.polyAmbient[firstPolyVertex + index]); + } else { + return PGL.nativeToJavaARGB(inGeo.ambient[index]); + } } else { return 0; } @@ -2316,8 +2441,14 @@ public class PShapeOpenGL extends PShape { return; } - inGeo.ambient[index] = PGL.javaToNativeARGB(ambient); - markForTessellation(); + if (root.tessUpdate) { + int tessIdx = firstPolyVertex + index; + tessGeo.polyAmbient[tessIdx] = PGL.javaToNativeARGB(ambient); + root.setModifiedPolyAmbient(tessIdx, tessIdx); + } else { + inGeo.ambient[index] = PGL.javaToNativeARGB(ambient); + markForTessellation(); + } setAmbient = true; } @@ -2325,7 +2456,11 @@ public class PShapeOpenGL extends PShape { @Override public int getSpecular(int index) { if (family == GROUP) { - return PGL.nativeToJavaARGB(inGeo.specular[index]); + if (root.tessUpdate) { + return PGL.nativeToJavaARGB(tessGeo.polySpecular[firstPolyVertex + index]); + } else { + return PGL.nativeToJavaARGB(inGeo.specular[index]); + } } else { return 0; } @@ -2380,15 +2515,26 @@ public class PShapeOpenGL extends PShape { return; } - inGeo.specular[index] = PGL.javaToNativeARGB(specular); - markForTessellation(); + if (root.tessUpdate) { + int tessIdx = firstPolyVertex + index; + tessGeo.polySpecular[tessIdx] = PGL.javaToNativeARGB(specular); + root.setModifiedPolySpecular(tessIdx, tessIdx); + } else { + inGeo.specular[index] = PGL.javaToNativeARGB(specular); + markForTessellation(); + } } @Override public int getEmissive(int index) { if (family == GROUP) { - return PGL.nativeToJavaARGB(inGeo.emissive[index]); + if (root.tessUpdate) { + return PGL.nativeToJavaARGB(tessGeo.polyEmissive[firstPolyVertex + index]); + } else { + return PGL.nativeToJavaARGB(inGeo.emissive[index]); + } + } else { return 0; } @@ -2443,15 +2589,25 @@ public class PShapeOpenGL extends PShape { return; } - inGeo.emissive[index] = PGL.javaToNativeARGB(emissive); - markForTessellation(); + if (root.tessUpdate) { + int tessIdx = firstPolyVertex + index; + tessGeo.polyEmissive[tessIdx] = PGL.javaToNativeARGB(emissive); + root.setModifiedPolyEmissive(tessIdx, tessIdx); + } else { + inGeo.emissive[index] = PGL.javaToNativeARGB(emissive); + markForTessellation(); + } } @Override public float getShininess(int index) { if (family == GROUP) { - return inGeo.shininess[index]; + if (root.tessUpdate) { + return tessGeo.polyShininess[firstPolyVertex + index]; + } else { + return inGeo.shininess[index]; + } } else { return 0; } @@ -2504,10 +2660,395 @@ public class PShapeOpenGL extends PShape { return; } - inGeo.shininess[index] = shine; - markForTessellation(); + if (root.tessUpdate) { + int tessIdx = firstPolyVertex + index; + tessGeo.polyShininess[tessIdx] = shininess; + root.setModifiedPolyShininess(tessIdx, tessIdx); + } else { + inGeo.shininess[index] = shine; + markForTessellation(); + } } + + /////////////////////////////////////////////////////////// + + // + + // Attribute getters and setters + + + public PVector getAttribPosition(String name, int index, PVector vec) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (vec == null) vec = new PVector(); + if (root.tessUpdate) { + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + int tessIdx = firstPolyVertex + index; + vec.x = tessAttrib[4 * tessIdx + 0]; + vec.y = tessAttrib[4 * tessIdx + 1]; + vec.z = tessAttrib[4 * tessIdx + 2]; + } else { + float[] array = inGeo.fattribs.get(name); + vec.x = array[3 * index + 0]; + vec.y = array[3 * index + 1]; + vec.z = array[3 * index + 2]; + } + + return vec; + } + + + public float getAttribPositionX(String name, int index) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (root.tessUpdate) { + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + return tessAttrib[4 * (firstPolyVertex + index) + 0]; + } else { + float[] array = inGeo.fattribs.get(name); + return array[3 * index + 0]; + } + } + + + public float getAttribPositionY(String name, int index) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (root.tessUpdate) { + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + return tessAttrib[4 * (firstPolyVertex + index) + 1]; + } else { + float[] array = inGeo.fattribs.get(name); + return array[3 * index + 1]; + } + } + + + public float getAttribPositionZ(String name, int index) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (root.tessUpdate) { + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + return tessAttrib[4 * (firstPolyVertex + index) + 2]; + } else { + float[] array = inGeo.fattribs.get(name); + return array[3 * index + 2]; + } + } + + + public PVector getAttribNormal(String name, int index, PVector vec) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (vec == null) vec = new PVector(); + if (root.tessUpdate) { + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + int tessIdx = firstPolyVertex + index; + vec.x = tessAttrib[3 * tessIdx + 0]; + vec.y = tessAttrib[3 * tessIdx + 1]; + vec.z = tessAttrib[3 * tessIdx + 2]; + } else { + float[] array = inGeo.fattribs.get(name); + vec.x = array[3 * index + 0]; + vec.y = array[3 * index + 1]; + vec.z = array[3 * index + 2]; + } + + return vec; + } + + + public float getAttribNormalX(String name, int index) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (root.tessUpdate) { + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + return tessAttrib[3 * (firstPolyVertex + index) + 0]; + } else { + float[] array = inGeo.fattribs.get(name); + return array[3 * index + 0]; + } + } + + + public float getAttribNormalY(String name, int index) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (root.tessUpdate) { + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + return tessAttrib[3 * (firstPolyVertex + index) + 1]; + } else { + float[] array = inGeo.fattribs.get(name); + return array[3 * index + 1]; + } + } + + + public float getAttribNormalZ(String name, int index) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (root.tessUpdate) { + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + return tessAttrib[3 * (firstPolyVertex + index) + 2]; + } else { + float[] array = inGeo.fattribs.get(name); + return array[3 * index + 2]; + } + } + + + public int getAttribColor(String name, int index) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (root.tessUpdate) { + int[] tessAttrib = tessGeo.ipolyAttribs.get(name); + int color = tessAttrib[firstPolyVertex + index]; + return PGL.nativeToJavaARGB(color); + } else { + int[] array = inGeo.iattribs.get(name); + return PGL.nativeToJavaARGB(array[index]); + } + } + + + public float[] getAttrib(String name, int index, float[] values) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (root.tessUpdate) { + if (values == null || values.length < attrib.tessSize) values = new float[attrib.tessSize]; + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + int tessIdx = firstPolyVertex + index; + PApplet.arrayCopy(tessAttrib, attrib.tessSize * tessIdx, values, 0, attrib.tessSize); + } else { + if (values == null || values.length < attrib.size) values = new float[attrib.size]; + float[] array = inGeo.fattribs.get(name); + PApplet.arrayCopy(array, attrib.size * index, values, 0, attrib.size); + } + + return values; + } + + + public int[] getAttrib(String name, int index, int[] values) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (root.tessUpdate) { + if (values == null || values.length < attrib.tessSize) values = new int[attrib.tessSize]; + int[] tessAttrib = tessGeo.ipolyAttribs.get(name); + int tessIdx = firstPolyVertex + index; + PApplet.arrayCopy(tessAttrib, attrib.tessSize * tessIdx, values, 0, attrib.tessSize); + } else { + if (values == null || values.length < attrib.size) values = new int[attrib.size]; + int[] array = inGeo.iattribs.get(name); + PApplet.arrayCopy(array, attrib.size * index, values, 0, attrib.size); + } + + return values; + } + + + public boolean[] getAttrib(String name, int index, boolean[] values) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) + throw new RuntimeException("Trying to get values of non existing attribute"); + + if (root.tessUpdate) { + if (values == null || values.length < attrib.tessSize) values = new boolean[attrib.tessSize]; + byte[] tessAttrib = tessGeo.bpolyAttribs.get(name); + int tessIdx = firstPolyVertex + index; + for (int i = 0; i < attrib.tessSize; i++) { + values[i] = (tessAttrib[tessIdx + i]!=0); + } + } else { + if (values == null || values.length < attrib.size) values = new boolean[attrib.size]; + byte[] array = inGeo.battribs.get(name); + for (int i = 0; i < attrib.size; i++) { + values[i] = (array[attrib.size * index + i]!=0); + } + } + + return values; + } + + + public void setAttribPosition(String name, int index, float x, float y, float z) { + if (openShape) { + PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setAttribPosition()"); + return; + } + + VertexAttribute attrib = attribImpl(name, VertexAttribute.POSITION, PGL.FLOAT, 3); + if (attrib == null) + throw new RuntimeException("Trying to set values of non existing attribute"); + + if (root.tessUpdate) { + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + int tessIdx = firstPolyVertex + index; + tessAttrib[4 * tessIdx + 0] = x; + tessAttrib[4 * tessIdx + 1] = y; + tessAttrib[4 * tessIdx + 2] = z; + } else { + float[] array = inGeo.fattribs.get(name); + array[3 * index + 0] = x; + array[3 * index + 1] = y; + array[3 * index + 2] = z; + markForTessellation(); + } + } + + + public void setAttribNormal(String name, int index, float nx, float ny, float nz) { + if (openShape) { + PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setAttribNormal()"); + return; + } + + VertexAttribute attrib = attribImpl(name, VertexAttribute.NORMAL, PGL.FLOAT, 3); + if (attrib == null) + throw new RuntimeException("Trying to set values of non existing attribute"); + + if (root.tessUpdate) { + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + int tessIdx = firstPolyVertex + index; + tessAttrib[3 * tessIdx + 0] = nx; + tessAttrib[3 * tessIdx + 1] = ny; + tessAttrib[3 * tessIdx + 2] = nz; + } else { + float[] array = inGeo.fattribs.get(name); + array[3 * index + 0] = nx; + array[3 * index + 1] = ny; + array[3 * index + 2] = nz; + markForTessellation(); + } + } + + + public void setAttribColor(String name, int index, int color) { + if (openShape) { + PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setAttribColor()"); + return; + } + + VertexAttribute attrib = attribImpl(name, VertexAttribute.COLOR, PGL.INT, 1); + if (attrib == null) + throw new RuntimeException("Trying to set values of non existing attribute"); + + if (root.tessUpdate) { + int[] tessAttrib = tessGeo.ipolyAttribs.get(name); + tessAttrib[firstPolyVertex + index] = PGL.javaToNativeARGB(color); + } else { + float[] array = inGeo.fattribs.get(name); + array[index] = PGL.javaToNativeARGB(color); + markForTessellation(); + } + } + + + public void setAttrib(String name, int index, float... values) { + if (openShape) { + PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setAttrib()"); + return; + } + + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.FLOAT, values.length); + if (attrib == null) + throw new RuntimeException("Trying to set values of non existing attribute"); + + if (root.tessUpdate) { + if (attrib.tessSize != values.length) + throw new RuntimeException("Length of values array is different from attribute tesselated size"); + float[] tessAttrib = tessGeo.fpolyAttribs.get(name); + int tessIdx = firstPolyVertex + index; + PApplet.arrayCopy(values, 0, tessAttrib, attrib.tessSize * tessIdx, attrib.tessSize); + } else { + if (attrib.size != values.length) + throw new RuntimeException("Length of values array is different from attribute size"); + float[] array = inGeo.fattribs.get(name); + PApplet.arrayCopy(values, 0, array, attrib.size * index, attrib.size); + markForTessellation(); + } + } + + + public void setAttrib(String name, int index, int... values) { + if (openShape) { + PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setAttrib()"); + return; + } + + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.INT, values.length); + if (attrib == null) + throw new RuntimeException("Trying to set values of non existing attribute"); + + if (root.tessUpdate) { + if (attrib.tessSize != values.length) + throw new RuntimeException("Length of values array is different from attribute tesselated size"); + int[] tessAttrib = tessGeo.ipolyAttribs.get(name); + int tessIdx = firstPolyVertex + index; + PApplet.arrayCopy(values, 0, tessAttrib, attrib.tessSize * tessIdx, attrib.tessSize); + } else { + if (attrib.size != values.length) + throw new RuntimeException("Length of values array is different from attribute size"); + int[] array = inGeo.iattribs.get(name); + PApplet.arrayCopy(values, 0, array, attrib.size * index, attrib.size); + markForTessellation(); + } + } + + + public void setAttrib(String name, int index, boolean... values) { + if (openShape) { + PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setAttrib()"); + return; + } + + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.BOOL, values.length); + if (attrib == null) + throw new RuntimeException("Trying to set values of non existing attribute"); + + if (root.tessUpdate) { + if (attrib.tessSize != values.length) + throw new RuntimeException("Length of values array is different from attribute tesselated size"); + byte[] tessAttrib = tessGeo.bpolyAttribs.get(name); + int tessIdx = firstPolyVertex + index; + for (int i = 0; i < attrib.tessSize; i++) { + tessAttrib[attrib.tessSize * tessIdx + i] = (byte)(values[i]?1:0); + } + } else { + if (attrib.size != values.length) + throw new RuntimeException("Length of values array is different from attribute size"); + byte[] array = inGeo.battribs.get(name); + for (int i = 0; i < attrib.size; i++) { + array[attrib.size * index + i] = (byte)(values[i]?1:0); + } + markForTessellation(); + } + } + + /////////////////////////////////////////////////////////// // @@ -2564,178 +3105,390 @@ public class PShapeOpenGL extends PShape { public PShape getTessellation() { updateTessellation(); - float[] vertices = tessGeo.polyVertices; - float[] normals = tessGeo.polyNormals; - int[] color = tessGeo.polyColors; - float[] uv = tessGeo.polyTexCoords; - short[] indices = tessGeo.polyIndices; + PShape polyTess = null; + PShape lineTess = null; + PShape pointTess = null; - PShape tess; -// if (is3D()) { -// //tess = PGraphics3D.createShapeImpl(pg, PShape.GEOMETRY); -// tess = pg.createShapeFamily(PShape.GEOMETRY); -// } else if (is2D()) { -// //tess = PGraphics2D.createShapeImpl(pg, PShape.GEOMETRY); -// tess = pg.createShapeFamily(PShape.GEOMETRY); -// } else { -// PGraphics.showWarning("This shape is not either 2D or 3D!"); -// return null; -// } - tess = pg.createShapeFamily(PShape.GEOMETRY); - tess.set3D(is3D); // if this is a 3D shape, make the new shape 3D as well - tess.beginShape(TRIANGLES); - tess.noStroke(); + float[] vertices; + float[] attribs; + int[] color; + float[] uv; + short[] indices; - IndexCache cache = tessGeo.polyIndexCache; - for (int n = firstPolyIndexCache; n <= lastPolyIndexCache; n++) { - int ioffset = cache.indexOffset[n]; - int icount = cache.indexCount[n]; - int voffset = cache.vertexOffset[n]; + if (0 < tessGeo.polyVertexCount) { + polyTess = pg.createShapeFamily(PShape.GEOMETRY); + polyTess.set3D(is3D); // if this is a 3D shape, make the new shape 3D as well + polyTess.beginShape(TRIANGLES); + polyTess.noStroke(); - for (int tr = ioffset / 3; tr < (ioffset + icount) / 3; tr++) { - int i0 = voffset + indices[3 * tr + 0]; - int i1 = voffset + indices[3 * tr + 1]; - int i2 = voffset + indices[3 * tr + 2]; + vertices = tessGeo.polyVertices; + attribs = tessGeo.polyNormals; + color = tessGeo.polyColors; + uv = tessGeo.polyTexCoords; + indices = tessGeo.polyIndices; + IndexCache cache = tessGeo.polyIndexCache; + for (int n = firstPolyIndexCache; n <= lastPolyIndexCache; n++) { + int ioffset = cache.indexOffset[n]; + int icount = cache.indexCount[n]; + int voffset = cache.vertexOffset[n]; - if (is3D()) { - float x0 = vertices[4 * i0 + 0]; - float y0 = vertices[4 * i0 + 1]; - float z0 = vertices[4 * i0 + 2]; - float x1 = vertices[4 * i1 + 0]; - float y1 = vertices[4 * i1 + 1]; - float z1 = vertices[4 * i1 + 2]; - float x2 = vertices[4 * i2 + 0]; - float y2 = vertices[4 * i2 + 1]; - float z2 = vertices[4 * i2 + 2]; + for (int tr = ioffset / 3; tr < (ioffset + icount) / 3; tr++) { + int i0 = voffset + indices[3 * tr + 0]; + int i1 = voffset + indices[3 * tr + 1]; + int i2 = voffset + indices[3 * tr + 2]; - float nx0 = normals[3 * i0 + 0]; - float ny0 = normals[3 * i0 + 1]; - float nz0 = normals[3 * i0 + 2]; - float nx1 = normals[3 * i1 + 0]; - float ny1 = normals[3 * i1 + 1]; - float nz1 = normals[3 * i1 + 2]; - float nx2 = normals[3 * i2 + 0]; - float ny2 = normals[3 * i2 + 1]; - float nz2 = normals[3 * i2 + 2]; + if (is3D()) { + float x0 = vertices[4 * i0 + 0]; + float y0 = vertices[4 * i0 + 1]; + float z0 = vertices[4 * i0 + 2]; + float x1 = vertices[4 * i1 + 0]; + float y1 = vertices[4 * i1 + 1]; + float z1 = vertices[4 * i1 + 2]; + float x2 = vertices[4 * i2 + 0]; + float y2 = vertices[4 * i2 + 1]; + float z2 = vertices[4 * i2 + 2]; + float nx0 = attribs[3 * i0 + 0]; + float ny0 = attribs[3 * i0 + 1]; + float nz0 = attribs[3 * i0 + 2]; + float nx1 = attribs[3 * i1 + 0]; + float ny1 = attribs[3 * i1 + 1]; + float nz1 = attribs[3 * i1 + 2]; + float nx2 = attribs[3 * i2 + 0]; + float ny2 = attribs[3 * i2 + 1]; + float nz2 = attribs[3 * i2 + 2]; + + int argb0 = PGL.nativeToJavaARGB(color[i0]); + int argb1 = PGL.nativeToJavaARGB(color[i1]); + int argb2 = PGL.nativeToJavaARGB(color[i2]); + + polyTess.fill(argb0); + polyTess.normal(nx0, ny0, nz0); + polyTess.vertex(x0, y0, z0, uv[2 * i0 + 0], uv[2 * i0 + 1]); + + polyTess.fill(argb1); + polyTess.normal(nx1, ny1, nz1); + polyTess.vertex(x1, y1, z1, uv[2 * i1 + 0], uv[2 * i1 + 1]); + + polyTess.fill(argb2); + polyTess.normal(nx2, ny2, nz2); + polyTess.vertex(x2, y2, z2, uv[2 * i2 + 0], uv[2 * i2 + 1]); + } else if (is2D()) { + float x0 = vertices[4 * i0 + 0], y0 = vertices[4 * i0 + 1]; + float x1 = vertices[4 * i1 + 0], y1 = vertices[4 * i1 + 1]; + float x2 = vertices[4 * i2 + 0], y2 = vertices[4 * i2 + 1]; + + int argb0 = PGL.nativeToJavaARGB(color[i0]); + int argb1 = PGL.nativeToJavaARGB(color[i1]); + int argb2 = PGL.nativeToJavaARGB(color[i2]); + + polyTess.fill(argb0); + polyTess.vertex(x0, y0, uv[2 * i0 + 0], uv[2 * i0 + 1]); + + polyTess.fill(argb1); + polyTess.vertex(x1, y1, uv[2 * i1 + 0], uv[2 * i1 + 1]); + + polyTess.fill(argb2); + polyTess.vertex(x2, y2, uv[2 * i2 + 0], uv[2 * i2 + 1]); + } + } + } + polyTess.endShape(); + } + if (0 < tessGeo.lineVertexCount) { + lineTess = pg.createShapeFamily(PShape.GEOMETRY); + lineTess.set3D(is3D); // if this is a 3D shape, make the new shape 3D as well + lineTess.beginShape(LINES); + lineTess.noFill(); + + vertices = tessGeo.lineVertices; + attribs = tessGeo.lineDirections; + color = tessGeo.lineColors; + indices = tessGeo.lineIndices; + IndexCache cache = tessGeo.lineIndexCache; + for (int n = firstLineIndexCache; n <= lastLineIndexCache; n++) { + int ioffset = cache.indexOffset[n]; + int icount = cache.indexCount[n]; + int voffset = cache.vertexOffset[n]; + + for (int ln = ioffset / 6; ln < (ioffset + icount) / 6; ln++) { + // Same as in rawLines() + int i0 = voffset + indices[6 * ln + 0]; + int i1 = voffset + indices[6 * ln + 5]; + float sw0 = 2 * attribs[4 * i0 + 3]; + float sw1 = 2 * attribs[4 * i1 + 3]; + + if (PGraphicsOpenGL.zero(sw0)) continue; + + float[] pt0 = {0, 0, 0, 0}; + float[] pt1 = {0, 0, 0, 0}; int argb0 = PGL.nativeToJavaARGB(color[i0]); int argb1 = PGL.nativeToJavaARGB(color[i1]); - int argb2 = PGL.nativeToJavaARGB(color[i2]); - tess.fill(argb0); - tess.normal(nx0, ny0, nz0); - tess.vertex(x0, y0, z0, uv[2 * i0 + 0], uv[2 * i0 + 1]); + PApplet.arrayCopy(vertices, 4 * i0, pt0, 0, 4); + PApplet.arrayCopy(vertices, 4 * i1, pt1, 0, 4); - tess.fill(argb1); - tess.normal(nx1, ny1, nz1); - tess.vertex(x1, y1, z1, uv[2 * i1 + 0], uv[2 * i1 + 1]); + lineTess.strokeWeight(sw0); + lineTess.stroke(argb0); + lineTess.vertex(pt0[X], pt0[Y], pt0[Z]); + lineTess.strokeWeight(sw1); + lineTess.stroke(argb1); + lineTess.vertex(pt1[X], pt1[Y], pt1[Z]); + } + } + lineTess.endShape(); + } + if (0 < tessGeo.pointVertexCount) { + pointTess = pg.createShapeFamily(PShape.GEOMETRY); + pointTess.set3D(is3D); // if this is a 3D shape, make the new shape 3D as well + pointTess.beginShape(POINTS); + pointTess.noFill(); - tess.fill(argb2); - tess.normal(nx2, ny2, nz2); - tess.vertex(x2, y2, z2, uv[2 * i2 + 0], uv[2 * i2 + 1]); - } else if (is2D()) { - float x0 = vertices[4 * i0 + 0], y0 = vertices[4 * i0 + 1]; - float x1 = vertices[4 * i1 + 0], y1 = vertices[4 * i1 + 1]; - float x2 = vertices[4 * i2 + 0], y2 = vertices[4 * i2 + 1]; + vertices = tessGeo.pointVertices; + attribs = tessGeo.pointOffsets; + color = tessGeo.pointColors; + indices = tessGeo.pointIndices; + IndexCache cache = tessGeo.pointIndexCache; + for (int n = 0; n < cache.size; n++) { + int ioffset = cache.indexOffset[n]; + int icount = cache.indexCount[n]; + int voffset = cache.vertexOffset[n]; + int pt = ioffset; + while (pt < (ioffset + icount) / 3) { + float size = attribs[2 * pt + 2]; + float weight; + int perim; + if (0 < size) { // round point + weight = +size / 0.5f; + perim = PApplet.min(PGraphicsOpenGL.MAX_POINT_ACCURACY, + PApplet.max(PGraphicsOpenGL.MIN_POINT_ACCURACY, + (int) (TWO_PI * weight / + PGraphicsOpenGL.POINT_ACCURACY_FACTOR))) + 1; + } else { // Square point + weight = -size / 0.5f; + perim = 5; + } + + int i0 = voffset + indices[3 * pt]; int argb0 = PGL.nativeToJavaARGB(color[i0]); - int argb1 = PGL.nativeToJavaARGB(color[i1]); - int argb2 = PGL.nativeToJavaARGB(color[i2]); + float[] pt0 = {0, 0, 0, 0}; - tess.fill(argb0); - tess.vertex(x0, y0, uv[2 * i0 + 0], uv[2 * i0 + 1]); + PApplet.arrayCopy(vertices, 4 * i0, pt0, 0, 4); - tess.fill(argb1); - tess.vertex(x1, y1, uv[2 * i1 + 0], uv[2 * i1 + 1]); + pointTess.strokeWeight(weight); + pointTess.stroke(argb0); + pointTess.vertex(pt0[X], pt0[Y], pt0[Z]); - tess.fill(argb2); - tess.vertex(x2, y2, uv[2 * i2 + 0], uv[2 * i2 + 1]); + pt += perim; } } + pointTess.endShape(); } - tess.endShape(); - return tess; + if (polyTess == null && lineTess == null && pointTess == null) { + return pg.createShapeFamily(PShape.GEOMETRY); + } else if (polyTess != null && lineTess == null && pointTess == null) { + return polyTess; + } else if (polyTess == null && lineTess != null && pointTess == null) { + return lineTess; + } else if (polyTess == null && lineTess == null && pointTess != null) { + return pointTess; + } else { + PShape group = pg.createShape(GROUP); + if (polyTess != null) group.addChild(polyTess); + if (lineTess != null) group.addChild(lineTess); + if (pointTess != null) group.addChild(pointTess); + return group; + } } - // Testing this method, not use as it might go away... - public float[] getTessellation(int kind, int data) { - updateTessellation(); - - if (kind == TRIANGLES) { - if (data == POSITION) { - if (is3D()) { - root.setModifiedPolyVertices(firstPolyVertex, lastPolyVertex); - } else if (is2D()) { - int last1 = lastPolyVertex + 1; - if (-1 < firstLineVertex) last1 = firstLineVertex; - if (-1 < firstPointVertex) last1 = firstPointVertex; - root.setModifiedPolyVertices(firstPolyVertex, last1 - 1); - } - return tessGeo.polyVertices; - } else if (data == NORMAL) { - if (is3D()) { - root.setModifiedPolyNormals(firstPolyVertex, lastPolyVertex); - } else if (is2D()) { - int last1 = lastPolyVertex + 1; - if (-1 < firstLineVertex) last1 = firstLineVertex; - if (-1 < firstPointVertex) last1 = firstPointVertex; - root.setModifiedPolyNormals(firstPolyVertex, last1 - 1); - } - return tessGeo.polyNormals; - } else if (data == TEXCOORD) { - if (is3D()) { - root.setModifiedPolyTexCoords(firstPolyVertex, lastPolyVertex); - } else if (is2D()) { - int last1 = lastPolyVertex + 1; - if (-1 < firstLineVertex) last1 = firstLineVertex; - if (-1 < firstPointVertex) last1 = firstPointVertex; - root.setModifiedPolyTexCoords(firstPolyVertex, last1 - 1); - } - return tessGeo.polyTexCoords; - } - } else if (kind == LINES) { - if (data == POSITION) { - if (is3D()) { - root.setModifiedLineVertices(firstLineVertex, lastLineVertex); - } else if (is2D()) { - root.setModifiedPolyVertices(firstLineVertex, lastLineVertex); - } - return tessGeo.lineVertices; - } else if (data == DIRECTION) { - if (is2D()) { - root.setModifiedLineAttributes(firstLineVertex, lastLineVertex); - } - return tessGeo.lineDirections; - } - } else if (kind == POINTS) { - if (data == POSITION) { - if (is3D()) { - root.setModifiedPointVertices(firstPointVertex, lastPointVertex); - } else if (is2D()) { - root.setModifiedPolyVertices(firstPointVertex, lastPointVertex); - } - return tessGeo.pointVertices; - } else if (data == OFFSET) { - if (is2D()) { - root.setModifiedPointAttributes(firstPointVertex, lastPointVertex); - } - return tessGeo.pointOffsets; - } - } - return null; - } /////////////////////////////////////////////////////////// // - // Geometry utils + // Tessellation update mode - // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + + @Override + public void beginTessellation(int kind) { + if (kind != TRIANGLES && kind != LINES && kind != POINTS) { + throw new IllegalArgumentException("The only valid kinds of geometry for tessellation update are TRIANGLES, LINES, or POINTS."); + } + + if (!root.tessUpdate) { + updateTessellation(); + if (!tessGeo.bufObjStreaming) { + throw new RuntimeException("Buffer object streaming is not available in the OpenGL renderer, so tessellation update cannot be used."); + } + + root.tessUpdate = true; + root.tessKind = is2D() ? TRIANGLES : kind; + + boolean createBuffer; + if (root.tessKind == TRIANGLES && hasPolys) { + createBuffer = bufPolyVertex == null; + if (createBuffer) bufPolyVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyVertex.glId); + tessGeo.initPolyVerticesBuffer(!createBuffer, false, PGL.bufferUsageRetained); + root.selVertices = tessGeo.polyVertices; + + createBuffer = bufPolyColor == null; + if (createBuffer) bufPolyColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyColor.glId); + tessGeo.initPolyColorsBuffer(!createBuffer, false, PGL.bufferUsageRetained); + + createBuffer = bufPolyNormal == null; + if (createBuffer) bufPolyNormal = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyNormal.glId); + tessGeo.initPolyNormalsBuffer(!createBuffer, false, PGL.bufferUsageRetained); + + createBuffer = bufPolyTexCoord == null; + if (createBuffer) bufPolyTexCoord = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyTexCoord.glId); + tessGeo.initPolyTexCoordsBuffer(!createBuffer, false, PGL.bufferUsageRetained); + + createBuffer = bufPolyAmbient == null; + if (createBuffer) bufPolyAmbient = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyAmbient.glId); + tessGeo.initPolyAmbientBuffer(!createBuffer, false, PGL.bufferUsageRetained); + + createBuffer = bufPolySpecular == null; + if (createBuffer) bufPolySpecular = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolySpecular.glId); + tessGeo.initPolySpecularBuffer(!createBuffer, false, PGL.bufferUsageRetained); + + createBuffer = bufPolyEmissive == null; + if (createBuffer) bufPolyEmissive = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyEmissive.glId); + tessGeo.initPolyEmissiveBuffer(!createBuffer, false, PGL.bufferUsageRetained); + + createBuffer = bufPolyShininess == null; + if (createBuffer) bufPolyShininess = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyShininess.glId); + tessGeo.initPolyShininessBuffer(!createBuffer, false, PGL.bufferUsageRetained); + + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + createBuffer = !attrib.bufferCreated(); + if (createBuffer) attrib.createBuffer(pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, attrib.buf.glId); + tessGeo.initPolyAttribsBuffer(attrib, !createBuffer, false, PGL.bufferUsageRetained); + } + } else if (root.tessKind == LINES && hasLines) { + createBuffer = bufLineVertex == null; + if (createBuffer) bufLineVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineVertex.glId); + tessGeo.initLineVerticesBuffer(!createBuffer, false, PGL.bufferUsageRetained); + root.selVertices = tessGeo.lineVertices; + + createBuffer = bufLineColor == null; + if (createBuffer) bufLineColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineColor.glId); + tessGeo.initLineColorsBuffer(!createBuffer, false, PGL.bufferUsageRetained); + + createBuffer = bufLineAttrib == null; + if (createBuffer) bufLineAttrib = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineAttrib.glId); + tessGeo.initLineDirectionsBuffer(!createBuffer, false, PGL.bufferUsageRetained); + } else if (root.tessKind == POINTS && hasPoints) { + createBuffer = bufPointVertex == null; + if (createBuffer) bufPointVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointVertex.glId); + tessGeo.initPointVerticesBuffer(!createBuffer, false, PGL.bufferUsageRetained); + root.selVertices = tessGeo.pointVertices; + + createBuffer = bufPointColor == null; + if (createBuffer) bufPointColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointColor.glId); + tessGeo.initPointColorsBuffer(!createBuffer, false, PGL.bufferUsageRetained); + + createBuffer = bufPointAttrib == null; + if (createBuffer) bufPointAttrib = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointAttrib.glId); + tessGeo.initPointOffsetsBuffer(!createBuffer, false, PGL.bufferUsageRetained); + } + + pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); + } + } + + @Override + public void endTessellation() { + if (root.tessUpdate) { + if (root.tessKind == TRIANGLES && hasPolys) { + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyVertex.glId); + tessGeo.finalPolyVerticesBuffer(firstModifiedPolyVertex, lastModifiedPolyVertex); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyColor.glId); + tessGeo.finalPolyColorsBuffer(firstModifiedPolyColor, lastModifiedPolyColor); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyNormal.glId); + tessGeo.finalPolyNormalsBuffer(firstModifiedPolyNormal, lastModifiedPolyNormal); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyTexCoord.glId); + tessGeo.finalPolyTexCoordsBuffer(firstModifiedPolyTexCoord, lastModifiedPolyTexCoord); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyAmbient.glId); + tessGeo.finalPolyAmbientBuffer(firstModifiedPolyAmbient, lastModifiedPolyAmbient); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolySpecular.glId); + tessGeo.finalPolySpecularBuffer(firstModifiedPolySpecular, lastModifiedPolySpecular); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyEmissive.glId); + tessGeo.finalPolyEmissiveBuffer(firstModifiedPolyEmissive, lastModifiedPolyEmissive); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyShininess.glId); + tessGeo.finalPolyShininessBuffer(firstModifiedPolyShininess, lastModifiedPolyShininess); + + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + pgl.bindBuffer(PGL.ARRAY_BUFFER, attrib.buf.glId); + tessGeo.finalPolyAttribsBuffer(attrib, attrib.firstModified, attrib.lastModified); + } + } else if (root.tessKind == LINES && hasLines) { + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineVertex.glId); + tessGeo.finalLineVerticesBuffer(firstModifiedLineVertex, lastModifiedLineVertex); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineColor.glId); + tessGeo.finalLineColorsBuffer(firstModifiedLineColor, lastModifiedLineColor); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineAttrib.glId); + tessGeo.finalLineDirectionsBuffer(firstModifiedLineAttribute, lastModifiedLineAttribute); + } else if (root.tessKind == POINTS && hasPoints) { + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointVertex.glId); + tessGeo.finalPointVerticesBuffer(firstModifiedPointVertex, lastModifiedPointVertex); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointColor.glId); + tessGeo.finalPointColorsBuffer(firstModifiedPointColor, lastModifiedPointColor); + + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointAttrib.glId); + tessGeo.finalPointOffsetsBuffer(firstModifiedPointAttribute, lastModifiedPointAttribute); + } + root.selVertices = null; + root.tessUpdate = false; + + // To avoid triggering a new tessellation in the draw method. + root.modified = false; + } + } + + + /////////////////////////////////////////////////////////// + + // + + /** + * Return true if this x, y coordinate is part of this shape. Only works + * with PATH shapes or GROUP shapes that contain other GROUPs or PATHs. + * This method is not imperfect and doesn't account for all cases + * (not all complex shapes: concave shapes or holes may have issues). + */ @Override public boolean contains(float x, float y) { - if (family == PATH) { + if (family == PATH || family == GEOMETRY) { + // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html boolean c = false; for (int i = 0, j = inGeo.vertexCount-1; i < inGeo.vertexCount; j = i++) { if (((inGeo.vertices[3 * i + 1] > y) != (inGeo.vertices[3 * j + 1] > y)) && @@ -2803,8 +3556,8 @@ public class PShapeOpenGL extends PShape { lastModifiedPolyColor = PConstants.MIN_INT; firstModifiedPolyNormal = PConstants.MAX_INT; lastModifiedPolyNormal = PConstants.MIN_INT; - firstModifiedPolyTexcoord = PConstants.MAX_INT; - lastModifiedPolyTexcoord = PConstants.MIN_INT; + firstModifiedPolyTexCoord = PConstants.MAX_INT; + lastModifiedPolyTexCoord = PConstants.MIN_INT; firstModifiedPolyAmbient = PConstants.MAX_INT; lastModifiedPolyAmbient = PConstants.MIN_INT; firstModifiedPolySpecular = PConstants.MAX_INT; @@ -2839,7 +3592,8 @@ public class PShapeOpenGL extends PShape { } if (tessGeo == null) { - tessGeo = PGraphicsOpenGL.newTessGeometry(pg, polyAttribs, PGraphicsOpenGL.RETAINED); + tessGeo = PGraphicsOpenGL.newTessGeometry(pg, polyAttribs, PGraphicsOpenGL.RETAINED, + PGL.bufferStreamingRetained); } tessGeo.clear(); @@ -3379,11 +4133,6 @@ public class PShapeOpenGL extends PShape { for (int i = 0; i < vertexCount; i++) { inGeo.addVertex(vertices[i][X], vertices[i][Y], VERTEX, false); } - } else { // drawing 3D vertices - for (int i = 0; i < vertexCount; i++) { - inGeo.addVertex(vertices[i][X], vertices[i][Y], vertices[i][Z], - VERTEX, false); - } } } else { // coded set of vertices int idx = 0; @@ -3423,57 +4172,6 @@ public class PShapeOpenGL extends PShape { idx++; break; - case BREAK: - brk = true; - } - } - } else { // tessellating a 3D path - for (int j = 0; j < vertexCodeCount; j++) { - switch (vertexCodes[j]) { - - case VERTEX: - inGeo.addVertex(vertices[idx][X], vertices[idx][Y], - vertices[idx][Z], brk); - brk = false; - idx++; - break; - - case QUADRATIC_VERTEX: - inGeo.addQuadraticVertex(vertices[idx+0][X], - vertices[idx+0][Y], - vertices[idx+0][Z], - vertices[idx+1][X], - vertices[idx+1][Y], - vertices[idx+0][Z], - brk); - brk = false; - idx += 2; - break; - - case BEZIER_VERTEX: - inGeo.addBezierVertex(vertices[idx+0][X], - vertices[idx+0][Y], - vertices[idx+0][Z], - vertices[idx+1][X], - vertices[idx+1][Y], - vertices[idx+1][Z], - vertices[idx+2][X], - vertices[idx+2][Y], - vertices[idx+2][Z], - brk); - brk = false; - idx += 3; - break; - - case CURVE_VERTEX: - inGeo.addCurveVertex(vertices[idx][X], - vertices[idx][Y], - vertices[idx][Z], - brk); - brk = false; - idx++; - break; - case BREAK: brk = true; } @@ -3737,8 +4435,7 @@ public class PShapeOpenGL extends PShape { protected boolean startStrokedTex(int n) { - return image != null && (n == firstLineIndexCache || - n == firstPointIndexCache); + return image != null && (n == firstLineIndexCache || n == firstPointIndexCache); } @@ -3907,12 +4604,14 @@ public class PShapeOpenGL extends PShape { initPolyBuffers(); } - if (hasLines && (needBufferInit || outdated)) { - initLineBuffers(); - } + if (is3D()) { + if (hasLines && (needBufferInit || outdated)) { + initLineBuffers(); + } - if (hasPoints && (needBufferInit || outdated)) { - initPointBuffers(); + if (hasPoints && (needBufferInit || outdated)) { + initPointBuffers(); + } } needBufferInit = false; @@ -3920,165 +4619,111 @@ public class PShapeOpenGL extends PShape { protected void initPolyBuffers() { - int size = tessGeo.polyVertexCount; - int sizef = size * PGL.SIZEOF_FLOAT; - int sizei = size * PGL.SIZEOF_INT; - - tessGeo.updatePolyVerticesBuffer(); - if (bufPolyVertex == null) - bufPolyVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); + boolean createBuffer = bufPolyVertex == null; + if (createBuffer) bufPolyVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyVertex.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.polyVerticesBuffer, glUsage); + tessGeo.initPolyVerticesBuffer(false, true, PGL.bufferUsageRetained); - tessGeo.updatePolyColorsBuffer(); - if (bufPolyColor == null) - bufPolyColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + createBuffer = bufPolyColor == null; + if (createBuffer) bufPolyColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyColor.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polyColorsBuffer, glUsage); + tessGeo.initPolyColorsBuffer(!createBuffer, true, PGL.bufferUsageRetained); - tessGeo.updatePolyNormalsBuffer(); - if (bufPolyNormal == null) - bufPolyNormal = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT); + createBuffer = bufPolyNormal == null; + if (createBuffer) bufPolyNormal = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyNormal.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, - tessGeo.polyNormalsBuffer, glUsage); + tessGeo.initPolyNormalsBuffer(!createBuffer, true, PGL.bufferUsageRetained); - tessGeo.updatePolyTexCoordsBuffer(); - if (bufPolyTexcoord == null) - bufPolyTexcoord = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT); - pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyTexcoord.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, - tessGeo.polyTexCoordsBuffer, glUsage); + createBuffer = bufPolyTexCoord == null; + if (createBuffer) bufPolyTexCoord = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyTexCoord.glId); + tessGeo.initPolyTexCoordsBuffer(!createBuffer, true, PGL.bufferUsageRetained); - tessGeo.updatePolyAmbientBuffer(); - if (bufPolyAmbient == null) - bufPolyAmbient = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + createBuffer = bufPolyAmbient == null; + if (createBuffer) bufPolyAmbient = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyAmbient.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polyAmbientBuffer, glUsage); + tessGeo.initPolyAmbientBuffer(!createBuffer, true, PGL.bufferUsageRetained); - tessGeo.updatePolySpecularBuffer(); - if (bufPolySpecular == null) - bufPolySpecular = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + createBuffer = bufPolySpecular == null; + if (createBuffer) bufPolySpecular = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolySpecular.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polySpecularBuffer, glUsage); + tessGeo.initPolySpecularBuffer(!createBuffer, true, PGL.bufferUsageRetained); - tessGeo.updatePolyEmissiveBuffer(); - if (bufPolyEmissive == null) - bufPolyEmissive = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + createBuffer = bufPolyEmissive == null; + if (createBuffer) bufPolyEmissive = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyEmissive.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polyEmissiveBuffer, glUsage); + tessGeo.initPolyEmissiveBuffer(!createBuffer, true, PGL.bufferUsageRetained); - tessGeo.updatePolyShininessBuffer(); - if (bufPolyShininess == null) - bufPolyShininess = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_FLOAT); + createBuffer = bufPolyShininess == null; + if (createBuffer) bufPolyShininess = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyShininess.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizef, - tessGeo.polyShininessBuffer, glUsage); + tessGeo.initPolyShininessBuffer(!createBuffer, true, PGL.bufferUsageRetained); for (String name: polyAttribs.keySet()) { VertexAttribute attrib = polyAttribs.get(name); - tessGeo.updateAttribBuffer(attrib.name); - if (!attrib.bufferCreated()) attrib.createBuffer(pgl); + createBuffer = !attrib.bufferCreated(); + if (createBuffer) attrib.createBuffer(pgl); pgl.bindBuffer(PGL.ARRAY_BUFFER, attrib.buf.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, attrib.sizeInBytes(size), - tessGeo.polyAttribBuffers.get(name), glUsage); + tessGeo.initPolyAttribsBuffer(attrib, !createBuffer, true, PGL.bufferUsageRetained); } pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - tessGeo.updatePolyIndicesBuffer(); - if (bufPolyIndex == null) - bufPolyIndex = new VertexBuffer(pg, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); + createBuffer = bufPolyIndex == null; + if (createBuffer) bufPolyIndex = new VertexBuffer(pg, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, PGL.bufferUsageRetained, true); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufPolyIndex.glId); - pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, - tessGeo.polyIndexCount * PGL.SIZEOF_INDEX, - tessGeo.polyIndicesBuffer, glUsage); - + tessGeo.initPolyIndicesBuffer(!createBuffer, true, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); } - protected void initLineBuffers() { - int size = tessGeo.lineVertexCount; - int sizef = size * PGL.SIZEOF_FLOAT; - int sizei = size * PGL.SIZEOF_INT; - - tessGeo.updateLineVerticesBuffer(); - if (bufLineVertex == null) - bufLineVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); + boolean createBuffer = bufLineVertex == null; + if (createBuffer) bufLineVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineVertex.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.lineVerticesBuffer, glUsage); + tessGeo.initLineVerticesBuffer(!createBuffer, true, PGL.bufferUsageRetained); - tessGeo.updateLineColorsBuffer(); - if (bufLineColor == null) - bufLineColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + createBuffer = bufLineColor == null; + if (createBuffer) bufLineColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineColor.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.lineColorsBuffer, glUsage); + tessGeo.initLineColorsBuffer(!createBuffer, true, PGL.bufferUsageRetained); - tessGeo.updateLineDirectionsBuffer(); - if (bufLineAttrib == null) - bufLineAttrib = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); + createBuffer = bufLineAttrib == null; + if (createBuffer) bufLineAttrib = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineAttrib.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.lineDirectionsBuffer, glUsage); + tessGeo.initLineDirectionsBuffer(!createBuffer, true, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - tessGeo.updateLineIndicesBuffer(); - if (bufLineIndex == null) - bufLineIndex = new VertexBuffer(pg, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); + createBuffer = bufLineIndex == null; + if (createBuffer) bufLineIndex = new VertexBuffer(pg, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, PGL.bufferUsageRetained, true); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufLineIndex.glId); - pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, - tessGeo.lineIndexCount * PGL.SIZEOF_INDEX, - tessGeo.lineIndicesBuffer, glUsage); - + tessGeo.initLineIndicesBuffer(!createBuffer, true, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); } protected void initPointBuffers() { - int size = tessGeo.pointVertexCount; - int sizef = size * PGL.SIZEOF_FLOAT; - int sizei = size * PGL.SIZEOF_INT; - - tessGeo.updatePointVerticesBuffer(); - if (bufPointVertex == null) - bufPointVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); + boolean createBuffer = bufPointVertex == null; + if (createBuffer) bufPointVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointVertex.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.pointVerticesBuffer, glUsage); + tessGeo.initPointVerticesBuffer(!createBuffer, true, PGL.bufferUsageRetained); - tessGeo.updatePointColorsBuffer(); - if (bufPointColor == null) - bufPointColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + createBuffer = bufPointColor == null; + if (createBuffer) bufPointColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointColor.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.pointColorsBuffer, glUsage); + tessGeo.initPointColorsBuffer(!createBuffer, true, PGL.bufferUsageRetained); - tessGeo.updatePointOffsetsBuffer(); - if (bufPointAttrib == null) - bufPointAttrib = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT); + createBuffer = bufPointAttrib == null; + if (createBuffer) bufPointAttrib = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointAttrib.glId); - pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, - tessGeo.pointOffsetsBuffer, glUsage); + tessGeo.initPointOffsetsBuffer(!createBuffer, true, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - tessGeo.updatePointIndicesBuffer(); - if (bufPointIndex == null) - bufPointIndex = new VertexBuffer(pg, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); + createBuffer = bufPointIndex == null; + if (createBuffer) bufPointIndex = new VertexBuffer(pg, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, PGL.bufferUsageRetained, true); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufPointIndex.glId); - pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, - tessGeo.pointIndexCount * PGL.SIZEOF_INDEX, - tessGeo.pointIndicesBuffer, glUsage); - + tessGeo.initPointIndicesBuffer(!createBuffer, true, PGL.bufferUsageRetained); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); } @@ -4089,7 +4734,7 @@ public class PShapeOpenGL extends PShape { bufPolyVertex.dispose(); bufPolyColor.dispose(); bufPolyNormal.dispose(); - bufPolyTexcoord.dispose(); + bufPolyTexCoord.dispose(); bufPolyAmbient.dispose(); bufPolySpecular.dispose(); bufPolyEmissive.dispose(); @@ -4154,12 +4799,12 @@ public class PShapeOpenGL extends PShape { lastModifiedPolyNormal = PConstants.MIN_INT; } if (modifiedPolyTexCoords) { - int offset = firstModifiedPolyTexcoord; - int size = lastModifiedPolyTexcoord - offset + 1; + int offset = firstModifiedPolyTexCoord; + int size = lastModifiedPolyTexCoord - offset + 1; copyPolyTexCoords(offset, size); modifiedPolyTexCoords = false; - firstModifiedPolyTexcoord = PConstants.MAX_INT; - lastModifiedPolyTexcoord = PConstants.MIN_INT; + firstModifiedPolyTexCoord = PConstants.MAX_INT; + lastModifiedPolyTexCoord = PConstants.MIN_INT; } if (modifiedPolyAmbient) { int offset = firstModifiedPolyAmbient; @@ -4260,167 +4905,106 @@ public class PShapeOpenGL extends PShape { protected void copyPolyVertices(int offset, int size) { - tessGeo.updatePolyVerticesBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyVertex.glId); - tessGeo.polyVerticesBuffer.position(4 * offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, - 4 * size * PGL.SIZEOF_FLOAT, tessGeo.polyVerticesBuffer); - tessGeo.polyVerticesBuffer.rewind(); + tessGeo.copyPolyVertices(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPolyColors(int offset, int size) { - tessGeo.updatePolyColorsBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyColor.glId); - tessGeo.polyColorsBuffer.position(offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT, tessGeo.polyColorsBuffer); - tessGeo.polyColorsBuffer.rewind(); + tessGeo.copyPolyColors(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPolyNormals(int offset, int size) { - tessGeo.updatePolyNormalsBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyNormal.glId); - tessGeo.polyNormalsBuffer.position(3 * offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, 3 * offset * PGL.SIZEOF_FLOAT, - 3 * size * PGL.SIZEOF_FLOAT, tessGeo.polyNormalsBuffer); - tessGeo.polyNormalsBuffer.rewind(); + tessGeo.copyPolyNormals(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPolyTexCoords(int offset, int size) { - tessGeo.updatePolyTexCoordsBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyTexcoord.glId); - tessGeo.polyTexCoordsBuffer.position(2 * offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, 2 * offset * PGL.SIZEOF_FLOAT, - 2 * size * PGL.SIZEOF_FLOAT, tessGeo.polyTexCoordsBuffer); - tessGeo.polyTexCoordsBuffer.rewind(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyTexCoord.glId); + tessGeo.copyPolyTexCoords(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPolyAmbient(int offset, int size) { - tessGeo.updatePolyAmbientBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyAmbient.glId); - tessGeo.polyAmbientBuffer.position(offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT, tessGeo.polyAmbientBuffer); - tessGeo.polyAmbientBuffer.rewind(); + tessGeo.copyPolyAmbient(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPolySpecular(int offset, int size) { - tessGeo.updatePolySpecularBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolySpecular.glId); - tessGeo.polySpecularBuffer.position(offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT, tessGeo.polySpecularBuffer); - tessGeo.polySpecularBuffer.rewind(); + tessGeo.copyPolySpecular(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPolyEmissive(int offset, int size) { - tessGeo.updatePolyEmissiveBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyEmissive.glId); - tessGeo.polyEmissiveBuffer.position(offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT, tessGeo.polyEmissiveBuffer); - tessGeo.polyEmissiveBuffer.rewind(); + tessGeo.copyPolyEmissive(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPolyShininess(int offset, int size) { - tessGeo.updatePolyShininessBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyShininess.glId); - tessGeo.polyShininessBuffer.position(offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_FLOAT, - size * PGL.SIZEOF_FLOAT, tessGeo.polyShininessBuffer); - tessGeo.polyShininessBuffer.rewind(); + tessGeo.copyPolyShininess(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPolyAttrib(VertexAttribute attrib, int offset, int size) { - tessGeo.updateAttribBuffer(attrib.name, offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, attrib.buf.glId); - Buffer buf = tessGeo.polyAttribBuffers.get(attrib.name); - buf.position(attrib.size * offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, attrib.sizeInBytes(offset), - attrib.sizeInBytes(size), buf); - buf.rewind(); + tessGeo.copyPolyAttribs(attrib, offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyLineVertices(int offset, int size) { - tessGeo.updateLineVerticesBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineVertex.glId); - tessGeo.lineVerticesBuffer.position(4 * offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, - 4 * size * PGL.SIZEOF_FLOAT, tessGeo.lineVerticesBuffer); - tessGeo.lineVerticesBuffer.rewind(); + tessGeo.copyLineVertices(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyLineColors(int offset, int size) { - tessGeo.updateLineColorsBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineColor.glId); - tessGeo.lineColorsBuffer.position(offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT, tessGeo.lineColorsBuffer); - tessGeo.lineColorsBuffer.rewind(); + tessGeo.copyLineColors(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyLineAttributes(int offset, int size) { - tessGeo.updateLineDirectionsBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineAttrib.glId); - tessGeo.lineDirectionsBuffer.position(4 * offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, - 4 * size * PGL.SIZEOF_FLOAT, tessGeo.lineDirectionsBuffer); - tessGeo.lineDirectionsBuffer.rewind(); + tessGeo.copyLineDirections(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPointVertices(int offset, int size) { - tessGeo.updatePointVerticesBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointVertex.glId); - tessGeo.pointVerticesBuffer.position(4 * offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, - 4 * size * PGL.SIZEOF_FLOAT, tessGeo.pointVerticesBuffer); - tessGeo.pointVerticesBuffer.rewind(); + tessGeo.copyPointVertices(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPointColors(int offset, int size) { - tessGeo.updatePointColorsBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointColor.glId); - tessGeo.pointColorsBuffer.position(offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT,tessGeo.pointColorsBuffer); - tessGeo.pointColorsBuffer.rewind(); + tessGeo.copyPointColors(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } protected void copyPointAttributes(int offset, int size) { - tessGeo.updatePointOffsetsBuffer(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointAttrib.glId); - tessGeo.pointOffsetsBuffer.position(2 * offset); - pgl.bufferSubData(PGL.ARRAY_BUFFER, 2 * offset * PGL.SIZEOF_FLOAT, - 2 * size * PGL.SIZEOF_FLOAT, tessGeo.pointOffsetsBuffer); - tessGeo.pointOffsetsBuffer.rewind(); + tessGeo.copyPointOffsets(offset, size); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4450,8 +5034,8 @@ public class PShapeOpenGL extends PShape { protected void setModifiedPolyTexCoords(int first, int last) { - if (first < firstModifiedPolyTexcoord) firstModifiedPolyTexcoord = first; - if (last > lastModifiedPolyTexcoord) lastModifiedPolyTexcoord = last; + if (first < firstModifiedPolyTexCoord) firstModifiedPolyTexCoord = first; + if (last > lastModifiedPolyTexCoord) lastModifiedPolyTexCoord = last; modifiedPolyTexCoords = true; modified = true; } @@ -4662,13 +5246,6 @@ public class PShapeOpenGL extends PShape { // Rendering methods - /* - public void draw() { - draw(pg); - } - */ - - @Override public void draw(PGraphics g) { if (g instanceof PGraphicsOpenGL) { @@ -4845,8 +5422,7 @@ public class PShapeOpenGL extends PShape { protected void render(PGraphicsOpenGL g, PImage texture) { if (root == null) { // Some error. Root should never be null. At least it should be 'this'. - throw new RuntimeException("Error rendering PShapeOpenGL, root shape is " + - "null"); + throw new RuntimeException("Error rendering PShapeOpenGL, root shape is null"); } if (hasPolys) { @@ -4946,7 +5522,7 @@ public class PShapeOpenGL extends PShape { } if (tex != null || needTexCoords) { - shader.setTexcoordAttribute(root.bufPolyTexcoord.glId, 2, PGL.FLOAT, + shader.setTexcoordAttribute(root.bufPolyTexCoord.glId, 2, PGL.FLOAT, 0, 2 * voffset * PGL.SIZEOF_FLOAT); shader.setTexture(tex); } @@ -5111,7 +5687,7 @@ public class PShapeOpenGL extends PShape { int voffset = cache.vertexOffset[n]; for (int ln = ioffset / 6; ln < (ioffset + icount) / 6; ln++) { - // Each line segment is defined by six indices since its + // Each line segment is defined by six indices since it's // formed by two triangles. We only need the first and last // vertices. // This bunch of vertices could also be the bevel triangles, diff --git a/core/src/processing/opengl/PSurfaceJOGL.java b/core/src/processing/opengl/PSurfaceJOGL.java index 70473327d..6a81f3e89 100644 --- a/core/src/processing/opengl/PSurfaceJOGL.java +++ b/core/src/processing/opengl/PSurfaceJOGL.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 @@ -27,7 +27,6 @@ package processing.opengl; import java.awt.Component; import java.awt.EventQueue; import java.awt.FileDialog; -import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Point; @@ -38,7 +37,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -51,12 +52,12 @@ import com.jogamp.nativewindow.ScalableSurface; import com.jogamp.nativewindow.util.Dimension; import com.jogamp.nativewindow.util.PixelFormat; import com.jogamp.nativewindow.util.PixelRectangle; -import com.jogamp.opengl.GLAnimatorControl; import com.jogamp.opengl.GLAutoDrawable; import com.jogamp.opengl.GLCapabilities; import com.jogamp.opengl.GLEventListener; import com.jogamp.opengl.GLException; import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.GLDrawableFactory; import com.jogamp.nativewindow.MutableGraphicsConfiguration; import com.jogamp.nativewindow.WindowClosingProtocol; import com.jogamp.newt.Display; @@ -82,7 +83,7 @@ import processing.awt.ShimAWT; public class PSurfaceJOGL implements PSurface { /** Selected GL profile */ - public static GLProfile profile; + static public GLProfile profile; public PJOGL pgl; @@ -95,8 +96,8 @@ public class PSurfaceJOGL implements PSurface { protected PApplet sketch; protected PGraphics graphics; - protected int sketchWidth0; - protected int sketchHeight0; + protected int sketchWidthRequested; + protected int sketchHeightRequested; protected int sketchWidth; protected int sketchHeight; @@ -108,87 +109,21 @@ public class PSurfaceJOGL implements PSurface { protected NewtCanvasAWT canvas; + protected static GLAutoDrawable sharedDrawable; + protected static ArrayList animators = new ArrayList<>(); + private final static Object sharedSyncMutex = new Object(); + private Object syncMutex; + protected int windowScaleFactor; - protected float[] currentPixelScale = {0, 0}; + protected float[] currentPixelScale = { 0, 0 }; - protected boolean external = false; +// protected boolean external = false; public PSurfaceJOGL(PGraphics graphics) { this.graphics = graphics; - this.pgl = (PJOGL) ((PGraphicsOpenGL)graphics).pgl; - } - - - /* - @Override - public int displayDensity() { - return shim.displayDensity(); - } - - - @Override - public int displayDensity(int display) { - return shim.displayDensity(display); - } - */ - - - // TODO rewrite before 4.0 release - public PImage loadImage(String path, Object... args) { - return ShimAWT.loadImage(sketch, path, args); - } - - - @Override - public void selectInput(String prompt, String callbackMethod, - File file, Object callbackObject) { - EventQueue.invokeLater(() -> { - // https://github.com/processing/processing/issues/3831 - boolean hide = (sketch != null) && - (PApplet.platform == PConstants.WINDOWS); - if (hide) setVisible(false); - - ShimAWT.selectImpl(prompt, callbackMethod, file, - callbackObject, null, FileDialog.LOAD); - - if (hide) setVisible(true); - }); - } - - - @Override - public void selectOutput(String prompt, String callbackMethod, - File file, Object callbackObject) { - EventQueue.invokeLater(() -> { - // https://github.com/processing/processing/issues/3831 - boolean hide = (sketch != null) && - (PApplet.platform == PConstants.WINDOWS); - if (hide) setVisible(false); - - ShimAWT.selectImpl(prompt, callbackMethod, file, - callbackObject, null, FileDialog.SAVE); - - if (hide) setVisible(true); - }); - } - - - @Override - public void selectFolder(String prompt, String callbackMethod, - File file, Object callbackObject) { - EventQueue.invokeLater(() -> { - // https://github.com/processing/processing/issues/3831 - boolean hide = (sketch != null) && - (PApplet.platform == PConstants.WINDOWS); - if (hide) setVisible(false); - - ShimAWT.selectFolderImpl(prompt, callbackMethod, file, - callbackObject, null); - - if (hide) setVisible(true); - }); + this.pgl = (PJOGL) ((PGraphicsOpenGL) graphics).pgl; } @@ -229,11 +164,19 @@ public class PSurfaceJOGL implements PSurface { screen = NewtFactory.createScreen(display, 0); screen.addReference(); + int displayNum = sketch.sketchDisplay(); + displayRect = getDisplayBounds(displayNum); + } + + + // TODO this code is mostly copied from code found in PSurfaceOpenGL, + // they should probably be merged to avoid divergence [fry 211122] + static protected Rectangle getDisplayBounds(int displayNum) { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] awtDevices = ge.getScreenDevices(); GraphicsDevice awtDisplayDevice = null; - int displayNum = sketch.sketchDisplay(); + if (displayNum > 0) { // if -1, use the default device if (displayNum <= awtDevices.length) { awtDisplayDevice = awtDevices[displayNum-1]; @@ -245,6 +188,9 @@ public class PSurfaceJOGL implements PSurface { } } } else if (0 < awtDevices.length) { + // TODO this seems like a bad idea: in lots of situations [0] will *not* + // be the default device. Not sure why this was added instead of + // just using getDefaultScreenDevice() below. [fry 211122] awtDisplayDevice = awtDevices[0]; } @@ -252,8 +198,7 @@ public class PSurfaceJOGL implements PSurface { awtDisplayDevice = ge.getDefaultScreenDevice(); } - GraphicsConfiguration config = awtDisplayDevice.getDefaultConfiguration(); - displayRect = config.getBounds(); + return awtDisplayDevice.getDefaultConfiguration().getBounds(); } @@ -316,6 +261,29 @@ public class PSurfaceJOGL implements PSurface { caps.setBackgroundOpaque(true); caps.setOnscreen(true); pgl.setCaps(caps); + + if (sharedDrawable == null) { + // Create a shared drawable to enable context sharing across multiple GL windows + // https://jogamp.org/deployment/jogamp-next/javadoc/jogl/javadoc/com/jogamp/opengl/GLSharedContextSetter.html + sharedDrawable = GLDrawableFactory.getFactory(profile).createDummyAutoDrawable(null, true, caps, null); + sharedDrawable.display(); + } + } + + + // To properly deal with synchronization when context sharing across multiple drawables (windows), we need a + // synchronization mutex object to ensure that rendering of each frame completes in their respective animator thread: + // https://jogamp.org/deployment/jogamp-next/javadoc/jogl/javadoc/com/jogamp/opengl/GLSharedContextSetter.html#synchronization + private Object getSyncMutex(GLAutoDrawable drawable) { + pgl.getGL(drawable); + if (pgl.needSharedObjectSync()) { + syncMutex = sharedSyncMutex; + } else { + if (syncMutex == null) { + syncMutex = new Object(); + } + } + return syncMutex; } @@ -328,13 +296,7 @@ public class PSurfaceJOGL implements PSurface { // https://github.com/processing/processing/issues/4690 window.setDefaultCloseOperation(WindowClosingProtocol.WindowClosingMode.DO_NOTHING_ON_CLOSE); -// if (displayDevice == null) { -// -// -// } else { -// window = GLWindow.create(displayDevice.getScreen(), pgl.getCaps()); -// } - + // macOS pixel density is handled transparently by the OS windowScaleFactor = (PApplet.platform == PConstants.MACOS) ? 1 : sketch.pixelDensity; @@ -352,46 +314,21 @@ public class PSurfaceJOGL implements PSurface { sketch.displayWidth = screenRect.width; sketch.displayHeight = screenRect.height; - sketchWidth0 = sketch.sketchWidth(); - sketchHeight0 = sketch.sketchHeight(); - - /* - // Trying to fix - // https://github.com/processing/processing/issues/3401 - if (sketch.displayWidth < sketch.width || - sketch.displayHeight < sketch.height) { - int w = sketch.width; - int h = sketch.height; - if (sketch.displayWidth < w) { - w = sketch.displayWidth; - } - if (sketch.displayHeight < h) { - h = sketch.displayHeight; - } -// sketch.setSize(w, h - 22 - 22); -// graphics.setSize(w, h - 22 - 22); - System.err.println("setting width/height to " + w + " " + h); - } - */ + // Sometimes the window manager or OS will resize the window. + // Keep track of the requested width/height to notify the user. + sketchWidthRequested = sketch.sketchWidth(); + sketchHeightRequested = sketch.sketchHeight(); sketchWidth = sketch.sketchWidth(); sketchHeight = sketch.sketchHeight(); -// System.out.println("init: " + sketchWidth + " " + sketchHeight); boolean fullScreen = sketch.sketchFullScreen(); - // Removing the section below because sometimes people want to do the - // full screen size in a window, and it also breaks insideSettings(). - // With 3.x, fullScreen() is so easy, that it's just better that way. + + // Before 3.x, we would set the window to full screen when the requested + // width/height was the size of the screen. But not everyone *wants* that + // to be full screen (they might want to drag the window, or who knows), + // so that code was removed because fullScreen() is easy to use instead. // https://github.com/processing/processing/issues/3545 - /* - // Sketch has already requested to be the same as the screen's - // width and height, so let's roll with full screen mode. - if (screenRect.width == sketchWidth && - screenRect.height == sketchHeight) { - fullScreen = true; - sketch.fullScreen(); - } - */ if (fullScreen || spanDisplays) { sketchWidth = screenRect.width / windowScaleFactor; @@ -420,28 +357,25 @@ public class PSurfaceJOGL implements PSurface { window.setTopLevelSize((int) displayRect.getWidth(), (int) displayRect.getHeight()); } } + + window.setSharedAutoDrawable(sharedDrawable); } protected void initListeners() { - NEWTMouseListener mouseListener = new NEWTMouseListener(); - window.addMouseListener(mouseListener); - NEWTKeyListener keyListener = new NEWTKeyListener(); - window.addKeyListener(keyListener); - NEWTWindowListener winListener = new NEWTWindowListener(); - window.addWindowListener(winListener); - - DrawListener drawlistener = new DrawListener(); - window.addGLEventListener(drawlistener); + window.addMouseListener(new NEWTMouseListener()); + window.addKeyListener(new NEWTKeyListener()); + window.addWindowListener(new NEWTWindowListener()); + window.addGLEventListener(new DrawListener()); } protected void initAnimator() { if (PApplet.platform == PConstants.WINDOWS) { - // Force Windows to keep timer resolution high by - // sleeping for time which is not a multiple of 10 ms. - // See section "Clocks and Timers on Windows": - // https://blogs.oracle.com/dholmes/entry/inside_the_hotspot_vm_clocks + // Force Windows to keep timer resolution high by creating a dummy + // thread that sleeps for a time that is not a multiple of 10 ms. + // See section titled "Clocks and Timers on Windows" in this post: + // https://web.archive.org/web/20160308031939/https://blogs.oracle.com/dholmes/entry/inside_the_hotspot_vm_clocks Thread highResTimerThread = new Thread(() -> { try { Thread.sleep(Long.MAX_VALUE); @@ -452,46 +386,33 @@ public class PSurfaceJOGL implements PSurface { } animator = new FPSAnimator(window, 60); + animators.add(animator); + drawException = null; - animator.setUncaughtExceptionHandler(new GLAnimatorControl.UncaughtExceptionHandler() { - @Override - public void uncaughtException(final GLAnimatorControl animator, - final GLAutoDrawable drawable, - final Throwable cause) { - synchronized (drawExceptionMutex) { - drawException = cause; - drawExceptionMutex.notify(); - } + animator.setUncaughtExceptionHandler((animator, drawable, cause) -> { + synchronized (drawExceptionMutex) { + drawException = cause; + drawExceptionMutex.notify(); } }); - drawExceptionHandler = new Thread(new Runnable() { - public void run() { - synchronized (drawExceptionMutex) { - try { - while (drawException == null) { - drawExceptionMutex.wait(); - } - // System.err.println("Caught exception: " + drawException.getMessage()); - if (drawException != null) { - Throwable cause = drawException.getCause(); - if (cause instanceof ThreadDeath) { - // System.out.println("caught ThreadDeath"); - // throw (ThreadDeath)cause; - } else if (cause instanceof RuntimeException) { - throw (RuntimeException) cause; - } else if (cause instanceof UnsatisfiedLinkError) { - throw new UnsatisfiedLinkError(cause.getMessage()); - } else if (cause == null) { - throw new RuntimeException(drawException.getMessage()); - } else { - throw new RuntimeException(cause); - } - } - } catch (InterruptedException e) { - return; + drawExceptionHandler = new Thread(() -> { + synchronized (drawExceptionMutex) { + try { + while (drawException == null) { + drawExceptionMutex.wait(); } - } + Throwable cause = drawException.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof UnsatisfiedLinkError) { + throw new UnsatisfiedLinkError(cause.getMessage()); + } else if (cause == null) { + throw new RuntimeException(drawException.getMessage()); + } else { + throw new RuntimeException(cause); + } + } catch (InterruptedException ignored) { } } }); drawExceptionHandler.start(); @@ -500,34 +421,19 @@ public class PSurfaceJOGL implements PSurface { @Override public void setTitle(final String title) { - display.getEDTUtil().invoke(false, new Runnable() { - @Override - public void run() { - window.setTitle(title); - } - }); + display.getEDTUtil().invoke(false, () -> window.setTitle(title)); } @Override public void setVisible(final boolean visible) { - display.getEDTUtil().invoke(false, new Runnable() { - @Override - public void run() { - window.setVisible(visible); - } - }); + display.getEDTUtil().invoke(false, () -> window.setVisible(visible)); } @Override public void setResizable(final boolean resizable) { - display.getEDTUtil().invoke(false, new Runnable() { - @Override - public void run() { - window.setResizable(resizable); - } - }); + display.getEDTUtil().invoke(false, () -> window.setResizable(resizable)); } @@ -539,17 +445,12 @@ public class PSurfaceJOGL implements PSurface { @Override public void setAlwaysOnTop(final boolean always) { - display.getEDTUtil().invoke(false, new Runnable() { - @Override - public void run() { - window.setAlwaysOnTop(always); - } - }); + display.getEDTUtil().invoke(false, () -> window.setAlwaysOnTop(always)); } protected void initIcons() { - IOUtil.ClassResources res = null; + IOUtil.ClassResources res; if (PJOGL.icons == null || PJOGL.icons.length == 0) { // Default Processing icons final int[] sizes = { 16, 32, 48, 64, 128, 256, 512 }; @@ -578,7 +479,7 @@ public class PSurfaceJOGL implements PSurface { @SuppressWarnings("resource") private String resourceFilename(String filename) { // The code below comes from PApplet.createInputRaw() with a few adaptations - InputStream stream = null; + InputStream stream; try { // First see if it's in a data folder. This may fail by throwing // a SecurityException. If so, this whole block will be skipped. @@ -605,19 +506,16 @@ public class PSurfaceJOGL implements PSurface { filename + ". Rename the file " + "or change your code."); } - } catch (IOException e) { } + } catch (IOException ignored) { } } stream = new FileInputStream(file); - if (stream != null) { - stream.close(); - return file.getCanonicalPath(); - } + stream.close(); + return file.getCanonicalPath(); // have to break these out because a general Exception might // catch the RuntimeException being thrown above - } catch (IOException ioe) { - } catch (SecurityException se) { } + } catch (IOException | SecurityException ignored) { } ClassLoader cl = sketch.getClass().getClassLoader(); @@ -630,7 +528,7 @@ public class PSurfaceJOGL implements PSurface { // this is an irritation of sun's java plug-in, which will return // a non-null stream for an object that doesn't exist. like all good // things, this is probably introduced in java 1.5. awesome! - // http://dev.processing.org/bugs/show_bug.cgi?id=359 + // https://download.processing.org/bugzilla/359.html if (!cn.equals("sun.plugin.cache.EmptyInputStream")) { stream.close(); return "data/" + filename; @@ -639,7 +537,7 @@ public class PSurfaceJOGL implements PSurface { // When used with an online script, also need to check without the // data folder, in case it's not in a subfolder called 'data'. - // http://dev.processing.org/bugs/show_bug.cgi?id=389 + // https://download.processing.org/bugzilla/389.html stream = cl.getResourceAsStream(filename); if (stream != null) { String cn = stream.getClass().getName(); @@ -648,39 +546,29 @@ public class PSurfaceJOGL implements PSurface { return filename; } } - } catch (IOException e) { } + } catch (IOException ignored) { } try { - // attempt to load from a local file, used when running as - // an application, or as a signed applet - try { // first try to catch any security exceptions - try { - String path = sketch.dataPath(filename); - stream = new FileInputStream(path); - if (stream != null) { - stream.close(); - return path; - } - } catch (IOException e2) { } + // attempt to load from a local file + try { + String path = sketch.dataPath(filename); + stream = new FileInputStream(path); + stream.close(); + return path; + } catch (IOException ignored) { } - try { - String path = sketch.sketchPath(filename); - stream = new FileInputStream(path); - if (stream != null) { - stream.close(); - return path; - } - } catch (Exception e) { } // ignored + try { + String path = sketch.sketchPath(filename); + stream = new FileInputStream(path); + stream.close(); + return path; + } catch (Exception ignored) { } - try { - stream = new FileInputStream(filename); - if (stream != null) { - stream.close(); - return filename; - } - } catch (IOException e1) { } - - } catch (SecurityException se) { } // online, whups + try { + stream = new FileInputStream(filename); + stream.close(); + return filename; + } catch (IOException ignored) { } } catch (Exception e) { //die(e.getMessage(), e); @@ -693,7 +581,6 @@ public class PSurfaceJOGL implements PSurface { @Override public void placeWindow(int[] location, int[] editorLocation) { - if (sketch.sketchFullScreen()) { return; } @@ -704,11 +591,9 @@ public class PSurfaceJOGL implements PSurface { int h = window.getHeight() + window.getInsets().getTotalHeight(); if (location != null) { -// System.err.println("place window at " + location[0] + ", " + location[1]); window.setTopLevelPosition(location[0], location[1]); } else if (editorLocation != null) { -// System.err.println("place window at editor location " + editorLocation[0] + ", " + editorLocation[1]); int locationX = editorLocation[0] - 20; int locationY = editorLocation[1]; @@ -717,22 +602,8 @@ public class PSurfaceJOGL implements PSurface { window.setTopLevelPosition(locationX - w, locationY); } else { // doesn't fit - /* - // if it fits inside the editor window, - // offset slightly from upper lefthand corner - // so that it's plunked inside the text area - locationX = editorLocation[0] + 66; - locationY = editorLocation[1] + 66; - - if ((locationX + w > sketch.displayWidth - 33) || - (locationY + h > sketch.displayHeight - 33)) { - // otherwise center on screen - */ locationX = (sketch.displayWidth - w) / 2; locationY = (sketch.displayHeight - h) / 2; - /* - } - */ window.setTopLevelPosition(locationX, locationY); } } else { // just center on screen @@ -745,7 +616,7 @@ public class PSurfaceJOGL implements PSurface { Point frameLoc = new Point(x, y); if (frameLoc.y < 0) { // Windows actually allows you to place frames where they can't be - // closed. Awesome. http://dev.processing.org/bugs/show_bug.cgi?id=1508 + // closed. Awesome. https://download.processing.org/bugzilla/1508.html window.setTopLevelPosition(frameLoc.x, 30); } } @@ -763,9 +634,11 @@ public class PSurfaceJOGL implements PSurface { } + /* public void setupExternalMessages() { external = true; } + */ public void startThread() { @@ -795,6 +668,10 @@ public class PSurfaceJOGL implements PSurface { drawExceptionHandler = null; } if (animator != null) { + // Stops all other animators to avoid exceptions when closing a window in a multiple window configuration + for (FPSAnimator ani: animators) { + if (ani != animator) ani.stop(); + } return animator.stop(); } else { return false; @@ -812,12 +689,7 @@ public class PSurfaceJOGL implements PSurface { public void setLocation(final int x, final int y) { - display.getEDTUtil().invoke(false, new Runnable() { - @Override - public void run() { - window.setTopLevelPosition(x, y); - } - }); + display.getEDTUtil().invoke(false, () -> window.setTopLevelPosition(x, y)); } @@ -861,11 +733,10 @@ public class PSurfaceJOGL implements PSurface { } - private float getCurrentPixelScale() { + protected float getCurrentPixelScale() { // Even if the graphics are retina, the user might have moved the window // into a non-retina monitor, so we need to check - window.getCurrentSurfaceScale(currentPixelScale); - return currentPixelScale[0]; + return window.getCurrentSurfaceScale(currentPixelScale)[0]; } @@ -912,43 +783,51 @@ public class PSurfaceJOGL implements PSurface { public void requestFocus() { - display.getEDTUtil().invoke(false, new Runnable() { - @Override - public void run() { - window.requestFocus(); - } - }); + display.getEDTUtil().invoke(false, () -> window.requestFocus()); } - class DrawListener implements GLEventListener { + public class DrawListener implements GLEventListener { + private boolean isInit = false; + public void display(GLAutoDrawable drawable) { + if (!isInit) return; + if (display.getEDTUtil().isCurrentThreadEDT()) { - // For some reason, the first two frames of the animator are run on the - // EDT, skipping rendering Processing's frame in that case. + // For some unknown reason, a few frames of the animator run on + // the EDT. For those, we just skip this draw call to avoid badness. + // See below for explanation of this two line hack. + pgl.beginRender(); + pgl.endRender(sketch.sketchWindowColor()); return; } if (sketch.frameCount == 0) { - if (sketchWidth < sketchWidth0 || sketchHeight < sketchHeight0) { - PGraphics.showWarning("The sketch has been automatically resized to fit the screen resolution"); + if (sketchWidth != sketchWidthRequested || sketchHeight != sketchHeightRequested) { + if (!sketch.sketchFullScreen()) { + // don't show the message when using fullScreen() + PGraphics.showWarning("The sketch has been resized from " + + "%d\u2715%d to %d\u2715%d by the window manager.", + sketchWidthRequested, sketchHeightRequested, sketchWidth, sketchHeight); + } } -// System.out.println("display: " + window.getWidth() + " "+ window.getHeight() + " - " + sketchWidth + " " + sketchHeight); requestFocus(); } if (!sketch.finished) { - pgl.getGL(drawable); - int pframeCount = sketch.frameCount; - sketch.handleDraw(); - if (pframeCount == sketch.frameCount || sketch.finished) { - // This hack allows the FBO layer to be swapped normally even if - // the sketch is no looping or finished because it does not call draw(), - // otherwise background artifacts may occur (depending on the hardware/drivers). - pgl.beginRender(); - pgl.endRender(sketch.sketchWindowColor()); + synchronized (getSyncMutex(drawable)) { + pgl.getGL(drawable); + int prevFrameCount = sketch.frameCount; + sketch.handleDraw(); + if (prevFrameCount == sketch.frameCount || sketch.finished) { + // This hack allows the FBO layer to be swapped normally even if + // the sketch is no looping or finished because it does not call draw(), + // otherwise background artifacts may occur (depending on the hardware/drivers). + pgl.beginRender(); + pgl.endRender(sketch.sketchWindowColor()); + } + PGraphicsOpenGL.completeFinishedPixelTransfers(); } - PGraphicsOpenGL.completeFinishedPixelTransfers(); } if (sketch.exitCalled()) { @@ -960,28 +839,41 @@ public class PSurfaceJOGL implements PSurface { } public void dispose(GLAutoDrawable drawable) { -// sketch.dispose(); + // do nothing, sketch.dispose() will be called with exitCalled() } public void init(GLAutoDrawable drawable) { - pgl.getGL(drawable); - pgl.init(drawable); - sketch.start(); + if (display.getEDTUtil().isCurrentThreadEDT()) { + return; + } - int c = graphics.backgroundColor; - pgl.clearColor(((c >> 16) & 0xff) / 255f, - ((c >> 8) & 0xff) / 255f, - ((c >> 0) & 0xff) / 255f, - ((c >> 24) & 0xff) / 255f); - pgl.clear(PGL.COLOR_BUFFER_BIT); + synchronized (getSyncMutex(drawable)) { + pgl.init(drawable); + sketch.start(); + + int c = graphics.backgroundColor; + pgl.clearColor(((c >> 16) & 0xff) / 255f, + ((c >> 8) & 0xff) / 255f, + (c & 0xff) / 255f, + ((c >> 24) & 0xff) / 255f); + pgl.clear(PGL.COLOR_BUFFER_BIT); + isInit = true; + } } public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) { - pgl.resetFBOLayer(); - pgl.getGL(drawable); - float scale = PApplet.platform == PConstants.MACOS ? - getCurrentPixelScale() : getPixelScale(); - setSize((int) (w / scale), (int) (h / scale)); + if (!isInit) return; + + if (display.getEDTUtil().isCurrentThreadEDT()) { + return; + } + + synchronized (getSyncMutex(drawable)) { + pgl.resetFBOLayer(); + float scale = PApplet.platform == PConstants.MACOS ? + getCurrentPixelScale() : getPixelScale(); + setSize((int) (w / scale), (int) (h / scale)); + } } } @@ -1014,9 +906,12 @@ public class PSurfaceJOGL implements PSurface { @Override public void windowMoved(com.jogamp.newt.event.WindowEvent arg0) { + /* if (external) { sketch.frameMoved(window.getX(), window.getY()); } + */ + sketch.postWindowMoved(window.getX(), window.getY()); } @Override @@ -1025,6 +920,7 @@ public class PSurfaceJOGL implements PSurface { @Override public void windowResized(com.jogamp.newt.event.WindowEvent arg0) { + sketch.postWindowResized(window.getWidth(), window.getHeight()); } } @@ -1117,7 +1013,7 @@ public class PSurfaceJOGL implements PSurface { break; } - int peCount = 0; + int peCount; if (peAction == MouseEvent.WHEEL) { // Invert wheel rotation count so it matches JAVA2D's // https://github.com/processing/processing/issues/3840 @@ -1141,6 +1037,7 @@ public class PSurfaceJOGL implements PSurface { if (pgl.presentMode()) { mx -= (int)pgl.presentX; my -= (int)pgl.presentY; + //noinspection IntegerDivisionInFloatingPointContext if (peAction == KeyEvent.RELEASE && pgl.insideStopButton(sx, sy - screenRect.height / windowScaleFactor)) { sketch.exit(); @@ -1173,7 +1070,7 @@ public class PSurfaceJOGL implements PSurface { short code = nativeEvent.getKeyCode(); char keyChar; int keyCode; - if (isPCodedKey(code)) { + if (isPCodedKey(code, nativeEvent.isPrintableKey())) { keyCode = mapToPConst(code); keyChar = PConstants.CODED; } else if (isHackyKey(code)) { @@ -1201,7 +1098,7 @@ public class PSurfaceJOGL implements PSurface { sketch.postEvent(ke); - if (!isPCodedKey(code) && !isHackyKey(code)) { + if (!isPCodedKey(code, nativeEvent.isPrintableKey()) && !isHackyKey(code)) { if (peAction == KeyEvent.PRESS) { // Create key typed event // TODO: combine dead keys with the following key @@ -1217,7 +1114,7 @@ public class PSurfaceJOGL implements PSurface { } - private static boolean isPCodedKey(short code) { + private static boolean isPCodedKey(short code, boolean printable) { return code == com.jogamp.newt.event.KeyEvent.VK_UP || code == com.jogamp.newt.event.KeyEvent.VK_DOWN || code == com.jogamp.newt.event.KeyEvent.VK_LEFT || @@ -1225,7 +1122,8 @@ public class PSurfaceJOGL implements PSurface { code == com.jogamp.newt.event.KeyEvent.VK_ALT || code == com.jogamp.newt.event.KeyEvent.VK_CONTROL || code == com.jogamp.newt.event.KeyEvent.VK_SHIFT || - code == com.jogamp.newt.event.KeyEvent.VK_WINDOWS; + code == com.jogamp.newt.event.KeyEvent.VK_WINDOWS || + (!printable && !isHackyKey(code)); } @@ -1233,6 +1131,7 @@ public class PSurfaceJOGL implements PSurface { // Relevant discussion and links here: // http://forum.jogamp.org/Newt-wrong-keycode-for-key-td4033690.html#a4033697 // (I don't think this is a complete solution). + @SuppressWarnings("SuspiciousNameCombination") private static int mapToPConst(short code) { switch (code) { case com.jogamp.newt.event.KeyEvent.VK_UP: @@ -1289,6 +1188,8 @@ public class PSurfaceJOGL implements PSurface { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + // CURSORS + class CursorInfo { PImage image; @@ -1326,22 +1227,27 @@ public class PSurfaceJOGL implements PSurface { if (cursor == null) { String name = cursorNames.get(kind); if (name != null) { - ImageIcon icon = - new ImageIcon(getClass().getResource("cursors/" + name + ".png")); - PImage img = new PImageAWT(icon.getImage()); - // Most cursors just use the center as the hotspot... - int x = img.width / 2; - int y = img.height / 2; - // ...others are more specific - if (kind == PConstants.ARROW) { - x = 10; y = 7; - } else if (kind == PConstants.HAND) { - x = 12; y = 8; - } else if (kind == PConstants.TEXT) { - x = 16; y = 22; + URL url = getClass().getResource("cursors/" + name + ".png"); + if (url != null) { + ImageIcon icon = new ImageIcon(url); + PImage img = new PImageAWT(icon.getImage()); + // Most cursors just use the center as the hotspot... + int x = img.width / 2; + int y = img.height / 2; + // ...others are more specific + if (kind == PConstants.ARROW) { + x = 10; + y = 7; + } else if (kind == PConstants.HAND) { + x = 12; + y = 8; + } else if (kind == PConstants.TEXT) { + x = 16; + y = 22; + } + cursor = new CursorInfo(img, x, y); + cursors.put(kind, cursor); } - cursor = new CursorInfo(img, x, y); - cursors.put(kind, cursor); } } if (cursor != null) { @@ -1353,47 +1259,105 @@ public class PSurfaceJOGL implements PSurface { public void setCursor(PImage image, int hotspotX, int hotspotY) { - Display disp = window.getScreen().getDisplay(); - BufferedImage bimg = (BufferedImage)image.getNative(); - DataBufferInt dbuf = (DataBufferInt)bimg.getData().getDataBuffer(); - int[] ipix = dbuf.getData(); - ByteBuffer pixels = ByteBuffer.allocate(ipix.length * 4); - pixels.asIntBuffer().put(ipix); + // TODO why is this first getting 'display' from 'window' instead of using + // this.display which is already set? In addition, this.display is + // even used to call getEDTUtil() down below. [fry 211123] + Display display = window.getScreen().getDisplay(); + BufferedImage img = (BufferedImage) image.getNative(); + int[] imagePixels = + ((DataBufferInt) img.getData().getDataBuffer()).getData(); + ByteBuffer pixels = ByteBuffer.allocate(imagePixels.length * 4); + pixels.asIntBuffer().put(imagePixels); PixelFormat format = PixelFormat.ARGB8888; - final Dimension size = new Dimension(bimg.getWidth(), bimg.getHeight()); - PixelRectangle pixelrect = new PixelRectangle.GenericPixelRect(format, size, 0, false, pixels); - final PointerIcon pi = disp.createPointerIcon(pixelrect, hotspotX, hotspotY); - display.getEDTUtil().invoke(false, new Runnable() { - @Override - public void run() { - window.setPointerVisible(true); - window.setPointerIcon(pi); - } + final Dimension size = new Dimension(img.getWidth(), img.getHeight()); + PixelRectangle rect = + new PixelRectangle.GenericPixelRect(format, size, 0, false, pixels); + final PointerIcon pi = display.createPointerIcon(rect, hotspotX, hotspotY); + this.display.getEDTUtil().invoke(false, () -> { + window.setPointerVisible(true); + window.setPointerIcon(pi); }); } public void showCursor() { - display.getEDTUtil().invoke(false, new Runnable() { - @Override - public void run() { - window.setPointerVisible(true); - } - }); + display.getEDTUtil().invoke(false, () -> window.setPointerVisible(true)); } public void hideCursor() { - display.getEDTUtil().invoke(false, new Runnable() { - @Override - public void run() { - window.setPointerVisible(false); - } + display.getEDTUtil().invoke(false, () -> window.setPointerVisible(false)); + } + + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + // TOOLKIT + + + @Override + public PImage loadImage(String path, Object... args) { + // Would like to rewrite this for 4.x, but the strategies for loading + // image data with GL seem unnecessarily complex, and not 100% necessary: + // we haven't had to remove as much AWT as expected. [fry 211123] + return ShimAWT.loadImage(sketch, path, args); + } + + + @Override + public boolean openLink(String url) { + return ShimAWT.openLink(url); + } + + + @Override + public void selectInput(String prompt, String callbackMethod, + File file, Object callbackObject) { + EventQueue.invokeLater(() -> { + // https://github.com/processing/processing/issues/3831 + boolean hide = (sketch != null) && + (PApplet.platform == PConstants.WINDOWS); + if (hide) setVisible(false); + + ShimAWT.selectImpl(prompt, callbackMethod, file, + callbackObject, null, FileDialog.LOAD); + + if (hide) setVisible(true); }); } - public boolean openLink(String url) { - return ShimAWT.openLink(url); + @Override + public void selectOutput(String prompt, String callbackMethod, + File file, Object callbackObject) { + EventQueue.invokeLater(() -> { + // https://github.com/processing/processing/issues/3831 + boolean hide = (sketch != null) && + (PApplet.platform == PConstants.WINDOWS); + if (hide) setVisible(false); + + ShimAWT.selectImpl(prompt, callbackMethod, file, + callbackObject, null, FileDialog.SAVE); + + if (hide) setVisible(true); + }); + } + + + @Override + public void selectFolder(String prompt, String callbackMethod, + File file, Object callbackObject) { + EventQueue.invokeLater(() -> { + // https://github.com/processing/processing/issues/3831 + boolean hide = (sketch != null) && + (PApplet.platform == PConstants.WINDOWS); + if (hide) setVisible(false); + + ShimAWT.selectFolderImpl(prompt, callbackMethod, file, + callbackObject, null); + + if (hide) setVisible(true); + }); } } diff --git a/core/src/processing/opengl/Texture.java b/core/src/processing/opengl/Texture.java index b6bf7d2bf..ecb67681b 100644 --- a/core/src/processing/opengl/Texture.java +++ b/core/src/processing/opengl/Texture.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -815,7 +815,7 @@ public class Texture implements PConstants { public void copyBufferFromSource(Object natRef, ByteBuffer byteBuf, int w, int h) { if (bufferCache == null) { - bufferCache = new LinkedList(); + bufferCache = new LinkedList<>(); } if (bufferCache.size() + 1 <= MAX_BUFFER_CACHE_SIZE) { @@ -872,7 +872,7 @@ public class Texture implements PConstants { // renderer draws the texture, and hence put the pixels put of sync, we // simply empty the cache. if (usedBuffers == null) { - usedBuffers = new LinkedList(); + usedBuffers = new LinkedList<>(); } while (0 < bufferCache.size()) { data = bufferCache.remove(0); @@ -911,7 +911,7 @@ public class Texture implements PConstants { // Putting the buffer in the used buffers list to dispose at the end of // draw. if (usedBuffers == null) { - usedBuffers = new LinkedList(); + usedBuffers = new LinkedList<>(); } usedBuffers.add(data); diff --git a/core/src/processing/opengl/VertexBuffer.java b/core/src/processing/opengl/VertexBuffer.java index 6315ba067..d29a907b5 100644 --- a/core/src/processing/opengl/VertexBuffer.java +++ b/core/src/processing/opengl/VertexBuffer.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -38,14 +38,14 @@ public class VertexBuffer { boolean index; protected PGL pgl; // The interface between Processing and OpenGL. - protected int context; // The context that created this texture. + protected int context; // The context that created this resource. private GLResourceVertexBuffer glres; - VertexBuffer(PGraphicsOpenGL pg, int target, int ncoords, int esize) { - this(pg, target, ncoords, esize, false); + VertexBuffer(PGraphicsOpenGL pg, int target, int ncoords, int esize, int usage) { + this(pg, target, ncoords, esize, usage, false); } - VertexBuffer(PGraphicsOpenGL pg, int target, int ncoords, int esize, boolean index) { + VertexBuffer(PGraphicsOpenGL pg, int target, int ncoords, int esize, int usage, boolean index) { pgl = pg.pgl; context = pgl.createEmptyContext(); @@ -54,7 +54,7 @@ public class VertexBuffer { this.elementSize = esize; this.index = index; create(); - init(); + init(usage); } protected void create() { @@ -62,11 +62,11 @@ public class VertexBuffer { glres = new GLResourceVertexBuffer(this); } - protected void init() { + protected void init(int usage) { int size = index ? ncoords * INIT_INDEX_BUFFER_SIZE * elementSize : ncoords * INIT_VERTEX_BUFFER_SIZE * elementSize; pgl.bindBuffer(target, glId); - pgl.bufferData(target, size, null, PGL.STATIC_DRAW); + pgl.bufferData(target, size, null, usage); } protected void dispose() { diff --git a/core/src/processing/opengl/shaders/ColorFrag.glsl b/core/src/processing/opengl/shaders/ColorFrag.glsl index 59adfdad3..1a9c1f6f3 100644 --- a/core/src/processing/opengl/shaders/ColorFrag.glsl +++ b/core/src/processing/opengl/shaders/ColorFrag.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 diff --git a/core/src/processing/opengl/shaders/ColorVert.glsl b/core/src/processing/opengl/shaders/ColorVert.glsl index 6e8820c22..aaab987c0 100644 --- a/core/src/processing/opengl/shaders/ColorVert.glsl +++ b/core/src/processing/opengl/shaders/ColorVert.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -31,4 +31,4 @@ void main() { gl_Position = transformMatrix * position; vertColor = color; -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/shaders/LightFrag.glsl b/core/src/processing/opengl/shaders/LightFrag.glsl index b9cd44726..4f775befa 100644 --- a/core/src/processing/opengl/shaders/LightFrag.glsl +++ b/core/src/processing/opengl/shaders/LightFrag.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -30,4 +30,4 @@ varying vec4 backVertColor; void main() { gl_FragColor = gl_FrontFacing ? vertColor : backVertColor; -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/shaders/LightVert-brcm.glsl b/core/src/processing/opengl/shaders/LightVert-brcm.glsl index b96caa4b3..f518ef90a 100644 --- a/core/src/processing/opengl/shaders/LightVert-brcm.glsl +++ b/core/src/processing/opengl/shaders/LightVert-brcm.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -151,4 +151,4 @@ void main() { vec4(totalBackDiffuse, 1) * color + vec4(totalBackSpecular, 0) * specular + vec4(emissive.rgb, 0); -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/shaders/LightVert-vc4.glsl b/core/src/processing/opengl/shaders/LightVert-vc4.glsl index ba7972676..b3dee92c9 100644 --- a/core/src/processing/opengl/shaders/LightVert-vc4.glsl +++ b/core/src/processing/opengl/shaders/LightVert-vc4.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -151,4 +151,4 @@ void main() { vec4(totalBackDiffuse, 1) * color + vec4(totalBackSpecular, 0) * specular + vec4(emissive.rgb, 0); -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/shaders/LightVert.glsl b/core/src/processing/opengl/shaders/LightVert.glsl index 470fd4b23..409e481e6 100644 --- a/core/src/processing/opengl/shaders/LightVert.glsl +++ b/core/src/processing/opengl/shaders/LightVert.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -148,4 +148,4 @@ void main() { vec4(totalBackDiffuse, 1) * color + vec4(totalBackSpecular, 0) * specular + vec4(emissive.rgb, 0); -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/shaders/LineFrag.glsl b/core/src/processing/opengl/shaders/LineFrag.glsl index 09fffcb85..7e4997251 100644 --- a/core/src/processing/opengl/shaders/LineFrag.glsl +++ b/core/src/processing/opengl/shaders/LineFrag.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -29,4 +29,4 @@ varying vec4 vertColor; void main() { gl_FragColor = vertColor; -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/shaders/LineVert.glsl b/core/src/processing/opengl/shaders/LineVert.glsl index 50946ba5a..3ae4e9923 100644 --- a/core/src/processing/opengl/shaders/LineVert.glsl +++ b/core/src/processing/opengl/shaders/LineVert.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 diff --git a/core/src/processing/opengl/shaders/MaskFrag.glsl b/core/src/processing/opengl/shaders/MaskFrag.glsl index 74fad83a9..1b3e09a61 100644 --- a/core/src/processing/opengl/shaders/MaskFrag.glsl +++ b/core/src/processing/opengl/shaders/MaskFrag.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -37,4 +37,4 @@ void main() { vec3 maskColor = texture2D(mask, vertTexCoord.st).rgb; float luminance = dot(maskColor, vec3(0.2126, 0.7152, 0.0722)); gl_FragColor = vec4(texColor, luminance); -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/shaders/PointFrag.glsl b/core/src/processing/opengl/shaders/PointFrag.glsl index 59adfdad3..e00c49e7a 100644 --- a/core/src/processing/opengl/shaders/PointFrag.glsl +++ b/core/src/processing/opengl/shaders/PointFrag.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -29,4 +29,4 @@ varying vec4 vertColor; void main() { gl_FragColor = vertColor; -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/shaders/PointVert.glsl b/core/src/processing/opengl/shaders/PointVert.glsl index 8249e9076..32fc0122c 100644 --- a/core/src/processing/opengl/shaders/PointVert.glsl +++ b/core/src/processing/opengl/shaders/PointVert.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 diff --git a/core/src/processing/opengl/shaders/TexFrag.glsl b/core/src/processing/opengl/shaders/TexFrag.glsl index 5e236ee91..ef54c3641 100644 --- a/core/src/processing/opengl/shaders/TexFrag.glsl +++ b/core/src/processing/opengl/shaders/TexFrag.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -34,4 +34,4 @@ varying vec4 vertTexCoord; void main() { gl_FragColor = texture2D(texture, vertTexCoord.st) * vertColor; -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/shaders/TexLightFrag.glsl b/core/src/processing/opengl/shaders/TexLightFrag.glsl index 1e9cc7e19..f30d28b93 100644 --- a/core/src/processing/opengl/shaders/TexLightFrag.glsl +++ b/core/src/processing/opengl/shaders/TexLightFrag.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -34,4 +34,4 @@ varying vec4 vertTexCoord; void main() { gl_FragColor = texture2D(texture, vertTexCoord.st) * (gl_FrontFacing ? vertColor : backVertColor); -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/shaders/TexLightVert-brcm.glsl b/core/src/processing/opengl/shaders/TexLightVert-brcm.glsl index 51e88ab05..24d6711f1 100644 --- a/core/src/processing/opengl/shaders/TexLightVert-brcm.glsl +++ b/core/src/processing/opengl/shaders/TexLightVert-brcm.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 diff --git a/core/src/processing/opengl/shaders/TexLightVert-vc4.glsl b/core/src/processing/opengl/shaders/TexLightVert-vc4.glsl index f54277e81..be81952e4 100644 --- a/core/src/processing/opengl/shaders/TexLightVert-vc4.glsl +++ b/core/src/processing/opengl/shaders/TexLightVert-vc4.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 diff --git a/core/src/processing/opengl/shaders/TexLightVert.glsl b/core/src/processing/opengl/shaders/TexLightVert.glsl index 6ef7d4bb7..17ed9cb6d 100644 --- a/core/src/processing/opengl/shaders/TexLightVert.glsl +++ b/core/src/processing/opengl/shaders/TexLightVert.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 diff --git a/core/src/processing/opengl/shaders/TexVert.glsl b/core/src/processing/opengl/shaders/TexVert.glsl index 3bc62e32f..6c5280fc1 100644 --- a/core/src/processing/opengl/shaders/TexVert.glsl +++ b/core/src/processing/opengl/shaders/TexVert.glsl @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 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 @@ -35,4 +35,4 @@ void main() { vertColor = color; vertTexCoord = texMatrix * vec4(texCoord, 1.0, 1.0); -} \ No newline at end of file +} diff --git a/core/todo.txt b/core/todo.txt index cc909d70e..37cc53755 100644 --- a/core/todo.txt +++ b/core/todo.txt @@ -1,27 +1,19 @@ -1277 (4.0b2) -X fix apparent problem in PShape with QUADRATIC_VERTEX (wrong vertex index) +1287 (4.0.2) +X Updating PApplet to use Java 17 (switch statements, etc) -contribs -X Remove two redundant variable assignments in PShader -X https://github.com/processing/processing4/pull/172 +_ concurrent StringDict et al +_ why no concurrent TreemMap? https://stackoverflow.com/a/17656453 +_ https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentSkipListMap.html - -_ put opengl libs for core into platform-specific subfolders? -_ may also be useful for Windows antivirus slowdowns: -_ https://github.com/processing/processing/issues/4783#issuecomment-269328168 - -_ setting surface size needs to happen outside draw() -_ surface.setSize() sadness etc -_ https://github.com/processing/processing4/issues/162 -_ https://github.com/processing/processing4/issues/186 -_ https://github.com/processing/processing/issues/4129 +_ how to handle fallback fonts +_ could implement this for the default, but also, SansSerif sucks +_ https://stackoverflow.com/a/9482676 +_ in addition, it would need to be implemented for textWidth() _ update P2D reference to make clear about drawing order and quality _ https://github.com/processing/processing/issues/5880 -_ why does GL flash red when resizing? - load/save image _ https://github.com/processing/processing4/wiki/Exorcising-AWT @@ -32,49 +24,30 @@ _ https://github.com/processing/processing4/pull/97 _ https://github.com/processing/processing4/pull/98 -noted by Casey -_ Sketches on Windows/Linux don't take UI sizing into account -_ https://github.com/processing/processing/issues/4894 -_ https://github.com/processing/processing/issues/4895 -_ https://github.com/processing/processing/issues/4897 - - api changes -_ static versions of selectInput/selectOutput/selectFolder removed from PApplet -_ java.awt.Frame object "frame" removed from PApplet (been warning since 2015) -X https://github.com/processing/processing4/issues/54 -_ protected PImage.checkAlpha() now public -_ all AWT calls have been moved out of PImage -_ this may be a problem for anything that was relying on those internals -_ removed MouseEvent.getClickCount() and MouseEvent.getAmount() -_ these had been deprecated, not clear they were used anywhere _ add callbacks to requestImage() and others +_ these would be a lot easier with newer Java syntax +_ (and also familiar to folks who have spent time with JS) _ new FloatList(float...) api todo _ Surface not available inside handleSettings() _ therefore impossible to get displays, displayWidth/Height, and pixelDensity -_ should surface be set on command line? -_ or can we just require that size() goes first, and inits surface *type* w/o sizes +_ (in a non-AWT way on a system that cannot use AWT) +_ does surface have to be passed on the command line? +_ can we just require that size() goes first +_ and have it set surface *type* w/o actually setting up the size info yet _ or even that it inits a surface-specific class for getting that info _ implement selectInput/Output/Folder methods in PSurfaceJOGL -_ implement selectInput/Output/Folder methods in PSurfaceFX -_ implement openLink() in PSurfaceFX _ 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 solution for frame/surface methods -_ https://github.com/processing/processing4/issues/53 _ Remove frame from PApplet _ https://github.com/processing/processing4/issues/54 _ Remove all usage of AWT from PApplet @@ -83,11 +56,12 @@ _ https://github.com/codeanticode/processing-lwjgl/wiki#making-awt-optional-in _ https://github.com/processing/processing4/issues/57 _ move loadImage() into surface _ move AWT code out of PImage, and into its own PImageJava2D class -_ how to handle with JavaFX? _ https://github.com/processing/processing4/issues/56 high-ish +_ Sort out display() being sometimes called from the EDT inside OpenGL +_ https://github.com/processing/processing4/issues/385 _ add separator option to loadTable() _ https://github.com/processing/processing/issues/5068 _ make setting the window icon automatic, based on files in local dirs @@ -96,9 +70,6 @@ X https://github.com/processing/processing/pull/5202 _ need to make this work behind the scenes instead _ create icon.png or have an 'icons' folder with multiple sizes _ don't override the window icon w/ p5 logo if already set -_ NullPointerException at java.awt.Window.init(Window.java:497) when using Airplay -_ https://github.com/processing/processing/issues/5620 -_ try to catch the NPE and warn the user about what's happening _ requestSize() and xxxxTitle() (to diminish use of 'surface') _ mostly held up by cross-renderer inconsistency with these _ textAlign(CENTER) and pixelDensity(2) aligning incorrectly with Java2D @@ -112,40 +83,21 @@ _ y coords on macOS seem to be one pixel off retina/hi-dpi/sizing -_ notes from jetbrains -_ https://intellij-support.jetbrains.com/hc/en-us/articles/360007994999-HiDPI-configuration -_ implement sketch scaling into PApplet -_ https://github.com/processing/processing/issues/4897 -_ Sketches on Windows don't take UI sizing into account -_ https://github.com/processing/processing/issues/4894 _ Sketches on Linux don't take UI scaling into account -_ https://github.com/processing/processing/issues/4895 -_ gohai says "xrdb -query" or "xdpyinfo" might work +_ gohai mentioned "xrdb -query" or "xdpyinfo" might work +_ https://github.com/processing/processing4/issues/407 +_ formerly at +_ https://github.com/processing/processing/issues/4895 +_ native code for checking the system dpi +_ https://github.com/processing/processing/issues/5758 _ should fullScreen() set width and height to displayWidth/Height _ or is that being set/unset used for any state info? _ present window draws in stages (OS X) -_ crash on startup when "Mirror Displays" selected (cantfix?) -_ suspect that this is a specific chipset since Oracle didn't reproduce -_ AMD Radeon HD 6770M was in the Oracle bug report -_ https://github.com/processing/processing/issues/2186 -_ https://bugs.openjdk.java.net/browse/JDK-8027391 -_ test with JG's 13" retina laptop - - -https://bkaradzic.github.io/bgfx/ -https://bkaradzic.github.io/bgfx/examples.html possible additions for 4.x? -_ Tint on FX2D now works properly, and improved JavaFX/Scenebuilder integration -_ https://github.com/processing/processing/pull/6051 -_ make offscreen PGraphicsFX2D work -_ https://github.com/processing/processing/issues/4638 -_ https://github.com/processing/processing/pull/4698 _ Add support for floating point textures to OpenGL (using a hint?) _ https://github.com/processing/processing/issues/3321 -_ Friendly Names for new Sketches (includes UI for switching it back) -_ https://github.com/processing/processing/pull/6048 misc @@ -214,7 +166,6 @@ _ decision: go with what looks like javascript/ios _ touchEvent(), gestureEvent()? _ touch event doesn't make sense for mouseMove on desktop, hover on Android _ probably go with pointer: more universal for touch/mouse -_ size() function that scales to screen, keeps aspect, re-scales mouse coords discussion/decisions @@ -269,7 +220,7 @@ _ idea: set frameCount to -1 when setup not run yet? _ then set frameCount to 0 when setup() starts? _ need to clean up the hints in the reference/source _ exactly how should pixel filling work with single pixel strokes? -_ http://dev.processing.org/bugs/show_bug.cgi?id=1025 (no gcode) +_ https://download.processing.org/bugzilla/1025.html (no gcode) _ y2 position of rectangles not same as y2 position of lines _ happens when the rectangle is flipped on the x or y axis _ probably a hack that draws the "last" point differently @@ -303,7 +254,7 @@ _ pdf.textMode(SHAPE) _ also set the font *after* the record has started _ maybe should instead make textMode(SHAPE) the norm? _ and people can change it to textMode(MODEL) if they want? -_ http://dev.processing.org/bugs/show_bug.cgi?id=1535 (no gcode) +_ https://download.processing.org/bugzilla/1535.html (no gcode) _ explain the new PGL interface _ decide how disconnectEvent should actually be handled (and name?) _ was disconnect always there? @@ -532,6 +483,8 @@ _ for PShape, need to be able to set the origin (flash people) CORE / PShapeSVG +_ loadShape() problems with SVGs that have . starting a number in a path +_ https://github.com/processing/processing4/issues/518 _ implement support for SVG gradients from Inkscape _ https://github.com/processing/processing/issues/1180 _ need to handle Environment not going to the right place +X Help > Libraries Reference > submenu items don't work +X same for Tools reference +X https://github.com/processing/processing/issues/5839 +X replace build/shared/lib/theme.txt with final kyanite.txt + +naming +X Friendly Names for new Sketches (includes UI for switching it back) +X https://github.com/processing/processing/issues/6045 +X https://github.com/processing/processing/pull/6048 +X https://github.com/processing/processing4/pull/144 +X "friendly" naming for sketches +X master list of words +X https://github.com/glitchdotcom/friendly-words/tree/master/words +X exact sources +X https://github.com/glitchdotcom/friendly-words/blob/master/words/predicates.txt +X https://github.com/glitchdotcom/friendly-words/blob/master/words/objects.txt +X how friendly names are used in the p5.js web editor +X https://github.com/processing/p5.js-web-editor/blob/develop/client/utils/generateRandomName.js +X p5.js uses predicate followed by an object +X add other naming options (cooking and classic) and prefs +X also support additional naming.json file in sketchbook folder +X add "Cosmos" name set + +bugs +X re-post bugzilla entries at https://download.processing.org/bugzilla/ +X create an index.html page for easier scraping +X replace dev.processing.org bug numbers +X http://dev.processing.org/bugs/show_bug.cgi?id=1188 +X with http://processing.org/bugs/bugzilla/1188.html +X or better yet, https://download.processing.org/bugzilla/1188.html +X replace code.google.com URLs with Github URLs (numbers are sorta in sync) +X not bothering with todo.txt and done.txt since it's historical +X many of them include both, and it's easy enough to search if it's of interest +X updated all that are still in the source and the active todo.txt files + +fonts +X can't ship before ui.font and language bits sorted out +X otherwise the override to use Source Sans Pro will hose other languages +X make note of language change (Greek also removed) +X sort out ui.font plus the other fonts inside theme.txt +X Toolkit.getSansFont() will use the internal ProcessingSansPro font +X which means it gets used inside ManagerFrame, +X and prevents the theme from updating the font +X while that font could exist in theme.txt, +X getSansFont() also handles the language fallback version +X that pref should come from the translations: i.e. an override setting +X decision: set the default sans, bold, and mono font in language file +X can be overridden in preferences.txt +o specify ui.sans.plain, ui.sans.bold, ui.sans.mono in theme.txt +o need a separate identifier for the builtin sans (now called processing.sans) +o and when theme wants to ask for a sans font +X fix fonts in the welcome page +X dialog formatting fixes +X move all (formerly Lucida) dialog formatting into Messages +X currently a couple other classes that use it +/ create a version that works nicely with FlatLaf +X implemented in one place; still needs to just be redone +X two tier dialog box (defaulting back to Lucida) +X font for stack trace dialogs is too small (and wrong) +X dialog box with stack trace (font is too small) +X actually fix the stack trace dialog with 12pt font size change +X text gutter doesn't seem to be hidpi +X or is it b/c screen not quite 2x? (nope) +X try to tweak the line numbers in the gutter a bit +X finally found the problem: Source Code Pro not hinting below 12pt (!?#$@(*) +X change to 12pt, add alpha params to theme.txt +X editor.gutter.linehighlight.color -> editor.gutter.highlight.color +X # transparency (0..100) for line numbers in gutter +X editor.gutter.text.active.alpha = 70 +X # transparency for lines not currently in use +X editor.gutter.text.inactive.alpha = 30 + +export application +X fix Export to Application on macOS with Apple Silicon +X gets rid of "Unable to load Java Runtime Environment" +X update appbundler main.m through https://github.com/TheInfiniteKind/appbundler/commit/5946207c9e29ab85887e94d5651b329e4669e2d6 +X rewrite variable substitution for Info.plist.tmpl +X cleans up readability +X would have broken w/ anything on the same line +X specify JVM version in exported applications +X appbundler seems to prefer Java 1.8 if installed +X specifying correct version for both Processing.app and Export to Application + +contrib +X add flatlaf.jar to the Windows config.xml +X https://github.com/processing/processing4/pull/498 +X Fix parsing of java error messages containing ":" +X https://github.com/processing/processing4/issues/492 +X https://github.com/processing/processing4/pull/493 +X Export Application gives java.lang.ClassNotFoundException: --full-screen error +X https://github.com/processing/processing4/issues/488 +X https://github.com/processing/processing4/pull/502 +X IDE treats {} brackets as code when they're inside a String, char or comment +X https://github.com/processing/processing4/issues/444 +X https://github.com/processing/processing4/pull/504 +X Add Java arguments to enable Anti-Aliasing in bash script +X anti-aliasing issues under KDE, does the cmd line arg break anything? +X https://docs.oracle.com/javase/7/docs/technotes/guides/2d/flags.html#aaFonts +X https://github.com/processing/processing4/pull/513 + +design +X color updates based on changes from Paul (220426) +o icons for the console items +X console.svg and error.svg already done +X console scroll bar colors +X https://github.com/processing/processing4/issues/265 +X remove the 2 pixel line from the tab bar in the footer +X also round both edges of the editor footer tabs +X because the deselected tab no longer has an outline +X look and feel plus fonts +X trying out https://www.formdev.com/flatlaf/ to clean things up +X using Source Sans Pro as interface font +X rewrite language selector to use fallback fonts +X tweaks to the splash/about screen from Paul +X Color Selector buttons misaligned +X redo layout for FlatLaf, also tweak further to remove some of the quirkiness +X Export to Application fonts are too tiny +X menu background colors +X better default fonts for Swing; argh +X file an issue with the images +X https://www.pushing-pixels.org/2017/01/17/using-san-francisco-font-in-swing-applications-on-a-mac.html +o Space Grotesk and Mono? +o https://fonts.google.com/specimen/Space+Mono +o https://fonts.google.com/specimen/Space+Grotesk +X replace foundation icon png that had a random black shape +X a few size/proportion tweaks from Paul +X visual artifacts on Linux with new UI +X remove editor.laf preference because it conflicts with FlatLaf +X menu crustiness, console background color not getting set, others? +X need to check on an actual Linux device, not a VM +X this was caused by Nimbus interactions with FlatLaf +X command key symbol missing in pop up menus +X overall layout/spacing/proportion +X icons for debug toolbar (VariableInspector.java) +X replace variables-1x and -2x with separate SVG files in debug +X implement 2x versions of the icons for the debugger window/variable inspector +X https://github.com/processing/processing/issues/3921 +X visual fixes for tab sizes, etc (with Paul) +X weirdness with gaps in tabs (editor too big, manager too small) +X contrib mgr: filter/dropdown vertical centering is too high +/ fake bold being used for tab name? (Windows only?) +X should be resolved with both fonts now being installed +X code completion icon updates (class, field, protected, method) +X these go into CompletionPanel.java +X also set the color and font with updateTheme() +X waiting on final colors from theme + +design/themes +X updated 4x4 for themes, foundation svg icon tweaks +X implement 4x4 by auto-generating from svg versions +X make drag events work properly +X add buttons for 'reload theme' and 'how to create themes' to theme fella +X remove the 'reload theme' tool +X both sets working, loading from folders +X fixed up html wiring for styles +X gradients +X add a couple with gradients to the selector box? +X if no matching theme selected, was highlighting column -1 +X reset the theme because of significant changes +X move away from writing theme.txt? +X instead store the theme name, for easier updating +X and a version in the sketchbook will always override +X (selecting a new theme will rename that file, but not replace a theme.txt file) +X include in this release b/c of potential for problems +X accent color and light/dark mode into the theme, and use with flatlaf +X style the popup menu for Mode using the theme +X console scroll bar colors +X update lib/theme.txt to clean up current Frankenstein status +X just replace with the blue default once that's updated + +design/errors +X errors table theme +X use errors.row.bgcolor as bgcolor for the error list +o change errors.row to errors.list in theme.txt +X add errors.bgcolor instead + +design/preferences +X get rid of text box for 'background color when presenting' +X redundant (can type numbers in the dialog) and fussy +X change "sketchbook location" to "sketchbook folder" +X (this is how it's referenced everywhere else in the interface) +X shorten sketchbook location text field +o sketchbook location to single line (drop "location"? say folder?) +X why is sketchbook location text selected when opening window? +X had focus by default, which apparently selects the text +X remove 'smooth text' (too rare: manual editing should suffice) +X switch to BoxLayout before making more layout changes? +X move 'enable complex text' up near language? +X better yet, move language down below font and interface scale +o turn it on when selecting CJKV as a language +X show 'requires restart' only after making a change +X fixed for interface zoom +X hidpiDisableBox +X inputMethodBox +X languageSelectionBox and languageRestartLabel +X preferences.zoom = Interface scale -> preferences.interface_scale = Scale +X preferences.zoom.auto -> preferences.interface_scale.auto +X set link color in prefs window to use accent color +X move 'requires restart' to one thing at the bottom +X preferences.enable_complex_text removed, replaced with +X preferences.enable_complex_text = Enable complex text input +X preferences.enable_complex_text.tip +X preferences.requires_restart = requires restart of Processing +X becomes preferecnes.restart_required = Restart Processing to apply changes +X put language & complex text on same line +X move colons into the language file +X show hand cursor with links for labels +o move delete previous pref to the Export to Application window +o get rid of the pref/just use the setting from export to app +X no clear alternative for disabling this, so remove from prefs window +X but still supported inside preferences.txt for those who must +X it's moving the files to the trash anyway, which should be safe enough +X finish rearranging item order in PreferencesFrame +X all set for now +o move to separate panel +o (maybe not, because not clear which will be inherited by other modes?) +o background when presenting +o continuously check +o code completion +o suggest imports +o increase memory +X opting not to: not enough items, and too disruptive for unclear benefit +X remove extra space between prefs lines + +design/manager +X add manager.panel constants for colors +X status panel not updating in updateTheme() +X StatusPanel.getBodyStyle() has hard-coded fonts/sizes +X changed manager.list.search to manager.search +X implement foundation icon using svg +X icons for contrib manager list entries (green/orange PNGs won't do) +X remove ability to rearrange columns in contrib manager +X why tf this is the default is beyond me +X set color of the sort order icon in the ListPanel table header +X also the color of the text? +X override flatlaf for components (search, buttons, dropdown menu) in manager +X popup menu coloring (contribs) +X progress bar in contrib manager +X need monochrome icon for foundation +o do we need other color states for list item icons +X they seem to be find for now +X add updateTheme() to contrib.ListPanel +X right now pulling Theme.getColor() directly +X but need to make sure repaint() is called anyway +X contrib manager theme +X identify coloring for icons +X how much of theme to inherit +X generate manager icons +o slightly taller tabs, though maybe they're better than the Editor? +o editor tabs are different height, as are the footer tabs +X important for prefs window background color too +X remove ManagerFrame constants for NORMAL_PLAIN, SMALL_PLAIN, etc +X these should be read from theme.txt instead +o or not used at all: the defaults from ui.font and FlatLaf should do +X only really need for the bold font +X improved 'close' icon (thicker x) +X better 'search' icon (search.svg with a less enormous eyeglass) +X replace foundation-16, foundation-32, foundation-64 in lib/icons +X contribs exclamation looks like an error, not "update available" +X clean up the updates panel in the manager +X fix column widths in 'updates' tab of contrib manager +X also make them resizable +X section headings look bad (not capitalized, not plural, no bg color change) +X color change might help, but a little fussy at the moment +X gets into whether the type weight should change, and starts affecting too much +X update available icon looks broken (bad winding rule?) + +manager +X make ContributionTab.FilterField into a static class +o then perhaps move to another source file +X nah, ContributionTabFilterField is a bit much +X move things around a bit so it's not quite a mess +o add reinstall option? +X nah, too fussy; not a big deal to do remove and install +X remove JProgressBar from ContributionTab/UpdateContributionTab +X StatusPanelDetail creates its own, which is the one used +X after download, list item doesn't update to show installed +X stays stuck with the downloading icon + +contrib +X Double-clicking a .pde file also opens an untitled/empty sketch +X https://github.com/processing/processing4/issues/477 +X https://github.com/processing/processing4/pull/479 +X Update PDE_es.properties +X https://github.com/processing/processing4/pull/480 +X https://github.com/processing/processing4/pull/481 +X Update PDE_de.properties +X https://github.com/processing/processing4/pull/483 + +status +X replace emoji buttons in status bar +X re-save svg files using svg 1.0 +X sort out hover/press states here (only hovers atm) +X also add state for shift-click to search +X theme colors for emoji buttons (new themes across the board) +X icons in the status bar (using emojis at the moment, now out of place) +X console collapse/expand button +X copy to clipboard button +X implement alpha for url (70, 90, 100) +X remove the color +X get shift down from the editor window and pass to status +X thicker version of the search icon for the status panel +X copy the icon over from the manager + +cleaning +o should default to the local Java on Windows and Linux +o have export apps default to the local JRE +o Linux is probably using the system JRE if available +o launch4j may be all set, but double-check +X um, no--we should use the embedded version, b/c who knows what happens +X Blank sketch opened even if opening an existing sketch by double-clicking +X https://github.com/processing/processing/issues/218 +o improve the speed of file copying +o use FileChannels, see FileInputStream.getChannel(), +o and use transferFrom() or transferTo().) +o could also use FileUtils in Apache's common io +o http://commons.apache.org/io/api-release/index.html +X Switch to getModifiersEx() in `processing.app` +X https://github.com/processing/processing4/issues/67 +X done in beta 2 +/ library compilations not ordered properly w/ sorting +/ do we still support library compilations? that was from 2016 +X https://github.com/processing/processing/issues/4630 + +decisions +o preferences in web frame? +o intro page using webkit (launching into examples) +o and maybe the reference too? +X nope, webkit embed way too large +o how to send messages from webkit server to PDE (i.e. for a color tool) +X skipping webkit embed for now +X should we use Java 11 instead of 17 to be less of an outlier? +X default rpi openjdk seems to be 11, we're not using any 17 features +X size change is negligible (17 may even be slightly smaller) +X or is this just a matter of 17 being new and it'll change quickly? +X JavaFX 17 (a good idea) seems to be compatible with Java 11 +X JavaFX 11 has fewer builds (no ARM, prolly no Apple Silicon) +X 11 LTS is supported until September 2026, 17 until 2029 +X https://dzone.com/articles/whats-new-between-java-11-and-java-17 +X switching to 17 as the default because it's now available in rpi os + + +1283 (4.0b8) +X fix logic for opening the correct 'main' tab in handleOpen() +X opening AspectHelper.js makes it the main tab +X through sketch.properties is not rewritten +X dropping folder into sketch window throws weird exception +X https://github.com/processing/processing4/issues/441 +X fix " does not exist" message on Linux startup +X no longer pass a blank sketch file name from startup script +X also remove unused readlink +X support multiple files passed on command line +X only show setWritable() failure in recent.txt when it is a problem +X NullPointerException when changing the theme +X https://github.com/processing/processing4/issues/476 +X suppress java.lang.NoSuchMethodError: accessibilityHitTest error +X https://github.com/processing/processing4/issues/368 +X change to "Modes..." or "Manage Modes..." and same for Libraries, etc +X toolbar.add_mode = Add Mode... -> +X toolbar.manage_modes = Manage Modes… +X menu.library.add_library = Add Library... -> +X menu.library.manage_libraries = Manage Libraries… +X menu.tools.add_tool = Add Tool... -> +X menu.tools.manage_tools = Manage Tools… +X update download.processing.org/reference.zip to be a static file +X switch language_gen.py to python3, other versions removed from macOS +o open dialog extensions only work for the current Mode +o i.e. in Java Mode, won't list .js files as candidates +X false alarm: the Mode wasn't installed properly +X set minimum size for scroll bar thumb +X https://github.com/processing/processing4/issues/473 +X on build of dist, immediately submit to Microsoft: https://aka.ms/wdsi +X https://www.microsoft.com/en-us/wdsi/submission/1f8f4dc4-73e7-484b-8d1c-d6e6d7551157 + +manager +X "Error during download and install of Python Mode for Processing" +X https://github.com/processing/processing/issues/5918 +X https://github.com/processing/processing4/issues/445 +X Manager fails to complete install of PythonMode when no windows open +X https://github.com/processing/processing/issues/5309 +X https://github.com/processing/processing4/issues/446 +X removed weird double call of installPreviouslyFailed() +X remove unused icon code from ManagerTabs +X implement updateTheme() +X remove the extra 2-pixel line at the top +o currently uses prepareGraphics(), do we need to remove that? +X looks like nope, that was sorted out separately +X remove extra ContribProgressMonitor class +X removing the current Mode (with no windows open) will cause an exception +X when next opening a sketch, nextMode is set to the old guy +X clicking "Update All" on the Updates tab throws NPE +X https://github.com/processing/processing4/issues/440 +X an incompatible Mode prevents the PDE from quitting after last window is closed +X https://github.com/processing/processing/issues/5112 +X https://github.com/processing/processing4/issues/448 +X could not reproduce on Linux, possible that Windows has a cp issue +X but hopefully more likely that it's been resolved with all the Mode cleanup +X categories +X remove "Starred" as a category (it was unused) +X use isFoundation() instead of isSpecial() for tagging list entries +X add "Renderer" as a category +X use https to get the contribs listing +X remove insane overbuilt search in contribs +X sure, we have over a hundred libraries, but we don't have millions +X search by contrib type (is:) wasn't even working properly +X removing accents was trashing some input too +X redo category tallying for contribs (Libraries only anyway?) +X gets rid of maze of code that's storing libraries by their category +X remove unnecessary code that tracks contribs by category +X this was hiding the issue that was causing contribs to add several times +X because the add() was inside the category loop of that code +X remove unused 'restart' flagging code, setRestartFlag() never used +X all that was left was maintenance of when that's been set, so... +X allow update of the current Mode +X if doing an update (not just delete), close sketches and re-open +X if doing a delete, require sketches to be closed +X change up StatusPanelDetail constructor to clean up accessors + +preproc +X correctly handling sketch renderer with fullScreen() +X also when using other display numbers +X https://github.com/processing/processing4/pull/474 +X https://github.com/processing/processing4/issues/471 +X return PreprocessorResult for Android Mode +X https://github.com/processing/processing4/pull/470 +X https://github.com/processing/processing4/issues/469 + +contrib +X Text caret position shifts when typing Japanese +X https://github.com/processing/processing4/issues/447 +X https://github.com/processing/processing4/pull/462 +X Some keys can't be entered on the On-Screen Keyboard +X https://github.com/processing/processing4/issues/403 +X https://github.com/processing/processing4/pull/461 +X Error with tweak mode when using underscores in numbers +X https://github.com/processing/processing4/issues/442 +X https://github.com/processing/processing4/pull/459 +X Fix IDE auto-formatting confused by method references +X https://github.com/processing/processing4/issues/279 +X https://github.com/processing/processing4/pull/436 + + +1282 (4.0b7) +X Bring back getMainProgram() for Python Mode +X https://github.com/processing/processing4/issues/409 +X Change straight quotes to smart quotes in the PDE.properties file +X look for other uses of Util.deleteFile() and replace with Platform calls + +sketchbook/open/deletions/modes +X test "obvious" sketch folder (and whether it prompts) +X opening Downloads > something.pde made a p5.js sketch with only an index.html +X if no sketch.properties, reset nextMode to the default mode +X deleting sketch removed contents of Download folder +X https://github.com/processing/processing4/issues/424 +X also very problematic for what happens with Save As +X ah, this is because it was just a single .pde file, yikes +X sketch.properties should be present, but won't be if the parent is renamed +o always write sketch.properties? (would help the later rename case) +X when loading, prompt to ask whether the parent folder is the sketch folder? +X if it is, write sketch.properties to set the main file +X if not, move it to its own folder "move blah.pde to a folder named blah" +X are you sure you want to delete the sketch "Downloads" +X instead of "are you sure you want to delete this sketch?" +X have 'delete' function move things to the trash +o or remove 'delete' as an option altogether +X new language string: warn.delete.sketch_folder so we can include folder name +X and renamed warn.delete.file to warn.delete.sketch_file +X remove selectMode() code from Base... just too funky +X should just go back to the default Mode, or the first that matches +X not enough Modes to warrant all the weirdness that could happen +o and for Android (the only legit case), local.properties should be set +o or we have changeMode() to make it an easy switch +X turns out, it wasn't getting set, but that's now fixed +o fix extension check for other modes +X https://github.com/processing/processing/issues/3980 +X not relevant b/c of https://github.com/processing/processing4/issues/189 +X You must first install tweak Mode to use this sketch +X https://github.com/processing/processing4/issues/415 +X change handleOpen() to take a Mode object +X allows Python and Android Mode to open example sketches in that Mode, +X without (retroactively) needing sketch.properties files in all folders + + +1281 (4.0b6) +X update to Java 17.0.2+8 +X move "Add Examples" to bottom of the Examples window +X remove anachronistic Preferences.save() that happens after opening a sketch +X change cmd-click in window title to point to sketch folder +X prevent NullPointerException on first use of Export to Application +X only showed up if no checkboxes were clicked +X when doing Save As, don't included exported applications + +change detector +X text in second line of custom dialogs was showing too large +X change detector had large text for the "either way" part of the message +X remove nonsense removeCode() error text +X errors when files removed during "git checkout " +X the tab has already disappeared, so complains it can't find the SketchCode +X cleaned this up a bit + +modes and properties +X major rewrite of handleOpen() et al +X fix bug when changing the Mode between Java and Android +X appears that it would have been broken for a long time +X add option to disable keeping sketch folder and main tab in sync +o sketch.properties not being written if initial mode is p5.js? +X lots of fixes for this, should be in better shape +X when creating a sketch within non-Java mode, should write the settings file +X so that it re-loads in the proper environment +X remove sketch.properties when moving back to the default? +X or can we not do this, because it's used to set the 'next' mode +X done in this release + +fixes +X multi-line strings not terminating properly +X https://github.com/processing/processing4/issues/398 +X https://github.com/processing/processing4/pull/400 + +cleaning/earlier +X Interface problems when moving to a monitor with Windows scaling to 125% +X https://github.com/processing/processing4/issues/296 +X update JavaFX to use the supported platforms +X run button not deactivating after window closes +X https://github.com/processing/processing/issues/5786 +X confirmed to be working in beta 6 +o teacher wants user input on the console +o https://github.com/processing/processing/issues/5779 +X wrote back to clarify it should be a Mode or Tool +X clean out the repo +X https://github.com/processing/processing/issues/1898 +X resolved by moving to the new repo + + +1280 (4.0b5) +o more reports of code completion not working on macOS +o "working in a6 but not b2" +o https://github.com/processing/processing4/issues/304 +X working again in beta 3 +X change the jdk download to include the arch +X when building on macOS, can't share folder b/c jdk zip is wrong arch +X but also requires checkout of processing-docs, so an unnecessary headache +X update themes with new token colors +X Windows virus/trojan complaints +X https://github.com/processing/processing4/issues/379 +X https://www.microsoft.com/en-us/wdsi/filesubmission +X https://www.microsoft.com/en-us/wdsi/submission/69a47313-6b5a-4c94-9f74-27beffce5460 +X add language support to Modes +X request and updated PR from Andres +X https://github.com/processing/processing4/issues/236 +X https://github.com/processing/processing4/pull/237 (updated by Andres) +X https://github.com/processing/processing/pull/2833 +X https://github.com/processing/processing/issues/3154 +X https://github.com/processing/processing/pull/3337 +X remove the old MovieMaker code +X clean out the unused class files and src +X also remove jai_imageio.jar +X tweak how URLs are opened for better compatibility +X debugging failed installation of .pdez files + +scaling, text, again +X IDE cursor position is wrong if font size is changed in preferences on macOS +X though at least one report that restarting the PDE doesn't fix the problem +X probably related to second displays, need to hook one up and test +X https://github.com/processing/processing4/issues/194 +X seems like the Windows workaround may be making this worse? +X users confirms the correctly working display swapped between beta 3 and 4 +X also updated the two older bugs +X https://github.com/processing/processing4/issues/226 +X https://github.com/processing/processing4/issues/342 +X caret is sometimes one pixel too tall +X cleaning up TextAreaPainter to be less cute; adding more clarifications +X SyntaxDebug removed with 1459d8f714e7e3e8816df010224c567ed5e42fa4 + +preproc from Sam +X Preproc bug fixes and improvements +X https://github.com/processing/processing4/pull/384 +X mixing active and static mode throws the "wrong" error +X https://github.com/processing/processing4/issues/290 +X Problem with function size(int arg, int arg) in Class +X https://github.com/processing/processing4/issues/317 +X Add support for multi-line string text blocks +X https://github.com/processing/processing4/issues/371 +X fullScreen() when specifying the display number was broken +X https://github.com/processing/processing4/pull/392 +X https://github.com/processing/processing4/issues/352 + +previous releases +X need icons for .pde, .pdex, .pdez +X use svg images for res-indep icons/gui? +X https://stackoverflow.com/a/2495712 +X (built and tested a version of this code if we want to use it) +X working to auto-generate icons, though not doing full res-indep for now +o put themes in folders by name +X not useful, at least not yet + +other cleaning +o unsupported java version when trying ant run with 7u65 +o no helpful message about how to automatically download 8u51 +o ignore-tools in build.xml not being called for some reason +o when variables used in size(), getting exceptions instead of any warning +o https://github.com/processing/processing/issues/3311 + +sketchbook window +X refresh option for sketchbook (bottom of window) +X add "Show Folder" entry to sketchbook window +X move sketchbook frame code to Base instead of Mode +X it was being called once for each Mode, but doesn't vary on per-Mode basis + + +1279 (4.0b4) +X remove contentTypes line because it breaks double-clicking files to open +X https://github.com/processing/processing4/issues/347 +X Move JavaFX to its own library +X https://github.com/processing/processing4/issues/348 +X JavaFX no longer supported for Tools, Modes as a result +o turn off javafx web if not using +X moved out with SVG library +X set minimum Java version for Windows launcher to 17.0.1 +X Shutting off VAqua due to interface ugliness and Contribution Manager freezing +X https://github.com/processing/processing4/issues/129 +X now with a release 9 to cover Big Sur +X https://violetlib.org/vaqua/downloads.html +X make the final call to remove, or put the libs on download.processing.org +X removing, with the theming integration, gonna head that direction instead +X bump ant from 1.10.10 to 1.10.12 +X bump JNA from 5.8.0 to 5.10.0 +X remove "Illegal reflective access" warning on Linux +X https://github.com/processing/processing4/issues/207 +X write release notes about not moving to RSyntaxArea +X https://github.com/processing/processing4/issues/355 +X https://github.com/processing/processing/issues/3199 +X https://github.com/processing/processing4/blob/master/app/src/processing/app/syntax/README.md +X re-implement settings.path to support portable versions +X https://github.com/processing/processing/issues/3948 +X moved to https://github.com/processing/processing4/issues/362 +X https://github.com/processing/processing4/pull/360 +X fixed with https://github.com/processing/processing4/commit/1a49263a94a2d7af2b4686286406f7896d207cd9 + +platforms/variants/export +X replacing macosx with macos in prefs and languages +X major rewrite of Export to Application for the six supported platforms +X now writes folders with different names instead of 'application.' +X move build/macosx to build/macos and make other necessary changes +X final supported platforms +X as far as adoptium is concerned: +X macos-x64, macos-aarch64 (m1), windows-x64, linux-x64, linux-arm32 (rpi) +X release files: macosx -> macos, linux64 -> linux, windows64 -> windows +X or macos-intel64, macos-applesi, linux-intel64, ... +X what should macos-aarch64 be called? +X Export Application can use nicer names, the libs thing is trickier +o macosx vs macosx64 in JavaFX +o the latter is making the export fail because it won't embed a Java VM +o may be because it's exporting twice and overwriting? +o or 64 takes precedence? +X doesn't matter with things being redone +X rewrite build.xml to support the five arch types +X remove exception cases for arm in the build/export code +X hopefully these are caught, but needs more testing + +windows scaling +X Fix "Could not delete disable_hidpi" message +X shows up on macOS/Linux after closing prefs +X remove old-school offscreen buffering from our custom components +X scale of opening screen +X attempting to use multi-resolution image loading +o make smaller at 125% +X loading the 2x version always, turn on smoothing and draw at 50% +X is Toolkit.setIcon() in splash slowing down the startup screen? +X nope, appears to be class loading out of our control (0.3s vs our code 0.1s) +X fix weird character offsets in text area +X Resolve scaling issues with Windows +X Editor cursor position offset to the right with fractional Windows scaling +X https://github.com/processing/processing4/issues/342 +X displayDensity() is returning 1 when run from the PDE +X https://github.com/processing/processing4/issues/339 +o include JNA so that sketches can also scale properly? +o what happens re: getting scaled/high-res graphics? +o make that a preference? (and double the size by default?) +X the Java 9 changes seem to handle this for us +X was looking crunchy on low-dpi screen set to 125% +X was this due to the args change in alpha 5? + +contribs +X select entire line when doing Edit > Copy on an empty selection +X https://github.com/processing/processing4/pull/100 + +cleaning +o crashed on startup w/ JavaScript mode as default b/c PdeKeyListener not found +o because it's in the other ClassLoader, can no longer rely on it +o remove JavaMode.errorLogsEnabled and JavaEditor.writeErrorsToFile() +X should be long since gone +o continue clearing out ProgressFrame +o also hook up the statusNotice() when done +X also should be long gone + +design +X icon for exported app +X icon for document +X update the foundation icons + +design (done in beta 3) +X update theme +X selector for theme that uses tiny images + +manager +X contrib list entry in the table sometimes contains markdown +X at least hide the syntax parts (and show the text) +X description panel in contribs contains markdown +X cursor even changes to link, but the links don't have colors, +X and no links open when clicked +X now changing color for link and actually opening the links +X set the font on the contrib install progress bar +X DetailPanel seems totally vestigial? +X updates count is off... maybe when compatible != available count? +X nope, just something weird with the qr code library +X probably not available anymore, not gonna bother digging further + +before beta 4 release +X javafx library linked in contribs manager +o test to make sure still working +o warnings about installation + + +1278 (4.0b3) +X Update appbundler with the latest from upstream +X Replace JDK 11 and JavaFX 16 with JDK 17 and JavaFX 17 +X https://github.com/processing/processing4/issues/285 +X update README to note that we're on 17 +X also change project notes on the front +X move up from JavaFX 17.0.0.1 to 17.0.1 +X cleaning up the Create Font dialog while tracking down #278 +/ https://github.com/processing/processing4/issues/278 +X couldn't reproduce, but probably an issue in there somewhere +X Remove Serif, SansSerif, Monospaced, Dialog, DialogInput from Create Font +X sort the list of font names +X skip fonts starting . and # because they're likely to confuse users +X get rid of version numbers in the name of the batik.jar file +X ffmpeg not downloading correctly on M1 machines +X https://github.com/processing/processing4/issues/319 +X use UTF-8 for readString() and write() in net client +X avoids platform-specific behavior; Java 18 also making UTF-8 the default +X https://github.com/processing/processing4/issues/336 +X System.out and System.err collision causing deadlock +X https://github.com/processing/processing4/issues/338 +X https://github.com/processing/processing/issues/5775 +X https://github.com/processing/processing/issues/5714 +X https://github.com/processing/processing/issues/6230 +X get rollovers working again for the toolbar buttons +X accidentally shut off sometime during the 3.x development process +X set document type for file associations so that basic quicklook works +o disable Theme Engine from Tools (in build.xml) +X or rename to Theme Update, and remove the browser bits +X test whether open/save dialog behaving on Ubuntu +o test with previous beta +X Open/Save crashing on Linux +X switching to Oracle JDK got it working, need to test with 17 +X https://github.com/processing/processing4/issues/306 +X update the README.md in the root for alpha 6 and beta 1, 2, 3 +X update https://github.com/processing/processing4/wiki/Changes-in-4.0 +X make notes about known issues in the README + +sam +X Error when calling smooth() on PGraphics +X https://github.com/processing/processing4/issues/272 +X Detect if calling special methods on PApplet or not (and restore unit tests) +X https://github.com/processing/processing4/pull/288 +X Move Mockito to a new version +X https://github.com/processing/processing4/issues/287 + +contribs +X Splash screen has default OpenJDK icon +X https://github.com/processing/processing4/pull/329 +X https://github.com/processing/processing4/issues/297 +X Update Ukrainian language strings +X https://github.com/processing/processing4/pull/301 + +cleaning +o import option for sketchbook (button to select files/folders/etc) +X use pdez instead + +design/theme +X need custom scroll bar for theme handling +X https://stackoverflow.com/q/16373459 +X auto-generate icons +X generate toolbar icons +X generate footer icons +X incorporate icon auto-generate into PDE +X autogenerate on theme update +X generate footer icons +X finish debugging theme update +X update gutter colors on theme update +X update the mode selector colors +X remove background color from console text +X make sure repaint is happening +X white corner on the scroll bar +X single line at top of editor (editor bg color?) +X line numbers extending into the horizontal scroll bar +X update the opening page to say 2022 +X replace the ugly icon +X https://developer.apple.com/design/resources/#macos-apps +o app, document, exported app +X indicator to show current theme +X back up themes that are not standard/have been modified + +windows scaling +/ Editor cursor position is offset to the right when Windows scaling >100% +X https://github.com/processing/processing4/issues/226 +o ship with a spare .exe for this one? +X partial fix, use the "Disable HiDPI Scaling" option in Preferences +X UI scaling issues on Windows still not resolved +X https://github.com/processing/processing4/issues/231 +X option to disable the Windows startup setting for scaling +X need to have a better workaround +o detect 150% scaling and disable the flag, otherwise set it? +X can't be detected reliably +X show a warning in the console for screen issues? + + +1277 (4.0b2) +X remove translated URLs that are not actually translated +X entries in the Help menu were going to ancient links +X https://github.com/processing/processing4/issues/250 +X Switch to getModifiersEx() in processing.app +X https://github.com/processing/processing4/issues/67 +X deal with getFontMetrics() deprecation warning in EditorToolbar +X Exported Application broken in 4.0 beta 1 on macOS when using P2D or P3D +X https://github.com/processing/processing4/issues/249 +X new issue: https://github.com/processing/processing4/issues/284 +X add -Dpython.console.encoding=UTF-8 to launcher +X https://github.com/jdf/Processing.py-Bugs/issues/322 +o key shortcuts broken on Katherine's laptop +o try with a clean user account +X double-check on other Big Sur machines (tried on VMware, was fine) +X worked after a reboot? +X "Massachusetts Institue of Technology" typo in loading screen +X https://github.com/processing/processing4/issues/254 +X https://github.com/processing/processing4/issues/280 (duplicate) +X Movie Maker broken with spaces in path (converted to %20) +X https://github.com/processing/processing4/issues/268 +X Debugger not working when selecting Debug from the menu +X https://github.com/processing/processing4/issues/282 +X turned out to be a problem when using menu, not button to enable/disable + +contribs +X fix minor typos in the PWM code for the IO library +X https://github.com/processing/processing4/pull/266 +X Fix welcome screen dismissing +X https://github.com/processing/processing4/issues/253 +X https://github.com/processing/processing4/issues/48 +X French translation: fixed typos and added missing items +X https://github.com/processing/processing4/pull/258 +X JSSC fix libs and sync with upstream +X https://github.com/processing/processing4/issues/119 +X https://github.com/processing/processing4/pull/229 +X Imports whose name contains "color" cause Syntax error +X Workaround for color to appear in fully qualified names +X https://github.com/processing/processing4/pull/246 +X https://github.com/processing/processing4/issues/240 + + 1276 (4.0b1) X really chatty console messages for people with old settings directories X ignore 'debug' entry in prefs dir if it's a leftover DebugMode directory @@ -2326,12 +3607,12 @@ X https://github.com/processing/processing/issues/3440 X Contribution Manager design is really rough X https://github.com/processing/processing/issues/3464 o library.properties and tool.properties should reflect supported modes -o http://code.google.com/p/processing/issues/detail?id=1050 +o https://github.com/processing/processing/issues/1088 X marked as WONTFIX in 2012 X re/move things from Google Code downloads X https://code.google.com/p/support/wiki/DownloadsFAQ o mismatched square brackets generate bizarre and/or misleading error messages -X http://code.google.com/p/processing/issues/detail?id=355 +X https://github.com/processing/processing/issues/394 X marked as WONTFIX in 2011 o using a keyword as a variable name gives unhelpful error message o http://code.google.com/p/processing/issues/detail?id=54 @@ -2342,6 +3623,7 @@ X http://code.google.com/p/processing/issues/detail?id=114 X https://github.com/processing/processing/issues/153 X verify (and document) public access members of PApplet X http://code.google.com/p/processing/issues/detail?id=83 +X https://github.com/processing/processing/issues/122 X remove PdeKeyListener, roll it into the Java InputHandler for JEditTextArea X move Java-specific InputHandler to its own subclass X key command for prev/next tab works, but not menu @@ -2375,6 +3657,7 @@ o save window positions on quit, and restore them (w/ a preference?) o new windows use same mode and dimensions as topmost window o saved window position problematic with multiple monitors o http://code.google.com/p/processing/issues/detail?id=27 +o https://github.com/processing/processing/issues/66 X Closing the last window doesn't cause PDE to save it's position/contents/etc X http://code.google.com/p/processing/issues/detail?id=103 X https://github.com/processing/processing/issues/142 @@ -2388,6 +3671,7 @@ o make sure the application is within the bounds of the current display? o (from 0, 0 to width, height) o messy since some displays have negative coords X http://code.google.com/p/processing/issues/detail?id=27 +X https://github.com/processing/processing/issues/66 o Resurrect the Eclipse plug-in project X http://code.google.com/p/processing/issues/detail?id=1031 X https://github.com/processing/processing/issues/1069 @@ -2594,6 +3878,7 @@ X Old version number shown in Mode Manager dialog X https://github.com/processing/processing/issues/2843 o Update Windows icons for multiple sizes, implement them in the PDE o http://code.google.com/p/processing/issues/detail?id=632 +o https://github.com/processing/processing/issues/671 X closed during the 2.x cycle X try to clean up the Recent menu with the home icon _ make sure it doesn't break on Windows @@ -2696,6 +3981,7 @@ X it's too difficult for students to debug their code X can the jdt be hooked in somewhat easily? X Replace current editor with more advanced version X http://code.google.com/p/processing/issues/detail?id=1032 +X https://github.com/processing/processing/issues/1070 o code coloring is imperfect because it's not based on a parser X rename location is awkward, do it on the tab? X date inconsistencies @@ -2921,8 +4207,8 @@ X https://github.com/processing/processing/pull/2382 X getCaretLocation() bug in syntax.im package X https://github.com/processing/processing/issues/2934 X finish up debian package support (see the processing.mess folder) -X http://code.google.com/p/processing/issues/detail?id=75 X these bits need to be checked to ensure that they work on other distros +X http://code.google.com/p/processing/issues/detail?id=75 X https://github.com/processing/processing/issues/114 X https://github.com/processing/processing/pull/2972 X https://github.com/processing/processing/issues/2973 @@ -3149,14 +4435,14 @@ X https://github.com/processing/processing/pull/2777 X Add rank (starred / recommended) to contributions manager items X https://github.com/processing/processing/issues/2580 o Improve detection and handling of missing semicolons +X should be fixed with PDE X (closed by Dan) o http://code.google.com/p/processing/issues/detail?id=136 -X should be fixed with PDE X (closed by Dan X https://github.com/processing/processing/issues/175 X missing brackets, unmatched brackets X examples added to the bug report X http://code.google.com/p/processing/issues/detail?id=6 -X closed by Shiffman, working better now X https://github.com/processing/processing/issues/45 +X closed by Shiffman, working better now X 64-bit versions of sound available on Windows and Linux pulls @@ -4460,24 +5746,24 @@ o http://java.sun.com/javase/6/docs/api/java/io/File.html#setExecutable(boolea o specifically add "eclipse" to integration description o link books and sample chapters from the "getting started" portion of the faq o if export fails (compile error) need to un-highlight the export button -o http://dev.processing.org/bugs/show_bug.cgi?id=39 +o https://download.processing.org/bugzilla/39.html o [LaunchRunner Error] processing.app.Base.main(String[]) threw an exception -o http://dev.processing.org/bugs/show_bug.cgi?id=821 +o https://download.processing.org/bugzilla/821.html o http://code.google.com/p/processing/issues/detail?id=102 X not seen for a long time, closed o dragging title bar while sketch running causes strange selection behavior -o http://dev.processing.org/bugs/show_bug.cgi?id=504 +o https://download.processing.org/bugzilla/504.html X closed as no longer causing a problem o "page setup" sucks in java -o http://dev.processing.org/bugs/show_bug.cgi?id=435 +o https://download.processing.org/bugzilla/435.html X marked later.. but not really gonna get into it o fonts smaller than 10 cause problems in the editor o generally causes trouble on osx o on windows and linux, changing the size throws things off -o http://dev.processing.org/bugs/show_bug.cgi?id=51 +o https://download.processing.org/bugzilla/51.html X ppl weren't restarting the editor o implement emacs keybindings (list is at the bug report) -o http://dev.processing.org/bugs/show_bug.cgi?id=401 +o https://download.processing.org/bugzilla/401.html X decided not to o error messages run off the edge and go invisible X http://code.google.com/p/processing/issues/detail?id=18 @@ -4491,7 +5777,7 @@ o implement and remove PdeEditorStatus stuff X http://code.google.com/p/processing/issues/detail?id=20 X decided nobody cared o errors during export don't show up properly (no red status bar) -o http://dev.processing.org/bugs/show_bug.cgi?id=194 +o https://download.processing.org/bugzilla/194.html X could not reproduce X prefs window not visible on windows taskbar X http://code.google.com/p/processing/issues/detail?id=63 @@ -4509,12 +5795,12 @@ o /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK o will return 1.5.0 (or maybe 1.6 for others?) X nope, just using a local JRE/JDK from now on o need to decide how to handle "stop" button in present mode -o http://dev.processing.org/bugs/show_bug.cgi?id=63 +o https://download.processing.org/bugzilla/63.html o when running externally, people need to write their own stop function o just get export to application working so this can be supported o for now, they're stuck w/ running in the env and getting the ugliness X quitting from present mode doesn't kill run button -X http://dev.processing.org/bugs/show_bug.cgi?id=80 +X https://download.processing.org/bugzilla/80.html X fixed earlier (verified in 1.0.9) o slow save/new because of large sketchbook o this is a total cluster, the rebuild is being called incessantly @@ -4570,28 +5856,28 @@ o port buffering not working properly o may just be a problem with thread starvation o bufferUntil() fires an event but continues to fill up the buffer o bug report includes patch from mellis -o http://dev.processing.org/bugs/show_bug.cgi?id=96 +o https://download.processing.org/bugzilla/96.html X fixed in 0140 o add prompt() method to Serial (simple dialog box that pops up) o Add Mark and Space parity to the Serial library -o http://dev.processing.org/bugs/show_bug.cgi?id=1087 +o https://download.processing.org/bugzilla/1087.html X opted not to a long while ago o process still visible in Sysinternals Process Explorer after quit X http://code.google.com/p/processing/issues/detail?id=259 X closed after no follow-up o setup() and draw() are not bold on osx (10.4) o likely osx refuses to do monaco bold -X http://dev.processing.org/bugs/show_bug.cgi?id=1047 +X https://download.processing.org/bugzilla/1047.html X closed a long while ago o track down error in PdeCompiler for message parsing o was missing the error about a package being gone o can comment out /System/Library/ as a test X way too old to know this one o Editor stops receiving keyboard input after running a program (8.10) -X http://dev.processing.org/bugs/show_bug.cgi?id=1161 +X https://download.processing.org/bugzilla/1161.html X had been closed as invalid o if someone has monaco disabled in font book, the app won't start -o http://dev.processing.org/bugs/show_bug.cgi?id=562 +o https://download.processing.org/bugzilla/562.html X Monaco can no longer be disabled X modes have their own methods for digging through sketch & libraries folders o therefore it need only check the sketch.txt file to see if it's ok @@ -4669,7 +5955,7 @@ X disallow underscore at beginning of sketch name X http://code.google.com/p/processing/issues/detail?id=1047 X fix problem with sanitized names potentially overwriting existing sketches (!) o do some testing for windows 7 on 64-bit -X http://dev.processing.org/bugs/show_bug.cgi?id=1424 +X https://download.processing.org/bugzilla/1424.html X add general outline of 1.5 and 2.0 releases to the faq X video is on its way out, no good/simple solution for fixing now X andreas' library will do good things eventually @@ -4691,7 +5977,7 @@ X get rid of restore sketch feature o remove pref for restoring sketches X implement recent sketches menu X add "recent files" list to open menu? -o http://dev.processing.org/bugs/show_bug.cgi?id=1335 +o https://download.processing.org/bugzilla/1335.html o don't re-open new window on top of another X changing how this is handled X detect mode and library example folders for recent menu @@ -4713,7 +5999,7 @@ o add tool to "Add custom html to sketch" o that copies applet.html, o opens sketch folder, o and gives info about what to do next (how to edit) -o http://dev.processing.org/bugs/show_bug.cgi?id=143 +o https://download.processing.org/bugzilla/143.html cleaning/earlier o PApplet.this doesn't work @@ -4725,9 +6011,9 @@ o can't use PApplet.this, doesn't seem to like that o instead, must pass variable to inner class X should be working (for a while) now because no longer using a temp name o quicktime likes to crash (not just on windows) -o http://dev.processing.org/bugs/show_bug.cgi?id=791 +o https://download.processing.org/bugzilla/791.html o strange insets for PApplet on certain window managers -o http://dev.processing.org/bugs/show_bug.cgi?id=1187 +o https://download.processing.org/bugzilla/1187.html X http://code.google.com/p/processing/issues/detail?id=161 X cannot fix o computationally intensive stuff runs really slow inside p5 @@ -4735,7 +6021,7 @@ o http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action o some reports of it not quitting properly, but not confirmed X problem with startup on vista 64 X try this with windows 7 64-bit (install on laptop?) -X http://dev.processing.org/bugs/show_bug.cgi?id=1246 +X https://download.processing.org/bugzilla/1246.html X System.getProperty("sun.arch.data.model").equals("32"). o add to troubleshooting: X remove/rename the preferences file @@ -4744,11 +6030,11 @@ o need to only load p5 libs from the p5 directory o update the text on the bugzilla page o NPE when double-clicking PDE on Mac OS X o is thread sync the problem? rebuild menus being called 2x? -o http://dev.processing.org/bugs/show_bug.cgi?id=1251 +o https://download.processing.org/bugzilla/1251.html o http://code.google.com/p/processing/issues/detail?id=177 X not seen for a while, closed o multiple entries in file menu -o http://dev.processing.org/bugs/show_bug.cgi?id=1260 +o https://download.processing.org/bugzilla/1260.html X should be fixed, not seen o "src/processing/xxxx/Xxxxxxxx.java uses unchecked or unsafe operations." X http://code.google.com/p/processing/issues/detail?id=101 @@ -4756,7 +6042,7 @@ o use pack200/unpack200 to make p5 download smaller X http://code.google.com/p/processing/issues/detail?id=95 X decided not to--no good way to do cross-platform version o problem with keyboard shortcuts when building from source -o http://dev.processing.org/bugs/show_bug.cgi?id=1231 +o https://download.processing.org/bugzilla/1231.html X either fixed or never confirmed o make source link on p5 site go direct to the source o or at least explain how to navigate @@ -4771,35 +6057,35 @@ X add stuff about the history into the people section X descended from dbn, but not dbn, etc.. o remove the 'search' item from yabb.cgi o add note to linux troubleshooting re: key input -o http://dev.processing.org/bugs/show_bug.cgi?id=1161 +o https://download.processing.org/bugzilla/1161.html X marked wontfix o add a command to launch an external editor -o http://dev.processing.org/bugs/show_bug.cgi?id=141 +o https://download.processing.org/bugzilla/141.html X getting rid of external editor o clipboard implementation example -X http://dev.processing.org/bugs/show_bug.cgi?id=167 +X https://download.processing.org/bugzilla/167.html o include a note that 'applet' folder will get emptied/rewritten o or rename the old applet folder to something else? (too messy) X http://code.google.com/p/processing/issues/detail?id=21 X added preference in more recent release X after deleting a tab code still get's preprocessed, pde file is not forgotten -X http://dev.processing.org/bugs/show_bug.cgi?id=1092 +X https://download.processing.org/bugzilla/1092.html X fixed in 1.0.8 (!) X export and export to application fail with umlauts in folder name -X http://dev.processing.org/bugs/show_bug.cgi?id=252 +X https://download.processing.org/bugzilla/252.html X fixed in 0140, but no confirmation o stop button needs to update itself and work properly o also editor buttons to light up and clear properly -X http://dev.processing.org/bugs/show_bug.cgi?id=396 +X https://download.processing.org/bugzilla/396.html o need someone to go out and test all scenarios of this X this particular version was fixed (though broken again later) o video library threading problems with other libraries -X http://dev.processing.org/bugs/show_bug.cgi?id=882 +X https://download.processing.org/bugzilla/882.html o using Capture.list() before size() sometimes works o also placing OpenGL inside the extensions directory X closing because was specific to QTJava o serial.available() broken with video -X http://dev.processing.org/bugs/show_bug.cgi?id=829 +X https://download.processing.org/bugzilla/829.html X old video library, so closed o sometimes not launching o this seems to be a threading problem, like an NPE on load/open @@ -4808,7 +6094,7 @@ X need something more specific than this X can't run opengl in 64-bit X http://code.google.com/p/processing/issues/detail?id=255 o PDE locks up during setup() (since no window shown) -o http://dev.processing.org/bugs/show_bug.cgi?id=687 +o https://download.processing.org/bugzilla/687.html o maybe better to do an example of running code on another thread o when it's done as a script X http://code.google.com/p/processing/issues/detail?id=86 @@ -4818,7 +6104,7 @@ X use wget to grab it if it doesn't exist o and include an md5hash to see if the file is correct X not necessary -- ant takes care of it o notes about getting best results from text in each renderer -o http://dev.processing.org/bugs/show_bug.cgi?id=466 +o https://download.processing.org/bugzilla/466.html X http://code.google.com/p/processing/issues/detail?id=64 X marked wontfix o Debugger highlights wrong line, and sometimes the wrong file @@ -4830,14 +6116,14 @@ o maybe a dropdown list thing, with the first just shown? X http://code.google.com/p/processing/issues/detail?id=19 X marked cantfix o using a processing keyword as a variable name gives unhelpful error message -X http://dev.processing.org/bugs/show_bug.cgi?id=213 +X https://download.processing.org/bugzilla/213.html X fixed issue specific to handleDisplay o not enough args for triangle (or args in general) o throws out bizarre message -o http://dev.processing.org/bugs/show_bug.cgi?id=17 +o https://download.processing.org/bugzilla/17.html X fixed up later X expecting RPAREN messages are ugly -X http://dev.processing.org/bugs/show_bug.cgi?id=15 +X https://download.processing.org/bugzilla/15.html o unchecking 'use external editor' sketch should not set modified o dangerous if a version that hasn't been re-loaded has possibility o to overwrite. i.e. make a change and save in external editor, @@ -4851,16 +6137,16 @@ X merged into new editor issue o failed export still copies random files o Failed compile on Export or Export to Application o still creates folder and leaves mess behind -X http://dev.processing.org/bugs/show_bug.cgi?id=1050 +X https://download.processing.org/bugzilla/1050.html X opted not to fix (rationale in the report) o javadoc comment in a static mode app doesn't get moved to the top -o http://dev.processing.org/bugs/show_bug.cgi?id=924 +o https://download.processing.org/bugzilla/924.html X http://code.google.com/p/processing/issues/detail?id=115 o make export put a timestamp in the html code (hidden or visible) -o http://dev.processing.org/bugs/show_bug.cgi?id=66 +o https://download.processing.org/bugzilla/66.html X http://code.google.com/p/processing/issues/detail?id=22 X when holding down shift, show the alternate behavior for EditorHeader -X http://dev.processing.org/bugs/show_bug.cgi?id=37 +X https://download.processing.org/bugzilla/37.html o implement new version of history o make history folder, and a zip (not gz) file for each entry o history causing trouble - super slow with a huge sketch @@ -4877,11 +6163,11 @@ X http://code.google.com/p/processing/issues/detail?id=26 video/nixed o if read() hasn't been called, can cause an error with java2d -o http://dev.processing.org/bugs/show_bug.cgi?id=1041 +o https://download.processing.org/bugzilla/1041.html o threading problems with video -o http://dev.processing.org/bugs/show_bug.cgi?id=868 +o https://download.processing.org/bugzilla/868.html o audio stops working after two seconds -o http://dev.processing.org/bugs/show_bug.cgi?id=277 +o https://download.processing.org/bugzilla/277.html o add more information about multiple camera inputs o add info about "access" errors being quicktime errors o documented in faq, add something to the lib "camera not installed" @@ -4896,10 +6182,10 @@ o need to prevent multiple QTSession open or close o static method shared across the lib, or some such o reading movie is really really slow (2-3 fps) o possible improvement from qtjava list added -X http://dev.processing.org/bugs/show_bug.cgi?id=40 +X https://download.processing.org/bugzilla/40.html o tearing and incomplete updates on capture? o putting read() inside draw() seems to eliminate this? -o http://dev.processing.org/bugs/show_bug.cgi?id=114 +o https://download.processing.org/bugzilla/114.html o when drawing large video, the two triangles for the rect are out of sync o only shows up in P3D o pause and frameRate aren't working @@ -4907,7 +6193,7 @@ o framerate does set the frequency which movieEvent will be called, o but it is not setting the "available" field corrrectly. o in fact, speed() should be used to set the rate, not frameRate o sketch .zip file in casey's email message -X http://dev.processing.org/bugs/show_bug.cgi?id=370 +X https://download.processing.org/bugzilla/370.html o wrong device name for video capture will cause a crash o couldn't get req'd component also happens when the capture isn't ready o may also mean that no camera is plugged in @@ -4923,7 +6209,7 @@ o when an exception comes through during cameraEvent, not printed o need to show an actual stack trace (InvocationTargetEx) o because otherwise it's impossible to debug this stuff o video library not working on export to web -o http://dev.processing.org/bugs/show_bug.cgi?id=1421 +o https://download.processing.org/bugzilla/1421.html o Movie needs the crop() functions ala Capture bugzilla (ouch) @@ -4956,13 +6242,13 @@ o need to change what people can edit in the bug report o do something about the bug summary field to not make it editable o maybe just move it away from its current location? o call it "bug title" instead of "summary"? -o http://dev.processing.org/bugs/show_bug.cgi?id=253 +o https://download.processing.org/bugzilla/253.html o layout problems with attachments page -o http://dev.processing.org/bugs/show_bug.cgi?id=254 +o https://download.processing.org/bugzilla/254.html o layout problems with logout page -o http://dev.processing.org/bugs/show_bug.cgi?id=255 +o https://download.processing.org/bugzilla/255.html o bug duplicate text field doesn't retain focus -o http://dev.processing.org/bugs/show_bug.cgi?id=256 +o https://download.processing.org/bugzilla/256.html o finish putting all the bugs into bugzilla o add a notation to the bugs site re: reading the faq and searching first X move p5 site bug reporting to bugzilla @@ -5186,7 +6472,7 @@ X http://code.google.com/p/processing/issues/detail?id=707 X Badly formed character constant exception X http://code.google.com/p/processing/issues/detail?id=714 o people not knowing how to use Java w/ OpenGL renderer -o http://dev.processing.org/bugs/show_bug.cgi?id=1259 +o https://download.processing.org/bugzilla/1259.html o add deployJava.js to local sketch folder (causes internet requirement) X http://code.google.com/p/processing/issues/detail?id=650 X http://www.java.com/js/deployJava.js @@ -5250,18 +6536,18 @@ X http://code.google.com/p/processing/issues/detail?id=664 cleaning X check to see whether this bug is fixed once 0140 is released X properly handle non-ascii chars in p5 folder name -X http://dev.processing.org/bugs/show_bug.cgi?id=49 +X https://download.processing.org/bugzilla/49.html X (could also just warn user to install elsewhere) o perhaps the get around this by building into sketch folder o when non-ascii chars in use, just launch everything externally X "Could not find the main class: processing.app.Base. Program will exit." X http://code.google.com/p/processing/issues/detail?id=151 -o http://dev.processing.org/bugs/show_bug.cgi?id=1126 +o https://download.processing.org/bugzilla/1126.html o "Fatal exception occurred. Program will exit." on startup o seems to be font related, deleting prefs will fix it -o http://dev.processing.org/bugs/show_bug.cgi?id=688 +o https://download.processing.org/bugzilla/688.html o Processing IDE icon ALT + Tab low res in Windows XP -o http://dev.processing.org/bugs/show_bug.cgi?id=1040 +o https://download.processing.org/bugzilla/1040.html 0196 pde (1.5) @@ -5273,12 +6559,12 @@ X would help with beta releases, and not having to edit by hand X add another fix for text focus handling X http://code.google.com/p/processing/issues/detail?id=627 X file-save stops running sketch -X http://dev.processing.org/bugs/show_bug.cgi?id=810 +X https://download.processing.org/bugzilla/810.html X http://code.google.com/p/processing/issues/detail?id=100 X fix bug in loadfile2 example X http://code.google.com/p/processing/issues/detail?id=522 o when running with external editor, hide the editor text area -o http://dev.processing.org/bugs/show_bug.cgi?id=20 +o https://download.processing.org/bugzilla/20.html X http://code.google.com/p/processing/issues/detail?id=9 X shift-indent without selection increases indention X http://code.google.com/p/processing/issues/detail?id=458 @@ -5306,17 +6592,17 @@ X need to check behavior for word and bbedit X http://code.google.com/p/processing/issues/detail?id=59 X http://code.google.com/p/processing/issues/detail?id=576 X find/replace all around very ugly, fix it up -o http://dev.processing.org/bugs/show_bug.cgi?id=67 +o https://download.processing.org/bugzilla/67.html X http://code.google.com/p/processing/issues/detail?id=23 X http://code.google.com/p/processing/issues/detail?id=580 X several tweaks -o http://dev.processing.org/bugs/show_bug.cgi?id=68 +o https://download.processing.org/bugzilla/68.html o only enable "find next" in menu after a find has happened X http://code.google.com/p/processing/issues/detail?id=24 several changes to the undo setup X sketch marked as modified too aggressively -o http://dev.processing.org/bugs/show_bug.cgi?id=328 +o https://download.processing.org/bugzilla/328.html X http://code.google.com/p/processing/issues/detail?id=57 X "save" clears undo information X http://code.google.com/p/processing/issues/detail?id=411 @@ -5327,7 +6613,7 @@ fixed in 0195 X rename/saveas doesn't properly have its focus set X under windows, immediately typing after rename doesn't select X the whole thing is selected, but not directly editable -o http://dev.processing.org/bugs/show_bug.cgi?id=31 +o https://download.processing.org/bugzilla/31.html X http://code.google.com/p/processing/issues/detail?id=13 @@ -5347,7 +6633,7 @@ X only affects linux and windows X http://code.google.com/p/processing/issues/detail?id=569 X move library examples to the examples menu o do this once the library documentation has been fixed up -X http://dev.processing.org/bugs/show_bug.cgi?id=1278 +X https://download.processing.org/bugzilla/1278.html X http://code.google.com/p/processing/issues/detail?id=181 X ctrl-slash not working with esfera on linux (line ending trouble?) X or maybe ctrl-slash just broken on linux? @@ -5387,7 +6673,7 @@ X save untitled sketch, then try to rename, says "save before renaming" earlier X Unsatisfied Link Error running OPENGL on 64-bit ubuntu -X http://dev.processing.org/bugs/show_bug.cgi?id=1557 +X https://download.processing.org/bugzilla/1557.html X on linux, mozilla shouldn't be the default browser anymore X is there a way to query the default browser? gnome-open? xdg-open? X cleaned up some things inside settings, but this was already done @@ -5423,7 +6709,7 @@ J http://code.google.com/p/processing/issues/detail?id=462 X fix for linux build script (thanks to Kevin Keraudren) X http://code.google.com/p/processing/issues/detail?id=466 o occasional exception in "copy for discourse" -o http://dev.processing.org/bugs/show_bug.cgi?id=729 +o https://download.processing.org/bugzilla/729.html X move compiler to mode.java.* instead of runner X fix the encoding on OS X for the "Fix Encoding and Reload" function X broken link on ye olde' http://processing.org/bugs/bugzilla @@ -5495,7 +6781,7 @@ X http://code.google.com/p/processing/issues/detail?id=335 X disabled input method support for now X make some fancy extendo things because the tabs get too big X either condense or popdown menu thingy -o http://dev.processing.org/bugs/show_bug.cgi?id=54 +o https://download.processing.org/bugzilla/54.html X http://code.google.com/p/processing/issues/detail?id=17 X don't show tabs when there are too many X maybe move the list of files to the top? @@ -5541,7 +6827,7 @@ J http://code.google.com/p/processing/issues/detail?id=420 J matching brace problem in PDE J http://code.google.com/p/processing/issues/detail?id=417 J fix syntax highlighting of focusGained and others -J http://dev.processing.org/bugs/show_bug.cgi?id=659 +J https://download.processing.org/bugzilla/659.html J http://code.google.com/p/processing/issues/detail?id=82 J compiling with static final global variable J http://code.google.com/p/processing/issues/detail?id=427 @@ -5559,14 +6845,14 @@ J http://code.google.com/p/processing/issues/detail?id=462 X fix for linux build script (thanks to Kevin Keraudren) X http://code.google.com/p/processing/issues/detail?id=466 o occasional exception in "copy for discourse" -o http://dev.processing.org/bugs/show_bug.cgi?id=729 +o https://download.processing.org/bugzilla/729.html fixed earlier X should really be doing the 'right' thing with sketch file handling X create temporary file when saving X once done writing, use remove the original and rename the old X some basic stuff like this.. -X http://dev.processing.org/bugs/show_bug.cgi?id=968 +X https://download.processing.org/bugzilla/968.html X http://code.google.com/p/processing/issues/detail?id=127 @@ -5604,7 +6890,7 @@ X add warning message when not using a version of sun java w/ p5 on linux X Ctrl-Z will undo, but not scroll to where the undo happens. X is this now somehow fixed? (or only on os x?) X so user thinks nothing is happening and overundo. -X http://dev.processing.org/bugs/show_bug.cgi?id=35 +X https://download.processing.org/bugzilla/35.html X http://code.google.com/p/processing/issues/detail?id=15 @@ -5627,7 +6913,7 @@ X and if it's there, don't require people to quit X Prevent horizontal scroll offset from disappearing X http://code.google.com/p/processing/issues/detail?id=280 o horizontal scroller gets weird sometimes -o http://dev.processing.org/bugs/show_bug.cgi?id=23 +o https://download.processing.org/bugzilla/23.html X Fix NullPointerException when making a new sketch on non-English systems X http://code.google.com/p/processing/issues/detail?id=283 X show warning message on linux if sun java is not in use @@ -5649,58 +6935,58 @@ X http://code.google.com/p/processing/issues/detail?id=278 0185 pde (pre) X add option to launch a sketch directly w/ linux X thanks to Larry Kyrala -X http://dev.processing.org/bugs/show_bug.cgi?id=1549 +X https://download.processing.org/bugzilla/1549.html X fix for PDF library to support createFont() on Linux -X http://dev.processing.org/bugs/show_bug.cgi?id=1566 +X https://download.processing.org/bugzilla/1566.html X thanks to Matthias Breuer X add option to change the formatting for untitled sketch naming -X http://dev.processing.org/bugs/show_bug.cgi?id=1091 +X https://download.processing.org/bugzilla/1091.html X Can't input full-width space when Japanese IME is on. -X http://dev.processing.org/bugs/show_bug.cgi?id=1531 +X https://download.processing.org/bugzilla/1531.html X fix from takachin X PDF library matrix is not reset between frames X also added begin/endDraw between frames -X http://dev.processing.org/bugs/show_bug.cgi?id=1227 +X https://download.processing.org/bugzilla/1227.html X add loading.gif to the js version of the applet loader X updating JRE to 6u20, and use new Google URL X copy the html tool from the arduino guys X http://code.google.com/p/processing/issues/detail?id=271 X Java update on OS X causing lots of problems -X http://dev.processing.org/bugs/show_bug.cgi?id=1564 -X http://dev.processing.org/bugs/show_bug.cgi?id=1569 +X https://download.processing.org/bugzilla/1564.html +X https://download.processing.org/bugzilla/1569.html X http://code.google.com/p/processing/issues/detail?id=258 o check into focus issues--probably using old focus api X add option to disable re-opening previous projects -X http://dev.processing.org/bugs/show_bug.cgi?id=1501 +X https://download.processing.org/bugzilla/1501.html X http://code.google.com/p/processing/issues/detail?id=245 0184 pde (pre) X other libraries that use opengl weren't using the jnlp launcher X fix OpenGL detection in sketches so that proper version of X export template is used -X http://dev.processing.org/bugs/show_bug.cgi?id=1530 +X https://download.processing.org/bugzilla/1530.html X single-line html comments not handled properly on export -X http://dev.processing.org/bugs/show_bug.cgi?id=1419 +X https://download.processing.org/bugzilla/1419.html X antlr grammar should not need to re-build unless changed -X http://dev.processing.org/bugs/show_bug.cgi?id=1541 +X https://download.processing.org/bugzilla/1541.html X many syntax errors now report "you're mixing active and static modes" -X http://dev.processing.org/bugs/show_bug.cgi?id=1539 +X https://download.processing.org/bugzilla/1539.html jdf stuff J unterminated string constant with '\"' -J http://dev.processing.org/bugs/show_bug.cgi?id=1534 +J https://download.processing.org/bugzilla/1534.html J "expecting LPAREN, found '='" with syntax errors -J http://dev.processing.org/bugs/show_bug.cgi?id=1532 +J https://download.processing.org/bugzilla/1532.html J Bracket idenfication {} is not aware of comments // -J http://dev.processing.org/bugs/show_bug.cgi?id=1450 +J https://download.processing.org/bugzilla/1450.html J inconsistent bracket highlighting with comments -J http://dev.processing.org/bugs/show_bug.cgi?id=43 +J https://download.processing.org/bugzilla/43.html J "unexpected token void" -> "You're mixing dynamic and static mode" -J http://dev.processing.org/bugs/show_bug.cgi?id=14 +J https://download.processing.org/bugzilla/14.html can't reproduce o shortcuts not working after rename? (osx and windows confirmed) -o http://dev.processing.org/bugs/show_bug.cgi?id=505 +o https://download.processing.org/bugzilla/505.html o undo has become sluggish o http://processing.org/bugs/show_bug.cgi?id=36 @@ -5710,7 +6996,7 @@ X additional updates to autoformat, jdf adding tests fixed in earlier releases X export application including video capture broken in snowleopard -X http://dev.processing.org/bugs/show_bug.cgi?id=1440 +X https://download.processing.org/bugzilla/1440.html X fixed in an earlier release @@ -5718,7 +7004,7 @@ X fixed in an earlier release preprocessor fixes J Compile errors for greater than/less than with parentheses -J http://dev.processing.org/bugs/show_bug.cgi?id=1525 +J https://download.processing.org/bugzilla/1525.html autoformat fixes o install astyle formatter @@ -5726,68 +7012,68 @@ o build on each platform, then "lipo -create astyle.* -output astyle" o break out as its own plugin J removes code with extra parens or braces J (an extra } brace deleting everything after it) -J http://dev.processing.org/bugs/show_bug.cgi?id=109 +J https://download.processing.org/bugzilla/109.html J progressively eats code w/ certain brace settings -J http://dev.processing.org/bugs/show_bug.cgi?id=235 +J https://download.processing.org/bugzilla/235.html J Auto Format hangs when a non-terminated ' is included -J http://dev.processing.org/bugs/show_bug.cgi?id=236 +J https://download.processing.org/bugzilla/236.html J hex colors don't get formatted -J http://dev.processing.org/bugs/show_bug.cgi?id=303 +J https://download.processing.org/bugzilla/303.html J another code example that crashes -J http://dev.processing.org/bugs/show_bug.cgi?id=363 +J https://download.processing.org/bugzilla/363.html J arrays are reformatted on each autoformat -J http://dev.processing.org/bugs/show_bug.cgi?id=259 +J https://download.processing.org/bugzilla/259.html J Auto Format says "too many right parentheses" when last line is a comment -J http://dev.processing.org/bugs/show_bug.cgi?id=754 +J https://download.processing.org/bugzilla/754.html J japanese characters in comments cause trouble with auto-format J (this may have been fixed in recent releases, not checked) J http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1153505384 J "too many right parens" error when there are not -J http://dev.processing.org/bugs/show_bug.cgi?id=867 +J https://download.processing.org/bugzilla/867.html J code without a new line at end crashes -J http://dev.processing.org/bugs/show_bug.cgi?id=880 +J https://download.processing.org/bugzilla/880.html J autoformat screws up with color arrays -J http://dev.processing.org/bugs/show_bug.cgi?id=760 +J https://download.processing.org/bugzilla/760.html 0181 pde (pre-release) preprocessor J unterminated string not caught by debugger -J http://dev.processing.org/bugs/show_bug.cgi?id=425 +J https://download.processing.org/bugzilla/425.html J allow doubles in preproc J (for casting, etc) particularly for Math.cos() et al J http://processing.org/bugs/show_bug.cgi?id=7 J Comments with non-ascii characters before import -> java.lang.OutOfMemoryError -J http://dev.processing.org/bugs/show_bug.cgi?id=1511 +J https://download.processing.org/bugzilla/1511.html J more preprocessor issues with new syntax -J http://dev.processing.org/bugs/show_bug.cgi?id=1515 -J http://dev.processing.org/bugs/show_bug.cgi?id=1516 -J http://dev.processing.org/bugs/show_bug.cgi?id=1517 -J http://dev.processing.org/bugs/show_bug.cgi?id=1518 -J http://dev.processing.org/bugs/show_bug.cgi?id=1519 +J https://download.processing.org/bugzilla/1515.html +J https://download.processing.org/bugzilla/1516.html +J https://download.processing.org/bugzilla/1517.html +J https://download.processing.org/bugzilla/1518.html +J https://download.processing.org/bugzilla/1519.html 0180 pde (pre-release) preprocessor J Update ANTLR grammar to support 1.5 syntax -J http://dev.processing.org/bugs/show_bug.cgi?id=598 +J https://download.processing.org/bugzilla/598.html J non-void functions confuse preprocessor -J http://dev.processing.org/bugs/show_bug.cgi?id=1442 +J https://download.processing.org/bugzilla/1442.html J casting problems in the parser J straighten out int() -> toInt() conversions J float u = float(x)/width; works. J float u = (float(x)/width); doesn't work: "unexpected token: float". J float u = (x/float(width)); works! -J http://dev.processing.org/bugs/show_bug.cgi?id=4 +J https://download.processing.org/bugzilla/4.html J return (int(5.5)) causes an error J preprocessor error if last line of code is a comment with no CR after it, J an OutOfMemoryError wants to happen, J but right now there's a hack to add a CR in PdePreprocessor -J http://dev.processing.org/bugs/show_bug.cgi?id=5 +J https://download.processing.org/bugzilla/5.html J preproc can't handle labels to break/continue nested loops -J http://dev.processing.org/bugs/show_bug.cgi?id=631 +J https://download.processing.org/bugzilla/631.html J toInt() needs to go away in time for the book.. fix parser bugs J add local variables to PdeRecognizer J remove static methods in PdePreprocessor, instead pull things from recog @@ -5796,44 +7082,44 @@ J (just gets removed by the preprocessor) J http://processing.org/bugs/show_bug.cgi?id=6 J unspecified return type creates compile error J or maybe not? followup said maybe not -J http://dev.processing.org/bugs/show_bug.cgi?id=379 +J https://download.processing.org/bugzilla/379.html J (already marked invalid -- jdf) J Blah.class will confuse the preproc -J http://dev.processing.org/bugs/show_bug.cgi?id=481 +J https://download.processing.org/bugzilla/481.html J non-matching curly brackets sometimes don't cause an error -J http://dev.processing.org/bugs/show_bug.cgi?id=507 +J https://download.processing.org/bugzilla/507.html J (duplicate of #6) J NullPointerException on unterminated comment at end of code J and OutOfMemoryError and weird lockup -J http://dev.processing.org/bugs/show_bug.cgi?id=16 +J https://download.processing.org/bugzilla/16.html J "missing a /* from the end of a comment" message -J http://dev.processing.org/bugs/show_bug.cgi?id=1512 +J https://download.processing.org/bugzilla/1512.html J "multipart/*" produces the error J even though things are inside a quoted txt block J NullPointerException on unterminated comment at end of code J and OutOfMemoryError and weird lockup -J http://dev.processing.org/bugs/show_bug.cgi?id=16 +J https://download.processing.org/bugzilla/16.html J also OutOfMemoryError if a quote is not terminated -J http://dev.processing.org/bugs/show_bug.cgi?id=763 +J https://download.processing.org/bugzilla/763.html J Error compiling when 'new' is first word in an 'else' block -J http://dev.processing.org/bugs/show_bug.cgi?id=1362 +J https://download.processing.org/bugzilla/1362.html J Parsing error when using char literals -J http://dev.processing.org/bugs/show_bug.cgi?id=281 +J https://download.processing.org/bugzilla/281.html J support for assertions -J http://dev.processing.org/bugs/show_bug.cgi?id=1188 +J https://download.processing.org/bugzilla/1188.html J syntax highlighting error with // and /* -J http://dev.processing.org/bugs/show_bug.cgi?id=609 +J https://download.processing.org/bugzilla/609.html 0179 pde (1.1) X another fix for CDATA section in applet.html for exported applets X updated about.jpg X Cannot find PDF library -X http://dev.processing.org/bugs/show_bug.cgi?id=1473 +X https://download.processing.org/bugzilla/1473.html X new examples.zip o Fixes for invoking Processing from arbitrary locations X already fixed with earlier code -X http://dev.processing.org/bugs/show_bug.cgi?id=1214 +X https://download.processing.org/bugzilla/1214.html X hide android tools if it's an actual release version @@ -5847,18 +7133,18 @@ X no changes 0176 pde (private) X hitting ESC inside Color Selector will quit Processing -X http://dev.processing.org/bugs/show_bug.cgi?id=1006 +X https://download.processing.org/bugzilla/1006.html X need to set exported applications to only run as 32-bit on osx X otherwise quicktime will break X lock the minimum size for the main processing editor frame X fix from Chris Lonnen X if it's made too small, stuff from the bottom disappears -X http://dev.processing.org/bugs/show_bug.cgi?id=25 +X https://download.processing.org/bugzilla/25.html X http://java.sun.com/javase/6/webnotes/install/jre/autodownload.html X http://java.sun.com/update/1.6.0/jinstall-6u18-windows-i586.cab X http://java.sun.com/javase/6/docs/technotes/guides/jweb/deployment_advice.html X http://java.sun.com/javase/6/webnotes/family-clsid.html -X http://dev.processing.org/bugs/show_bug.cgi?id=1353 +X https://download.processing.org/bugzilla/1353.html X switch to better deployment script for applets o download java kernel X not gonna bother with this -- download is not the issue w/ java @@ -5867,31 +7153,31 @@ X compile antlr inside the initial setup of the work dir X done for macosx X fix this for windows and linux X PApplet.main() overwritten -X http://dev.processing.org/bugs/show_bug.cgi?id=1446 +X https://download.processing.org/bugzilla/1446.html o need to do a better job of error handling inside main() X applets now use the deployjava.js file X not opengl, but the others do X NullPointerException in JOGLAppletLanucher with Java 6 Update 18 on Windows X switching to more efficient JNLP export -X http://dev.processing.org/bugs/show_bug.cgi?id=1452 +X https://download.processing.org/bugzilla/1452.html X lack of java.awt.Polygon import breaks the yellowtail example X and java.io.File (other exceptions?) o just add a simple poly class? or don't use a poly? X add imports for anything that's in the reference (selections from java.io) o 'Array' in reference should be 'arrays' lowercase X processing 0142 japanese input problem -X http://dev.processing.org/bugs/show_bug.cgi?id=854 +X https://download.processing.org/bugzilla/854.html X update JNA to version 3.2.4 to support Windows 7 64-bit -X http://dev.processing.org/bugs/show_bug.cgi?id=1424 +X https://download.processing.org/bugzilla/1424.html X fix LITERAL_class in PDE code (help from Christian Thiemann) -X http://dev.processing.org/bugs/show_bug.cgi?id=1466 +X https://download.processing.org/bugzilla/1466.html X replace applet.html and applet-opengl.html -X http://dev.processing.org/bugs/show_bug.cgi?id=1057 +X https://download.processing.org/bugzilla/1057.html X update applet.html to point at java 6u10 with the new auto-update stuff o also update applet on the home page to do the same X move build scripts to something better like ant X too much to maintain the multiple versions, too much code -X http://dev.processing.org/bugs/show_bug.cgi?id=151 +X https://download.processing.org/bugzilla/151.html X remove the intermediate build folders X move the jdk stuff outside of the build X http://processing.org/download/jre-6u18.zip, .tgz @@ -5908,10 +7194,10 @@ X this was fixed in a much earlier release X create font with user-specified charsets o remember previous font selection when returning to the window X "smooth" option being ignored in the Create Font tool -X http://dev.processing.org/bugs/show_bug.cgi?id=1461 +X https://download.processing.org/bugzilla/1461.html X other font bugs handled -X http://dev.processing.org/bugs/show_bug.cgi?id=98 -X http://dev.processing.org/bugs/show_bug.cgi?id=1111 +X https://download.processing.org/bugzilla/98.html +X https://download.processing.org/bugzilla/1111.html o when resizing window, only resize the text display area o just a matter of moving around the panels and BorderLayout X it's a mess, just disable resizing @@ -5921,11 +7207,11 @@ X update to jogl 1.1.1 X do this with enhanced library/platform support X see how platform is determined by jogl / use for lib import X apple bug may have been fixed -X http://dev.processing.org/bugs/show_bug.cgi?id=786 +X https://download.processing.org/bugzilla/786.html X javadoc "advanced" reference by beta X and then finalizing it towards 1.0 X not saving sketch when exiting (only with windows lan setup) -X http://dev.processing.org/bugs/show_bug.cgi?id=1193 +X https://download.processing.org/bugzilla/1193.html 0175 pde (private) @@ -5936,7 +7222,7 @@ X when shift is down, change text of the toolbar item 0174 pde (private) X fix ant.jar/ant-launcher.jar error in the windows/linux build scripts -X http://dev.processing.org/bugs/show_bug.cgi?id=1403 +X https://download.processing.org/bugzilla/1403.html X replace com.apple.eawt.Application invocation to deal with deprecation X this may cause problems with older releases (or on 10.4 or 10.5), not sure X application = new com.apple.eawt.Application(); @@ -5945,106 +7231,106 @@ X application = com.apple.eawt.Application.getApplication(); 0173 pde (private) X change build scripts to use UTF-8 for encoding with javac -X http://dev.processing.org/bugs/show_bug.cgi?id=1394 +X https://download.processing.org/bugzilla/1394.html X fix problem with Android HTML dialog box X several
          items showing up when first loading -X http://dev.processing.org/bugs/show_bug.cgi?id=1395 +X https://download.processing.org/bugzilla/1395.html 0172 pde (private) X use xdg-open as launcher on linux -X http://dev.processing.org/bugs/show_bug.cgi?id=1358 +X https://download.processing.org/bugzilla/1358.html X change to different class id for export and export-opengl X Default wildcard imports are causing naming conflicts -X http://dev.processing.org/bugs/show_bug.cgi?id=1103 +X https://download.processing.org/bugzilla/1103.html X imports inside comments are being included 0171 pde (1.0.9) X ugh, remove the dimmed menus -X http://dev.processing.org/bugs/show_bug.cgi?id=786 +X https://download.processing.org/bugzilla/786.html 0170 pde (1.0.8) X update java on linux/windows to 6u16 X update quaqua to 6.0.1 X preferences broken -X http://dev.processing.org/bugs/show_bug.cgi?id=1320 -X http://dev.processing.org/bugs/show_bug.cgi?id=1322 -X http://dev.processing.org/bugs/show_bug.cgi?id=1325 -X http://dev.processing.org/bugs/show_bug.cgi?id=1329 -X http://dev.processing.org/bugs/show_bug.cgi?id=1336 -X http://dev.processing.org/bugs/show_bug.cgi?id=1337 -X http://dev.processing.org/bugs/show_bug.cgi?id=1344 +X https://download.processing.org/bugzilla/1320.html +X https://download.processing.org/bugzilla/1322.html +X https://download.processing.org/bugzilla/1325.html +X https://download.processing.org/bugzilla/1329.html +X https://download.processing.org/bugzilla/1336.html +X https://download.processing.org/bugzilla/1337.html +X https://download.processing.org/bugzilla/1344.html X check for usequartz problems in snow leopard X "Unrecognized option: -d32" on OS X 10.4 -X http://dev.processing.org/bugs/show_bug.cgi?id=1324 +X https://download.processing.org/bugzilla/1324.html X slow response or spinning wheel on osx X remove menu dimming code -X http://dev.processing.org/bugs/show_bug.cgi?id=786 +X https://download.processing.org/bugzilla/786.html X new version of minim: 2.0.2 X http://code.compartmental.net/tools/minim/ X Outdated "Get the latest Java Plug-in here" -X http://dev.processing.org/bugs/show_bug.cgi?id=1331 +X https://download.processing.org/bugzilla/1331.html X when disk is full, Processing zeroes files when it tries to save changes X even saveStream() does this.. simple fix, b/c it's all one fxn X or at least should be routed through a single fxn -X http://dev.processing.org/bugs/show_bug.cgi?id=967 +X https://download.processing.org/bugzilla/967.html X problems with save on close -X http://dev.processing.org/bugs/show_bug.cgi?id=1193 +X https://download.processing.org/bugzilla/1193.html X deleting new tab causes troubles X florian had another version of this -X http://dev.processing.org/bugs/show_bug.cgi?id=1332 +X https://download.processing.org/bugzilla/1332.html X Saving the project with the same name as an existing tab may delete code -X http://dev.processing.org/bugs/show_bug.cgi?id=1102 +X https://download.processing.org/bugzilla/1102.html X after deleting a tab code still get's preprocessed, pde file is not forgotten -X http://dev.processing.org/bugs/show_bug.cgi?id=1092 +X https://download.processing.org/bugzilla/1092.html 0169 pde (1.0.7) X Erroneous line highlighting is off by one if there is no setup() routine -X http://dev.processing.org/bugs/show_bug.cgi?id=1263 +X https://download.processing.org/bugzilla/1263.html X Auto-format kills Unicode characters -X http://dev.processing.org/bugs/show_bug.cgi?id=1312 +X https://download.processing.org/bugzilla/1312.html X tweaks for Mac OS X Snow Leopard, to force it to run in 32-bit mode 0168 pde (1.0.6) X suggest declaring PDF's members protected -X http://dev.processing.org/bugs/show_bug.cgi?id=1276 +X https://download.processing.org/bugzilla/1276.html X update Info.plist to be 32/64 explicit and also the stubs for update 4 X problems with jogl expired certificates X https://jogl.dev.java.net/servlets/ProjectDocumentList?folderID=9260&expandFolder=9260&folderID=0 -X http://dev.processing.org/bugs/show_bug.cgi?id=1271 +X https://download.processing.org/bugzilla/1271.html X preferences.txt selectable (or open parent folder) X but probably needs to prevent people from editing while in use -X http://dev.processing.org/bugs/show_bug.cgi?id=1279 +X https://download.processing.org/bugzilla/1279.html X Auto format problem with program deeper then 10 levels -X http://dev.processing.org/bugs/show_bug.cgi?id=1297 +X https://download.processing.org/bugzilla/1297.html X fix a crash on startup problem (console being null) X Recursive subfolder copy when exporting application -X http://dev.processing.org/bugs/show_bug.cgi?id=1295 +X https://download.processing.org/bugzilla/1295.html X java update 15 for windows and linux fixed earlier (java 6 update) X mangled menu text with java 6u10 o need to try adding the d3d flag to the .exe -X http://dev.processing.org/bugs/show_bug.cgi?id=1065 +X https://download.processing.org/bugzilla/1065.html o -Dsun.java2d.noddraw=true contributed X can't launch from a symlink in /usr/bin -X http://dev.processing.org/bugs/show_bug.cgi?id=825 +X https://download.processing.org/bugzilla/825.html X fixed by Ferdinand Kasper of Vienna 0167 pde (1.0.5) X fix tab key and focus issue in the editor -X http://dev.processing.org/bugs/show_bug.cgi?id=1267 +X https://download.processing.org/bugzilla/1267.html X Support for smooth text in the PDE editor -X http://dev.processing.org/bugs/show_bug.cgi?id=1266 +X https://download.processing.org/bugzilla/1266.html X duplicate entries for sketchbook in the file menu -X http://dev.processing.org/bugs/show_bug.cgi?id=1260 +X https://download.processing.org/bugzilla/1260.html 0166 pde (1.0.4) @@ -6052,18 +7338,18 @@ X try adding a space to the name of the help menu for beachball problems X this works, might be useful to make the switch (done) X remove isManagingFocus problem inside JEditTextArea X IDE crashed when changing color scheme on windows -X http://dev.processing.org/bugs/show_bug.cgi?id=1237 +X https://download.processing.org/bugzilla/1237.html X space in front of linux shell script prevents it from running -X http://dev.processing.org/bugs/show_bug.cgi?id=1250 +X https://download.processing.org/bugzilla/1250.html X macosx finder info on application says 1.0.1 for 1.0.3 X fix build script to catch this / make it update the file -X http://dev.processing.org/bugs/show_bug.cgi?id=1226 +X https://download.processing.org/bugzilla/1226.html X update to java 6u13 on windows X fix error message "Non-String for 8 value in 'Properties' sub-dictionary in 'Java' sub-dictionary of Info.plist" X -X option not supported in 10.4 -X http://dev.processing.org/bugs/show_bug.cgi?id=1179 +X https://download.processing.org/bugzilla/1179.html X Slow response or spinning wheel from the menu bar on Mac OS X -X http://dev.processing.org/bugs/show_bug.cgi?id=786 +X https://download.processing.org/bugzilla/786.html X disable sketchbook and examples menus on os x? X update to java 6u14 on windows X update to java 6u14 on linux @@ -6075,62 +7361,62 @@ X no changes in this release 0164 pde (1.0.2) X Empty "code" folder causes problems with Export -X http://dev.processing.org/bugs/show_bug.cgi?id=1084 +X https://download.processing.org/bugzilla/1084.html X 8.3 filenames being used when opening sketch -X http://dev.processing.org/bugs/show_bug.cgi?id=1089 +X https://download.processing.org/bugzilla/1089.html X add -X switch to cp on osx build so that extended attrs are not copied -X http://dev.processing.org/bugs/show_bug.cgi?id=1098 +X https://download.processing.org/bugzilla/1098.html X add JVMArchs to Info.plist so that stupid Apple error msg doesn't appear X StringIndexOutOfBoundsException caused by import statements with no dots -X http://dev.processing.org/bugs/show_bug.cgi?id=1145 +X https://download.processing.org/bugzilla/1145.html X Pressing in "Are you sure you want to Quit?" dialog quits Processing -X http://dev.processing.org/bugs/show_bug.cgi?id=1134 +X https://download.processing.org/bugzilla/1134.html X Fix QUADS and QUAD_STRIP with P2D -X http://dev.processing.org/bugs/show_bug.cgi?id=1162 +X https://download.processing.org/bugzilla/1162.html X ArrayIndexOutOfBoundsException when drawing curves in P3D and OPENGL -X http://dev.processing.org/bugs/show_bug.cgi?id=1153 +X https://download.processing.org/bugzilla/1153.html X problems with negatve arc() angles in OpenGL, P3D, other inconsistencies -X http://dev.processing.org/bugs/show_bug.cgi?id=1095 +X https://download.processing.org/bugzilla/1095.html invalid X Sketchbook sub-menu is empty after changing Sketchbook location preference -X http://dev.processing.org/bugs/show_bug.cgi?id=1123 +X https://download.processing.org/bugzilla/1123.html X something about setting memory options -X http://dev.processing.org/bugs/show_bug.cgi?id=1159 +X https://download.processing.org/bugzilla/1159.html X something bizarre about syntax errors -X http://dev.processing.org/bugs/show_bug.cgi?id=1161 +X https://download.processing.org/bugzilla/1161.html X "JDWP unable to initialize: Error 111 from JNI GetEnv" on Mac OS X PPC -X http://dev.processing.org/bugs/show_bug.cgi?id=959 +X https://download.processing.org/bugzilla/959.html X Saving sketch with the same name as a class or primitive breaks sketch -X http://dev.processing.org/bugs/show_bug.cgi?id=1165 +X https://download.processing.org/bugzilla/1165.html earlier X "An error occurred while starting the application" with Processing 0154+ X Maybe provide the old exe or another alternative? X Have someone try this on lab machines until we can find one that breaks -X http://dev.processing.org/bugs/show_bug.cgi?id=986 +X https://download.processing.org/bugzilla/986.html 0163 pde (1.0.1) X ArrayIndexOutOfBoundsException with File > New (Processing 1.0) X maybe a /tmp permissions problem? X are we not checking errors properly on this route? -X http://dev.processing.org/bugs/show_bug.cgi?id=1067 +X https://download.processing.org/bugzilla/1067.html X need to look into why this didn't give a better error message X "[JavaAppLauncher Error] CallStaticVoidMethod() threw an exception" X on startup with OS X -X http://dev.processing.org/bugs/show_bug.cgi?id=1063 -X http://dev.processing.org/bugs/show_bug.cgi?id=1078 +X https://download.processing.org/bugzilla/1063.html +X https://download.processing.org/bugzilla/1078.html X Fix some "An error occurred while starting the application" problems X due to the weird sketch folder naming issue X implement multi-line tab via tab key (also outdent) o add preference for indent size X the bracket isn't working on osx because of an apple menu bug -X http://dev.processing.org/bugs/show_bug.cgi?id=1075 +X https://download.processing.org/bugzilla/1075.html X "editor.indent" setting does not work properly -X http://dev.processing.org/bugs/show_bug.cgi?id=1073 +X https://download.processing.org/bugzilla/1073.html X "space-import-space-quote-semicolon" Causes Error in String or Comment -X http://dev.processing.org/bugs/show_bug.cgi?id=1064 +X https://download.processing.org/bugzilla/1064.html X the changes page doesn't have a toc entry for the 1.0 release notes X add minim to the changes page @@ -6139,7 +7425,7 @@ X add minim to the changes page X update revisions.html X write revisions.txt X in 0149, removed /System/Library/Java -X http://dev.processing.org/bugs/show_bug.cgi?id=1045 +X https://download.processing.org/bugzilla/1045.html X do we need to shore up server setup for 1.0 release pounding? o what's the deal with disk space? o update known problems reference @@ -6153,7 +7439,7 @@ X fix dist scripts so that the name "1.0" can be used X Ignore dot files (.DS_Store), dot folders (.svn) while copying X on mac, window opens to prevent quit on close X but the window is not properly set as untitled -X http://dev.processing.org/bugs/show_bug.cgi?id=700 +X https://download.processing.org/bugzilla/700.html X doesn't need to be fixed because of new setup for closing @@ -6180,12 +7466,12 @@ o why is quaqua making the error window enormous? X not sure why, but just inserted
          elements to fix it X write revisions.txt updates based on 0158 changes X Error after creating more than 26 temporary sketches in one day -X http://dev.processing.org/bugs/show_bug.cgi?id=1039 +X https://download.processing.org/bugzilla/1039.html X AIOOBE when you get to z on new sketches X improve export application window layout spacing (osx) X also disable stop button item when full screen not selected o Focus not returning to editor properly on Linux -X http://dev.processing.org/bugs/show_bug.cgi?id=1031 +X https://download.processing.org/bugzilla/1031.html X can't get this to replicate in previous release (0158) @@ -6197,7 +7483,7 @@ X update to java 6u10 for linux and windows 0158 pde X missing semicolons - better error message -o http://dev.processing.org/bugs/show_bug.cgi?id=12 +o https://download.processing.org/bugzilla/12.html o need to highlight the previous line as well (or instead) X clean up some of the new/open code internally X export to application options dialog @@ -6225,7 +7511,7 @@ X this seems to have fixed itself X change windows and linux to use jdk 6u10 X pressing run turns up nothing o (in particular while cpu load is a little higher on g5?) -o http://dev.processing.org/bugs/show_bug.cgi?id=852 +o https://download.processing.org/bugzilla/852.html o this may just be a macosx (ppc?) bug o check if platform is MACOSX and font is monospaced (?) o if so, nuke the setting and change it back to Monaco @@ -6245,9 +7531,9 @@ X also deal with hint() changes 0157 pde X show sketch folder fails for directories containing umlauts -X http://dev.processing.org/bugs/show_bug.cgi?id=1010 +X https://download.processing.org/bugzilla/1010.html X Find in Reference does not open Firefox in 0156 for Linux -X http://dev.processing.org/bugs/show_bug.cgi?id=1012 +X https://download.processing.org/bugzilla/1012.html X add error message when trying to open sketch from the menu X check into use of platform names in export sketch / export application X opting not to deal with this because of size of populations @@ -6260,36 +7546,36 @@ X fix typo in sketch renaming notice message X line numbers not showing up for unknown var or class names o "anything named" error is weird X don't open more than one "create font" or "color selector" -X http://dev.processing.org/bugs/show_bug.cgi?id=830 +X https://download.processing.org/bugzilla/830.html X make processing tools use the tools api -X http://dev.processing.org/bugs/show_bug.cgi?id=886 +X https://download.processing.org/bugzilla/886.html X rename GettingStarted_Shape example invalid o launch4j "An error occurred while starting the application" -o http://dev.processing.org/bugs/show_bug.cgi?id=986 +o https://download.processing.org/bugzilla/986.html o tabs menu not working on osx ppc (can't confirm) -o http://dev.processing.org/bugs/show_bug.cgi?id=993 +o https://download.processing.org/bugzilla/993.html 0155 pde X "Save canceled" message when saving untitled sketches without first renaming -X http://dev.processing.org/bugs/show_bug.cgi?id=987 +X https://download.processing.org/bugzilla/987.html X when saving a sketch over itself with "Save As", just do "Save" instead X fix loadShape() transformation parsing and empty paths (thanks ricardmp) -X http://dev.processing.org/bugs/show_bug.cgi?id=982 +X https://download.processing.org/bugzilla/982.html X moviemaker can't make exact 30fps output -X http://dev.processing.org/bugs/show_bug.cgi?id=988 +X https://download.processing.org/bugzilla/988.html X automatically create and open the 'libraries' folder when there's an error 0154 pde X clean up PATH for users who have garbage in the PATH -X http://dev.processing.org/bugs/show_bug.cgi?id=974 +X https://download.processing.org/bugzilla/974.html X disallow .java tabs with same name as the sketch X a .java tab with same name as the sketch is allowed (oog!) X particularly look at "save as" scenario -X http://dev.processing.org/bugs/show_bug.cgi?id=543 +X https://download.processing.org/bugzilla/543.html X bug report: X create a new sketch, write something in it X create a new tab, name it "something.java", write something into it @@ -6298,34 +7584,34 @@ X the contents in the files are not the same, but the main-tab is X showing the contents of the .java tab, so if you press save X you will overwrite your original code from the main-tab. X com.sun.jdi.AbsentInformationException when running a sketch -X http://dev.processing.org/bugs/show_bug.cgi?id=971 +X https://download.processing.org/bugzilla/971.html cleaning X processing cancels shutdown on mac os x -X http://dev.processing.org/bugs/show_bug.cgi?id=539 +X https://download.processing.org/bugzilla/539.html X fixed in an older version X jikes bugs mean some code just won't compile: X include javac? would this be a good solution for linux? -X http://dev.processing.org/bugs/show_bug.cgi?id=8 +X https://download.processing.org/bugzilla/8.html X an empty .java tab will throw an error -X http://dev.processing.org/bugs/show_bug.cgi?id=10 +X https://download.processing.org/bugzilla/10.html X warn about writing non-1.1 code. -X http://dev.processing.org/bugs/show_bug.cgi?id=11 +X https://download.processing.org/bugzilla/11.html X java.lang.NoClassDefFoundError: quicktime/std/StdQTException X people not installing qt? no QTJAVA set? -X http://dev.processing.org/bugs/show_bug.cgi?id=669 +X https://download.processing.org/bugzilla/669.html o simulate this by removing qtjava.zip, then make a handler for it o which will open the reference for it X use the registry key, and warn the user when it's not there X mouse wheel broken in the text editor? (windows jdk 1.5?) -X http://dev.processing.org/bugs/show_bug.cgi?id=24 +X https://download.processing.org/bugzilla/24.html 0153 pde X delete files before adding, otherwise case changes are not preserved -X http://dev.processing.org/bugs/show_bug.cgi?id=969 +X https://download.processing.org/bugzilla/969.html X ClassNotFoundException: quicktime.std.StdQTException with release 0152 -X http://dev.processing.org/bugs/show_bug.cgi?id=970 +X https://download.processing.org/bugzilla/970.html previous o put the "had to rename sketch" message in the msg bar @@ -6335,28 +7621,28 @@ X just print it to the console 0152 pde X movie.read() breaking, which breaks the examples -X http://dev.processing.org/bugs/show_bug.cgi?id=961 +X https://download.processing.org/bugzilla/961.html X movie width/height broken -X http://dev.processing.org/bugs/show_bug.cgi?id=962 +X https://download.processing.org/bugzilla/962.html 0151 pde o Fix error message spew on Linux when using "Save As" -X http://dev.processing.org/bugs/show_bug.cgi?id=951 +X https://download.processing.org/bugzilla/951.html X can't fix, it's a sun bug X Change sketch naming error to only print to the console 0150 pde X ArrayIndexOutOfBoundsException after pressing Run with release 0149 -X http://dev.processing.org/bugs/show_bug.cgi?id=949 +X https://download.processing.org/bugzilla/949.html X update java to release 1.6.0_07 on windows and linux -X http://dev.processing.org/bugs/show_bug.cgi?id=950 +X https://download.processing.org/bugzilla/950.html 0149 pde X fix problem with error line highlighting -X http://dev.processing.org/bugs/show_bug.cgi?id=888 +X https://download.processing.org/bugzilla/888.html X additional cleanup to various Sketch, Runner, Compiler classes X preproc cleanup X remove various preproc imports @@ -6365,7 +7651,7 @@ X use accurate line numbering internally rather than mashing lines in preproc X remove preprocName X remove appletClassName X command line support -X http://dev.processing.org/bugs/show_bug.cgi?id=219 +X https://download.processing.org/bugzilla/219.html o would still require awt, but would take a sketch on cmd line X modify showError() et al to not use awt when running from command line X build it and then exit @@ -6373,23 +7659,23 @@ X notations have been added to the bug report that cover the plw changes X also an option to launch p5, load a sketch and run it X command to launch p5, load sketch and run in present mode X not that useful because users should just use export application -X http://dev.processing.org/bugs/show_bug.cgi?id=889 +X https://download.processing.org/bugzilla/889.html X Toolmenu won't show until I compile Mangler -X http://dev.processing.org/bugs/show_bug.cgi?id=892 +X https://download.processing.org/bugzilla/892.html X update quaqua to 4.4.7 on macosx (http://www.randelshofer.ch/quaqua/) X now supports 10.5 an 64 bit jnilib X add note to prefs dialog that multiple jar export only works w/o libs -X http://dev.processing.org/bugs/show_bug.cgi?id=907 +X https://download.processing.org/bugzilla/907.html X transport error 202 -X http://dev.processing.org/bugs/show_bug.cgi?id=895 +X https://download.processing.org/bugzilla/895.html X renaming the main tab adds .pde to the sketch folder name 0148 -X http://dev.processing.org/bugs/show_bug.cgi?id=922 +X https://download.processing.org/bugzilla/922.html X not always updating on rename (maybe a mac problem?) -X http://dev.processing.org/bugs/show_bug.cgi?id=56 +X https://download.processing.org/bugzilla/56.html X error messages not clearing the message area -X http://dev.processing.org/bugs/show_bug.cgi?id=912 +X https://download.processing.org/bugzilla/912.html o expecting EOF, found 'void? -o http://dev.processing.org/bugs/show_bug.cgi?id=905 +o https://download.processing.org/bugzilla/905.html o last fixed 5/30, maybe check the svn for those two days o this might be the MULTILINE mess X was a typo in the code @@ -6399,13 +7685,13 @@ X add "environment" to the help menu X fix other instances of match() using the wrong array indices X add additional newline hack so that autoformat complains less X video capture problems with opengl (on mac os x) -X http://dev.processing.org/bugs/show_bug.cgi?id=882 +X https://download.processing.org/bugzilla/882.html X sketch export results in 100x100 default size, regardless of size() setting -X http://dev.processing.org/bugs/show_bug.cgi?id=945 +X https://download.processing.org/bugzilla/945.html X third tab throws NullPointerException -X http://dev.processing.org/bugs/show_bug.cgi?id=940 +X https://download.processing.org/bugzilla/940.html o sketch must be saved to use a constructor -X http://dev.processing.org/bugs/show_bug.cgi?id=929 +X https://download.processing.org/bugzilla/929.html X reference bug, example cannot have same name as an inner class structural @@ -6438,7 +7724,7 @@ o http://processing.org/discourse/yabb/YaBB.cgi?board=Proce55ing_software_bugs o if size() not found in export/compile, ask the user o have size(myWidth, myHeight) set a static var in PGraphics o for the last size that was used, use as default for fill-in field -o http://dev.processing.org/bugs/show_bug.cgi?id=64 +o https://download.processing.org/bugzilla/64.html X using beginGL().. also import javax.media.opengl.*; o lighting will not work o move stuff about getting gl object and java2d stuff here @@ -6470,13 +7756,13 @@ X update match(), write new reference for matchAll() windows launcher X windows jdk sometimes not getting picked up, even if it's there X notes somewhere about choosing the right jvm... -X http://dev.processing.org/bugs/show_bug.cgi?id=878 +X https://download.processing.org/bugzilla/878.html X finding older versions of java on windows -X http://dev.processing.org/bugs/show_bug.cgi?id=545 +X https://download.processing.org/bugzilla/545.html o eclipse launcher for windows o http://dev.eclipse.org/viewcvs/index.cgi/platform-launcher/library/win32/eclipseWin.c?view=markup&content-type=text%2Fvnd.viewcvs-markup&revision=1.12 X make .pde files double-clickable from windows -X http://dev.processing.org/bugs/show_bug.cgi?id=683 +X https://download.processing.org/bugzilla/683.html X on drag, are these already passed to argv[]? o http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/getcommandline.asp o http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/programmersguide/shell_basics/shell_basics_extending/fileassociations/fa_intro.asp @@ -6525,11 +7811,11 @@ X add tools folder to make and dist scripts 0147 pde X inside Sketch.java, don't hardwire the file extension types X arduino uses .c, .cpp, .h instead of .java -X http://dev.processing.org/bugs/show_bug.cgi?id=807 +X https://download.processing.org/bugzilla/807.html X major changes to save/save as/rename setup - keep an eye out X need to add local java folder to path on linux X otherwise if no linux installed, complaining that no java found -X http://dev.processing.org/bugs/show_bug.cgi?id=879 +X https://download.processing.org/bugzilla/879.html tools X refactor code to use more getter/setter methods @@ -6560,7 +7846,7 @@ X get/setSelStart/End X get/setSelText X get/setText X make dynamically loaded plugins and "tools" menu -X http://dev.processing.org/bugs/show_bug.cgi?id=124 +X https://download.processing.org/bugzilla/124.html X tools api: X init() -> run when p5 is first launched o isAvailable() -> true/false whether the option should be dimmed @@ -6590,7 +7876,7 @@ X and getting the error to show up in the window inside p5 X also highlighting the correct line X Exceptions not being reported properly to the PDE X lines with errors not highlighting -X http://dev.processing.org/bugs/show_bug.cgi?id=877 +X https://download.processing.org/bugzilla/877.html X was ok in 0144, but 0145 things broke X probably b/c not catching ex inside the run() method X getMessage() not sufficient for exceptions coming through @@ -6605,15 +7891,15 @@ cleaning X NullPointerException inside setup() comes up weird X because NullPointerException is a RuntimeException X maybe the renderer exception is something different? newrendex? -X http://dev.processing.org/bugs/show_bug.cgi?id=78 +X https://download.processing.org/bugzilla/78.html o weird exception in the run button watcher X http://processing.org/bugs/show_bug.cgi?id=42 X exceptions in draw() apps aren't caught X the program resize(200, 200); just does nothing (doesn't complain) -X http://dev.processing.org/bugs/show_bug.cgi?id=81 +X https://download.processing.org/bugzilla/81.html X exception in setup() on external app doesn't kill run button X also doesn't kill external vm -X http://dev.processing.org/bugs/show_bug.cgi?id=79 +X https://download.processing.org/bugzilla/79.html X fixed in the 0140s o make editor nicer o tab on selection indents whole block @@ -6635,7 +7921,7 @@ X write revisions.txt entry that covers changes since 0135 X format for discourse is ignoring the selection X also shouldn't add (so much) extra space to the beginning and end o add tool for running in jview -o http://dev.processing.org/bugs/show_bug.cgi?id=142 +o https://download.processing.org/bugzilla/142.html o calls export, or only available after export (or when an applet dir exists) o warn user that applet html will be over-written X no longer supporting 1.1 @@ -6643,7 +7929,7 @@ X on startup, make sure that the jdi classes are available X if not, tell the user to install a friggin jdk X this way we can release a windows version w/o java X don't open more than one copy of the preferences window -X http://dev.processing.org/bugs/show_bug.cgi?id=830 +X https://download.processing.org/bugzilla/830.html X modify namespace handling in xml lib X changed getFullName() to getName() X changed getName() to getLocalName() @@ -6653,9 +7939,9 @@ text handling X block comment - don't bother with last line if starting it X (same behavior as eclipse) X add block comment to right-click menu -X http://dev.processing.org/bugs/show_bug.cgi?id=840 +X https://download.processing.org/bugzilla/840.html X add increase/decrease indent to edit and right-click edit menu -X http://dev.processing.org/bugs/show_bug.cgi?id=841 +X https://download.processing.org/bugzilla/841.html fix problems with vm crashing errors (OutOfMemoryError, etc) o too many NPEs on loadimage may freeze the app (visualizar example?) @@ -6670,38 +7956,38 @@ o this is particularly bad with threaded applications 0143 pde X fixed build problems with macosx and linux, thanks to reports o need to compare with localized version of javac strings -X http://dev.processing.org/bugs/show_bug.cgi?id=828 +X https://download.processing.org/bugzilla/828.html X just moving to ecj instead of javac X preproc code showing through since it's on line 0: -X http://dev.processing.org/bugs/show_bug.cgi?id=831 +X https://download.processing.org/bugzilla/831.html X also need to handle "unexpected type" error in that example X "Target VM failed to initialize: VM initialization failed" when trying to run -X http://dev.processing.org/bugs/show_bug.cgi?id=796 +X https://download.processing.org/bugzilla/796.html X add better error message for memory too high 0142 pde X fix "cannot parse error text" with duplicate var declaration X also found that errors were continuing to process after the first -X http://dev.processing.org/bugs/show_bug.cgi?id=820 +X https://download.processing.org/bugzilla/820.html X web colors < 6 chars produce bizarre error: -X http://dev.processing.org/bugs/show_bug.cgi?id=196 +X https://download.processing.org/bugzilla/196.html X fix a problem with errors that are at the end of the codea 0141 pde X hide javac warning messages X "xxxx xxxx uses unsafe operations.", "recompile with -Xlint:xxxx" -X http://dev.processing.org/bugs/show_bug.cgi?id=817 +X https://download.processing.org/bugzilla/817.html X move copy for discourse into the edit menu (ctrl-shift-c) X nanoxml getChildren() et al should use getFullName() not getName() -X http://dev.processing.org/bugs/show_bug.cgi?id=813 +X https://download.processing.org/bugzilla/813.html charset changes X make sure that export is using utf8 for writing the .pde files etc X should be primarily loadFile() and saveFile() inside Base X change pde files to use utf8 -X http://dev.processing.org/bugs/show_bug.cgi?id=743 +X https://download.processing.org/bugzilla/743.html o 1) if file contains binary data and o 2) its mod date is earlier than when p5 0125 was installed o point the user to Tools -> Reload sketch with local encoding @@ -6714,24 +8000,24 @@ X need to specify the default encoding fixed earlier X stop button sometimes causes lockups when libraries or code folder is in use -X http://dev.processing.org/bugs/show_bug.cgi?id=126 +X https://download.processing.org/bugzilla/126.html 0140 pde X fallback locations for sketchbook and data folders are disabled X move openFolder, openFolderAvailable, openURL to Platform classes X can't get documents/prefs folder on vista -X http://dev.processing.org/bugs/show_bug.cgi?id=585 +X https://download.processing.org/bugzilla/585.html X closing until further notice o vista disables aero theme when p5 is run o http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1196016889 X can't seem to verify this X occasional division by zero on windows -X http://dev.processing.org/bugs/show_bug.cgi?id=777 +X https://download.processing.org/bugzilla/777.html X should be fixed, but need to verify once a release candidate is ready X two fixes for readBytesUntil() and bufferUntil() X also not calling serialEvent() -X http://dev.processing.org/bugs/show_bug.cgi?id=96 +X https://download.processing.org/bugzilla/96.html X fix goof with console preference in preferences.txt X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Syntax;action=display;num=1213042400 X need to add usequartz when running externally? @@ -6748,9 +8034,9 @@ X use proper external launcher via debugger api X http://java.sun.com/javase/technologies/core/toolsapis/jpda/ X small gray gap shows up in color picker X getMaximumSize() not being respected on Windows (Linux?) -X http://dev.processing.org/bugs/show_bug.cgi?id=310 +X https://download.processing.org/bugzilla/310.html X same as bug #656 -X http://dev.processing.org/bugs/show_bug.cgi?id=656 +X https://download.processing.org/bugzilla/656.html further cleanup for simpler building (refactor lots of Base) o settings.path.fallback not being used @@ -6765,7 +8051,7 @@ X even though that location might exist already (just word it better) X "changing back to default sketchbook location" X move tools.jar into jre/lib folder X implement windows registry lookups via reflection -X http://dev.processing.org/bugs/show_bug.cgi?id=723 +X https://download.processing.org/bugzilla/723.html X or move to jna for registry? X https://jna.dev.java.net/source/browse/jna/trunk/jnalib/contrib/ntservice/src/jnacontrib/win32/Registry.java?rev=293&view=markup X remove libs from build/shared that are in app/lib @@ -6779,30 +8065,30 @@ X make.sh creating work/lib dirs.. no longer necessary? wontfix X Programs run with release 0136+ are sometimes slower due to Java 1.5 -X http://dev.processing.org/bugs/show_bug.cgi?id=798 +X https://download.processing.org/bugzilla/798.html 0139 pde X update quaqua to 3.9.5 on macosx (http://www.randelshofer.ch/quaqua/) X xml.getIntAttribute() returns a float -X http://dev.processing.org/bugs/show_bug.cgi?id=790 +X https://download.processing.org/bugzilla/790.html X include memory settings with exported applications on macosx -X http://dev.processing.org/bugs/show_bug.cgi?id=803 +X https://download.processing.org/bugzilla/803.html X error highlighting broken -X http://dev.processing.org/bugs/show_bug.cgi?id=795 +X https://download.processing.org/bugzilla/795.html 0138 pde X importing a library results in "expecting EOF, found ..." error -X http://dev.processing.org/bugs/show_bug.cgi?id=788 +X https://download.processing.org/bugzilla/788.html X remove console variable from preferences.txt X run only works with primary window in 0136, 0137 -X http://dev.processing.org/bugs/show_bug.cgi?id=784 +X https://download.processing.org/bugzilla/784.html X throwing a stackoverflowexception because the console is broken o hint(ENABLE_AUTO_GUNZIP) or rather hint(DISABLE_AUTO_GUNZIP) X cannot do this because hint() lives in PGraphics, this is PApplet X code folder being ignored on export to application -X http://dev.processing.org/bugs/show_bug.cgi?id=469 +X https://download.processing.org/bugzilla/469.html X fixed in release 0137 X remove debug messages from export @@ -6813,13 +8099,13 @@ X 1) a lib is in use, 2) code folder, 3) multiple files X remove oro.jar dependency (not needed with PApplet.match) X this is kind of messy and requires a bit of testing to ensure proper X sometimes huge jar files when exporting with a code folder -X http://dev.processing.org/bugs/show_bug.cgi?id=541 +X https://download.processing.org/bugzilla/541.html X applet export with multiple jars having trouble (related?) -X http://dev.processing.org/bugs/show_bug.cgi?id=701 +X https://download.processing.org/bugzilla/701.html X applet export fails with opengl/code folder -X http://dev.processing.org/bugs/show_bug.cgi?id=714 +X https://download.processing.org/bugzilla/714.html X synchronized (something) { } is horking up the preproc -X http://dev.processing.org/bugs/show_bug.cgi?id=136 +X https://download.processing.org/bugzilla/136.html o inside the preproc o change the arrays of default imports (now using 1.5+, so no 1.1,1.3,1.4) X don't bother, they're cumulative @@ -6828,27 +8114,27 @@ X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=os_core_pde;action= X update preferences.txt whenever opening/closing X otherwise recovery from restart can be annoying X fix classpath problem with processing.exe -X http://dev.processing.org/bugs/show_bug.cgi?id=780 +X https://download.processing.org/bugzilla/780.html 0136 pde X fix XMLElement(String s), had a bad constructor X color selector not drawing properly (fix thanks to fli) -X http://dev.processing.org/bugs/show_bug.cgi?id=656 +X https://download.processing.org/bugzilla/656.html X color selector broken on vista (no colors at all) -X http://dev.processing.org/bugs/show_bug.cgi?id=584 +X https://download.processing.org/bugzilla/584.html X added fonts, quadratic curves to svg X need to remove the font stuff, also the changes for 'public' X move my edits into a subclass o Capture.settings() mangles external iSight image X fixed in newer qtjava -o http://dev.processing.org/bugs/show_bug.cgi?id=496 +o https://download.processing.org/bugzilla/496.html X add to p5 app bundle X mention re: apple slowness X -Dapple.awt.graphics.UseQuartz=true X net library dies unceremoniously on "Connection Refused" X just need to catch another exception -X http://dev.processing.org/bugs/show_bug.cgi?id=751 +X https://download.processing.org/bugzilla/751.html X ctrl-/ to comment block X eeepc support for environment: X splitPane.setMinimumSize(new Dimension(600, 600)); @@ -6856,9 +8142,9 @@ X change to: splitPane.setMinimumSize(new Dimension(600, 400)); o prolly need to have a param for this guy X switch to nanoxml instead of nanoxml-lite (29k vs. 5k) X check against ods -X http://dev.processing.org/bugs/show_bug.cgi?id=757 +X https://download.processing.org/bugzilla/757.html X space after OPENGL param breaks export -X http://dev.processing.org/bugs/show_bug.cgi?id=769 +X https://download.processing.org/bugzilla/769.html X svg demos are broken X because of weird ENTITY setup X because of weird (default?) filling problem @@ -6950,7 +8236,7 @@ X we'll support 1.5 a little more now, but only libraries, not syntax X no support for java 1.6 anytime soon X to use 1.5+ syntax, use p5 embedded in eclipse X what's with this fill() bug? -X http://dev.processing.org/bugs/show_bug.cgi?id=468 +X https://download.processing.org/bugzilla/468.html X background() also not making it through to raw recorder X this should be fixed, just double-check X add option to prefs to override memory settings @@ -6965,19 +8251,19 @@ X probably need to traverse into xml and candy folders.. others? 0134 pde X fix problem with fractional px sizes and adobe cs3 -X http://dev.processing.org/bugs/show_bug.cgi?id=667 +X https://download.processing.org/bugzilla/667.html X fix candy bug where width/height were undefined if "px" was used X some svg files include "px" in the width/height parameters X shortcuts in tab menu should use shift X remove shortcut for rename, and use ctrl-shift-n for new tab -X http://dev.processing.org/bugs/show_bug.cgi?id=665 +X https://download.processing.org/bugzilla/665.html X fix problem with "save as" under linux X say that quicktime 6 is no longer supported X winvdig issues? X add note that "quicktime alternative" is not sufficient X problems with serial on leopard X grab rxtx libraries from arduino, they've disabled locking -X http://dev.processing.org/bugs/show_bug.cgi?id=227 +X https://download.processing.org/bugzilla/227.html X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Electronics;action=display;num=1193459849 X add note to reference about close/quit behavior X also add note that export will delete folders @@ -6987,7 +8273,7 @@ X lots of additional ref changes for array functions 0133 pde X fix problem with 'cancel' on "move sketch, create folder, continue"? msg -X http://dev.processing.org/bugs/show_bug.cgi?id=658 +X https://download.processing.org/bugzilla/658.html o archive sketch shouldn't include applet or application dirs o or should it? this was how projects were being uploaded for class... X ESC won't cancel rename @@ -7016,12 +8302,12 @@ X add note about gcj/gij to the platform notes o when writing javadoc for p5 sketches, only write from first tab X already was using code[0] X processing.video not working on windows in 126 through 130 -X http://dev.processing.org/bugs/show_bug.cgi?id=654 +X https://download.processing.org/bugzilla/654.html X auto-delete of sketches might be dangerous X and probably no longer necessary X should toolbar new/open replace the items in the current window? X closing last sketch window... open an untitled document? -X http://dev.processing.org/bugs/show_bug.cgi?id=634 +X https://download.processing.org/bugzilla/634.html X when opening from the toolbar, replaces items in that window X when new/open from menu, creates a new window o don't bug the user about new release of p5 when they have it @@ -7037,10 +8323,10 @@ X add server/client notification thing for the nyu guys X add an event to Server that notifies when a Client disconnects X add docs for disconnectEvent for Client X proccessing.net.Client.write(int) terminates sketch when client disconnects. -X http://dev.processing.org/bugs/show_bug.cgi?id=537 +X https://download.processing.org/bugzilla/537.html X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=LibraryProblems;action=display;num=1180713192 X Server.write(bytes[]) hangs sketch on client disconnect. -X http://dev.processing.org/bugs/show_bug.cgi?id=538 +X https://download.processing.org/bugzilla/538.html o move the useful serial buffering fxns into net library X make the Client list public X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Syntax;action=display;num=1116056805 @@ -7079,7 +8365,7 @@ X though this will only happen when the prefs file is deleted X mark untitled sketches as modified when adding files X opening an already open sketch will result in two identical sketches open X just bring the other window to the front -X http://dev.processing.org/bugs/show_bug.cgi?id=636 +X https://download.processing.org/bugzilla/636.html X replace untitled windows with opened sketches X but do so without opening a new window and hiding the other X re-enable update check (0126 wasn't reporting anything.. ugh) @@ -7095,7 +8381,7 @@ o or in the sketchbook in a subfolder named 'libraries'? o what should the policy be? X keywords.txt needs revamp X CLOSE is missing -X http://dev.processing.org/bugs/show_bug.cgi?id=637 +X https://download.processing.org/bugzilla/637.html X syntax highlighting X add MIN_FLOAT, MAX_FLOAT, MIN_INT, MAX_INT X match() method @@ -7111,7 +8397,7 @@ X http://processing.org/bugs/show_bug.cgi?id=41 0126 pde o support for hashmap<> with 1.5 syntax -o http://dev.processing.org/bugs/show_bug.cgi?id=459 +o https://download.processing.org/bugzilla/459.html X handle open/sketchbook/examples differently X move examples folder to top-level menu X move open menu to its own @@ -7122,16 +8408,16 @@ X added getChild(name/path), getChildren(name/path) to xml library o add notes about these to the reference o needs a better example that includes subitems X fix problem with export.txt files not working -X http://dev.processing.org/bugs/show_bug.cgi?id=625 +X https://download.processing.org/bugzilla/625.html X readStringUntil broken in net library -X http://dev.processing.org/bugs/show_bug.cgi?id=606 +X https://download.processing.org/bugzilla/606.html X add imageicon for the find dialog X also the tools menu items and preferences -X http://dev.processing.org/bugs/show_bug.cgi?id=627 +X https://download.processing.org/bugzilla/627.html X fix problem with ctrl-, typing a comma on linux X on linux, drag and drop didn't accept X gets a zillion different items that come in -X http://dev.processing.org/bugs/show_bug.cgi?id=595 +X https://download.processing.org/bugzilla/595.html X show sketch folder on linux X fix for release 0126 X add notes to the docs about how to fix if not working @@ -7180,24 +8466,24 @@ X make clear that open() with one param should only have a single param 0125 pde X prev/next tab conflicting with typing brackets on french macs -X http://dev.processing.org/bugs/show_bug.cgi?id=480 +X https://download.processing.org/bugzilla/480.html X update javadoc reference to include xml, candy, etc X update to 1.4.2_12 on linux and windows X export to web, wrong character encoding in html -X http://dev.processing.org/bugs/show_bug.cgi?id=474 +X https://download.processing.org/bugzilla/474.html X generate xhtml-1.0-strict (standards compliant) code for exported applets -X http://dev.processing.org/bugs/show_bug.cgi?id=490 +X https://download.processing.org/bugzilla/490.html X add auto-install of cab file inside applet.html -X http://dev.processing.org/bugs/show_bug.cgi?id=181 +X https://download.processing.org/bugzilla/181.html X http://java.sun.com/j2se/1.4.2/docs/guide/plugin/developer_guide/using_tags.html X http://java.sun.com/j2se/1.5.0/docs/guide/javaws/developersguide/launch.html#creating X http://java.sun.com/update/1.4.2/jinstall-1_4_2_09-windows-i586.cab X or the 1.5 versions: X http://java.sun.com/update/1.5.0/jinstall-1_5_0_03-windows-i586.cab X PDE does not refresh code when using an External Editor -X http://dev.processing.org/bugs/show_bug.cgi?id=515 +X https://download.processing.org/bugzilla/515.html X preprocessor cannot handle L or l added to 'long' values -X http://dev.processing.org/bugs/show_bug.cgi?id=492 +X https://download.processing.org/bugzilla/492.html X change constructors for Capture, also framerate to frameRate X need to update reference X need to update example to use proper ordering @@ -7217,10 +8503,10 @@ X find/replace - replace should do auto find next(?) X or have a replace & find button X placing "replace" next to "find" ... (hitting "replace all" by accident) X have a button "replace & find next" -X http://dev.processing.org/bugs/show_bug.cgi?id=68 +X https://download.processing.org/bugzilla/68.html X only rebuild sketchbook on "save as" or "rename" of sketch X currently it's rebuilding whenever "save" called too -X http://dev.processing.org/bugs/show_bug.cgi?id=357 +X https://download.processing.org/bugzilla/357.html X ignore ._ files when reading jar and zip X Ignoring /Users/fry/coconut/processing/build/macosx/work/libraries/opengl/library/._jogl-natives-linux-i586.jar (error in opening zip file) X look into deleting from p5 bugs db @@ -7264,21 +8550,21 @@ X create "Documents" folder for the user if it doesn't exist X just default to the user's home directory if no 'sketchbook' folder X otherwise ask the user to select a folder X fix QTSession.open/close problem in Capture.list() -X http://dev.processing.org/bugs/show_bug.cgi?id=472 +X https://download.processing.org/bugzilla/472.html X fix some of the error handling while running a sketch X now a little better at highlighting the correct line X slight improvements to some preproc/compiler error messages -X http://dev.processing.org/bugs/show_bug.cgi?id=12 -X http://dev.processing.org/bugs/show_bug.cgi?id=13 -X http://dev.processing.org/bugs/show_bug.cgi?id=15 +X https://download.processing.org/bugzilla/12.html +X https://download.processing.org/bugzilla/13.html +X https://download.processing.org/bugzilla/15.html X deal with strange problem with KeyListener and {} on same line -X http://dev.processing.org/bugs/show_bug.cgi?id=484 +X https://download.processing.org/bugzilla/484.html X copy custom applet.html file on "Save As" -X http://dev.processing.org/bugs/show_bug.cgi?id=485 +X https://download.processing.org/bugzilla/485.html o preferences file gone corrupt (on osx only?) o changing font size, o http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1160057791 -X http://dev.processing.org/bugs/show_bug.cgi?id=406 +X https://download.processing.org/bugzilla/406.html o temporarily added log4j and jalopy (for the autoformatter) o need to decide where these go, or if they're included at all @@ -7296,7 +8582,7 @@ X honor the svg designation for layers that are marked 'hidden' X improve editor listener to not add so many extraneous indents X with text selected, hitting left or right arrow should move to beginning/end X right now, it deselects the text and moves over one char -X http://dev.processing.org/bugs/show_bug.cgi?id=349 +X https://download.processing.org/bugzilla/349.html X add alt-shift-arrow-down and command-shift-arrow-down X select all from that point to beginning (or end) of doc X ctrl-shift up/down has some weird issue, but oh well @@ -7306,17 +8592,17 @@ o option for behavior of HOME and END X right now goes to begin/end of line.. should be begin/end of file? X change to begin/end of file, and use cmd or alt, based on platform X cmd-{ and cmd-} no longer work after the menu is shown -X http://dev.processing.org/bugs/show_bug.cgi?id=402 +X https://download.processing.org/bugzilla/402.html X clicking "cancel" on close still quitting the app -X http://dev.processing.org/bugs/show_bug.cgi?id=440 +X https://download.processing.org/bugzilla/440.html X discourse format sucked, changed to format for discourse -X http://dev.processing.org/bugs/show_bug.cgi?id=447 +X https://download.processing.org/bugzilla/447.html X changing macosx to allow Java 1.5 to be used X updated to antlr 2.7.7 fixed earlier or wontfix X double-click only selects part of underscored word -X http://dev.processing.org/bugs/show_bug.cgi?id=261 +X https://download.processing.org/bugzilla/261.html X this is actually a feature svg completed @@ -7342,7 +8628,7 @@ X fix button fatness on osx X quaqua already takes care of this for us X implement page setup and print X pretty printing of code in project -X http://dev.processing.org/bugs/show_bug.cgi?id=27 +X https://download.processing.org/bugzilla/27.html X also turn off line highlighting while printing X fix bug with 'hidden' code causing an error inside rebuildMenu() @@ -7359,7 +8645,7 @@ o file a bug for this stuff X figure out how to cancel 'save changes' on macosx and windows X macosx handleQuit seems to force termination (at least on 1.3) X http://developer.apple.com/qa/qa2001/qa1187.html -X http://dev.processing.org/bugs/show_bug.cgi?id=32 +X https://download.processing.org/bugzilla/32.html X need to test on windows to make sure it works X try the better mac l&f o Contents/Resources/Java can take jnilib files @@ -7367,7 +8653,7 @@ o set file type/creator for .pde files of examples o use disk:// notation as panther alternative o so that it doesn't download the disk, just mounts it o although.. this only works on panther.. how many are using it? -X filed as http://dev.processing.org/bugs/show_bug.cgi?id=431 +X filed as https://download.processing.org/bugzilla/431.html o changing font size in editor not updating all font objects o particularly after backspace X make notice that restart is required @@ -7383,9 +8669,9 @@ X several bits of reference are updated, incorporate new zip file 0119 pde X .java files weren't working in 116+ -X http://dev.processing.org/bugs/show_bug.cgi?id=405 +X https://download.processing.org/bugzilla/405.html X need new copy of Capture example that has a new name -X http://dev.processing.org/bugs/show_bug.cgi?id=408 +X https://download.processing.org/bugzilla/408.html 0118 pde @@ -7415,21 +8701,21 @@ X be more strict about opening sketches with illegal characters X in previous releases, it was possible to rename sketches outside p5 X to something that was illegal. now those files are ignored X better fix for find/replace focus issue on osx -X http://dev.processing.org/bugs/show_bug.cgi?id=244 +X https://download.processing.org/bugzilla/244.html X give that field focus explicitly, rather than just for typing X menu option/command key to switch between tabs -X http://dev.processing.org/bugs/show_bug.cgi?id=55 +X https://download.processing.org/bugzilla/55.html X fix movie playback on intel macs -X http://dev.processing.org/bugs/show_bug.cgi?id=313 +X https://download.processing.org/bugzilla/313.html X when using external editor, update the code on export X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1158549785 o p5 becomes a memory hog (benelek and glen murphy) o not confirmed for a long time o even without sketches open, perhaps not gc'ing properly o objects probably not getting finalized -o http://dev.processing.org/bugs/show_bug.cgi?id=29 +o https://download.processing.org/bugzilla/29.html X add pref item for setting the memory properly -X http://dev.processing.org/bugs/show_bug.cgi?id=233 +X https://download.processing.org/bugzilla/233.html o external apps should inherit memory settings from p5 itself X too confusing to set the memory in two places X or perhaps, have a setting in the ide for it @@ -7441,7 +8727,7 @@ o menu weirdness (benelek) o when you've got a menu open, move a cursor over the text area o and back over the menu, the text-area cursor type remains. X mark this as wontfix, since it's just a java bug -X http://dev.processing.org/bugs/show_bug.cgi?id=30 +X https://download.processing.org/bugzilla/30.html X remove the second movie from the movie playback example X keep checking whether google has started to honor robots.txt X http://www.google.com/search?hl=en&lr=&q=angleMode+site%3Aprocessing.org&btnG=Search @@ -7449,7 +8735,7 @@ X add load/updatePixels stuff to the pixels[] reference X make mention of doing it to main pix array, and on an image X move fwd/back tab into the tab menu X however it includes a bug that loses focus (at least on osx) -X http://dev.processing.org/bugs/show_bug.cgi?id=402 +X https://download.processing.org/bugzilla/402.html X "export folder" tool has been removed, if temporarily X add info to the new faq X where to find out about embedding PApplet (the dev ref) @@ -7474,17 +8760,17 @@ video X quicktime in applets X probably need to do a better job with openStream X video library in applets doesn't work -X http://dev.processing.org/bugs/show_bug.cgi?id=44 +X https://download.processing.org/bugzilla/44.html X may be fixed by simply signing the applet, or may just work in places X exported movies seem to have trouble (windows only?) -X http://dev.processing.org/bugs/show_bug.cgi?id=231 +X https://download.processing.org/bugzilla/231.html X video doesn't work in applets X on export, need to first import applet's packages before qt et al X video working in applets? (no, never did in alpha so untested) X Movie should perhaps work? -X http://dev.processing.org/bugs/show_bug.cgi?id=44 +X https://download.processing.org/bugzilla/44.html X capture.settings() changes size of capture -X http://dev.processing.org/bugs/show_bug.cgi?id=366 +X https://download.processing.org/bugzilla/366.html X fix submitted by hansi faq additions @@ -7567,7 +8853,7 @@ X on unix machines it's also possible to use a symlink o what's the long delay when hitting "save as" on osx? o the first time, it's very slow.. presumably an awt problem X renaming a sketch should rebuild the sketch menu -X http://dev.processing.org/bugs/show_bug.cgi?id=332 +X https://download.processing.org/bugzilla/332.html 0114 pde @@ -7576,7 +8862,7 @@ X fix "ignoring illegal line...macosx is missing libjogl_cg.jnilib" error msg 0113 pde X easier export to gl applet, finalize support for applets -X http://dev.processing.org/bugs/show_bug.cgi?id=166 +X https://download.processing.org/bugzilla/166.html X add additional html file (applet-jogl.html) X parse for renderer on export (and remove comments) X when the renderer is OPENGL, change to: @@ -7586,7 +8872,7 @@ X include the jogl.jar file itself X use separate jar files o can natives be included as jar files without trouble? X make multiple jar files thing work as an option -X http://dev.processing.org/bugs/show_bug.cgi?id=62 +X https://download.processing.org/bugzilla/62.html X applet default is one file, application default is multiple X buttons on side of sketch do default (last) behavior X don't slurp out the contents of code folders when exporting to application @@ -7595,17 +8881,17 @@ X don't slurp out the contents of code folders when exporting to application 0112 pde X PortInUseException with serial for 0111+ on Mac OS X X need to fix the setup scripts to cover the new lock file location -X http://dev.processing.org/bugs/show_bug.cgi?id=315 +X https://download.processing.org/bugzilla/315.html 0111 pde X switch back to rev b3 of jogl so that applets will work X fix color picker: -X http://dev.processing.org/bugs/show_bug.cgi?id=308 +X https://download.processing.org/bugzilla/308.html X also add esc/ctrl-w for closing the picker X update information on mactels in the faq, also java 1.6 X find/build universal version of rxtx -X http://dev.processing.org/bugs/show_bug.cgi?id=311 +X https://download.processing.org/bugzilla/311.html X update the faq @@ -7617,12 +8903,12 @@ X no changes to the pde, only fixes for jogl X turn off resizing of the color picker window X new set of windows quicktime issues introduced in 102+ X seems to be ignoring any paths that contain spaces -X http://dev.processing.org/bugs/show_bug.cgi?id=299 +X https://download.processing.org/bugzilla/299.html X fix windows make.sh (or move back to Makefile) X currently rewrites exe on each build, even if not updated X fix linux dist script to work more like the exported apps X no relative paths, etc -X http://dev.processing.org/bugs/show_bug.cgi?id=298 +X https://download.processing.org/bugzilla/298.html 0108 pde @@ -7640,7 +8926,7 @@ X add discourse formatter tool 0107 pde X fix yet another save bug, context menu paste/cut not setting modified X undoing to the code's original state won't unset it as "modified" -X http://dev.processing.org/bugs/show_bug.cgi?id=248 +X https://download.processing.org/bugzilla/248.html 0106 pde @@ -7654,7 +8940,7 @@ X no changes, only fixes for opengl 0104 pde X removed "yep yep yep" when using "Create Font" X p5 not saving changes on quit, even if you say 'yes' -X http://dev.processing.org/bugs/show_bug.cgi?id=276 +X https://download.processing.org/bugzilla/276.html _ http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1139519266 X update osx for intel binary (if necessary) X if running on 10.4, univerals jikes installed in /usr/bin/jikes @@ -7662,7 +8948,7 @@ X updated the jikes included with p5 to be the universal version X add notes to the faq about status X set java version to be 1.4, not 1.4+ X fix LD_LIBRARY_PATH issues for applications on some linux distros -X http://dev.processing.org/bugs/show_bug.cgi?id=234 +X https://download.processing.org/bugzilla/234.html 0103 pde @@ -7674,7 +8960,7 @@ X p5's exe has trouble when PATH has quotes (or spaces?) X detect bad classpath or path settings X some sort of path/classpath tester/fixer app for windows X that " norton" thing in the path makes things a mess -X http://dev.processing.org/bugs/show_bug.cgi?id=112 +X https://download.processing.org/bugzilla/112.html X CLASSPATH figured out what to do with quotes, but not PATH X quotes has issues on win2k vpc.. useful testbed @@ -7685,7 +8971,7 @@ X no changes, only dipose() method fix for pdf 0100 pde X missing import PApplet in library howto.txt -X http://dev.processing.org/bugs/show_bug.cgi?id=263 +X https://download.processing.org/bugzilla/263.html X switch to using date as default for archive sketch @@ -7693,7 +8979,7 @@ X switch to using date as default for archive sketch X make buttons for editor status taller on macosx X also fix the editor text field placement a bit X hack to fix find/replace issues on macosx -X http://dev.processing.org/bugs/show_bug.cgi?id=70 +X https://download.processing.org/bugzilla/70.html X right now, typing works, but no caret, no blue highlight X and on second find run, should instead select all the find string X so that typing will replace it directly @@ -7704,14 +8990,14 @@ X involve adding key listeners to every friggin component X ESC should fake a cancel button press X ENTER should do the default option X (might be a matter of setting the default action for the window?) -X http://dev.processing.org/bugs/show_bug.cgi?id=34 +X https://download.processing.org/bugzilla/34.html X hack to fix non-terminated multi-line comments -X http://dev.processing.org/bugs/show_bug.cgi?id=16 +X https://download.processing.org/bugzilla/16.html X improved error message for bad sketch names to include the sketch path X http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1134466565 X make editor save button highlight on ctrl-s X same goes for the other editor buttons -X http://dev.processing.org/bugs/show_bug.cgi?id=242 +X https://download.processing.org/bugzilla/242.html X deal with "could not delete stderr.txt" messages X probably screwed up the temp folder stuff X build folder is randomized, being recreated on each build @@ -7729,7 +9015,7 @@ X rather than having it convert to spaces X need a smarter handler (rather than the editor listener) X could look at previous line for its indent X and when hitting } do a proper outdent (if only spaces before it) -X http://dev.processing.org/bugs/show_bug.cgi?id=22 +X https://download.processing.org/bugzilla/22.html o if the previous line contains no ; then next line is indent o need to strip out comments for this tho o maybe preproc through and remove comments to spaces? @@ -7750,20 +9036,20 @@ X move the readme stuff for each file into the files themselves X also put a general rundown of the preproc into Preprocessor.java X subclasses need to use "public void keyPressed" not "void keyPressed" X tweak linux version shell script to properly set directory name -X http://dev.processing.org/bugs/show_bug.cgi?id=234 +X https://download.processing.org/bugzilla/234.html bugs in auto-format X tools need to use a compound edit -X http://dev.processing.org/bugs/show_bug.cgi?id=139 +X https://download.processing.org/bugzilla/139.html X ArrayIndexOutOfBoundsException when trying to Auto Format -X http://dev.processing.org/bugs/show_bug.cgi?id=110 +X https://download.processing.org/bugzilla/110.html X if an error occurs during format, report it and don't change text X this should already be happening anyway 0097 pde export-to-application -X http://dev.processing.org/bugs/show_bug.cgi?id=60 +X https://download.processing.org/bugzilla/60.html X make a note in the faq that it's implemented X add manifest.mf to exported applets so that applications will work X include main class info for executable jar file with jdk > 1.2 @@ -7783,7 +9069,7 @@ X this will need to detect whether the user has their own main() o or even PApplet.main(new String[] { getClass().getName() }); X save code before export, otherwise .pde exported is empty X ask user ok or cancel to save code before exporting -X http://dev.processing.org/bugs/show_bug.cgi?id=157 +X https://download.processing.org/bugzilla/157.html X can macosx jnilib files be placed in the resources dir? X shift-export should export as application X write support for linux export @@ -7801,7 +9087,7 @@ X how to handle qtjava in exports? X problem because on export, the qtjava import happens before lib import X why is this different between compiling and exporting? X was a problem because example was named "Movie" -X http://dev.processing.org/bugs/show_bug.cgi?id=230 +X https://download.processing.org/bugzilla/230.html 0096 pde @@ -7812,12 +9098,12 @@ o gets messy because of how the classpath et al is handled o maybe just split out the preproc? o could avoid doing things like the packages etc X subfolders in the 'data' directory don't work -X http://dev.processing.org/bugs/show_bug.cgi?id=65 +X https://download.processing.org/bugzilla/65.html X package macosx with a dmg -X http://dev.processing.org/bugs/show_bug.cgi?id=116 +X https://download.processing.org/bugzilla/116.html X add "reference" and "examples" categories to bugzilla X add mayscript tag to html when javascript lib is in use -X http://dev.processing.org/bugs/show_bug.cgi?id=211 +X https://download.processing.org/bugzilla/211.html 0095 pde @@ -7827,24 +9113,24 @@ X no changes 0094 pde X don't write stdout.txt/stderr.txt to the build folder X writing to the p5 folder was causing security problems -X http://dev.processing.org/bugs/show_bug.cgi?id=177 +X https://download.processing.org/bugzilla/177.html X bug in console that was causing stderr and stdout X to be written over one another.. oops X console text selection immediately de-selects X suspect console is updated every 250 ms even when the app isn't running X simplest to just not update the console if nothing is waiting in buffer -X http://dev.processing.org/bugs/show_bug.cgi?id=180 +X https://download.processing.org/bugzilla/180.html X problem with using qtjava is probably the quotes.. X remove them because they're matching quotes elsewhere X drag & drop implementation to add files to sketch -X http://dev.processing.org/bugs/show_bug.cgi?id=21 +X https://download.processing.org/bugzilla/21.html X test drag and drop on windows o also test on linux X make simple tool for casey to rebuild all the examples at once X first select a folder, then will open each sketch in turn, and export X just make it easier to go to the next sketch X need to rebuild with this release because of 1.3/1.4 issues -X http://dev.processing.org/bugs/show_bug.cgi?id=117 +X https://download.processing.org/bugzilla/117.html o or add tool to hit 'next' to go through examples o just make it "next file in folder" since examples are all over o also need to copy examples locally @@ -7861,19 +9147,19 @@ X scanning sketchbook folder may be extremely slow X when lots of frames saved out, takes forever to scan the folder X some dumb sorting code was responsible X and each file was being treated as a directory. oops. -X http://dev.processing.org/bugs/show_bug.cgi?id=84 +X https://download.processing.org/bugzilla/84.html o rebuild jikes with --enable-static --disable-shared -X http://dev.processing.org/bugs/show_bug.cgi?id=47 +X https://download.processing.org/bugzilla/47.html X switched to use the version from the rpm on the sf.net site X with fix of bug #47, svn binaries are no longer liabilities on linux -X http://dev.processing.org/bugs/show_bug.cgi?id=48 +X https://download.processing.org/bugzilla/48.html o auto-run the javadoc in dist.sh o doctor a copy of the css file to use p5 defaults o and re-copy the css in after generating the doc each time X timing fix introduce regression on linux X extra (NUL?) chars are added X i.e. on first run, the ten blank lines each have a li'l box -X http://dev.processing.org/bugs/show_bug.cgi?id=118 +X https://download.processing.org/bugzilla/118.html X faq: "my applet doesn't work on export"... where to check for errors X very common: cached version is being used X go through methods to properly flush the cache @@ -7905,7 +9191,7 @@ o convert spaces to underscores and vice versa for sketch/tab names o underscoring everything is kinda nasty o only needs to be underscored when passed off to java o although then if people *want* underscores, there's gonna be trouble -o http://dev.processing.org/bugs/show_bug.cgi?id=76 +o https://download.processing.org/bugzilla/76.html o if in full java mode o if extends PApplet.. or rather, put PApplet cast into a o try/catch block.. if it doesn't work, try applet. if that diff --git a/java/.classpath b/java/.classpath index fa8666d22..5607a9971 100644 --- a/java/.classpath +++ b/java/.classpath @@ -24,6 +24,7 @@ - + + diff --git a/java/application/Info.plist.tmpl b/java/application/Info.plist.tmpl index 5c8c8488f..b6b5ac53f 100644 --- a/java/application/Info.plist.tmpl +++ b/java/application/Info.plist.tmpl @@ -37,6 +37,9 @@ JVMMainClassName @@sketch@@ + JVMVersion + @@jvm_version@@ + LSMinimumSystemVersion 10.14.6 @@ -45,7 +48,7 @@ LSArchitecturePriority - x86_64 + @@lsarchitecturepriority@@ LSEnvironment diff --git a/java/application/application.icns b/java/application/application.icns index e4be24527..3965370e0 100644 Binary files a/java/application/application.icns and b/java/application/application.icns differ diff --git a/java/application/application.ico b/java/application/application.ico index e1af8f97c..c4bbdfa11 100644 Binary files a/java/application/application.ico and b/java/application/application.ico differ diff --git a/java/application/launch4j/bin/ld-macosx b/java/application/launch4j/bin/ld-macos similarity index 100% rename from java/application/launch4j/bin/ld-macosx rename to java/application/launch4j/bin/ld-macos diff --git a/java/application/launch4j/bin/windres-macosx b/java/application/launch4j/bin/windres-macos similarity index 100% rename from java/application/launch4j/bin/windres-macosx rename to java/application/launch4j/bin/windres-macos diff --git a/java/application/stub-macos-aarch64 b/java/application/stub-macos-aarch64 new file mode 100755 index 000000000..e4285433c Binary files /dev/null and b/java/application/stub-macos-aarch64 differ diff --git a/java/application/stub-macos-x86_64 b/java/application/stub-macos-x86_64 new file mode 100755 index 000000000..916e4be33 Binary files /dev/null and b/java/application/stub-macos-x86_64 differ diff --git a/java/build.xml b/java/build.xml index 36bf9b824..1a5bcdfd8 100644 --- a/java/build.xml +++ b/java/build.xml @@ -54,7 +54,6 @@ - @@ -75,8 +74,11 @@ - - + + + + + diff --git a/java/keywords.txt b/java/keywords.txt index eafb70235..bf02b74d9 100644 --- a/java/keywords.txt +++ b/java/keywords.txt @@ -1,5 +1,10 @@ -# THE TEXT BELOW IS HAND WRITTEN AND FOUND IN THE FILE "keywords_base.txt" -# IN THE PROCESSING-DOCS REPO. DON'T EDITS THE keywords.txt FILE DIRECTLY. +# WELCOME TO KEYWORDS.TXT, THE FILE THAT DETERMINES +# THE COLOR CODING AND REFERENCE LINKS WITHIN THE +# PROCESSING DEVELOPMENT ENVIRONMENT (PDE) + +# THE TEXT THAT FOLLOWS IS FROM keywords_base.txt +# IN THE PROCESSING-WEBSITE REPO. IF YOU WANT TO MAKE A CHANGE, +# DO IT THERE, DO NOT EDIT THE keywords.txt FILE DIRECTLY. # For an explanation of these tags, see Token.java # trunk/processing/app/src/processing/app/syntax/Token.java @@ -89,7 +94,6 @@ GRAY LITERAL2 filter_ GREEN_MASK LITERAL2 GROUP LITERAL2 HALF LITERAL2 -HALF_PI LITERAL2 HALF_PI HAND LITERAL2 cursor_ HARD_LIGHT LITERAL2 blend_ HINT_COUNT LITERAL2 @@ -126,7 +130,6 @@ PDF LITERAL2 size_ P2D LITERAL2 size_ P3D LITERAL2 size_ PERSPECTIVE LITERAL2 -PI LITERAL2 PI PIE LITERAL2 PIXEL_CENTER LITERAL2 POINT LITERAL2 @@ -138,7 +141,6 @@ PROJECT LITERAL2 strokeCap_ QUAD LITERAL2 createShape_ QUAD_STRIP LITERAL2 beginShape_ QUADS LITERAL2 beginShape_ -QUARTER_PI LITERAL2 QUARTER_PI RAD_TO_DEG LITERAL2 RADIUS LITERAL2 RADIANS LITERAL2 @@ -165,7 +167,6 @@ SVG LITERAL2 SVIDEO LITERAL2 TAB LITERAL2 keyCode TARGA LITERAL2 -TAU LITERAL2 TAU TEXT LITERAL2 cursor_ TFF LITERAL2 THIRD_PI LITERAL2 @@ -178,7 +179,6 @@ TRIANGLES LITERAL2 beginShape_ TRIANGLE_STRIP LITERAL2 beginShape_ TUNER LITERAL2 TWO LITERAL2 -TWO_PI LITERAL2 TWO_PI UP LITERAL2 keyCode WAIT LITERAL2 cursor_ WHITESPACE LITERAL2 @@ -216,7 +216,7 @@ void KEYWORD1 void volatile KEYWORD1 -# Java keywords which can be followed by a parenthesis +# Java keywords that can be followed by a parenthesis assert KEYWORD6 case KEYWORD6 case @@ -235,7 +235,6 @@ Byte KEYWORD5 BufferedReader KEYWORD5 BufferedReader Character KEYWORD5 Class KEYWORD5 class -Double KEYWORD5 Float KEYWORD5 Integer KEYWORD5 HashMap KEYWORD5 HashMap @@ -249,11 +248,10 @@ byte KEYWORD5 byte char KEYWORD5 char color KEYWORD5 color_datatype double KEYWORD5 double -float KEYWORD5 float +float KEYWORD5 float int KEYWORD5 int long KEYWORD5 long -short KEYWORD5 -var KEYWORD5 +var KEYWORD5 # Flow structures @@ -327,10 +325,6 @@ setLong FUNCTION2 length KEYWORD2 String -# Temporary additions 3 September 2012 as the reference is getting updated -#end FUNCTION1 -#addChild FUNCTION1 - # Operators are without KEYWORDS += addassign @@ -369,25 +363,24 @@ length KEYWORD2 String ; semicolon -= subtractassign -# Suppressed from Generate to avoid conflicts with variables inside methods + +# Suppressed from Generate to avoid conflicts with +# variables inside methods width KEYWORD4 width_ height KEYWORD4 height_ -PVector FUNCTION1 PVector ArrayList FUNCTION1 ArrayList HashMap FUNCTION1 HashMap -# pixelHeight is not generating correctly: https://github.com/processing/processing-docs/issues/260 -pixelHeight KEYWORD4 pixelHeight - - -# THE TEXT ABOVE IS HAND-WRITTEN AND FOUND IN THE FILE "keywords_base.txt" in processing/processing-docs/generate +# THE TEXT ABOVE IS HAND-WRITTEN AND FOUND IN THE FILE +# "keywords_base.txt" in processing/processing-website # # THE TEXT BELOW IS AUTO-GENERATED # # SO +# PLEASE # DON'T # TOUCH # IT @@ -418,13 +411,13 @@ bezierTangent FUNCTION1 bezierTangent_ bezierVertex FUNCTION1 bezierVertex_ binary FUNCTION1 binary_ blend FUNCTION1 blend_ -blendColor FUNCTION1 blendColor_ blendMode FUNCTION1 blendMode_ blue FUNCTION1 blue_ box FUNCTION1 box_ brightness FUNCTION1 brightness_ camera FUNCTION1 camera_ ceil FUNCTION1 ceil_ +circle FUNCTION1 circle_ clear FUNCTION1 clear_ clip FUNCTION1 clip_ color FUNCTION1 color_ @@ -478,6 +471,8 @@ get FUNCTION2 FloatDict_get_ hasKey FUNCTION2 FloatDict_hasKey_ keyArray FUNCTION2 FloatDict_keyArray_ keys FUNCTION2 FloatDict_keys_ +maxIndex FUNCTION2 FloatDict_maxIndex_ +minIndex FUNCTION2 FloatDict_minIndex_ mult FUNCTION2 FloatDict_mult_ remove FUNCTION2 FloatDict_remove_ set FUNCTION2 FloatDict_set_ @@ -517,7 +512,7 @@ frustum FUNCTION1 frustum_ fullScreen FUNCTION1 fullScreen_ get FUNCTION1 get_ green FUNCTION1 green_ -HALF_PI LITERAL2 HALF_PI +HALF_PI KEYWORD4 HALF_PI hex FUNCTION1 hex_ hint FUNCTION1 hint_ hour FUNCTION1 hour_ @@ -600,8 +595,8 @@ setJSONObject FUNCTION2 JSONObject_setJSONObject_ setString FUNCTION2 JSONObject_setString_ key KEYWORD4 key keyCode KEYWORD4 keyCode -keyPressed FUNCTION4 keyPressed keyPressed KEYWORD4 keyPressed +keyPressed FUNCTION4 keyPressed keyReleased FUNCTION4 keyReleased keyTyped FUNCTION4 keyTyped launch FUNCTION1 launch_ @@ -626,6 +621,7 @@ log FUNCTION1 log_ loop FUNCTION1 loop_ mag FUNCTION1 mag_ map FUNCTION1 map_ +mask FUNCTION1 mask_ match FUNCTION1 match_ matchAll FUNCTION1 matchAll_ max FUNCTION1 max_ @@ -640,8 +636,8 @@ mouseButton KEYWORD4 mouseButton mouseClicked FUNCTION4 mouseClicked mouseDragged FUNCTION4 mouseDragged mouseMoved FUNCTION4 mouseMoved -mousePressed FUNCTION4 mousePressed mousePressed KEYWORD4 mousePressed +mousePressed FUNCTION4 mousePressed mouseReleased FUNCTION4 mouseReleased mouseWheel FUNCTION4 mouseWheel mouseX KEYWORD4 mouseX @@ -669,13 +665,14 @@ parseJSONObject FUNCTION1 parseJSONObject_ parseXML FUNCTION1 parseXML_ perspective FUNCTION1 perspective_ PFont KEYWORD5 PFont -list FUNCTION1 PFont_list_ +list FUNCTION2 PFont_list_ PGraphics KEYWORD5 PGraphics beginDraw FUNCTION2 PGraphics_beginDraw_ endDraw FUNCTION2 PGraphics_endDraw_ -PI LITERAL2 PI +PI KEYWORD4 PI PImage KEYWORD5 PImage blend FUNCTION2 PImage_blend_ +blendColor FUNCTION2 PImage_blendColor_ copy FUNCTION2 PImage_copy_ filter FUNCTION2 PImage_filter_ get FUNCTION2 PImage_get_ @@ -687,13 +684,14 @@ save FUNCTION2 PImage_save_ set FUNCTION2 PImage_set_ updatePixels FUNCTION2 PImage_updatePixels_ pixelDensity FUNCTION1 pixelDensity_ -pixelHeight FUNCTION1 pixelHeight_ +pixelHeight KEYWORD4 pixelHeight pixels KEYWORD4 pixels pixelWidth KEYWORD4 pixelWidth pmouseX KEYWORD4 pmouseX pmouseY KEYWORD4 pmouseY point FUNCTION1 point_ pointLight FUNCTION1 pointLight_ +pop FUNCTION1 pop_ popMatrix FUNCTION1 popMatrix_ popStyle FUNCTION1 popStyle_ pow FUNCTION1 pow_ @@ -704,7 +702,7 @@ println FUNCTION1 println_ printMatrix FUNCTION1 printMatrix_ printProjection FUNCTION1 printProjection_ PShader KEYWORD5 PShader -PShader FUNCTION2 PShader_set_ +set FUNCTION2 PShader_set_ PShape KEYWORD5 PShape addChild FUNCTION2 PShape_addChild_ beginContour FUNCTION2 PShape_beginContour_ @@ -729,6 +727,7 @@ setStroke FUNCTION2 PShape_setStroke_ setVertex FUNCTION2 PShape_setVertex_ setVisible FUNCTION2 PShape_setVisible_ translate FUNCTION2 PShape_translate_ +push FUNCTION1 push_ pushMatrix FUNCTION1 pushMatrix_ pushStyle FUNCTION1 pushStyle_ PVector KEYWORD5 PVector @@ -741,7 +740,6 @@ dist FUNCTION2 PVector_dist_ div FUNCTION2 PVector_div_ dot FUNCTION2 PVector_dot_ fromAngle FUNCTION2 PVector_fromAngle_ -get FUNCTION2 PVector_get_ heading FUNCTION2 PVector_heading_ lerp FUNCTION2 PVector_lerp_ limit FUNCTION2 PVector_limit_ @@ -757,7 +755,7 @@ setMag FUNCTION2 PVector_setMag_ sub FUNCTION2 PVector_sub_ quad FUNCTION1 quad_ quadraticVertex FUNCTION1 quadraticVertex_ -QUARTER_PI LITERAL2 QUARTER_PI +QUARTER_PI KEYWORD4 QUARTER_PI radians FUNCTION1 radians_ random FUNCTION1 random_ randomGaussian FUNCTION1 randomGaussian_ @@ -794,7 +792,10 @@ selectFolder FUNCTION1 selectFolder_ selectInput FUNCTION1 selectInput_ selectOutput FUNCTION1 selectOutput_ set FUNCTION1 set_ +setLocation FUNCTION1 setLocation_ +setResizable FUNCTION1 setResizable_ settings FUNCTION4 settings +setTitle FUNCTION1 setTitle_ setup FUNCTION4 setup shader FUNCTION1 shader_ shape FUNCTION1 shape_ @@ -816,6 +817,7 @@ splitTokens FUNCTION1 splitTokens_ spotLight FUNCTION1 spotLight_ sq FUNCTION1 sq_ sqrt FUNCTION1 sqrt_ +square FUNCTION1 square_ StringDict KEYWORD5 StringDict clear FUNCTION2 StringDict_clear_ get FUNCTION2 StringDict_get_ @@ -865,6 +867,7 @@ getRowCount FUNCTION2 Table_getRowCount_ getString FUNCTION2 Table_getString_ getStringColumn FUNCTION2 Table_getStringColumn_ matchRow FUNCTION2 Table_matchRow_ +matchRowIterator FUNCTION2 Table_matchRowIterator_ matchRows FUNCTION2 Table_matchRows_ removeColumn FUNCTION2 Table_removeColumn_ removeRow FUNCTION2 Table_removeRow_ @@ -873,6 +876,7 @@ rows FUNCTION2 Table_rows_ setFloat FUNCTION2 Table_setFloat_ setInt FUNCTION2 Table_setInt_ setString FUNCTION2 Table_setString_ +sort FUNCTION2 Table_sort_ trim FUNCTION2 Table_trim_ TableRow KEYWORD5 TableRow getColumnCount FUNCTION2 TableRow_getColumnCount_ @@ -884,7 +888,7 @@ setFloat FUNCTION2 TableRow_setFloat_ setInt FUNCTION2 TableRow_setInt_ setString FUNCTION2 TableRow_setString_ tan FUNCTION1 tan_ -TAU LITERAL2 TAU +TAU KEYWORD4 TAU text FUNCTION1 text_ textAlign FUNCTION1 textAlign_ textAscent FUNCTION1 textAscent_ @@ -902,7 +906,7 @@ tint FUNCTION1 tint_ translate FUNCTION1 translate_ triangle FUNCTION1 triangle_ trim FUNCTION1 trim_ -TWO_PI LITERAL2 TWO_PI +TWO_PI KEYWORD4 TWO_PI unbinary FUNCTION1 unbinary_ unhex FUNCTION1 unhex_ updatePixels FUNCTION1 updatePixels_ @@ -912,12 +916,13 @@ addChild FUNCTION2 XML_addChild_ format FUNCTION2 XML_format_ getAttributeCount FUNCTION2 XML_getAttributeCount_ getChild FUNCTION2 XML_getChild_ +getChildCount FUNCTION2 XML_getChildCount_ getChildren FUNCTION2 XML_getChildren_ getContent FUNCTION2 XML_getContent_ getFloat FUNCTION2 XML_getFloat_ -getContent FUNCTION2 XML_getFloatContent_ +getFloatContent FUNCTION2 XML_getFloatContent_ getInt FUNCTION2 XML_getInt_ -getContent FUNCTION2 XML_getIntContent_ +getIntContent FUNCTION2 XML_getIntContent_ getName FUNCTION2 XML_getName_ getParent FUNCTION2 XML_getParent_ getString FUNCTION2 XML_getString_ @@ -925,10 +930,12 @@ hasAttribute FUNCTION2 XML_hasAttribute_ hasChildren FUNCTION2 XML_hasChildren_ listAttributes FUNCTION2 XML_listAttributes_ listChildren FUNCTION2 XML_listChildren_ +parse FUNCTION2 XML_parse_ removeChild FUNCTION2 XML_removeChild_ setContent FUNCTION2 XML_setContent_ setFloat FUNCTION2 XML_setFloat_ setInt FUNCTION2 XML_setInt_ +setLong FUNCTION2 XML_setLong_ setName FUNCTION2 XML_setName_ setString FUNCTION2 XML_setString_ toString FUNCTION2 XML_toString_ diff --git a/java/libraries/dxf/processing4-dxf.iml b/java/libraries/dxf/processing4-dxf.iml new file mode 100644 index 000000000..97b6e2863 --- /dev/null +++ b/java/libraries/dxf/processing4-dxf.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/libraries/dxf/src/processing/dxf/RawDXF.java b/java/libraries/dxf/src/processing/dxf/RawDXF.java index 1de48e4a3..1a603e005 100644 --- a/java/libraries/dxf/src/processing/dxf/RawDXF.java +++ b/java/libraries/dxf/src/processing/dxf/RawDXF.java @@ -233,7 +233,7 @@ public class RawDXF extends PGraphics { public void write(String cmd, float val) { writer.println(cmd); // Don't number format, will cause trouble on systems that aren't en-US - // http://dev.processing.org/bugs/show_bug.cgi?id=495 + // https://download.processing.org/bugzilla/495.html writer.println(val); } diff --git a/java/libraries/io/src/processing/io/PWM.java b/java/libraries/io/src/processing/io/PWM.java index 46b173ed6..56dbcfc01 100644 --- a/java/libraries/io/src/processing/io/PWM.java +++ b/java/libraries/io/src/processing/io/PWM.java @@ -192,7 +192,7 @@ public class PWM { } // set period - String fn = fn = String.format("/sys/class/pwm/%s/pwm%d/period", chip, channel); + String fn = String.format("/sys/class/pwm/%s/pwm%d/period", chip, channel); // convert to nanoseconds int ret = NativeInterface.writeFile(fn, String.format("%d", (int)(1000000000 / period))); if (ret < 0) { @@ -200,7 +200,7 @@ public class PWM { } // set duty cycle - fn = fn = String.format("/sys/class/pwm/%s/pwm%d/duty_cycle", chip, channel); + fn = String.format("/sys/class/pwm/%s/pwm%d/duty_cycle", chip, channel); if (duty < 0.0 || 1.0 < duty) { System.err.println("Duty cycle must be between 0.0 and 1.0."); throw new IllegalArgumentException("Illegal argument"); diff --git a/java/libraries/javafx/.classpath b/java/libraries/javafx/.classpath deleted file mode 100644 index cfc5fc9ac..000000000 --- a/java/libraries/javafx/.classpath +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/java/libraries/javafx/.gitignore b/java/libraries/javafx/.gitignore deleted file mode 100644 index 40f0c37ab..000000000 --- a/java/libraries/javafx/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# 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 deleted file mode 100644 index 7dbb5da11..000000000 --- a/java/libraries/javafx/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - 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 deleted file mode 100644 index cd8d089a1..000000000 --- a/java/libraries/javafx/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index 82d95c16f..000000000 --- a/java/libraries/javafx/build.xml +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/libraries/javafx/library.properties b/java/libraries/javafx/library.properties deleted file mode 100644 index 00d042f25..000000000 --- a/java/libraries/javafx/library.properties +++ /dev/null @@ -1,2 +0,0 @@ -name = JavaFX -version = 1 diff --git a/java/libraries/javafx/src/processing/javafx/PGraphicsFX2D.java b/java/libraries/javafx/src/processing/javafx/PGraphicsFX2D.java deleted file mode 100644 index 4e73a1709..000000000 --- a/java/libraries/javafx/src/processing/javafx/PGraphicsFX2D.java +++ /dev/null @@ -1,2330 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2015 The Processing Foundation - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation, version 2.1. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General - Public License along with this library; if not, write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA -*/ - -package processing.javafx; - -import com.sun.javafx.geom.Path2D; -import com.sun.javafx.geom.PathIterator; -import com.sun.javafx.geom.Shape; - -import java.nio.IntBuffer; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; - -import javafx.scene.SnapshotParameters; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.effect.BlendMode; -import javafx.scene.image.PixelFormat; -import javafx.scene.image.PixelReader; -import javafx.scene.image.PixelWriter; -import javafx.scene.image.WritableImage; -import javafx.scene.image.WritablePixelFormat; -import javafx.scene.paint.Color; -import javafx.scene.shape.ArcType; -import javafx.scene.shape.StrokeLineCap; -import javafx.scene.shape.StrokeLineJoin; -import javafx.scene.text.Font; -import javafx.scene.text.Text; -import javafx.scene.transform.Affine; -import javafx.scene.transform.Transform; - -import processing.core.*; - - -public class PGraphicsFX2D extends PGraphics { - GraphicsContext context; - - static final WritablePixelFormat argbFormat = - PixelFormat.getIntArgbInstance(); - - WritableImage snapshotImage; - - Path2D workPath = new Path2D(); - Path2D auxPath = new Path2D(); - boolean openContour; - boolean adjustedForThinLines; - /// break the shape at the next vertex (next vertex() call is a moveto()) - boolean breakShape; - - private float[] pathCoordsBuffer = new float[6]; - - /// coordinates for internal curve calculation - float[] curveCoordX; - float[] curveCoordY; - float[] curveDrawX; - float[] curveDrawY; - - int transformCount; - Affine[] transformStack = new Affine[MATRIX_STACK_DEPTH]; - -// Line2D.Float line = new Line2D.Float(); -// Ellipse2D.Float ellipse = new Ellipse2D.Float(); -// Rectangle2D.Float rect = new Rectangle2D.Float(); -// Arc2D.Float arc = new Arc2D.Float(); -// -// protected Color tintColorObject; -// -// protected Color fillColorObject; -// public boolean fillGradient; -// public Paint fillGradientObject; -// -// protected Color strokeColorObject; -// public boolean strokeGradient; -// public Paint strokeGradientObject; - - - - ////////////////////////////////////////////////////////////// - - // INTERNAL - - - public PGraphicsFX2D() { } - - - //public void setParent(PApplet parent) - - - //public void setPrimary(boolean primary) - - - //public void setPath(String path) - - - //public void setSize(int width, int height) - - - //public void dispose() - - - @Override - public PSurface createSurface() { - return surface = new PSurfaceFX(this); - } - - - /** Returns the javafx.scene.canvas.GraphicsContext used by this renderer. */ - @Override - public Object getNative() { - return context; - } - - - ////////////////////////////////////////////////////////////// - - // FRAME - - -// @Override -// public boolean canDraw() { -// return true; -// } - - - @Override - public void beginDraw() { - checkSettings(); - resetMatrix(); // reset model matrix - vertexCount = 0; - } - - - @Override - public void endDraw() { - flush(); - - if (!primaryGraphics) { - // TODO this is probably overkill for most tasks... - loadPixels(); - } - } - - - - ////////////////////////////////////////////////////////////// - - // SETTINGS - - - //protected void checkSettings() - - - //protected void defaultSettings() - - - //protected void reapplySettings() - - - - ////////////////////////////////////////////////////////////// - - // HINT - - - //public void hint(int which) - - - - ////////////////////////////////////////////////////////////// - - // SHAPE CREATION - - - //protected PShape createShapeFamily(int type) - - - //protected PShape createShapePrimitive(int kind, float... p) - - - - ////////////////////////////////////////////////////////////// - - // SHAPE - - - @Override - public void beginShape(int kind) { - shape = kind; - vertexCount = 0; - curveVertexCount = 0; - - workPath.reset(); - auxPath.reset(); - - flushPixels(); - - if (drawingThinLines()) { - adjustedForThinLines = true; - translate(0.5f, 0.5f); - } - } - - - //public boolean edge(boolean e) - - - //public void normal(float nx, float ny, float nz) { - - - //public void textureMode(int mode) - - - @Override - public void texture(PImage image) { - showMethodWarning("texture"); - } - - - @Override - public void vertex(float x, float y) { - if (vertexCount == vertices.length) { - float[][] temp = new float[vertexCount<<1][VERTEX_FIELD_COUNT]; - System.arraycopy(vertices, 0, temp, 0, vertexCount); - vertices = temp; - //message(CHATTER, "allocating more vertices " + vertices.length); - } - // not everyone needs this, but just easier to store rather - // than adding another moving part to the code... - vertices[vertexCount][X] = x; - vertices[vertexCount][Y] = y; - vertexCount++; - - switch (shape) { - - case POINTS: - point(x, y); - break; - - case LINES: - if ((vertexCount % 2) == 0) { - line(vertices[vertexCount-2][X], - vertices[vertexCount-2][Y], x, y); - } - break; - - case TRIANGLES: - if ((vertexCount % 3) == 0) { - triangle(vertices[vertexCount - 3][X], - vertices[vertexCount - 3][Y], - vertices[vertexCount - 2][X], - vertices[vertexCount - 2][Y], - x, y); - } - break; - - case TRIANGLE_STRIP: - if (vertexCount >= 3) { - triangle(vertices[vertexCount - 2][X], - vertices[vertexCount - 2][Y], - vertices[vertexCount - 1][X], - vertices[vertexCount - 1][Y], - vertices[vertexCount - 3][X], - vertices[vertexCount - 3][Y]); - } - break; - - case TRIANGLE_FAN: - if (vertexCount >= 3) { - // This is an unfortunate implementation because the stroke for an - // adjacent triangle will be repeated. However, if the stroke is not - // redrawn, it will replace the adjacent line (when it lines up - // perfectly) or show a faint line (when off by a small amount). - // The alternative would be to wait, then draw the shape as a - // polygon fill, followed by a series of vertices. But that's a - // poor method when used with PDF, DXF, or other recording objects, - // since discrete triangles would likely be preferred. - triangle(vertices[0][X], - vertices[0][Y], - vertices[vertexCount - 2][X], - vertices[vertexCount - 2][Y], - x, y); - } - break; - - case QUAD: - case QUADS: - if ((vertexCount % 4) == 0) { - quad(vertices[vertexCount - 4][X], - vertices[vertexCount - 4][Y], - vertices[vertexCount - 3][X], - vertices[vertexCount - 3][Y], - vertices[vertexCount - 2][X], - vertices[vertexCount - 2][Y], - x, y); - } - break; - - case QUAD_STRIP: - // 0---2---4 - // | | | - // 1---3---5 - if ((vertexCount >= 4) && ((vertexCount % 2) == 0)) { - quad(vertices[vertexCount - 4][X], - vertices[vertexCount - 4][Y], - vertices[vertexCount - 2][X], - vertices[vertexCount - 2][Y], - x, y, - vertices[vertexCount - 3][X], - vertices[vertexCount - 3][Y]); - } - break; - - case POLYGON: - if (workPath.getNumCommands() == 0 || breakShape) { - workPath.moveTo(x, y); - breakShape = false; - } else { - workPath.lineTo(x, y); - } - break; - } - } - - - @Override - public void vertex(float x, float y, float z) { - showDepthWarningXYZ("vertex"); - } - - - @Override - public void vertex(float[] v) { - vertex(v[X], v[Y]); - } - - - @Override - public void vertex(float x, float y, float u, float v) { - showVariationWarning("vertex(x, y, u, v)"); - } - - - @Override - public void vertex(float x, float y, float z, float u, float v) { - showDepthWarningXYZ("vertex"); - } - - - @Override - public void beginContour() { - if (openContour) { - PGraphics.showWarning("Already called beginContour()"); - return; - } - - // draw contours to auxiliary path so main path can be closed later - Path2D contourPath = auxPath; - auxPath = workPath; - workPath = contourPath; - - if (contourPath.getNumCommands() > 0) { // first contour does not break - breakShape = true; - } - - openContour = true; - } - - - @Override - public void endContour() { - if (!openContour) { - PGraphics.showWarning("Need to call beginContour() first"); - return; - } - - if (workPath.getNumCommands() > 0) workPath.closePath(); - - Path2D temp = workPath; - workPath = auxPath; - auxPath = temp; - - openContour = false; - } - - - @Override - public void endShape(int mode) { - if (openContour) { // correct automagically, notify user - endContour(); - PGraphics.showWarning("Missing endContour() before endShape()"); - } - if (workPath.getNumCommands() > 0) { - if (shape == POLYGON) { - if (mode == CLOSE) { - workPath.closePath(); - } - if (auxPath.getNumCommands() > 0) { - workPath.append(auxPath, false); - } - drawShape(workPath); - } - } - shape = 0; - if (adjustedForThinLines) { - adjustedForThinLines = false; - translate(-0.5f, -0.5f); - } - loaded = false; - } - - - private void drawShape(Shape s) { - context.beginPath(); - PathIterator pi = s.getPathIterator(null); - while (!pi.isDone()) { - int piType = pi.currentSegment(pathCoordsBuffer); - switch (piType) { - case PathIterator.SEG_MOVETO: - context.moveTo(pathCoordsBuffer[0], pathCoordsBuffer[1]); - break; - case PathIterator.SEG_LINETO: - context.lineTo(pathCoordsBuffer[0], pathCoordsBuffer[1]); - break; - case PathIterator.SEG_QUADTO: - context.quadraticCurveTo(pathCoordsBuffer[0], pathCoordsBuffer[1], - pathCoordsBuffer[2], pathCoordsBuffer[3]); - break; - case PathIterator.SEG_CUBICTO: - context.bezierCurveTo(pathCoordsBuffer[0], pathCoordsBuffer[1], - pathCoordsBuffer[2], pathCoordsBuffer[3], - pathCoordsBuffer[4], pathCoordsBuffer[5]); - break; - case PathIterator.SEG_CLOSE: - context.closePath(); - break; - default: - showWarning("Unknown segment type " + piType); - } - pi.next(); - } - if (fill) context.fill(); - if (stroke) context.stroke(); - } - - - - ////////////////////////////////////////////////////////////// - - // CLIPPING - - - @Override - protected void clipImpl(float x1, float y1, float x2, float y2) { - //g2.setClip(new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1)); - showTodoWarning("clip()", 3274); - } - - - @Override - public void noClip() { - //g2.setClip(null); - showTodoWarning("noClip()", 3274); - } - - - - ////////////////////////////////////////////////////////////// - - // BLEND - - - @Override - protected void blendModeImpl() { - BlendMode mode = BlendMode.SRC_OVER; - switch (blendMode) { - case REPLACE: showWarning("blendMode(REPLACE) is not supported"); break; - case BLEND: break; // this is SRC_OVER, the default - case ADD: mode = BlendMode.ADD; break; // everyone's favorite - case SUBTRACT: showWarning("blendMode(SUBTRACT) is not supported"); break; - case LIGHTEST: mode = BlendMode.LIGHTEN; break; - case DARKEST: mode = BlendMode.DARKEN; break; - case DIFFERENCE: mode = BlendMode.DIFFERENCE; break; - case EXCLUSION: mode = BlendMode.EXCLUSION; break; - case MULTIPLY: mode = BlendMode.MULTIPLY; break; - case SCREEN: mode = BlendMode.SCREEN; break; - case OVERLAY: mode = BlendMode.OVERLAY; break; - case HARD_LIGHT: mode = BlendMode.HARD_LIGHT; break; - case SOFT_LIGHT: mode = BlendMode.SOFT_LIGHT; break; - case DODGE: mode = BlendMode.COLOR_DODGE; break; - case BURN: mode = BlendMode.COLOR_BURN; break; - } - context.setGlobalBlendMode(mode); - } - - - - ////////////////////////////////////////////////////////////// - - // BEZIER VERTICES - - - @Override - protected void bezierVertexCheck() { - if (shape == 0 || shape != POLYGON) { - throw new RuntimeException("beginShape() or beginShape(POLYGON) " + - "must be used before bezierVertex() or quadraticVertex()"); - } - if (workPath.getNumCommands() == 0) { - throw new RuntimeException("vertex() must be used at least once " + - "before bezierVertex() or quadraticVertex()"); - } - } - - @Override - public void bezierVertex(float x1, float y1, - float x2, float y2, - float x3, float y3) { - bezierVertexCheck(); - workPath.curveTo(x1, y1, x2, y2, x3, y3); - } - - - @Override - public void bezierVertex(float x2, float y2, float z2, - float x3, float y3, float z3, - float x4, float y4, float z4) { - showDepthWarningXYZ("bezierVertex"); - } - - - - ////////////////////////////////////////////////////////////// - - // QUADRATIC BEZIER VERTICES - - - @Override - public void quadraticVertex(float ctrlX, float ctrlY, - float endX, float endY) { - bezierVertexCheck(); - workPath.quadTo(ctrlX, ctrlY, endX, endY); - } - - - @Override - public void quadraticVertex(float x2, float y2, float z2, - float x4, float y4, float z4) { - showDepthWarningXYZ("quadVertex"); - } - - - - ////////////////////////////////////////////////////////////// - - // CURVE VERTICES - - - @Override - protected void curveVertexSegment(float x1, float y1, - float x2, float y2, - float x3, float y3, - float x4, float y4) { - if (curveCoordX == null) { - curveCoordX = new float[4]; - curveCoordY = new float[4]; - curveDrawX = new float[4]; - curveDrawY = new float[4]; - } - - curveCoordX[0] = x1; - curveCoordY[0] = y1; - - curveCoordX[1] = x2; - curveCoordY[1] = y2; - - curveCoordX[2] = x3; - curveCoordY[2] = y3; - - curveCoordX[3] = x4; - curveCoordY[3] = y4; - - curveToBezierMatrix.mult(curveCoordX, curveDrawX); - curveToBezierMatrix.mult(curveCoordY, curveDrawY); - - // since the paths are continuous, - // only the first point needs the actual moveto - if (workPath.getNumCommands() == 0) { - workPath.moveTo(curveDrawX[0], curveDrawY[0]); - breakShape = false; - } - - workPath.curveTo(curveDrawX[1], curveDrawY[1], - curveDrawX[2], curveDrawY[2], - curveDrawX[3], curveDrawY[3]); - } - - - @Override - public void curveVertex(float x, float y, float z) { - showDepthWarningXYZ("curveVertex"); - } - - - - ////////////////////////////////////////////////////////////// - - // RENDERER - - @Override - public void flush() { - flushPixels(); - } - - - protected void flushPixels() { - boolean hasPixels = modified && pixels != null; - if (hasPixels) { - // If the user has been manipulating individual pixels, - // the changes need to be copied to the screen before - // drawing any new geometry. - int mx1 = getModifiedX1(); - int mx2 = getModifiedX2(); - int my1 = getModifiedY1(); - int my2 = getModifiedY2(); - int mw = mx2 - mx1; - int mh = my2 - my1; - - if (pixelDensity == 1) { - PixelWriter pw = context.getPixelWriter(); - pw.setPixels(mx1, my1, mw, mh, argbFormat, pixels, - mx1 + my1 * pixelWidth, pixelWidth); - } else { - // The only way to push all the pixels is to draw a scaled-down image - if (snapshotImage == null || - snapshotImage.getWidth() != pixelWidth || - snapshotImage.getHeight() != pixelHeight) { - snapshotImage = new WritableImage(pixelWidth, pixelHeight); - } - - PixelWriter pw = snapshotImage.getPixelWriter(); - pw.setPixels(mx1, my1, mw, mh, argbFormat, pixels, - mx1 + my1 * pixelWidth, pixelWidth); - context.save(); - resetMatrix(); - context.scale(1d / pixelDensity, 1d / pixelDensity); - context.drawImage(snapshotImage, mx1, my1, mw, mh, mx1, my1, mw, mh); - context.restore(); - } - } - - modified = false; - } - - - protected void beforeContextDraw() { - flushPixels(); - loaded = false; - } - - - ////////////////////////////////////////////////////////////// - - // POINT, LINE, TRIANGLE, QUAD - - - @Override - public void point(float x, float y) { - if (stroke) { -// if (strokeWeight > 1) { - line(x, y, x + EPSILON, y + EPSILON); -// } else { -// set((int) screenX(x, y), (int) screenY(x, y), strokeColor); -// } - } - } - - - @Override - public void line(float x1, float y1, float x2, float y2) { - beforeContextDraw(); - if (drawingThinLines()) { - x1 += 0.5f; - x2 += 0.5f; - y1 += 0.5f; - y2 += 0.5f; - } - context.strokeLine(x1, y1, x2, y2); - } - - - @Override - public void triangle(float x1, float y1, float x2, float y2, - float x3, float y3) { - beforeContextDraw(); - if (drawingThinLines()) { - x1 += 0.5f; - x2 += 0.5f; - x3 += 0.5f; - y1 += 0.5f; - y2 += 0.5f; - y3 += 0.5f; - } - context.beginPath(); - context.moveTo(x1, y1); - context.lineTo(x2, y2); - context.lineTo(x3, y3); - context.closePath(); - if (fill) context.fill(); - if (stroke) context.stroke(); - } - - - @Override - public void quad(float x1, float y1, float x2, float y2, - float x3, float y3, float x4, float y4) { - beforeContextDraw(); - if (drawingThinLines()) { - x1 += 0.5f; - x2 += 0.5f; - x3 += 0.5f; - x4 += 0.5f; - y1 += 0.5f; - y2 += 0.5f; - y3 += 0.5f; - y4 += 0.5f; - } - context.beginPath(); - context.moveTo(x1, y1); - context.lineTo(x2, y2); - context.lineTo(x3, y3); - context.lineTo(x4, y4); - context.closePath(); - if (fill) context.fill(); - if (stroke) context.stroke(); - } - - - - ////////////////////////////////////////////////////////////// - - // RECT - - - //public void rectMode(int mode) - - - //public void rect(float a, float b, float c, float d) - - - @Override - protected void rectImpl(float x1, float y1, float x2, float y2) { - beforeContextDraw(); - if (drawingThinLines()) { - x1 += 0.5f; - x2 += 0.5f; - y1 += 0.5f; - y2 += 0.5f; - } - if (fill) context.fillRect(x1, y1, x2 - x1, y2 - y1); - if (stroke) context.strokeRect(x1, y1, x2 - x1, y2 - y1); - } - - - - ////////////////////////////////////////////////////////////// - - // ELLIPSE - - - //public void ellipseMode(int mode) - - - //public void ellipse(float a, float b, float c, float d) - - - @Override - protected void ellipseImpl(float x, float y, float w, float h) { - beforeContextDraw(); - if (drawingThinLines()) { - x += 0.5f; - y += 0.5f; - } - if (fill) context.fillOval(x, y, w, h); - if (stroke) context.strokeOval(x, y, w, h); - } - - - - ////////////////////////////////////////////////////////////// - - // ARC - - - //public void arc(float a, float b, float c, float d, - // float start, float stop) - - - @Override - protected void arcImpl(float x, float y, float w, float h, - float start, float stop, int mode) { - beforeContextDraw(); - - if (drawingThinLines()) { - x += 0.5f; - y += 0.5f; - } - - // 0 to 90 in java would be 0 to -90 for p5 renderer - // but that won't work, so -90 to 0? - start = -start; - stop = -stop; - - 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 compatibility. - ArcType fillMode = ArcType.ROUND; // Arc2D.PIE - ArcType strokeMode = ArcType.OPEN; - - if (mode == OPEN) { - fillMode = ArcType.OPEN; - - } else if (mode == PIE) { - strokeMode = ArcType.ROUND; // PIE - - } else if (mode == CHORD) { - fillMode = ArcType.CHORD; - strokeMode = ArcType.CHORD; - } - - if (fill) { - context.fillArc(x, y, w, h, PApplet.degrees(start), PApplet.degrees(sweep), fillMode); - } - if (stroke) { - context.strokeArc(x, y, w, h, PApplet.degrees(start), PApplet.degrees(sweep), strokeMode); - } - } - - - - ////////////////////////////////////////////////////////////// - - // BOX - - - //public void box(float size) - - - @Override - public void box(float w, float h, float d) { - showMethodWarning("box"); - } - - - - ////////////////////////////////////////////////////////////// - - // SPHERE - - - //public void sphereDetail(int res) - - - //public void sphereDetail(int ures, int vres) - - - @Override - public void sphere(float r) { - showMethodWarning("sphere"); - } - - - - ////////////////////////////////////////////////////////////// - - // BEZIER - - - //public float bezierPoint(float a, float b, float c, float d, float t) - - - //public float bezierTangent(float a, float b, float c, float d, float t) - - - //protected void bezierInitCheck() - - - //protected void bezierInit() - - - /** Ignored (not needed) by this renderer. */ - @Override - public void bezierDetail(int detail) { } - - - //public void bezier(float x1, float y1, - // float x2, float y2, - // float x3, float y3, - // float x4, float y4) - - - //public void bezier(float x1, float y1, float z1, - // float x2, float y2, float z2, - // float x3, float y3, float z3, - // float x4, float y4, float z4) - - - - ////////////////////////////////////////////////////////////// - - // CURVE - - - //public float curvePoint(float a, float b, float c, float d, float t) - - - //public float curveTangent(float a, float b, float c, float d, float t) - - - /** Ignored (not needed) by this renderer. */ - @Override - public void curveDetail(int detail) { } - - - //public void curveTightness(float tightness) - - - //protected void curveInitCheck() - - - //protected void curveInit() - - - //public void curve(float x1, float y1, - // float x2, float y2, - // float x3, float y3, - // float x4, float y4) - - - //public void curve(float x1, float y1, float z1, - // float x2, float y2, float z2, - // float x3, float y3, float z3, - // float x4, float y4, float z4) - - - - ////////////////////////////////////////////////////////////// - - // SMOOTH - - -// @Override -// public void smooth() { -// smooth = true; -// -// if (quality == 0) { -// quality = 4; // change back to bicubic -// } -// } - - -// @Override -// public void smooth(int quality) { -//// this.quality = quality; -//// if (quality == 0) { -//// noSmooth(); -//// } else { -//// smooth(); -//// } -// showMissingWarning("smooth"); -// } -// -// -// @Override -// public void noSmooth() { -// showMissingWarning("noSmooth"); -// } - - - - ////////////////////////////////////////////////////////////// - - // IMAGE - - - //public void imageMode(int mode) - - - //public void image(PImage image, float x, float y) - - - //public void image(PImage image, float x, float y, float c, float d) - - - //public void image(PImage image, - // float a, float b, float c, float d, - // int u1, int v1, int u2, int v2) - - - /** - * Handle renderer-specific image drawing. - */ - @Override - protected void imageImpl(PImage who, - float x1, float y1, float x2, float y2, - int u1, int v1, int u2, int v2) { - // Image not ready yet, or an error - if (who.width <= 0 || who.height <= 0) return; - - ImageCache cash = (ImageCache) getCache(who); - - // Nuke the cache if the image was resized - if (cash != null) { - if (who.pixelWidth != cash.image.getWidth() || - who.pixelHeight != cash.image.getHeight()) { - cash = null; - } - } - - if (cash == null) { - //System.out.println("making new image cache"); - cash = new ImageCache(); //who); - setCache(who, cash); - who.updatePixels(); // mark the whole thing for update - who.setModified(); - } - - // If image previously was tinted, or the color changed - // or the image was tinted, and tint is now disabled - if ((tint && !cash.tinted) || - (tint && (cash.tintedColor != tintColor)) || - (!tint && cash.tinted)) { - // For tint change, mark all pixels as needing update. - who.updatePixels(); - } - - if (who.isModified()) { - if (who.pixels == null) { - // This might be a PGraphics that hasn't been drawn to yet. - // Can't just bail because the cache has been created above. - // https://github.com/processing/processing/issues/2208 - who.pixels = new int[who.pixelWidth * who.pixelHeight]; - } - cash.update(who, tint, tintColor); - who.setModified(false); - } - - u1 *= who.pixelDensity; - v1 *= who.pixelDensity; - u2 *= who.pixelDensity; - v2 *= who.pixelDensity; - - context.drawImage(((ImageCache) getCache(who)).image, - u1, v1, u2-u1, v2-v1, - x1, y1, x2-x1, y2-y1); - } - - - static class ImageCache { - boolean tinted; - int tintedColor; - int[] tintedTemp; // one row of tinted pixels - //BufferedImage image; - WritableImage image; - - /** - * Update the pixels of the cache image. Already determined that the tint - * has changed, or the pixels have changed, so should just go through - * with the update without further checks. - */ - public void update(PImage source, boolean tint, int tintColor) { - //int bufferType = BufferedImage.TYPE_INT_ARGB; - int targetType = ARGB; - boolean opaque = (tintColor & 0xFF000000) == 0xFF000000; - if (source.format == RGB) { - if (!tint || opaque) { - //bufferType = BufferedImage.TYPE_INT_RGB; - targetType = RGB; - } - } -// boolean wrongType = (image != null) && (image.getType() != bufferType); -// if ((image == null) || wrongType) { -// image = new BufferedImage(source.width, source.height, bufferType); -// } - // Must always use an ARGB image, otherwise will write zeros - // in the alpha channel when drawn to the screen. - // https://github.com/processing/processing/issues/2030 -// if (image == null) { -// image = new BufferedImage(source.width, source.height, -// BufferedImage.TYPE_INT_ARGB); -// } - if (image == null) { - image = new WritableImage(source.pixelWidth, source.pixelHeight); - } - - //WritableRaster wr = image.getRaster(); - PixelWriter pw = image.getPixelWriter(); - if (tint) { - if (tintedTemp == null || tintedTemp.length != source.pixelWidth) { - tintedTemp = new int[source.pixelWidth]; - } - int a2 = (tintColor >> 24) & 0xff; -// System.out.println("tint color is " + a2); -// System.out.println("source.pixels[0] alpha is " + (source.pixels[0] >>> 24)); - int r2 = (tintColor >> 16) & 0xff; - int g2 = (tintColor >> 8) & 0xff; - int b2 = (tintColor) & 0xff; - - //if (bufferType == BufferedImage.TYPE_INT_RGB) { - if (targetType == RGB) { - // The target image is opaque, meaning that the source image has no - // alpha (is not ARGB), and the tint has no alpha. - int index = 0; - for (int y = 0; y < source.pixelHeight; y++) { - for (int x = 0; x < source.pixelWidth; x++) { - int argb1 = source.pixels[index++]; - int r1 = (argb1 >> 16) & 0xff; - int g1 = (argb1 >> 8) & 0xff; - int b1 = (argb1) & 0xff; - - // Prior to 2.1, the alpha channel was commented out here, - // but can't remember why (just thought unnecessary b/c of RGB?) - // https://github.com/processing/processing/issues/2030 - tintedTemp[x] = 0xFF000000 | - (((r2 * r1) & 0xff00) << 8) | - ((g2 * g1) & 0xff00) | - (((b2 * b1) & 0xff00) >> 8); - } - //wr.setDataElements(0, y, source.width, 1, tintedTemp); - pw.setPixels(0, y, source.pixelWidth, 1, argbFormat, tintedTemp, 0, source.pixelWidth); - } - // could this be any slower? -// float[] scales = { tintR, tintG, tintB }; -// float[] offsets = new float[3]; -// RescaleOp op = new RescaleOp(scales, offsets, null); -// op.filter(image, image); - - } else { // targetType == ARGB - if (source.format == RGB && - (tintColor & 0xffffff) == 0xffffff) { - int hi = tintColor & 0xff000000; - int index = 0; - for (int y = 0; y < source.pixelHeight; y++) { - for (int x = 0; x < source.pixelWidth; x++) { - tintedTemp[x] = hi | (source.pixels[index++] & 0xFFFFFF); - } - //wr.setDataElements(0, y, source.width, 1, tintedTemp); - pw.setPixels(0, y, source.pixelWidth, 1, argbFormat, tintedTemp, 0, source.pixelWidth); - } - } else { - int index = 0; - for (int y = 0; y < source.pixelHeight; y++) { - if (source.format == RGB) { - int alpha = tintColor & 0xFF000000; - for (int x = 0; x < source.pixelWidth; x++) { - int argb1 = source.pixels[index++]; - int r1 = (argb1 >> 16) & 0xff; - int g1 = (argb1 >> 8) & 0xff; - int b1 = (argb1) & 0xff; - tintedTemp[x] = alpha | - (((r2 * r1) & 0xff00) << 8) | - ((g2 * g1) & 0xff00) | - (((b2 * b1) & 0xff00) >> 8); - } - } else if (source.format == ARGB) { - for (int x = 0; x < source.pixelWidth; x++) { - int argb1 = source.pixels[index++]; - int a1 = (argb1 >> 24) & 0xff; - int r1 = (argb1 >> 16) & 0xff; - int g1 = (argb1 >> 8) & 0xff; - int b1 = (argb1) & 0xff; - tintedTemp[x] = - (((a2 * a1) & 0xff00) << 16) | - (((r2 * r1) & 0xff00) << 8) | - ((g2 * g1) & 0xff00) | - (((b2 * b1) & 0xff00) >> 8); - } - } else if (source.format == ALPHA) { - int lower = tintColor & 0xFFFFFF; - for (int x = 0; x < source.pixelWidth; x++) { - int a1 = source.pixels[index++]; - tintedTemp[x] = - (((a2 * a1) & 0xff00) << 16) | lower; - } - } - //wr.setDataElements(0, y, source.width, 1, tintedTemp); - pw.setPixels(0, y, source.pixelWidth, 1, argbFormat, tintedTemp, 0, source.pixelWidth); - } - } - // Not sure why ARGB images take the scales in this order... -// float[] scales = { tintR, tintG, tintB, tintA }; -// float[] offsets = new float[4]; -// RescaleOp op = new RescaleOp(scales, offsets, null); -// op.filter(image, image); - } - } else { // !tint - if (targetType == RGB && (source.pixels[0] >> 24 == 0)) { - // If it's an RGB image and the high bits aren't set, need to set - // the high bits to opaque because we're drawing ARGB images. - source.filter(OPAQUE); - // Opting to just manipulate the image here, since it shouldn't - // affect anything else (and alpha(get(x, y)) should return 0xff). - // Wel also make no guarantees about the values of the pixels array - // in a PImage and how the high bits will be set. - } - // If no tint, just shove the pixels on in there verbatim - //wr.setDataElements(0, 0, source.width, source.height, source.pixels); - //System.out.println("moving the big one"); - pw.setPixels(0, 0, source.pixelWidth, source.pixelHeight, - argbFormat, source.pixels, 0, source.pixelWidth); - } - this.tinted = tint; - this.tintedColor = tintColor; - -// GraphicsConfiguration gc = parent.getGraphicsConfiguration(); -// compat = gc.createCompatibleImage(image.getWidth(), -// image.getHeight(), -// Transparency.TRANSLUCENT); -// -// Graphics2D g = compat.createGraphics(); -// g.drawImage(image, 0, 0, null); -// g.dispose(); - } - } - - - - ////////////////////////////////////////////////////////////// - - // SHAPE - - - //public void shapeMode(int mode) - - - //public void shape(PShape shape) - - - //public void shape(PShape shape, float x, float y) - - - //public void shape(PShape shape, float x, float y, float c, float d) - - - ////////////////////////////////////////////////////////////// - - // SHAPE I/O - - - @Override - public PShape loadShape(String filename) { - return loadShape(filename, null); - } - - - @Override - public PShape loadShape(String filename, String options) { - String extension = PApplet.getExtension(filename); - if (extension.equals("svg") || extension.equals("svgz")) { - return new PShapeSVG(parent.loadXML(filename)); - } - PGraphics.showWarning("Unsupported format: " + filename); - return null; - } - - - - ////////////////////////////////////////////////////////////// - - // TEXT ATTRIBUTES - - - protected FontCache fontCache = new FontCache(); - - // Is initialized when defaultFontOrDeath() is called - // and mirrors PGraphics.textFont field - protected FontInfo textFontInfo; - - - @Override - protected PFont createFont(String name, float size, - boolean smooth, char[] charset) { - PFont font = super.createFont(name, size, smooth, charset); - if (font.isStream()) { - fontCache.nameToFilename.put(font.getName(), name); - } - return font; - } - - - @Override - protected void defaultFontOrDeath(String method, float size) { - super.defaultFontOrDeath(method, size); - handleTextFont(textFont, size); - } - - - @Override - protected boolean textModeCheck(int mode) { - return mode == MODEL; - } - - - @Override - public float textAscent() { - if (textFont == null) { - defaultFontOrDeath("textAscent"); - } - if (textFontInfo.font == null) { - return super.textAscent(); - } - return textFontInfo.ascent; - } - - - @Override - public float textDescent() { - if (textFont == null) { - defaultFontOrDeath("textDescent"); - } - if (textFontInfo.font == null) { - return super.textDescent(); - } - return textFontInfo.descent; - } - - - static final class FontInfo { - // TODO: this should be based on memory consumption - // this should be enough e.g. for all grays and alpha combos - static final int MAX_CACHED_COLORS_PER_FONT = 1 << 16; - - // used only when there is native font - Font font; - float ascent; - float descent; - - // used only when there is no native font - // maps 32-bit color to the arrays of tinted glyph images - Map tintCache; - } - - - static final class FontCache { - static final int MAX_CACHE_SIZE = 512; - - // keeps track of filenames of fonts loaded from ttf and otf files - Map nameToFilename = new HashMap<>(); - - // keeps track of fonts which should be rendered as pictures - // so we don't go through native font search process every time - final HashSet nonNativeNames = new HashSet<>(); - - // 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 consumption - final LinkedHashMap cache = - 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 - final Key retrievingKey = new Key(); - - // text node used for measuring sizes of text - final Text measuringText = new Text(); - - FontInfo get(String name, float size) { - if (nonNativeNames.contains(name)) { - // Don't have native font, using glyph images. - // Size is set to zero, because all sizes of this font - // should share one FontInfo with one tintCache. - size = 0; - } - retrievingKey.name = name; - retrievingKey.size = size; - return cache.get(retrievingKey); - } - - void put(String name, float size, FontInfo fontInfo) { - if (fontInfo.font == null) { - // Don't have native font, using glyph images. - // Size is set to zero, because all sizes of this font - // should share one FontInfo with one tintCache. - nonNativeNames.add(name); - size = 0; - } - Key key = new Key(); - key.name = name; - key.size = size; - cache.put(key, fontInfo); - } - - FontInfo createFontInfo(Font font) { - FontInfo result = new FontInfo(); - result.font = font; - if (font != null) { - // measure ascent and descent - measuringText.setFont(result.font); - measuringText.setText(" "); - float lineHeight = (float) measuringText.getLayoutBounds().getHeight(); - result.ascent = (float) measuringText.getBaselineOffset(); - result.descent = lineHeight - result.ascent; - } - return result; - } - - static final class Key { - String name; - float size; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Key that = (Key) o; - if (Float.compare(that.size, size) != 0) return false; - return name.equals(that.name); - } - - @Override - public int hashCode() { - int result = name.hashCode(); - result = 31 * result + (size != +0.0f ? Float.floatToIntBits(size) : 0); - return result; - } - } - } - - - /////////////////////////////////////////////////////////////// - - // TEXT - - // None of the variations of text() are overridden from PGraphics. - - - - ////////////////////////////////////////////////////////////// - - // TEXT IMPL - - - @Override - protected void textFontImpl(PFont which, float size) { - handleTextFont(which, size); - handleTextSize(size); - } - - - @Override - protected void textSizeImpl(float size) { - handleTextFont(textFont, size); - handleTextSize(size); - } - - - /** - * FX specific. When setting font or size, new font has to - * be created. Both textFontImpl and textSizeImpl call this one. - * @param which font to be set, not null - * @param size size to be set, greater than zero - */ - protected void handleTextFont(PFont which, float size) { - textFont = which; - - String fontName = which.getName(); - String fontPsName = which.getPostScriptName(); - - textFontInfo = fontCache.get(fontName, size); - if (textFontInfo == null) { - Font font = null; - - if (which.isStream()) { - // Load from ttf or otf file - String filename = fontCache.nameToFilename.get(fontName); - font = Font.loadFont(parent.createInput(filename), size); - } - - if (font == null) { - // Look up font name - font = new Font(fontName, size); - if (!fontName.equalsIgnoreCase(font.getName())) { - // Look up font postscript name - font = new Font(fontPsName, size); - if (!fontPsName.equalsIgnoreCase(font.getName())) { - font = null; // Done with it - } - } - } - - if (font == null && which.getNative() != null) { - // Ain't got nothing, but AWT has something, so glyph images are not - // going to be used for this font; go with the default font then - font = new Font(size); - } - - textFontInfo = fontCache.createFontInfo(font); - fontCache.put(fontName, size, textFontInfo); - } - - context.setFont(textFontInfo.font); - } - - - @Override - protected void textLineImpl(char[] buffer, int start, int stop, float x, float y) { - if (textFontInfo.font == null) { - super.textLineImpl(buffer, start, stop, x, y); - } else { - context.fillText(new String(buffer, start, stop - start), x, y); - } - } - - - protected PImage getTintedGlyphImage(PFont.Glyph glyph, int tintColor) { - if (textFontInfo.tintCache == null) { - textFontInfo.tintCache = new LinkedHashMap<>(16, 0.75f, true) { - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > FontInfo.MAX_CACHED_COLORS_PER_FONT; - } - }; - } - PImage[] tintedGlyphs = textFontInfo.tintCache.get(tintColor); - int index = glyph.index; - if (tintedGlyphs == null || tintedGlyphs.length <= index) { - PImage[] newArray = new PImage[textFont.getGlyphCount()]; - if (tintedGlyphs != null) { - System.arraycopy(tintedGlyphs, 0, newArray, 0, tintedGlyphs.length); - } - tintedGlyphs = newArray; - textFontInfo.tintCache.put(tintColor, tintedGlyphs); - } - PImage tintedGlyph = tintedGlyphs[index]; - if (tintedGlyph == null) { - tintedGlyph = glyph.image.copy(); - tintedGlyphs[index] = tintedGlyph; - } - return tintedGlyph; - } - - - @Override - protected void textCharImpl(char ch, float x, float y) { //, float z) { - PFont.Glyph glyph = textFont.getGlyph(ch); - if (glyph != null) { - if (textMode == MODEL) { - 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 + leftExtent * textSize; - float y1 = y - topExtent * textSize; - float x2 = x1 + wide * textSize; - float y2 = y1 + high * textSize; - - PImage glyphImage = (fillColor == 0xFFFFFFFF) ? - glyph.image : getTintedGlyphImage(glyph, fillColor); - - textCharModelImpl(glyphImage, - x1, y1, x2, y2, - glyph.width, glyph.height); - } - } else if (ch != ' ' && ch != 127) { - showWarning("No glyph found for the " + ch + - " (\\u" + PApplet.hex(ch, 4) + ") character"); - } - } - - - @Override - protected float textWidthImpl(char[] buffer, int start, int stop) { - if (textFont == null) { - defaultFontOrDeath("textWidth"); - } - - if (textFontInfo.font == null) { - return super.textWidthImpl(buffer, start, stop); - } - - fontCache.measuringText.setFont(textFontInfo.font); - fontCache.measuringText.setText(new String(buffer, start, stop - start)); - return (float) fontCache.measuringText.getLayoutBounds().getWidth(); - } - - - - ////////////////////////////////////////////////////////////// - - // MATRIX STACK - - - @Override - public void pushMatrix() { - if (transformCount == transformStack.length) { - throw new RuntimeException("pushMatrix() cannot use push more than " + - transformStack.length + " times"); - } - transformStack[transformCount] = context.getTransform(transformStack[transformCount]); - transformCount++; - } - - - @Override - public void popMatrix() { - if (transformCount == 0) { - throw new RuntimeException("missing a pushMatrix() " + - "to go with that popMatrix()"); - } - transformCount--; - context.setTransform(transformStack[transformCount]); - } - - - - ////////////////////////////////////////////////////////////// - - // MATRIX TRANSFORMS - - - @Override - public void translate(float tx, float ty) { - context.translate(tx, ty); - } - - - //public void translate(float tx, float ty, float tz) - - - @Override - public void rotate(float angle) { - context.rotate(PApplet.degrees(angle)); - } - - - @Override - public void rotateX(float angle) { - showDepthWarning("rotateX"); - } - - - @Override - public void rotateY(float angle) { - showDepthWarning("rotateY"); - } - - - @Override - public void rotateZ(float angle) { - showDepthWarning("rotateZ"); - } - - - @Override - public void rotate(float angle, float vx, float vy, float vz) { - showVariationWarning("rotate"); - } - - - @Override - public void scale(float s) { - context.scale(s, s); - } - - - @Override - public void scale(float sx, float sy) { - context.scale(sx, sy); - } - - - @Override - public void scale(float sx, float sy, float sz) { - showDepthWarningXYZ("scale"); - } - - - @Override - public void shearX(float angle) { - Affine temp = new Affine(); - temp.appendShear(Math.tan(angle), 0); - context.transform(temp); - } - - - @Override - public void shearY(float angle) { - Affine temp = new Affine(); - temp.appendShear(0, Math.tan(angle)); - context.transform(temp); - } - - - - ////////////////////////////////////////////////////////////// - - // MATRIX MORE - - - @Override - public void resetMatrix() { - context.setTransform(new Affine()); - } - - - //public void applyMatrix(PMatrix2D source) - - - @Override - public void applyMatrix(float n00, float n01, float n02, - float n10, float n11, float n12) { - context.transform(n00, n10, n01, n11, n02, n12); - } - - - //public void applyMatrix(PMatrix3D source) - - - @Override - public void applyMatrix(float n00, float n01, float n02, float n03, - float n10, float n11, float n12, float n13, - float n20, float n21, float n22, float n23, - float n30, float n31, float n32, float n33) { - showVariationWarning("applyMatrix"); - } - - - - ////////////////////////////////////////////////////////////// - - // MATRIX GET/SET - - - @Override - public PMatrix getMatrix() { - return getMatrix((PMatrix2D) null); - } - - - @Override - public PMatrix2D getMatrix(PMatrix2D target) { - if (target == null) { - target = new PMatrix2D(); - } - //double[] transform = new double[6]; - // TODO This is not tested; apparently Affine is a full 3x4 - Affine t = context.getTransform(); //.getMatrix(transform); -// target.set((float) transform[0], (float) transform[2], (float) transform[4], -// (float) transform[1], (float) transform[3], (float) transform[5]); - target.set((float) t.getMxx(), (float) t.getMxy(), (float) t.getTx(), - (float) t.getMyx(), (float) t.getMyy(), (float) t.getTy()); - return target; - } - - - @Override - public PMatrix3D getMatrix(PMatrix3D target) { - showVariationWarning("getMatrix"); - return target; - } - - - //public void setMatrix(PMatrix source) - - - @Override - public void setMatrix(PMatrix2D source) { - context.setTransform(source.m00, source.m10, - source.m01, source.m11, - source.m02, source.m12); - } - - - @Override - public void setMatrix(PMatrix3D source) { - showVariationWarning("setMatrix"); - } - - - @Override - public void printMatrix() { - getMatrix((PMatrix2D) null).print(); - } - - - -// ////////////////////////////////////////////////////////////// -// -// // CAMERA and PROJECTION -// -// // Inherit the plaintive warnings from PGraphics -// -// -// //public void beginCamera() -// //public void endCamera() -// //public void camera() -// //public void camera(float eyeX, float eyeY, float eyeZ, -// // float centerX, float centerY, float centerZ, -// // float upX, float upY, float upZ) -// //public void printCamera() -// -// //public void ortho() -// //public void ortho(float left, float right, -// // float bottom, float top, -// // float near, float far) -// //public void perspective() -// //public void perspective(float fov, float aspect, float near, float far) -// //public void frustum(float left, float right, -// // float bottom, float top, -// // float near, float far) -// //public void printProjection() - - - - ////////////////////////////////////////////////////////////// - - // SCREEN and MODEL transforms - - - @Override - public float screenX(float x, float y) { - return (float) context.getTransform().transform(x, y).getX(); - } - - - @Override - public float screenY(float x, float y) { - return (float) context.getTransform().transform(x, y).getY(); - } - - - @Override - public float screenX(float x, float y, float z) { - showDepthWarningXYZ("screenX"); - return 0; - } - - - @Override - public float screenY(float x, float y, float z) { - showDepthWarningXYZ("screenY"); - return 0; - } - - - @Override - public float screenZ(float x, float y, float z) { - showDepthWarningXYZ("screenZ"); - return 0; - } - - - //public float modelX(float x, float y, float z) - - - //public float modelY(float x, float y, float z) - - - //public float modelZ(float x, float y, float z) - - - -// ////////////////////////////////////////////////////////////// -// -// // STYLE -// -// // pushStyle(), popStyle(), style() and getStyle() inherited. - - - - ////////////////////////////////////////////////////////////// - - // STROKE CAP/JOIN/WEIGHT - - - @Override - public void strokeCap(int cap) { - super.strokeCap(cap); - if (strokeCap == ROUND) { - context.setLineCap(StrokeLineCap.ROUND); - } else if (strokeCap == PROJECT) { - context.setLineCap(StrokeLineCap.SQUARE); - } else { - context.setLineCap(StrokeLineCap.BUTT); - } - } - - - @Override - public void strokeJoin(int join) { - super.strokeJoin(join); - if (strokeJoin == MITER) { - context.setLineJoin(StrokeLineJoin.MITER); - } else if (strokeJoin == ROUND) { - context.setLineJoin(StrokeLineJoin.ROUND); - } else { - context.setLineJoin(StrokeLineJoin.BEVEL); - } - } - - - @Override - public void strokeWeight(float weight) { - super.strokeWeight(weight); - context.setLineWidth(weight); - } - - - - ////////////////////////////////////////////////////////////// - - // STROKE - - // noStroke() and stroke() inherited from PGraphics. - - - @Override - protected void strokeFromCalc() { - super.strokeFromCalc(); - context.setStroke(new Color(strokeR, strokeG, strokeB, strokeA)); - } - - - protected boolean drawingThinLines() { - // align strokes to pixel centers when drawing thin lines - return stroke && strokeWeight == 1; - } - - - - ////////////////////////////////////////////////////////////// - - // TINT - - // noTint() and tint() inherited from PGraphics. - - - - ////////////////////////////////////////////////////////////// - - // FILL - - // noFill() and fill() inherited from PGraphics. - - - @Override - protected void fillFromCalc() { - super.fillFromCalc(); - context.setFill(new Color(fillR, fillG, fillB, fillA)); - } - - - -// ////////////////////////////////////////////////////////////// -// -// // MATERIAL PROPERTIES -// -// -// //public void ambient(int rgb) -// //public void ambient(float gray) -// //public void ambient(float x, float y, float z) -// //protected void ambientFromCalc() -// //public void specular(int rgb) -// //public void specular(float gray) -// //public void specular(float x, float y, float z) -// //protected void specularFromCalc() -// //public void shininess(float shine) -// //public void emissive(int rgb) -// //public void emissive(float gray) -// //public void emissive(float x, float y, float z ) -// //protected void emissiveFromCalc() -// -// -// -// ////////////////////////////////////////////////////////////// -// -// // LIGHTS -// -// -// //public void lights() -// //public void noLights() -// //public void ambientLight(float red, float green, float blue) -// //public void ambientLight(float red, float green, float blue, -// // float x, float y, float z) -// //public void directionalLight(float red, float green, float blue, -// // float nx, float ny, float nz) -// //public void pointLight(float red, float green, float blue, -// // float x, float y, float z) -// //public void spotLight(float red, float green, float blue, -// // float x, float y, float z, -// // float nx, float ny, float nz, -// // float angle, float concentration) -// //public void lightFalloff(float constant, float linear, float quadratic) -// //public void lightSpecular(float x, float y, float z) -// //protected void lightPosition(int num, float x, float y, float z) -// //protected void lightDirection(int num, float x, float y, float z) - - - - ////////////////////////////////////////////////////////////// - - // BACKGROUND - - - @Override - public void backgroundImpl() { - - // if pixels are modified, we don't flush them (just mark them flushed) - // because they would be immediately overwritten by the background anyway - modified = false; - loaded = false; - - // Save drawing context (transform, fill, blend mode, etc.) - context.save(); - - // Reset transform to identity - context.setTransform(new Affine()); - - // This only takes into account cases where this is the primary surface. - // Not sure what we do with offscreen anyway. - context.setFill(new Color(backgroundR, backgroundG, backgroundB, backgroundA)); - context.setGlobalBlendMode(BlendMode.SRC_OVER); - context.fillRect(0, 0, width, height); - - // Restore drawing context (transform, fill, blend mode, etc.) - context.restore(); - } - - - -// ////////////////////////////////////////////////////////////// -// -// // COLOR MODE -// -// // All colorMode() variations are inherited from PGraphics. -// -// -// -// ////////////////////////////////////////////////////////////// -// -// // COLOR CALC -// -// // colorCalc() and colorCalcARGB() inherited from PGraphics. -// -// -// -// ////////////////////////////////////////////////////////////// -// -// // COLOR DATATYPE STUFFING -// -// // final color() variations inherited. -// -// -// -// ////////////////////////////////////////////////////////////// -// -// // COLOR DATATYPE EXTRACTION -// -// // final methods alpha, red, green, blue, -// // hue, saturation, and brightness all inherited. -// -// -// -// ////////////////////////////////////////////////////////////// -// -// // COLOR DATATYPE INTERPOLATION -// -// // both lerpColor variants inherited. -// -// -// -// ////////////////////////////////////////////////////////////// -// -// // BEGIN/END RAW -// -// -// @Override -// public void beginRaw(PGraphics recorderRaw) { -// showMethodWarning("beginRaw"); -// } -// -// -// @Override -// public void endRaw() { -// showMethodWarning("endRaw"); -// } -// -// -// -// ////////////////////////////////////////////////////////////// -// -// // WARNINGS and EXCEPTIONS -// -// // showWarning and showException inherited. -// -// -// -// ////////////////////////////////////////////////////////////// -// -// // RENDERER SUPPORT QUERIES -// -// -// //public boolean displayable() // true -// -// -// //public boolean is2D() // true -// -// -// //public boolean is3D() // false - - - - ////////////////////////////////////////////////////////////// - - // PIMAGE METHODS - - - @Override - public void loadPixels() { - if ((pixels == null) || (pixels.length != pixelWidth * pixelHeight)) { - pixels = new int[pixelWidth * pixelHeight]; - loaded = false; - } - - if (!loaded) { - if (snapshotImage == null || - snapshotImage.getWidth() != pixelWidth || - snapshotImage.getHeight() != pixelHeight) { - snapshotImage = new WritableImage(pixelWidth, pixelHeight); - } - - SnapshotParameters sp = null; - if (pixelDensity != 1) { - sp = new SnapshotParameters(); - sp.setTransform(Transform.scale(pixelDensity, pixelDensity)); - } - snapshotImage = ((PSurfaceFX) surface).canvas.snapshot(sp, snapshotImage); - PixelReader pr = snapshotImage.getPixelReader(); - pr.getPixels(0, 0, pixelWidth, pixelHeight, argbFormat, pixels, 0, pixelWidth); - - loaded = true; - modified = false; - } - } - - - - ////////////////////////////////////////////////////////////// - - // GET/SET PIXELS - - - @Override - public int get(int x, int y) { - loadPixels(); - return super.get(x, y); - } - - - @Override - protected void getImpl(int sourceX, int sourceY, - int sourceWidth, int sourceHeight, - PImage target, int targetX, int targetY) { - loadPixels(); - super.getImpl(sourceX, sourceY, sourceWidth, sourceHeight, - target, targetX, targetY); - } - - - @Override - public void set(int x, int y, int argb) { - loadPixels(); - super.set(x, y, argb); - } - - - @Override - protected void setImpl(PImage sourceImage, - int sourceX, int sourceY, - int sourceWidth, int sourceHeight, - int targetX, int targetY) { - sourceImage.loadPixels(); - - int sourceOffset = sourceX + sourceImage.pixelWidth * sourceY; - - PixelWriter pw = context.getPixelWriter(); - pw.setPixels(targetX, targetY, sourceWidth, sourceHeight, - argbFormat, - sourceImage.pixels, - sourceOffset, - sourceImage.pixelWidth); - - // Let's keep them loaded - if (loaded) { - int sourceStride = sourceImage.pixelWidth; - int targetStride = pixelWidth; - int targetOffset = targetX + targetY * targetStride; - for (int i = 0; i < sourceHeight; i++) { - System.arraycopy(sourceImage.pixels, sourceOffset + i * sourceStride, - pixels, targetOffset + i * targetStride, sourceWidth); - } - } - } - - - ////////////////////////////////////////////////////////////// - - // MASK - - - static final String MASK_WARNING = - "mask() cannot be used on the main drawing surface"; - - - @Override - public void mask(PImage alpha) { - showWarning(MASK_WARNING); - } - - - - ////////////////////////////////////////////////////////////// - - // FILTER - - // Because the PImage versions call loadPixels() and - // updatePixels(), no need to override anything here. - - - //public void filter(int kind) - - - //public void filter(int kind, float param) - - - - ////////////////////////////////////////////////////////////// - - // COPY - - -// @Override -// public void copy(int sx, int sy, int sw, int sh, -// int dx, int dy, int dw, int dh) { -// if ((sw != dw) || (sh != dh)) { -// g2.drawImage(image, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null); -// -// } else { -// dx = dx - sx; // java2d's "dx" is the delta, not dest -// dy = dy - sy; -// g2.copyArea(sx, sy, sw, sh, dx, dy); -// } -// } - - -// @Override -// public void copy(PImage src, -// int sx, int sy, int sw, int sh, -// int dx, int dy, int dw, int dh) { -// g2.drawImage((Image) src.getNative(), -// dx, dy, dx + dw, dy + dh, -// sx, sy, sx + sw, sy + sh, null); -// } - - - - ////////////////////////////////////////////////////////////// - - // BLEND - - - //static public int blendColor(int c1, int c2, int mode) - - - //public void blend(int sx, int sy, int sw, int sh, - // int dx, int dy, int dw, int dh, int mode) - - - //public void blend(PImage src, - // int sx, int sy, int sw, int sh, - // int dx, int dy, int dw, int dh, int mode) - - - - ////////////////////////////////////////////////////////////// - - // SAVE - - - //public void save(String filename) - - - - ////////////////////////////////////////////////////////////// - - /** - * Display a warning that the specified method is simply unavailable. - */ - static public void showTodoWarning(String method, int issue) { - showWarning(method + "() is not yet available: " + - "https://github.com/processing/processing/issues/" + issue); - } -} diff --git a/java/libraries/javafx/src/processing/javafx/PSurfaceFX.java b/java/libraries/javafx/src/processing/javafx/PSurfaceFX.java deleted file mode 100644 index 470ae71bc..000000000 --- a/java/libraries/javafx/src/processing/javafx/PSurfaceFX.java +++ /dev/null @@ -1,1090 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2015 The Processing Foundation - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation, version 2.1. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General - Public License along with this library; if not, write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA -*/ - -package processing.javafx; - -import com.sun.glass.ui.Screen; - -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.awt.Rectangle; -import java.io.File; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.SynchronousQueue; - -import javafx.animation.Animation; -import javafx.animation.KeyFrame; -import javafx.animation.Timeline; -import javafx.application.Application; -import javafx.application.Platform; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.event.EventType; -import javafx.scene.Cursor; -import javafx.scene.ImageCursor; -import javafx.scene.Scene; -import javafx.scene.SceneAntialiasing; -import javafx.scene.canvas.Canvas; -import javafx.scene.image.Image; -import javafx.scene.image.PixelFormat; -import javafx.scene.image.WritableImage; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.input.MouseEvent; -import javafx.scene.input.ScrollEvent; -import javafx.scene.layout.StackPane; -import javafx.stage.Stage; -import javafx.stage.StageStyle; -import javafx.stage.WindowEvent; -import javafx.util.Duration; -import processing.awt.ShimAWT; -import processing.core.*; - - -public class PSurfaceFX implements PSurface { - PApplet sketch; - - PGraphicsFX2D fx; - Stage stage; - Canvas canvas; - - final Animation animation; - float frameRate = 60; - - private SynchronousQueue drawExceptionQueue = new SynchronousQueue<>(); - - public PSurfaceFX(PGraphicsFX2D graphics) { - fx = graphics; - canvas = new ResizableCanvas(); - - // set up main drawing loop - KeyFrame keyFrame = new KeyFrame(Duration.millis(1000), - new EventHandler() { - public void handle(ActionEvent event) { - long startNanoTime = System.nanoTime(); - try { - sketch.handleDraw(); - } catch (Throwable e) { - // Let exception handler thread crash with our exception - drawExceptionQueue.offer(e); - // Stop animating right now so nothing runs afterwards - // and crash frame can be for example traced by println() - animation.stop(); - return; - } - long drawNanos = System.nanoTime() - startNanoTime; - - if (sketch.exitCalled()) { - // using Platform.runLater() didn't work -// Platform.runLater(new Runnable() { -// public void run() { - // instead of System.exit(), safely shut down JavaFX this way - Platform.exit(); -// } -// }); - } - if (sketch.frameCount > 5) { - animation.setRate(-PApplet.min(1e9f / drawNanos, frameRate)); - } - } - }); - animation = new Timeline(keyFrame); - animation.setCycleCount(Animation.INDEFINITE); - - // key frame has duration of 1 second, so the rate of the animation - // should be set to frames per second - - // setting rate to negative so that event fires at the start of - // the key frame and first frame is drawn immediately - animation.setRate(-frameRate); - } - - - public Object getNative() { - return canvas; - } - - - class ResizableCanvas extends Canvas { - - public ResizableCanvas() { - widthProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue value, - Number oldWidth, Number newWidth) { -// sketch.width = newWidth.intValue(); - sketch.setSize(newWidth.intValue(), sketch.height); -// draw(); - fx.setSize(sketch.width, sketch.height); - } - }); - heightProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue value, - Number oldHeight, Number newHeight) { -// sketch.height = newHeight.intValue(); - sketch.setSize(sketch.width, newHeight.intValue()); -// draw(); - fx.setSize(sketch.width, sketch.height); - } - }); - - //addEventHandler(eventType, eventHandler); - - EventHandler mouseHandler = new EventHandler<>() { - public void handle(MouseEvent e) { - fxMouseEvent(e); - } - }; - - setOnMousePressed(mouseHandler); - setOnMouseReleased(mouseHandler); - setOnMouseClicked(mouseHandler); - setOnMouseEntered(mouseHandler); - setOnMouseExited(mouseHandler); - - setOnMouseDragged(mouseHandler); - setOnMouseMoved(mouseHandler); - - setOnScroll(new EventHandler() { - public void handle(ScrollEvent e) { - fxScrollEvent(e); - } - }); - - EventHandler keyHandler = new EventHandler<>() { - public void handle(KeyEvent e) { - fxKeyEvent(e); - } - }; - - setOnKeyPressed(keyHandler); - setOnKeyReleased(keyHandler); - setOnKeyTyped(keyHandler); - - setFocusTraversable(false); // prevent tab from de-focusing - - focusedProperty().addListener(new ChangeListener() { - public void changed(ObservableValue value, - Boolean oldValue, Boolean newValue) { - if (newValue.booleanValue()) { - sketch.focused = true; - sketch.focusGained(); - } else { - sketch.focused = false; - sketch.focusLost(); - } - } - }); - } - - public Stage getStage() { - return stage; - } - - @Override - public boolean isResizable() { - return true; - } - - @Override - public double prefWidth(double height) { - return getWidth(); - } - - @Override - public double prefHeight(double width) { - return getHeight(); - } - } - - - // TODO rewrite before 4.0 release - public PImage loadImage(String path, Object... args) { - return ShimAWT.loadImage(sketch, path, args); - } - - - @Override - public void selectInput(String prompt, String callbackMethod, - File file, Object callbackObject) { - ShimAWT.selectInput(prompt, callbackMethod, file, callbackObject); - } - - - @Override - public void selectOutput(String prompt, String callbackMethod, - File file, Object callbackObject) { - ShimAWT.selectOutput(prompt, callbackMethod, file, callbackObject); - } - - - @Override - public void selectFolder(String prompt, String callbackMethod, - File file, Object callbackObject) { - ShimAWT.selectFolder(prompt, callbackMethod, file, callbackObject); - } - - - public void initOffscreen(PApplet sketch) { - } - - - static public class PApplicationFX extends Application { - static public PSurfaceFX surface; -// static String title; // title set at launch -// static boolean resizable; // set at launch - - public PApplicationFX() { } - - @Override - public void start(final Stage stage) { -// if (title != null) { -// stage.setTitle(title); -// } - - PApplet sketch = surface.sketch; - - // See JEP 263 - float renderScale = Screen.getMainScreen().getRecommendedOutputScaleX(); - if (PApplet.platform == PConstants.MACOS) { - for (Screen s : Screen.getScreens()) { - renderScale = Math.max(renderScale, s.getRecommendedOutputScaleX()); - } - } - if (sketch.pixelDensity == 2 && renderScale < 2) { - sketch.pixelDensity = 1; - sketch.g.pixelDensity = 1; - System.err.println("pixelDensity(2) is not available for this display"); - } - - // Use AWT display code, because FX orders screens in different way - GraphicsDevice displayDevice = null; - - GraphicsEnvironment environment = - GraphicsEnvironment.getLocalGraphicsEnvironment(); - - int displayNum = sketch.sketchDisplay(); - if (displayNum > 0) { // if -1, use the default device - GraphicsDevice[] devices = environment.getScreenDevices(); - if (displayNum <= devices.length) { - displayDevice = devices[displayNum - 1]; - } else { - System.err.format("Display %d does not exist, " + - "using the default display instead.%n", displayNum); - for (int i = 0; i < devices.length; i++) { - System.err.format("Display %d is %s%n", (i+1), devices[i]); - } - } - } - if (displayDevice == null) { - displayDevice = environment.getDefaultScreenDevice(); - } - - boolean fullScreen = sketch.sketchFullScreen(); - boolean spanDisplays = sketch.sketchDisplay() == PConstants.SPAN; - - Rectangle primaryScreenRect = displayDevice.getDefaultConfiguration().getBounds(); - Rectangle screenRect = primaryScreenRect; - if (fullScreen || spanDisplays) { - double minX = screenRect.getMinX(); - double maxX = screenRect.getMaxX(); - double minY = screenRect.getMinY(); - double maxY = screenRect.getMaxY(); - if (spanDisplays) { - for (GraphicsDevice s : environment.getScreenDevices()) { - Rectangle bounds = s.getDefaultConfiguration().getBounds(); - minX = Math.min(minX, bounds.getMinX()); - maxX = Math.max(maxX, bounds.getMaxX()); - minY = Math.min(minY, bounds.getMinY()); - maxY = Math.max(maxY, bounds.getMaxY()); - } - } - screenRect = new Rectangle((int) minX, (int) minY, - (int) (maxX - minX), (int) (maxY - minY)); - } - - // Set the displayWidth/Height variables inside PApplet, so that they're - // usable and can even be returned by the sketchWidth()/Height() methods. - sketch.displayWidth = (int) screenRect.getWidth(); - sketch.displayHeight = (int) screenRect.getHeight(); - - int sketchWidth = sketch.sketchWidth(); - int sketchHeight = sketch.sketchHeight(); - - if (fullScreen || spanDisplays) { - sketchWidth = (int) screenRect.getWidth(); - sketchHeight = (int) screenRect.getHeight(); - - stage.initStyle(StageStyle.UNDECORATED); - stage.setX(screenRect.getMinX()); - stage.setY(screenRect.getMinY()); - stage.setWidth(screenRect.getWidth()); - stage.setHeight(screenRect.getHeight()); - } - - Canvas canvas = surface.canvas; - surface.fx.context = canvas.getGraphicsContext2D(); - StackPane stackPane = new StackPane(); - stackPane.getChildren().add(canvas); - canvas.widthProperty().bind(stackPane.widthProperty()); - canvas.heightProperty().bind(stackPane.heightProperty()); - - int width = sketchWidth; - int height = sketchHeight; - int smooth = sketch.sketchSmooth(); - - // Workaround for https://bugs.openjdk.java.net/browse/JDK-8136495 - // https://github.com/processing/processing/issues/3823 - if ((PApplet.platform == PConstants.MACOS || - PApplet.platform == PConstants.LINUX) && - PApplet.javaVersionName.compareTo("1.8.0_60") >= 0 && - PApplet.javaVersionName.compareTo("1.8.0_72") < 0) { - System.err.println("smooth() disabled for JavaFX with this Java version due to Oracle bug"); - System.err.println("https://github.com/processing/processing/issues/3795"); - smooth = 0; - } - - SceneAntialiasing sceneAntialiasing = (smooth == 0) ? - SceneAntialiasing.DISABLED : SceneAntialiasing.BALANCED; - - stage.setScene(new Scene(stackPane, width, height, false, sceneAntialiasing)); - - // initFrame in different thread is waiting for - // the stage, assign it only when it is all set up - surface.stage = stage; - } - - @Override - public void stop() throws Exception { - surface.sketch.dispose(); - } - } - - - //public Frame initFrame(PApplet sketch, java.awt.Color backgroundColor, - public void initFrame(PApplet sketch) {/*, int backgroundColor, - int deviceIndex, boolean fullScreen, - boolean spanDisplays) {*/ - this.sketch = sketch; - PApplicationFX.surface = this; - //Frame frame = new DummyFrame(); - new Thread(new Runnable() { - public void run() { - Application.launch(PApplicationFX.class); - } - }).start(); - - // wait for stage to be initialized on its own thread before continuing - while (stage == null) { - try { - //System.out.println("waiting for launch"); - Thread.sleep(5); - } catch (InterruptedException e) { } - } - - startExceptionHandlerThread(); - - setProcessingIcon(stage); - } - - - private void startExceptionHandlerThread() { - Thread exceptionHandlerThread = new Thread(() -> { - Throwable drawException; - try { - drawException = drawExceptionQueue.take(); - } catch (InterruptedException e) { - return; - } - // Adapted from PSurfaceJOGL - if (drawException != null) { - if (drawException instanceof ThreadDeath) { -// System.out.println("caught ThreadDeath"); -// throw (ThreadDeath)cause; - } else if (drawException instanceof RuntimeException) { - throw (RuntimeException) drawException; - } else if (drawException instanceof UnsatisfiedLinkError) { - throw new UnsatisfiedLinkError(drawException.getMessage()); - } else { - throw new RuntimeException(drawException); - } - } - }); - exceptionHandlerThread.setDaemon(true); - exceptionHandlerThread.setName("Processing-FX-ExceptionHandler"); - exceptionHandlerThread.start(); - } - - - /** Set the window (and dock, or whatever necessary) title. */ - public void setTitle(String title) { -// PApplicationFX.title = title; // store this in case the stage still null -// if (stage != null) { - stage.setTitle(title); -// } - } - - - /** Show or hide the window. */ - @Override - public void setVisible(final boolean visible) { - Platform.runLater(new Runnable() { - public void run() { - if (visible) { - stage.show(); - canvas.requestFocus(); - } else { - stage.hide(); - } - } - }); - } - - - /** Set true if we want to resize things (default is not resizable) */ - public void setResizable(boolean resizable) { -// PApplicationFX.resizable = resizable; -// if (stage != null) { - stage.setResizable(resizable); -// } - } - - - public void setIcon(PImage icon) { - int w = icon.pixelWidth; - int h = icon.pixelHeight; - WritableImage im = new WritableImage(w, h); - im.getPixelWriter().setPixels(0, 0, w, h, - PixelFormat.getIntArgbInstance(), - icon.pixels, - 0, w); - - Stage stage = (Stage) canvas.getScene().getWindow(); - stage.getIcons().clear(); - stage.getIcons().add(im); - } - - - List iconImages; - - protected void setProcessingIcon(Stage stage) { - // Adapted from PSurfaceAWT - // Note: FX chooses wrong icon size, should be fixed in Java 9, see: - // https://bugs.openjdk.java.net/browse/JDK-8091186 - // Removing smaller sizes helps a bit, but big ones are downsized - try { - if (iconImages == null) { - iconImages = new ArrayList<>(); - final int[] sizes = { 48, 64, 128, 256, 512 }; - - for (int sz : sizes) { - URL url = PApplet.class.getResource("/icon/icon-" + sz + ".png"); - Image image = new Image(url.toString()); - iconImages.add(image); - } - } - List icons = stage.getIcons(); - icons.clear(); - icons.addAll(iconImages); - } catch (Exception e) { } // harmless; keep this to ourselves - } - - - @Override - public void setAlwaysOnTop(boolean always) { - stage.setAlwaysOnTop(always); - } - - - /* - @Override - public void placeWindow(int[] location) { - //setFrameSize(); - - if (location != null) { - // a specific location was received from the Runner - // (applet has been run more than once, user placed window) - stage.setX(location[0]); - stage.setY(location[1]); - - } else { // just center on screen - // Can't use frame.setLocationRelativeTo(null) because it sends the - // frame to the main display, which undermines the --display setting. -// frame.setLocation(screenRect.x + (screenRect.width - sketchWidth) / 2, -// screenRect.y + (screenRect.height - sketchHeight) / 2); - } - if (stage.getY() < 0) { - // Windows actually allows you to place frames where they can't be - // closed. Awesome. http://dev.processing.org/bugs/show_bug.cgi?id=1508 - //frame.setLocation(frameLoc.x, 30); - stage.setY(30); - } - - //setCanvasSize(); - - // TODO add window closing behavior -// frame.addWindowListener(new WindowAdapter() { -// @Override -// public void windowClosing(WindowEvent e) { -// System.exit(0); -// } -// }); - - // TODO handle frame resizing events -// setupFrameResizeListener(); - - if (sketch.getGraphics().displayable()) { - setVisible(true); - } - } - */ - - - @Override - public void placeWindow(int[] location, int[] editorLocation) { - if (sketch.sketchFullScreen()) { - PApplet.hideMenuBar(); - return; - } - - int wide = sketch.width; // stage.getWidth() is NaN here - //int high = sketch.height; // stage.getHeight() - - if (location != null) { - // a specific location was received from the Runner - // (applet has been run more than once, user placed window) - stage.setX(location[0]); - stage.setY(location[1]); - - } else if (editorLocation != null) { - int locationX = editorLocation[0] - 20; - int locationY = editorLocation[1]; - - if (locationX - wide > 10) { - // if it fits to the left of the window - stage.setX(locationX - wide); - stage.setY(locationY); - - } else { // doesn't fit - stage.centerOnScreen(); - } - } else { // just center on screen - stage.centerOnScreen(); - } - } - - - // http://download.java.net/jdk8/jfxdocs/javafx/stage/Stage.html#setFullScreenExitHint-java.lang.String- - // http://download.java.net/jdk8/jfxdocs/javafx/stage/Stage.html#setFullScreenExitKeyCombination-javafx.scene.input.KeyCombination- - public void placePresent(int stopColor) { - // TODO Auto-generated method stub - PApplet.hideMenuBar(); - } - - - @Override - public void setupExternalMessages() { - stage.xProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue value, - Number oldX, Number newX) { - sketch.frameMoved(newX.intValue(), stage.yProperty().intValue()); - } - }); - - stage.yProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue value, - Number oldY, Number newY) { - sketch.frameMoved(stage.xProperty().intValue(), newY.intValue()); - } - }); - - stage.setOnCloseRequest(new EventHandler() { - public void handle(WindowEvent we) { - sketch.exit(); - } - }); - } - - - public void setLocation(int x, int y) { - stage.setX(x); - stage.setY(y); - } - - - public void setSize(int wide, int high) { - // When the surface is set to resizable via surface.setResizable(true), - // a crash may occur if the user sets the window to size zero. - // https://github.com/processing/processing/issues/5052 - if (high <= 0) { - high = 1; - } - if (wide <= 0) { - wide = 1; - } - - //System.out.format("%s.setSize(%d, %d)%n", getClass().getSimpleName(), width, height); - Scene scene = stage.getScene(); - double decorH = stage.getWidth() - scene.getWidth(); - double decorV = stage.getHeight() - scene.getHeight(); - stage.setWidth(wide + decorH); - stage.setHeight(high + decorV); - fx.setSize(wide, high); - } - - -// public Component getComponent() { -// return null; -// } - - - public void setSmooth(int level) { - // TODO Auto-generated method stub - - } - - - public void setFrameRate(float fps) { - // setting rate to negative so that event fires at the start of - // the key frame and first frame is drawn immediately - if (fps > 0) { - frameRate = fps; - animation.setRate(-frameRate); - } - } - - -// @Override -// public void requestFocus() { -// canvas.requestFocus(); -// } - - Cursor lastCursor = Cursor.DEFAULT; - - public void setCursor(int kind) { - Cursor c; - switch (kind) { - case PConstants.ARROW: c = Cursor.DEFAULT; break; - case PConstants.CROSS: c = Cursor.CROSSHAIR; break; - case PConstants.HAND: c = Cursor.HAND; break; - case PConstants.MOVE: c = Cursor.MOVE; break; - case PConstants.TEXT: c = Cursor.TEXT; break; - case PConstants.WAIT: c = Cursor.WAIT; break; - default: c = Cursor.DEFAULT; break; - } - lastCursor = c; - canvas.getScene().setCursor(c); - } - - - public void setCursor(PImage image, int hotspotX, int hotspotY) { - int w = image.pixelWidth; - int h = image.pixelHeight; - WritableImage im = new WritableImage(w, h); - im.getPixelWriter().setPixels(0, 0, w, h, - PixelFormat.getIntArgbInstance(), - image.pixels, - 0, w); - ImageCursor c = new ImageCursor(im, hotspotX, hotspotY); - lastCursor = c; - canvas.getScene().setCursor(c); - } - - - public void showCursor() { - canvas.getScene().setCursor(lastCursor); - } - - - public void hideCursor() { - canvas.getScene().setCursor(Cursor.NONE); - } - - - public boolean openLink(String url) { - return ShimAWT.openLink(url); - } - - - public void startThread() { - animation.play(); - } - - - public void pauseThread() { - animation.pause(); - } - - - public void resumeThread() { - animation.play(); - } - - - public boolean stopThread() { - animation.stop(); - return true; - } - - - public boolean isStopped() { - return animation.getStatus() == Animation.Status.STOPPED; - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - /* - protected void addListeners() { - - canvas.addMouseListener(new MouseListener() { - - public void mousePressed(java.awt.event.MouseEvent e) { - nativeMouseEvent(e); - } - - public void mouseReleased(java.awt.event.MouseEvent e) { - nativeMouseEvent(e); - } - - public void mouseClicked(java.awt.event.MouseEvent e) { - nativeMouseEvent(e); - } - - public void mouseEntered(java.awt.event.MouseEvent e) { - nativeMouseEvent(e); - } - - public void mouseExited(java.awt.event.MouseEvent e) { - nativeMouseEvent(e); - } - }); - - canvas.addMouseMotionListener(new MouseMotionListener() { - - public void mouseDragged(java.awt.event.MouseEvent e) { - nativeMouseEvent(e); - } - - public void mouseMoved(java.awt.event.MouseEvent e) { - nativeMouseEvent(e); - } - }); - - canvas.addMouseWheelListener(new MouseWheelListener() { - - public void mouseWheelMoved(MouseWheelEvent e) { - nativeMouseEvent(e); - } - }); - - canvas.addKeyListener(new KeyListener() { - - public void keyPressed(java.awt.event.KeyEvent e) { - nativeKeyEvent(e); - } - - - public void keyReleased(java.awt.event.KeyEvent e) { - nativeKeyEvent(e); - } - - - public void keyTyped(java.awt.event.KeyEvent e) { - nativeKeyEvent(e); - } - }); - - canvas.addFocusListener(new FocusListener() { - - public void focusGained(FocusEvent e) { - sketch.focused = true; - sketch.focusGained(); - } - - public void focusLost(FocusEvent e) { - sketch.focused = false; - sketch.focusLost(); - } - }); - } - */ - - - static Map, Integer> mouseMap = - new HashMap<>(); - static { - mouseMap.put(MouseEvent.MOUSE_PRESSED, processing.event.MouseEvent.PRESS); - mouseMap.put(MouseEvent.MOUSE_RELEASED, processing.event.MouseEvent.RELEASE); - mouseMap.put(MouseEvent.MOUSE_CLICKED, processing.event.MouseEvent.CLICK); - mouseMap.put(MouseEvent.MOUSE_DRAGGED, processing.event.MouseEvent.DRAG); - mouseMap.put(MouseEvent.MOUSE_MOVED, processing.event.MouseEvent.MOVE); - mouseMap.put(MouseEvent.MOUSE_ENTERED, processing.event.MouseEvent.ENTER); - mouseMap.put(MouseEvent.MOUSE_EXITED, processing.event.MouseEvent.EXIT); - } - - protected void fxMouseEvent(MouseEvent fxEvent) { - // the 'amount' is the number of button clicks for a click event, - // or the number of steps/clicks on the wheel for a mouse wheel event. - int count = fxEvent.getClickCount(); - - int action = mouseMap.get(fxEvent.getEventType()); - - int modifiers = 0; - if (fxEvent.isShiftDown()) { - modifiers |= processing.event.Event.SHIFT; - } - if (fxEvent.isControlDown()) { - modifiers |= processing.event.Event.CTRL; - } - if (fxEvent.isMetaDown()) { - modifiers |= processing.event.Event.META; - } - if (fxEvent.isAltDown()) { - modifiers |= processing.event.Event.ALT; - } - - int button = 0; - switch (fxEvent.getButton()) { - case PRIMARY: - button = PConstants.LEFT; - break; - case SECONDARY: - button = PConstants.RIGHT; - break; - case MIDDLE: - button = PConstants.CENTER; - break; - case NONE: - case BACK: - case FORWARD: - // not currently handled - break; - } - - //long when = nativeEvent.getWhen(); // from AWT - long when = System.currentTimeMillis(); - int x = (int) fxEvent.getX(); // getSceneX()? - int y = (int) fxEvent.getY(); - - sketch.postEvent(new processing.event.MouseEvent(fxEvent, when, - action, modifiers, - x, y, button, count)); - } - - // https://docs.oracle.com/javase/8/javafx/api/javafx/scene/input/ScrollEvent.html - protected void fxScrollEvent(ScrollEvent fxEvent) { - // the number of steps/clicks on the wheel for a mouse wheel event. - int count = (int) -(fxEvent.getDeltaY() / fxEvent.getMultiplierY()); - - int action = processing.event.MouseEvent.WHEEL; - - int modifiers = 0; - if (fxEvent.isShiftDown()) { - modifiers |= processing.event.Event.SHIFT; - } - if (fxEvent.isControlDown()) { - modifiers |= processing.event.Event.CTRL; - } - if (fxEvent.isMetaDown()) { - modifiers |= processing.event.Event.META; - } - if (fxEvent.isAltDown()) { - modifiers |= processing.event.Event.ALT; - } - - // FX does not supply button info - int button = 0; - - long when = System.currentTimeMillis(); - int x = (int) fxEvent.getX(); // getSceneX()? - int y = (int) fxEvent.getY(); - - sketch.postEvent(new processing.event.MouseEvent(fxEvent, when, - action, modifiers, - x, y, button, count)); - } - - - protected void fxKeyEvent(javafx.scene.input.KeyEvent fxEvent) { - int action = 0; - EventType et = fxEvent.getEventType(); - if (et == KeyEvent.KEY_PRESSED) { - action = processing.event.KeyEvent.PRESS; - } else if (et == KeyEvent.KEY_RELEASED) { - action = processing.event.KeyEvent.RELEASE; - } else if (et == KeyEvent.KEY_TYPED) { - action = processing.event.KeyEvent.TYPE; - } - - int modifiers = 0; - if (fxEvent.isShiftDown()) { - modifiers |= processing.event.Event.SHIFT; - } - if (fxEvent.isControlDown()) { - modifiers |= processing.event.Event.CTRL; - } - if (fxEvent.isMetaDown()) { - modifiers |= processing.event.Event.META; - } - if (fxEvent.isAltDown()) { - modifiers |= processing.event.Event.ALT; - } - - long when = System.currentTimeMillis(); - - char keyChar = getKeyChar(fxEvent); - int keyCode = getKeyCode(fxEvent); - sketch.postEvent(new processing.event.KeyEvent(fxEvent, when, - action, modifiers, - keyChar, keyCode)); - } - - - private int getKeyCode(KeyEvent fxEvent) { - if (fxEvent.getEventType() == KeyEvent.KEY_TYPED) { - return 0; - } - - KeyCode kc = fxEvent.getCode(); - switch (kc) { - case ALT_GRAPH: - return PConstants.ALT; - default: - break; - } - return kc.getCode(); - } - - - private char getKeyChar(KeyEvent fxEvent) { - KeyCode kc = fxEvent.getCode(); - - // Overriding chars for some - // KEY_PRESSED and KEY_RELEASED events - switch (kc) { - case UP: - case KP_UP: - case DOWN: - case KP_DOWN: - case LEFT: - case KP_LEFT: - case RIGHT: - case KP_RIGHT: - case ALT: - case ALT_GRAPH: - case CONTROL: - case SHIFT: - case CAPS: - case META: - case WINDOWS: - case CONTEXT_MENU: - case HOME: - case PAGE_UP: - case PAGE_DOWN: - case END: - case PAUSE: - case PRINTSCREEN: - case INSERT: - case NUM_LOCK: - case SCROLL_LOCK: - case F1: - case F2: - case F3: - case F4: - case F5: - case F6: - case F7: - case F8: - case F9: - case F10: - case F11: - case F12: - return PConstants.CODED; - case ENTER: - return '\n'; - case DIVIDE: - return '/'; - case MULTIPLY: - return '*'; - case SUBTRACT: - return '-'; - case ADD: - return '+'; - case NUMPAD0: - return '0'; - case NUMPAD1: - return '1'; - case NUMPAD2: - return '2'; - case NUMPAD3: - return '3'; - case NUMPAD4: - return '4'; - case NUMPAD5: - return '5'; - case NUMPAD6: - return '6'; - case NUMPAD7: - return '7'; - case NUMPAD8: - return '8'; - case NUMPAD9: - return '9'; - case DECIMAL: - // KEY_TYPED does not go through here and will produce - // dot or comma based on the keyboard layout. - // For KEY_PRESSED and KEY_RELEASED, let's just go with - // the dot. Users can detect the key by its keyCode. - return '.'; - case UNDEFINED: - // KEY_TYPED has KeyCode: UNDEFINED - // and falls through here - break; - default: - break; - } - - // Just go with what FX gives us for the rest of - // KEY_PRESSED and KEY_RELEASED and all of KEY_TYPED - String ch; - if (fxEvent.getEventType() == KeyEvent.KEY_TYPED) { - ch = fxEvent.getCharacter(); - } else { - ch = kc.getChar(); - } - - if (ch.length() < 1) return PConstants.CODED; - if (ch.startsWith("\r")) return '\n'; // normalize enter key - return ch.charAt(0); - } -} diff --git a/java/libraries/net/processing4-net.iml b/java/libraries/net/processing4-net.iml new file mode 100644 index 000000000..97b6e2863 --- /dev/null +++ b/java/libraries/net/processing4-net.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/libraries/net/src/processing/net/Client.java b/java/libraries/net/src/processing/net/Client.java index 5598c355f..048c6799b 100644 --- a/java/libraries/net/src/processing/net/Client.java +++ b/java/libraries/net/src/processing/net/Client.java @@ -29,19 +29,21 @@ import processing.core.*; import java.io.*; import java.lang.reflect.*; import java.net.*; +import java.nio.charset.StandardCharsets; /** - * - * A client connects to a server and sends data back and forth. If anything - * goes wrong with the connection, for example the host is not there or is + * + * A client connects to a server and sends data back and forth. If anything + * goes wrong with the connection, for example the host is not there or is * listening on a different port, an exception is thrown. - * + * * @webref client * @webBrief The client class is used to create client Objects which connect to a server to exchange data * @instanceName client any variable of type Client * @usage Application * @see_external LIB_net/clientEvent */ +@SuppressWarnings("unused") public class Client implements Runnable { protected static final int MAX_BUFFER_SIZE = 1 << 27; // 128 MB @@ -60,13 +62,13 @@ public class Client implements Runnable { final Object bufferLock = new Object[0]; - byte buffer[] = new byte[32768]; + byte[] buffer = new byte[32768]; int bufferIndex; int bufferLast; boolean disposeRegistered = false; - - + + /** * @param parent typically use "this" * @param host address of the server @@ -95,14 +97,14 @@ public class Client implements Runnable { clientEventMethod = parent.getClass().getMethod("clientEvent", Client.class); } catch (Exception e) { - // no such method, or an error.. which is fine, just ignore + // no such method, or an error... which is fine, just ignore } // do the same for disconnectEvent(Client c); try { disconnectEventMethod = parent.getClass().getMethod("disconnectEvent", Client.class); } catch (Exception e) { - // no such method, or an error.. which is fine, just ignore + // no such method, or an error... which is fine, just ignore } } catch (IOException e) { @@ -111,10 +113,9 @@ public class Client implements Runnable { } } - + /** * @param socket any object of type Socket - * @throws IOException */ public Client(PApplet parent, Socket socket) throws IOException { this.parent = parent; @@ -133,28 +134,28 @@ public class Client implements Runnable { clientEventMethod = parent.getClass().getMethod("clientEvent", Client.class); } catch (Exception e) { - // no such method, or an error.. which is fine, just ignore + // no such method, or an error... which is fine, just ignore } // do the same for disconnectEvent(Client c); try { disconnectEventMethod = parent.getClass().getMethod("disconnectEvent", Client.class); } catch (Exception e) { - // no such method, or an error.. which is fine, just ignore + // no such method, or an error... which is fine, just ignore } } /** - * - * Disconnects from the server. Use to shut the connection when you're + * + * Disconnects from the server. Use to shut the connection when you're * finished with the Client. - * + * * @webref client * @webBrief Disconnects from the server * @usage application */ - public void stop() { + public void stop() { if (disconnectEventMethod != null && thread != null){ try { disconnectEventMethod.invoke(parent, this); @@ -180,7 +181,7 @@ public class Client implements Runnable { * Disconnect from the server: internal use only. *

          * This should only be called by the internal functions in PApplet, - * use stop() instead from within your own applets. + * use stop() instead from within your own sketches. */ public void dispose() { thread = null; @@ -201,7 +202,7 @@ public class Client implements Runnable { } catch (Exception e) { e.printStackTrace(); } - + try { if (socket != null) { socket.close(); @@ -229,7 +230,7 @@ public class Client implements Runnable { while (input != null) { int readCount; - // try to read a byte using a blocking read. + // try to read a byte using a blocking read. // An exception will occur when the sketch is exits. try { readCount = input.read(readBuffer, 0, readBuffer.length); @@ -239,7 +240,7 @@ public class Client implements Runnable { stop(); return; } - + // read returns -1 if end-of-stream occurs (for example if the host disappears) if (readCount == -1) { System.err.println("Client got end-of-stream."); @@ -302,10 +303,10 @@ public class Client implements Runnable { /** - * + * * Returns true if this client is still active and hasn't run * into any trouble. - * + * * @webref client * @webBrief Returns true if this client is still active * @usage application @@ -316,9 +317,9 @@ public class Client implements Runnable { /** - * + * * Returns the IP address of the computer to which the Client is attached. - * + * * @webref client * @usage application * @webBrief Returns the IP address of the machine as a String @@ -332,10 +333,10 @@ public class Client implements Runnable { /** - * - * Returns the number of bytes available. When any client has bytes + * + * Returns the number of bytes available. When any client has bytes * available from the server, it returns the number of bytes. - * + * * @webref client * @usage application * @webBrief Returns the number of bytes in the buffer waiting to be read @@ -348,9 +349,9 @@ public class Client implements Runnable { /** - * + * * Empty the buffer, removes all the data stored there. - * + * * @webref client * @usage application * @webBrief Clears the buffer @@ -364,11 +365,11 @@ public class Client implements Runnable { /** - * - * Returns a number between 0 and 255 for the next byte that's waiting in - * the buffer. Returns -1 if there is no byte, although this should be - * avoided by first cheacking available() to see if any data is available. - * + * + * Returns a number between 0 and 255 for the next byte that's waiting in + * the buffer. Returns -1 if there is no byte, although this should be + * avoided by first checking available() to see if any data is available. + * * @webref client * @usage application * @webBrief Returns a value from the buffer @@ -388,10 +389,10 @@ public class Client implements Runnable { /** - * - * Returns the next byte in the buffer as a char. Returns -1 or + * + * Returns the next byte in the buffer as a char. Returns -1 or * 0xffff if nothing is there. - * + * * @webref client * @usage application * @webBrief Returns the next byte in the buffer as a char @@ -405,21 +406,21 @@ public class Client implements Runnable { /** - * - * Reads a group of bytes from the buffer. The version with no parameters - * returns a byte array of all data in the buffer. This is not efficient, - * but is easy to use. The version with the byteBuffer parameter is - * more memory and time efficient. It grabs the data in the buffer and puts - * it into the byte array passed in and returns an int value for the number - * of bytes read. If more bytes are available than can fit into the + * + * Reads a group of bytes from the buffer. The version with no parameters + * returns a byte array of all data in the buffer. This is not efficient, + * but is easy to use. The version with the byteBuffer parameter is + * more memory and time efficient. It grabs the data in the buffer and puts + * it into the byte array passed in and returns an int value for the number + * of bytes read. If more bytes are available than can fit into the * byteBuffer, only those that fit are read. - * + * *

          Advanced

          * Return a byte array of anything that's in the serial buffer. * Not particularly memory/speed efficient, because it creates * a byte array on each read, but it's easier to use than * readBytes(byte b[]) (see below). - * + * * @webref client * @usage application * @webBrief Reads a group of bytes from the buffer @@ -429,7 +430,7 @@ public class Client implements Runnable { if (bufferIndex == bufferLast) return null; int length = bufferLast - bufferIndex; - byte outgoing[] = new byte[length]; + byte[] outgoing = new byte[length]; System.arraycopy(buffer, bufferIndex, outgoing, 0, length); bufferIndex = 0; // rewind @@ -455,7 +456,7 @@ public class Client implements Runnable { int length = bufferLast - bufferIndex; if (length > max) length = max; - byte outgoing[] = new byte[length]; + byte[] outgoing = new byte[length]; System.arraycopy(buffer, bufferIndex, outgoing, 0, length); bufferIndex += length; @@ -478,10 +479,10 @@ public class Client implements Runnable { * Returns an int for how many bytes were read. If more bytes * are available than can fit into the byte array, only those * that will fit are read. - * + * * @param bytebuffer passed in byte array to be altered */ - public int readBytes(byte bytebuffer[]) { + public int readBytes(byte[] bytebuffer) { synchronized (bufferLock) { if (bufferIndex == bufferLast) return 0; @@ -500,18 +501,18 @@ public class Client implements Runnable { /** - * - * Reads from the port into a buffer of bytes up to and including a - * particular character. If the character isn't in the buffer, 'null' is - * returned. The version with no byteBuffer parameter returns a byte - * array of all data up to and including the interesting byte. This - * is not efficient, but is easy to use. The version with the - * byteBuffer parameter is more memory and time efficient. It grabs - * the data in the buffer and puts it into the byte array passed in and - * returns an int value for the number of bytes read. If the byte buffer is - * not large enough, -1 is returned and an error is printed to the message + * + * Reads from the port into a buffer of bytes up to and including a + * particular character. If the character isn't in the buffer, 'null' is + * returned. The version with no byteBuffer parameter returns a byte + * array of all data up to and including the interesting byte. This + * is not efficient, but is easy to use. The version with the + * byteBuffer parameter is more memory and time efficient. It grabs + * the data in the buffer and puts it into the byte array passed in and + * returns an int value for the number of bytes read. If the byte buffer is + * not large enough, -1 is returned and an error is printed to the message * area. If nothing is in the buffer, 0 is returned. - * + * * @webref client * @usage application * @webBrief Reads from the buffer of bytes up to and including a particular character @@ -533,7 +534,7 @@ public class Client implements Runnable { if (found == -1) return null; int length = found - bufferIndex + 1; - byte outgoing[] = new byte[length]; + byte[] outgoing = new byte[length]; System.arraycopy(buffer, bufferIndex, outgoing, 0, length); bufferIndex += length; @@ -556,10 +557,10 @@ public class Client implements Runnable { * and an error message is printed on the console. * If nothing is in the buffer, zero is returned. * If 'interesting' byte is not in the buffer, then 0 is returned. - * + * * @param byteBuffer passed in byte array to be altered */ - public int readBytesUntil(int interesting, byte byteBuffer[]) { + public int readBytesUntil(int interesting, byte[] byteBuffer) { byte what = (byte)interesting; synchronized (bufferLock) { @@ -595,53 +596,54 @@ public class Client implements Runnable { /** - * - * Returns the all the data from the buffer as a String. This method - * assumes the incoming characters are ASCII. If you want to transfer - * Unicode data, first convert the String to a byte stream in the - * representation of your choice (i.e. UTF8 or two-byte Unicode data), and - * send it as a byte array. - * + * + * Returns the all the data from the buffer as a String. + * + * In 4.0 beta 3, changed to using UTF-8 as the encoding, + * otherwise the behavior is platform-dependent. + * * @webref client * @usage application * @webBrief Returns the buffer as a String */ public String readString() { - byte b[] = readBytes(); - if (b == null) return null; - return new String(b); + byte[] b = readBytes(); + if (b == null) { + return null; + } + return new String(b, StandardCharsets.UTF_8); } /** - * - * Combination of readBytesUntil() and readString(). Returns + * + * Combination of readBytesUntil() and readString(). Returns * null if it doesn't find what you're looking for. - * + * *

          Advanced

          - *

          - * If you want to move Unicode data, you can first convert the - * String to a byte stream in the representation of your choice - * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array. - * + * In 4.0 beta 3, changed to using UTF-8 as the encoding, + * otherwise the behavior is platform-dependent. + * * @webref client * @usage application * @webBrief Returns the buffer as a String up to and including a particular character * @param interesting character designated to mark the end of the data */ public String readStringUntil(int interesting) { - byte b[] = readBytesUntil(interesting); - if (b == null) return null; - return new String(b); + byte[] b = readBytesUntil(interesting); + if (b == null) { + return null; + } + return new String(b, StandardCharsets.UTF_8); } /** - * - * Writes data to a server specified when constructing the client, or writes - * data to the specific client obtained from the Server available() + * + * Writes data to a server specified when constructing the client, or writes + * data to the specific client obtained from the Server available() * method. - * + * * @webref client * @usage application * @webBrief Writes bytes, chars, ints, bytes[], Strings @@ -652,26 +654,19 @@ public class Client implements Runnable { output.write(data & 0xff); // for good measure do the & output.flush(); // hmm, not sure if a good idea - } catch (Exception e) { // null pointer or serial port dead - //errorMessage("write", e); - //e.printStackTrace(); - //dispose(); - //disconnect(e); + } catch (Exception e) { // null pointer or serial port dead e.printStackTrace(); stop(); } } - public void write(byte data[]) { + public void write(byte[] data) { try { output.write(data); output.flush(); // hmm, not sure if a good idea - } catch (Exception e) { // null pointer or serial port dead - //errorMessage("write", e); - //e.printStackTrace(); - //disconnect(e); + } catch (Exception e) { // null pointer or serial port dead e.printStackTrace(); stop(); } @@ -679,42 +674,10 @@ public class Client implements Runnable { /** - *

          Advanced

          - * Write a String to the output. Note that this doesn't account - * for Unicode (two bytes per char), nor will it send UTF8 - * characters.. It assumes that you mean to send a byte buffer - * (most often the case for networking and serial i/o) and - * will only use the bottom 8 bits of each char in the string. - * (Meaning that internally it uses String.getBytes) - * - * If you want to move Unicode data, you can first convert the - * String to a byte stream in the representation of your choice - * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array. + * In 4.0 beta 3, changed to using UTF-8 as the encoding, + * otherwise the behavior is platform-dependent. */ public void write(String data) { - write(data.getBytes()); + write(data.getBytes(StandardCharsets.UTF_8)); } - - - /** - * Handle disconnect due to an Exception being thrown. - */ - /* - protected void disconnect(Exception e) { - dispose(); - if (e != null) { - e.printStackTrace(); - } - } - */ - - - /** - * General error reporting, all corralled here just in case - * I think of something slightly more intelligent to do. - */ - //public void errorMessage(String where, Exception e) { - //parent.die("Error inside Client." + where + "()", e); - //e.printStackTrace(System.err); - //} } diff --git a/java/libraries/net/src/processing/net/Server.java b/java/libraries/net/src/processing/net/Server.java index dc354df44..3e7535bb2 100644 --- a/java/libraries/net/src/processing/net/Server.java +++ b/java/libraries/net/src/processing/net/Server.java @@ -33,17 +33,17 @@ import java.net.*; /** - * - * A server sends and receives data to and from its associated clients - * (other programs connected to it). When a server is started, it begins - * listening for connections on the port specified by the port - * parameter. Computers have many ports for transferring data and some are - * commonly used so be sure to not select one of these. For example, web + * + * A server sends and receives data to and from its associated clients + * (other programs connected to it). When a server is started, it begins + * listening for connections on the port specified by the port + * parameter. Computers have many ports for transferring data and some are + * commonly used so be sure to not select one of these. For example, web * servers usually use port 80 and POP mail uses port 110. - * + * * @webref server * @usage application - * @webBrief The server class is used to create server objects which send + * @webBrief The server class is used to create server objects which send * and receives data to and from its associated clients (other programs connected to it) * @instanceName server any variable of type Server */ @@ -61,7 +61,7 @@ public class Server implements Runnable { /** Array of client objects, useful length is determined by clientCount. */ public Client[] clients; - + /** * @param parent typically use "this" * @param port port used to transfer data @@ -69,12 +69,12 @@ public class Server implements Runnable { public Server(PApplet parent, int port) { this(parent, port, null); } - - + + /** * @param parent typically use "this" * @param port port used to transfer data - * @param host when multiple NICs are in use, the ip (or name) to bind from + * @param host when multiple NICs are in use, the ip (or name) to bind from */ public Server(PApplet parent, int port, String host) { this.parent = parent; @@ -94,7 +94,7 @@ public class Server implements Runnable { parent.registerMethod("dispose", this); - // reflection to check whether host applet has a call for + // reflection to check whether host sketch has a call for // public void serverEvent(Server s, Client c); // which is called when a new guy connects try { @@ -114,9 +114,9 @@ public class Server implements Runnable { /** - * + * * Disconnect a particular client. - * + * * @webref server * @webBrief Disconnect a particular client * @param client the client to disconnect @@ -130,8 +130,8 @@ public class Server implements Runnable { } } } - - + + protected void removeIndex(int index) { synchronized (clientsLock) { clientCount--; @@ -143,8 +143,8 @@ public class Server implements Runnable { clients[clientCount] = null; } } - - + + protected void disconnectAll() { synchronized (clientsLock) { for (int i = 0; i < clientCount; i++) { @@ -158,8 +158,8 @@ public class Server implements Runnable { clientCount = 0; } } - - + + protected void addClient(Client client) { synchronized (clientsLock) { if (clientCount == clients.length) { @@ -168,8 +168,8 @@ public class Server implements Runnable { clients[clientCount++] = client; } } - - + + protected int clientIndex(Client client) { synchronized (clientsLock) { for (int i = 0; i < clientCount; i++) { @@ -181,20 +181,20 @@ public class Server implements Runnable { } } - + /** - * + * * Returns true if this server is still active and hasn't run * into any trouble. - * + * * @webref server * @webBrief Return true if this server is still active */ public boolean active() { return thread != null; } - - + + static public String ip() { try { return InetAddress.getLocalHost().getHostAddress(); @@ -211,9 +211,9 @@ public class Server implements Runnable { int lastAvailable = -1; /** - * + * * Returns the next client in line with a new message. - * + * * @webref server * @webBrief Returns the next client in line with a new message * @usage application @@ -247,13 +247,13 @@ public class Server implements Runnable { /** - * + * * Disconnects all clients and stops the server. - * + * *

          Advanced

          - * Use this to shut down the server if you finish using it while your applet - * is still running. Otherwise, it will be automatically be shut down by the - * host PApplet using dispose(), which is identical. + * Use this to shut down the server if you finish using it while your sketch + * is still running. Otherwise, it will be automatically be shut down by the + * host PApplet using dispose(), which is identical. * @webref server * @webBrief Disconnects all clients and stops the server * @usage application @@ -323,10 +323,10 @@ public class Server implements Runnable { /** - * - * Writes a value to all the connected clients. It sends bytes out from the + * + * Writes a value to all the connected clients. It sends bytes out from the * Server object. - * + * * @webref server * @webBrief Writes data to all connected clients * @param data data to write @@ -344,7 +344,7 @@ public class Server implements Runnable { } } } - + public void write(byte data[]) { synchronized (clientsLock) { @@ -359,7 +359,7 @@ public class Server implements Runnable { } } } - + public void write(String data) { synchronized (clientsLock) { diff --git a/java/libraries/pdf/processing4-pdf.iml b/java/libraries/pdf/processing4-pdf.iml new file mode 100644 index 000000000..5d6ac8011 --- /dev/null +++ b/java/libraries/pdf/processing4-pdf.iml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/libraries/pdf/src/processing/pdf/PGraphicsPDF.java b/java/libraries/pdf/src/processing/pdf/PGraphicsPDF.java index d937afaf6..c0bb22f17 100644 --- a/java/libraries/pdf/src/processing/pdf/PGraphicsPDF.java +++ b/java/libraries/pdf/src/processing/pdf/PGraphicsPDF.java @@ -167,7 +167,7 @@ public class PGraphicsPDF extends PGraphicsJava2D { vertexCount = 0; // Also need to push the matrix since the matrix doesn't reset on each run - // http://dev.processing.org/bugs/show_bug.cgi?id=1227 + // https://download.processing.org/bugzilla/1227.html pushMatrix(); } @@ -206,7 +206,7 @@ public class PGraphicsPDF extends PGraphicsJava2D { // This won't fix the issue if the same thing happens with // other removable drive devices, but should fix the // initial/problem as cited by the bug report: - // http://dev.processing.org/bugs/show_bug.cgi?id=478 + // https://download.processing.org/bugzilla/478.html // If not, will need to use the other fileExists() code below. continue; } @@ -246,7 +246,7 @@ public class PGraphicsPDF extends PGraphicsJava2D { /** * Recursive walk to get all subdirectories for font fun. * Patch submitted by Matthias Breuer. - * (Bug 1566) + * (Bug 1566) */ static protected void traverseDir(File folder, DefaultFontMapper mapper) { File[] files = folder.listFiles(); @@ -261,10 +261,10 @@ public class PGraphicsPDF extends PGraphicsJava2D { // endDraw() needs to be overridden so that the endDraw() from // PGraphicsJava2D is not inherited (it calls loadPixels). - // http://dev.processing.org/bugs/show_bug.cgi?id=1169 + // https://download.processing.org/bugzilla/1169.html public void endDraw() { // Also need to pop the matrix since the matrix doesn't reset on each run - // http://dev.processing.org/bugs/show_bug.cgi?id=1227 + // https://download.processing.org/bugzilla/1227.html popMatrix(); } diff --git a/java/libraries/serial/library/jssc.txt b/java/libraries/serial/library/jssc.txt index a73d19788..db5177a34 100644 --- a/java/libraries/serial/library/jssc.txt +++ b/java/libraries/serial/library/jssc.txt @@ -1,4 +1,4 @@ -This is using a modified version of Java Simple Serial Connector by Alexey Sokolov. See https://github.com/gohai/java-simple-serial-connector for details on the modifications. +This is using a modified version of Java Simple Serial Connector by Alexey Sokolov. See https://github.com/gohai/java-simple-serial-connector and https://github.com/sampottinger/jssc for details on the modifications to support Processing. To compile the C++ portion of the library on OS X: g++ -shared [or: -dynamiclib?] -arch i386 -arch x86_64 -I/System/Library/Frameworks/IOKit.framework/Versions/A/Headers -I$JAVA_HOME/include -I$JAVA_HOME/include/darwin -framework IOKit -framework CoreFoundation -o libjSSC-2.6.jnilib jssc.cpp diff --git a/java/libraries/serial/processing4-serial.iml b/java/libraries/serial/processing4-serial.iml new file mode 100644 index 000000000..0ca1d9326 --- /dev/null +++ b/java/libraries/serial/processing4-serial.iml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/libraries/serial/src/processing/serial/Serial.java b/java/libraries/serial/src/processing/serial/Serial.java index 6ff3909d8..a0b5f8462 100644 --- a/java/libraries/serial/src/processing/serial/Serial.java +++ b/java/libraries/serial/src/processing/serial/Serial.java @@ -34,9 +34,9 @@ import jssc.*; /** - * + * * Class for sending and receiving data using the serial communication protocol. - * + * * @webref serial * @webBrief Class for sending and receiving data using the serial communication protocol * @instanceName serial any variable of type Serial @@ -186,7 +186,7 @@ public class Serial implements SerialPortEventListener { /** * Returns the number of bytes available. - * + * * @generate Serial_available.xml * @webref serial * @webBrief Returns the number of bytes available @@ -212,7 +212,7 @@ public class Serial implements SerialPortEventListener { /** * Sets a specific byte to buffer until before calling serialEvent(). - * + * * @generate Serial_bufferUntil.xml * @webref serial * @webBrief Sets a specific byte to buffer until before calling serialEvent() @@ -266,7 +266,7 @@ public class Serial implements SerialPortEventListener { /** * Returns last byte received or -1 if there is none available. - * + * * @generate Serial_last.xml *

          Advanced

          * Same as read() but returns the very last value received @@ -292,7 +292,7 @@ public class Serial implements SerialPortEventListener { /** * Returns the last byte received as a char or -1 if there is none available. - * + * * @generate Serial_lastChar.xml * @webref serial * @webBrief Returns the last byte received as a char or -1 if there is none available @@ -304,9 +304,9 @@ public class Serial implements SerialPortEventListener { /** - * Gets a list of all available serial ports. Use println() to write the + * Gets a list of all available serial ports. Use println() to write the * information to the text window. - * + * * @generate Serial_list.xml * @webref serial * @webBrief Gets a list of all available serial ports @@ -320,10 +320,10 @@ public class Serial implements SerialPortEventListener { /** - * Returns a number between 0 and 255 for the next byte that's waiting in the buffer. - * Returns -1 if there is no byte, although this should be avoided by first cheacking + * Returns a number between 0 and 255 for the next byte that's waiting in the buffer. + * Returns -1 if there is no byte, although this should be avoided by first cheacking * available() to see if data is available. - * + * * @generate Serial_read.xml * @webref serial * @webBrief Returns a number between 0 and 255 for the next byte that's waiting in the buffer @@ -346,11 +346,11 @@ public class Serial implements SerialPortEventListener { /** - * Reads a group of bytes from the buffer or null if there are none available. The version - * with no parameters returns a byte array of all data in the buffer. This is not efficient, but - * is easy to use. The version with the byteBuffer parameter is more memory and time - * efficient. It grabs the data in the buffer and puts it into the byte array passed in and returns - * an int value for the number of bytes read. If more bytes are available than can fit into the + * Reads a group of bytes from the buffer or null if there are none available. The version + * with no parameters returns a byte array of all data in the buffer. This is not efficient, but + * is easy to use. The version with the byteBuffer parameter is more memory and time + * efficient. It grabs the data in the buffer and puts it into the byte array passed in and returns + * an int value for the number of bytes read. If more bytes are available than can fit into the * byteBuffer, only those that fit are read. * @generate Serial_readBytes.xml * @webref serial @@ -370,7 +370,7 @@ public class Serial implements SerialPortEventListener { return ret; } } - + /** *

          Advanced

          @@ -432,18 +432,18 @@ public class Serial implements SerialPortEventListener { return toCopy; } } - + /** - * Reads from the port into a buffer of bytes up to and including a particular character. If the - * character isn't in the buffer, null is returned. The version with without the - * byteBuffer parameter returns a byte array of all data up to and including the - * interesting byte. This is not efficient, but is easy to use. The version with the - * byteBuffer parameter is more memory and time efficient. It grabs the data in the buffer - * and puts it into the byte array passed in and returns an int value for the number of bytes read. - * If the byte buffer is not large enough, -1 is returned and an error is printed to the message + * Reads from the port into a buffer of bytes up to and including a particular character. If the + * character isn't in the buffer, null is returned. The version with without the + * byteBuffer parameter returns a byte array of all data up to and including the + * interesting byte. This is not efficient, but is easy to use. The version with the + * byteBuffer parameter is more memory and time efficient. It grabs the data in the buffer + * and puts it into the byte array passed in and returns an int value for the number of bytes read. + * If the byte buffer is not large enough, -1 is returned and an error is printed to the message * area. If nothing is in the buffer, 0 is returned. - * + * * @generate Serial_readBytesUntil.xml * @webref serial * @webBrief Reads from the port into a buffer of bytes up to and including a particular character @@ -527,7 +527,7 @@ public class Serial implements SerialPortEventListener { /** - * Returns the next byte in the buffer as a char. Returns -1 or 0xffff + * Returns the next byte in the buffer as a char. Returns -1 or 0xffff * if nothing is there. * * @generate Serial_readChar.xml @@ -541,9 +541,9 @@ public class Serial implements SerialPortEventListener { /** - * Returns all the data from the buffer as a String or null if there is nothing available. - * This method assumes the incoming characters are ASCII. If you want to transfer Unicode data, - * first convert the String to a byte stream in the representation of your choice (i.e. UTF8 or + * Returns all the data from the buffer as a String or null if there is nothing available. + * This method assumes the incoming characters are ASCII. If you want to transfer Unicode data, + * first convert the String to a byte stream in the representation of your choice (i.e. UTF8 or * two-byte Unicode data), and send it as a byte array. * * @generate Serial_readString.xml @@ -560,7 +560,7 @@ public class Serial implements SerialPortEventListener { /** - * Combination of readBytesUntil() and readString(). Returns null + * Combination of readBytesUntil() and readString(). Returns null * if it doesn't find what you're looking for. * * @generate Serial_readStringUntil.xml @@ -585,11 +585,11 @@ public class Serial implements SerialPortEventListener { /** - * Called when data is available. Use one of the read() methods to capture this data. - * The serialEvent() can be set with buffer() to only trigger after a certain - * number of data elements are read and can be set with bufferUntil() to only trigger - * after a specific character is read. The which parameter contains the name of the - * port where new data is available, but is only useful when there is more than one serial + * Called when data is available. Use one of the read() methods to capture this data. + * The serialEvent() can be set with buffer() to only trigger after a certain + * number of data elements are read and can be set with bufferUntil() to only trigger + * after a specific character is read. The which parameter contains the name of the + * port where new data is available, but is only useful when there is more than one serial * connection open and it's necessary to distinguish between the two. * * @generate serialEvent.xml @@ -630,7 +630,7 @@ public class Serial implements SerialPortEventListener { // serialAvailable() does not provide any real benefits over using // available() and read() inside draw - but this function has no // thread-safety issues since it's being invoked during pre in the context - // of the Processing applet + // of the Processing sketch serialEventMethod.invoke(parent, this); } catch (Exception e) { System.err.println("Error, disabling serialEvent() for "+port.getPortName()); @@ -676,7 +676,7 @@ public class Serial implements SerialPortEventListener { /** * Stops data communication on this port. Use to shut the connection when you're finished with the Serial. - * + * * @generate Serial_stop.xml * @webref serial * @webBrief Stops data communication on this port @@ -723,7 +723,7 @@ public class Serial implements SerialPortEventListener { /** * Writes bytes, chars, ints, bytes[], Strings to the serial port - * + * *

          Advanced

          * Write a String to the output. Note that this doesn't account * for Unicode (two bytes per char), nor will it send UTF8 diff --git a/java/libraries/svg/.classpath b/java/libraries/svg/.classpath index cf20983c2..e252c4b4e 100644 --- a/java/libraries/svg/.classpath +++ b/java/libraries/svg/.classpath @@ -1,7 +1,7 @@ - + diff --git a/java/libraries/svg/.gitignore b/java/libraries/svg/.gitignore index 50434fd98..3ebf24add 100644 --- a/java/libraries/svg/.gitignore +++ b/java/libraries/svg/.gitignore @@ -1 +1 @@ -library/batik-all-*.jar +library/batik.jar diff --git a/java/libraries/svg/build.xml b/java/libraries/svg/build.xml index e103b6dd7..725c55b60 100644 --- a/java/libraries/svg/build.xml +++ b/java/libraries/svg/build.xml @@ -1,16 +1,15 @@ - - - - + - - + + + + - - + @@ -34,42 +32,52 @@ ignoreerrors="${batik.ignorable}" usetimestamp="true" /> - - + - + + + - + - + + + + + + + + + + diff --git a/java/libraries/svg/processing4-svg.iml b/java/libraries/svg/processing4-svg.iml new file mode 100644 index 000000000..484404723 --- /dev/null +++ b/java/libraries/svg/processing4-svg.iml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/libraries/svg/src/processing/svg/PGraphicsSVG.java b/java/libraries/svg/src/processing/svg/PGraphicsSVG.java index 11ec0eb3f..3b170353b 100644 --- a/java/libraries/svg/src/processing/svg/PGraphicsSVG.java +++ b/java/libraries/svg/src/processing/svg/PGraphicsSVG.java @@ -1,7 +1,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2013-2015 The Processing Foundation + Copyright (c) 2013-2022 The Processing Foundation Copyright (c) 2012 Ben Fry and Casey Reas This library is free software; you can redistribute it and/or @@ -95,14 +95,14 @@ public class PGraphicsSVG extends PGraphicsJava2D { vertexCount = 0; // Also need to push the matrix since the matrix doesn't reset on each run - // http://dev.processing.org/bugs/show_bug.cgi?id=1227 + // https://download.processing.org/bugzilla/1227.html pushMatrix(); } public void endDraw() { // Also need to pop the matrix since the matrix doesn't reset on each run - // http://dev.processing.org/bugs/show_bug.cgi?id=1227 + // https://download.processing.org/bugzilla/1227.html popMatrix(); // Figure out where the output goes. If the sketch is calling setOutput() @@ -126,7 +126,7 @@ public class PGraphicsSVG extends PGraphicsJava2D { // This needs to be overridden so that the endDraw() from PGraphicsJava2D // is not inherited (it calls loadPixels). - // http://dev.processing.org/bugs/show_bug.cgi?id=1169 + // https://download.processing.org/bugzilla/1169.html // Finally, stream out SVG to the standard output using UTF-8 encoding. boolean useCSS = true; // we want to use CSS style attributes diff --git a/java/mode/jdtCompilerAdapter.jar b/java/mode/jdtCompilerAdapter.jar old mode 100755 new mode 100644 diff --git a/java/processing4-java.iml b/java/processing4-java.iml new file mode 100644 index 000000000..0ac279898 --- /dev/null +++ b/java/processing4-java.iml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/src/processing/mode/java/AutoFormat.java b/java/src/processing/mode/java/AutoFormat.java index bb2e5bad1..4b0b4b9bc 100644 --- a/java/src/processing/mode/java/AutoFormat.java +++ b/java/src/processing/mode/java/AutoFormat.java @@ -732,7 +732,7 @@ public class AutoFormat implements Formatter { case ':': // Java 8 :: operator. if (peek() == ':') { - result.append(c).append(nextChar()); + buf.append(c).append(nextChar()); break; } diff --git a/java/src/processing/mode/java/Commander.java b/java/src/processing/mode/java/Commander.java index 2a329180c..26e191900 100644 --- a/java/src/processing/mode/java/Commander.java +++ b/java/src/processing/mode/java/Commander.java @@ -26,7 +26,7 @@ package processing.mode.java; import java.io.File; import java.io.IOException; import java.io.PrintStream; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import processing.app.Base; import processing.app.Platform; @@ -37,6 +37,7 @@ import processing.app.SketchException; import processing.app.Util; import processing.app.contrib.ModeContribution; import processing.core.PApplet; +import processing.data.StringDict; import processing.mode.java.runner.Runner; @@ -54,12 +55,13 @@ public class Commander implements RunnerListener { static final String outputArg = "--output="; static final String exportApplicationArg = "--export"; static final String noJavaArg = "--no-java"; - static final String platformArg = "--platform="; - static final String bitsArg = "--bits="; +// static final String platformArg = "--platform="; +// static final String bitsArg = "--bits="; // static final String preferencesArg = "--preferences="; + static final String variantArg = "--variant="; static final int HELP = -1; - static final int PREPROCESS = 0; +// static final int PREPROCESS = 0; static final int BUILD = 1; static final int RUN = 2; static final int PRESENT = 3; @@ -80,27 +82,23 @@ public class Commander implements RunnerListener { boolean outputSet = false; // set an output folder boolean force = false; // replace that no good output folder // String preferencesPath = null; - int platform = PApplet.platform; // default to this platform +// int platform = PApplet.platform; // default to this platform // int platformBits = Base.getNativeBits(); + String variant = Platform.getVariant(); int task = HELP; boolean embedJava = true; - try { - if (Platform.isWindows()) { - // On Windows, it needs to use the default system encoding. - // https://github.com/processing/processing/issues/1633 - systemOut = new PrintStream(System.out, true); - systemErr = new PrintStream(System.err, true); - } else { - // On OS X, the output goes as MacRoman or something else useless. - // http://code.google.com/p/processing/issues/detail?id=1418 - // (Not sure about Linux, but this has worked since 2.0) - systemOut = new PrintStream(System.out, true, "UTF-8"); - systemErr = new PrintStream(System.err, true, "UTF-8"); - } - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - System.exit(1); + if (Platform.isWindows()) { + // On Windows, it needs to use the default system encoding. + // https://github.com/processing/processing/issues/1633 + systemOut = new PrintStream(System.out, true); + systemErr = new PrintStream(System.err, true); + } else { + // OS X formerly used MacRoman or something else useless. + // (Not sure about Linux, but this has worked since 2.0) + // https://github.com/processing/processing/issues/1456 + systemOut = new PrintStream(System.out, true, StandardCharsets.UTF_8); + systemErr = new PrintStream(System.err, true, StandardCharsets.UTF_8); } int argOffset = 0; @@ -135,13 +133,11 @@ public class Commander implements RunnerListener { } else if (arg.equals(noJavaArg)) { embedJava = false; - } else if (arg.startsWith(platformArg)) { - String platformStr = arg.substring(platformArg.length()); - platform = Platform.getIndex(platformStr); - if (platform == -1) { - complainAndQuit(platformStr + " should instead be " + - "'windows', 'macosx', or 'linux'.", true); - } + } else if (arg.startsWith("--platform")) { + complainAndQuit("Use --variant instead of --platform with Processing 4.", true); + + } else if (arg.startsWith(variantArg)) { + variant = arg.substring(variantArg.length()); } else if (arg.startsWith(sketchArg)) { sketchPath = arg.substring(sketchArg.length()); @@ -258,8 +254,7 @@ public class Commander implements RunnerListener { JavaBuild build = new JavaBuild(sketch); build.build(true); if (build != null) { - String variant = Platform.getVariant(); - success = build.exportApplication(outputFolder, platform, variant, embedJava); + success = build.exportApplication(outputFolder, variant, embedJava); } } } @@ -349,18 +344,46 @@ public class Commander implements RunnerListener { out.println("--present Preprocess, compile, and run a sketch in presentation mode."); out.println(); out.println("--export Export an application."); - out.println("--no-java Do not embed Java. Use at your own risk!"); - out.println("--platform Specify the platform (export to application only)."); - out.println(" Should be one of 'windows', 'macosx', or 'linux'."); - +// out.println("--platform Specify the platform (export to application only)."); +// out.println(" Should be one of 'windows', 'macosx', or 'linux'."); + out.println("--variant Specify the platform and architecture (Export only)."); + out.println("--no-java Do not embed Java."); out.println(); + + out.println("Starting with 4.0, the --platform option has been removed"); + out.println("because of the variety of platforms and architectures now available."); + out.println("Use the --variant option instead, for instance:"); + out.println(); + /* + out.println("platform variant"); + out.println("--------------------------- -------------"); + out.println("macOS (Intel 64-bit) macos-x86_64"); + out.println("macOS (Apple Silicon) macos-aarch64"); + out.println("Windows (Intel 64-bit) windows-amd64"); + out.println("Linux (Intel 64-bit) linux-amd64"); + out.println("Linux (Raspberry Pi 32-bit) linux-arm"); + out.println("Linux (Raspberry Pi 64-bit) linux-aarch64"); + */ + out.println("variant platform"); + out.println("------------- ---------------------------"); + + StringDict variants = Platform.getSupportedVariants(); + for (String key : variants.keys()) { + out.print(key); + int spaces = 15 - key.length(); // 13 dashes, two spaces + for (int i = 0; i < spaces; i++) { + System.out.print(" "); + } + System.out.println(variants.get(key)); + } + out.println(); + out.println("The --build, --run, --present, or --export must be the final parameter"); out.println("passed to Processing. Arguments passed following one of those four will"); out.println("be passed through to the sketch itself, and therefore available to the"); out.println("sketch via the 'args' field. To pass options understood by PApplet.main(),"); out.println("write a custom main() method so that the preprocessor does not add one."); out.println("https://github.com/processing/processing/wiki/Command-Line"); - out.println(); } diff --git a/java/src/processing/mode/java/Compiler.java b/java/src/processing/mode/java/Compiler.java index 94448bf56..aaef2e8c3 100644 --- a/java/src/processing/mode/java/Compiler.java +++ b/java/src/processing/mode/java/Compiler.java @@ -63,8 +63,8 @@ public class Compiler { "-g", "-Xemacs", //"-noExit", // not necessary for ecj - "-source", "11", - "-target", "11", + "-source", "11", // TODO: 17 if using new language features + "-target", "11", // TODO: 17 if using new language features "-encoding", "utf8", "-classpath", classpathEmptyRemoved, "-nowarn", // we're not currently interested in warnings (works in ecj) @@ -133,7 +133,7 @@ public class Compiler { // get first line, which contains file name, line number, // and at least the first line of the error message - String errorFormat = "([\\w\\d_]+.java):(\\d+):\\s*(.*):\\s*(.*)\\s*"; + String errorFormat = "([\\w\\d_]+\\.java):(\\d+):\\s*([^:]*):\\s*(.*)\\s*"; String[] pieces = PApplet.match(line, errorFormat); //PApplet.println(pieces); diff --git a/java/src/processing/mode/java/CompletionCandidate.java b/java/src/processing/mode/java/CompletionCandidate.java index 9dd29ed75..4e8c2b980 100644 --- a/java/src/processing/mode/java/CompletionCandidate.java +++ b/java/src/processing/mode/java/CompletionCandidate.java @@ -171,13 +171,6 @@ public class CompletionCandidate implements Comparable { } - /* - Object getWrappedObject() { - return wrappedObject; - } - */ - - public String getElementName() { return elementName; } @@ -198,33 +191,6 @@ public class CompletionCandidate implements Comparable { } - // TODO this is gross [fry 180326] - /* - private String getNoHtmlLabel(){ - if (!label.contains("")) { - return label; - - } else { - StringBuilder ans = new StringBuilder(label); - while (ans.indexOf("<") > -1) { - int a = ans.indexOf("<"), b = ans.indexOf(">"); - if (a > b) break; - ans.replace(a, b+1, ""); - } - return ans.toString(); - } - } - */ - - - boolean startsWith(String newWord) { -// System.out.println("checking " + newWord); -// return getNoHtmlLabel().toLowerCase().startsWith(newWord); - // this seems to be elementName in all cases [fry 180326] - return elementName.startsWith(newWord); - } - - CompletionCandidate withLabelAndCompString(String withLabel, String withCompletion) { return new CompletionCandidate(elementName, @@ -289,10 +255,8 @@ public class CompletionCandidate implements Comparable { } } labelBuilder.append(')'); - if (method.getReturnType() != null) { - labelBuilder.append(" : "); - labelBuilder.append(method.getReturnType().getSimpleName()); - } + labelBuilder.append(" : "); + labelBuilder.append(method.getReturnType().getSimpleName()); labelBuilder.append(" - "); labelBuilder.append(method.getDeclaringClass().getSimpleName()); @@ -335,10 +299,8 @@ public class CompletionCandidate implements Comparable { } } labelBuilder.append(")"); - if (method.getReturnType() != null) { - labelBuilder.append(" : "); - labelBuilder.append(method.getReturnType().getSimpleName()); - } + labelBuilder.append(" : "); + labelBuilder.append(method.getReturnType().getSimpleName()); labelBuilder.append(" - "); labelBuilder.append(method.getDeclaringClass().getSimpleName()); labelBuilder.append(""); diff --git a/java/src/processing/mode/java/CompletionGenerator.java b/java/src/processing/mode/java/CompletionGenerator.java index 1bf55c64b..e5c127872 100644 --- a/java/src/processing/mode/java/CompletionGenerator.java +++ b/java/src/processing/mode/java/CompletionGenerator.java @@ -20,7 +20,6 @@ along with this program; if not, write to the Free Software Foundation, Inc. package processing.mode.java; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -68,8 +67,6 @@ public class CompletionGenerator { public CompletionGenerator(JavaMode mode) { this.mode = mode; - //addCompletionPopupListner(); - //loadJavaDoc(); } @@ -87,8 +84,6 @@ public class CompletionGenerator { CompletionCandidate[] cand = new CompletionCandidate[params.size() + 1]; cand[0] = new CompletionCandidate(md); for (int i = 0; i < params.size(); i++) { -// cand[i + 1] = new CompletionCandidate(params.get(i).toString(), "", "", -// CompletionCandidate.LOCAL_VAR); cand[i + 1] = new CompletionCandidate((SingleVariableDeclaration) params.get(i)); } return cand; @@ -123,65 +118,6 @@ public class CompletionGenerator { } -// /** -// * 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() beginning with b -// */ -// public static ASTNode resolveExpression(ASTNode nearestNode, -// ASTNode expression, boolean noCompare) { -// log("Resolving " + getNodeAsString(expression) + " noComp " -// + noCompare); -// if (expression instanceof SimpleName) { -// return findDeclaration2(((SimpleName) expression), nearestNode); -// } else if (expression instanceof MethodInvocation) { -// log("3. Method Invo " -// + ((MethodInvocation) expression).getName()); -// return findDeclaration2(((MethodInvocation) expression).getName(), -// nearestNode); -// } else if (expression instanceof FieldAccess) { -// log("2. Field access " -// + getNodeAsString(((FieldAccess) expression).getExpression()) + "|||" -// + getNodeAsString(((FieldAccess) expression).getName())); -// if (noCompare) { -// /* -// * ASTNode ret = findDeclaration2(((FieldAccess) expression).getName(), -// * nearestNode); log("Found as ->"+getNodeAsString(ret)); -// * return ret; -// */ -// return findDeclaration2(((FieldAccess) expression).getName(), -// nearestNode); -// } else { -// -// /* -// * Note how for the next recursion, noCompare is reversed. Let's say -// * I've typed getABC().quark.nin where nin is incomplete(ninja being the -// * field), when execution first enters here, it calls resolveExpr again -// * for "getABC().quark" where we know that quark field must be complete, -// * so we toggle noCompare. And kaboom. -// */ -// return resolveExpression(nearestNode, -// ((FieldAccess) expression).getExpression(), -// true); -// } -// //return findDeclaration2(((FieldAccess) expression).getExpression(), nearestNode); -// } else if (expression instanceof QualifiedName) { -// log("1. Resolving " -// + ((QualifiedName) expression).getQualifier() + " ||| " -// + ((QualifiedName) expression).getName()); -// if (noCompare) { // no compare, as in "abc.hello." need to resolve hello here -// return findDeclaration2(((QualifiedName) expression).getName(), -// nearestNode); -// } else { -// //User typed "abc.hello.by" (bye being complete), so need to resolve "abc.hello." only -// return findDeclaration2(((QualifiedName) expression).getQualifier(), -// nearestNode); -// } -// } -// -// return null; -// } - - /** * 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, @@ -889,23 +825,6 @@ public class CompletionGenerator { } - /* - protected SketchOutline sketchOutline; - - public void showSketchOutline() { - if (editor.hasJavaTabs()) return; - - sketchOutline = new SketchOutline(editor, codeTree); - sketchOutline.show(); - } - - - public void showTabOutline() { - new TabOutline(editor).show(); - } - */ - - /** * 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 @@ -961,7 +880,7 @@ public class CompletionGenerator { } } } else { - parent = parent.getParent(); // Move one up the ast. V V IMP!! + parent = parent.getParent(); // Move one up the AST. Very important. } } else if (parent.getNodeType() == ASTNode.FIELD_ACCESS) { FieldAccess fa = (FieldAccess) parent; @@ -1327,7 +1246,7 @@ public class CompletionGenerator { static class ClassMember { private Field field; private Method method; - private Constructor cons; +// private Constructor cons; private Class thisClass; private final String stringVal; private String classType; @@ -1477,7 +1396,7 @@ public class CompletionGenerator { static private ASTNode definedIn(ASTNode node, String name, - ArrayList constrains) { + List constrains) { if (node == null) return null; if (constrains != null) { @@ -1618,43 +1537,6 @@ public class CompletionGenerator { } -// public void jdocWindowVisible(boolean visible) { -// // frmJavaDoc.setVisible(visible); -// } - -// public static String readFile2(String path) { -// BufferedReader reader = null; -// try { -// reader = new BufferedReader( -// new InputStreamReader( -// new FileInputStream( -// new File( -// path)))); -// } catch (FileNotFoundException e) { -// e.printStackTrace(); -// } -// try { -// StringBuilder ret = new StringBuilder(); -// // ret.append("package " + className + ";\n"); -// String line; -// while ((line = reader.readLine()) != null) { -// ret.append(line); -// ret.append("\n"); -// } -// return ret.toString(); -// } catch (IOException e) { -// e.printStackTrace(); -// } finally { -// try { -// reader.close(); -// } catch (IOException e) { -// e.printStackTrace(); -// } -// } -// return null; -// } - - static private void log(Object object) { Messages.log(object == null ? "null" : object.toString()); } @@ -1668,7 +1550,7 @@ public class CompletionGenerator { newWord = newWord.toLowerCase(); for (CompletionCandidate comp : candidates) { //if (comp.getNoHtmlLabel().toLowerCase().startsWith(newWord)) { - if (comp.startsWith(newWord)) { + if (comp.getElementName().startsWith(newWord)) { newCandidate.add(comp); } } @@ -1926,112 +1808,4 @@ public class CompletionGenerator { } return defListModel; } - - - - /// JavaDocs ----------------------------------------------------------------- - - //TODO: Work on this later. -/* - protected TreeMap jdocMap; - - - protected void loadJavaDoc() { - jdocMap = new TreeMap<>(); - - // presently loading only p5 reference for PApplet - // TODO: use something like ExecutorService here [jv] - new Thread(new Runnable() { - @Override - public void run() { - try { - loadJavaDoc(jdocMap, editor.getMode().getReferenceFolder()); - } catch (Exception e) { - e.printStackTrace(); - } - } - }).start(); - } - - - static void loadJavaDoc(TreeMap jdocMap, - File referenceFolder) throws IOException { - Document doc; - - FileFilter fileFilter = new FileFilter() { - @Override - public boolean accept(File file) { - if(!file.getName().endsWith("_.html")) - return false; - int k = 0; - for (int i = 0; i < file.getName().length(); i++) { - if(file.getName().charAt(i)== '_') - k++; - if(k > 1) - return false; - } - return true; - } - }; - - for (File docFile : referenceFolder.listFiles(fileFilter)) { - doc = Jsoup.parse(docFile, null); - Elements elm = doc.getElementsByClass("ref-item"); - String msg = ""; - String methodName = docFile.getName().substring(0, docFile.getName().indexOf('_')); - //System.out.println(methodName); - for (org.jsoup.nodes.Element ele : elm) { - msg = "
          " - + ele.html() + "
          "; - //mat.replaceAll(""); - msg = msg.replaceAll("img src=\"", "img src=\"" - + referenceFolder.toURI().toURL().toString() + "/"); - //System.out.println(ele.text()); - } - jdocMap.put(methodName, msg); - } - //System.out.println("JDoc loaded " + jdocMap.size()); - } - - - public void updateJavaDoc(final CompletionCandidate candidate) { - String methodmatch = candidate.toString(); - if (methodmatch.indexOf('(') != -1) { - methodmatch = methodmatch.substring(0, methodmatch.indexOf('(')); - } - - //log("jdoc match " + methodmatch); - String temp = " "; - for (final String key : jdocMap.keySet()) { - if (key.startsWith(methodmatch) && key.length() > 3) { - log("Matched jdoc " + key); - if (candidate.getWrappedObject() != null) { - String definingClass = ""; - if (candidate.getWrappedObject() instanceof Field) - definingClass = ((Field) candidate.getWrappedObject()) - .getDeclaringClass().getName(); - else if (candidate.getWrappedObject() instanceof Method) - definingClass = ((Method) candidate.getWrappedObject()) - .getDeclaringClass().getName(); - if (definingClass.equals("processing.core.PApplet")) { - temp = (jdocMap.get(key)); - break; - } - } - } - } - - final String jdocString = temp; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - javadocPane.setText(jdocString); - scrollPane.getVerticalScrollBar().setValue(0); - //frmJavaDoc.setVisible(!jdocString.equals(" ")); - editor.toFront(); - editor.ta.requestFocus(); - } - }); - } -*/ - } diff --git a/java/src/processing/mode/java/CompletionPanel.java b/java/src/processing/mode/java/CompletionPanel.java index b23df460e..bda85d75e 100644 --- a/java/src/processing/mode/java/CompletionPanel.java +++ b/java/src/processing/mode/java/CompletionPanel.java @@ -23,12 +23,14 @@ package processing.mode.java; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; +import java.awt.Dimension; import java.awt.Font; -import java.awt.FontMetrics; import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; +import java.util.HashMap; +import java.util.Map; import javax.swing.DefaultListModel; import javax.swing.ImageIcon; @@ -37,53 +39,47 @@ import javax.swing.JList; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.ListSelectionModel; -import javax.swing.text.BadLocationException; +import javax.swing.border.EmptyBorder; import processing.app.Base; import processing.app.Messages; import processing.app.Mode; import processing.app.syntax.JEditTextArea; +import processing.app.ui.Theme; import processing.app.ui.Toolkit; public class CompletionPanel { - /** - * The completion list generated by ASTGenerator - */ - private JList completionList; + static final int MAX_HEIGHT = 300; + static final int ICON_SIZE = 16; - /** - * The popup menu in which the suggestion list is shown - */ - private JPopupMenu popupMenu; + /** The completion list generated by ASTGenerator */ + final private JList completionList; - /** - * Partial word which triggered the code completion and which needs to be completed - */ - private String subWord; + /** The popup menu in which the suggestion list is shown */ + final private JPopupMenu popupMenu; - /** - * Position where the completion has to be inserted - */ - private int insertionPosition; + /** Partial word that triggered the code completion */ + final private String subWord; - private JavaTextArea textarea; + /** Position where the completion has to be inserted */ + final private int caretPos; - /** - * Scroll pane in which the completion list is displayed - */ - private JScrollPane scrollPane; + /** Scroll pane in which the completion list is displayed */ + final private JScrollPane scrollPane; protected JavaEditor editor; - static protected final int MOUSE_COMPLETION = 10, KEYBOARD_COMPLETION = 20; - - private boolean horizontalScrollBarVisible = false; + static protected final int MOUSE_COMPLETION = 10; + static protected final int KEYBOARD_COMPLETION = 20; static public ImageIcon classIcon; static public ImageIcon fieldIcon; static public ImageIcon methodIcon; - static public ImageIcon localVarIcon; + static public ImageIcon localIcon; + + static private final Map iconColors = new HashMap<>(); + static private final Map iconCache = new HashMap<>(); static Color selectionBgColor; static Color textColor; @@ -91,48 +87,34 @@ public class CompletionPanel { /** * Triggers the completion popup - * @param textarea - * @param position - insertion position(caret pos) - * @param subWord - Partial word which triggered the code completion and which needs to be completed + * @param caret - insertion caret position + * @param subWord - Partial word that triggered the code completion * @param items - completion candidates * @param location - Point location where popup list is to be displayed */ - public CompletionPanel(final JEditTextArea textarea, - int position, String subWord, + public CompletionPanel(JavaEditor editor, int caret, String subWord, DefaultListModel items, - final Point location, JavaEditor editor) { - this.textarea = (JavaTextArea) textarea; + Point location) { this.editor = editor; - this.insertionPosition = position; + this.caretPos = caret; if (subWord.indexOf('.') != -1 && subWord.indexOf('.') != subWord.length()-1) { this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1); } else { this.subWord = subWord; } - if (classIcon == null) { - Mode mode = editor.getMode(); - - File dir = new File(mode.getFolder(), "theme/completion"); - classIcon = Toolkit.getIconX(dir, "class_obj"); - methodIcon = Toolkit.getIconX(dir, "methpub_obj"); - fieldIcon = Toolkit.getIconX(dir, "field_protected_obj"); - localVarIcon = Toolkit.getIconX(dir, "field_default_obj"); - - //selectionBgColor = mode.getColor(""); // no theme.txt for Java Mode - selectionBgColor = new Color(0xffF0F0F0); - textColor = new Color(0xff222222); - } + // more like initTheme(), but using this for consistency + updateTheme(); popupMenu = new JPopupMenu(); - popupMenu.removeAll(); - popupMenu.setOpaque(false); - popupMenu.setBorder(null); +// popupMenu.setLightWeightPopupEnabled(true); // doesn't help? +// popupMenu.removeAll(); // uh, this is already empty + popupMenu.setOpaque(false); // why? [fry 220803] + // setting border null just makes it use the default 2px border + popupMenu.setBorder(new EmptyBorder(0, 0, 0, 0)); scrollPane = new JScrollPane(); -// styleScrollPane(); - //scrollPane.setViewportView(completionList = createSuggestionList(position, items)); - completionList = new JList(items) { + completionList = new JList<>(items) { { setSelectionMode(ListSelectionModel.SINGLE_SELECTION); setSelectedIndex(0); @@ -155,72 +137,51 @@ public class CompletionPanel { scrollPane.setBorder(null); popupMenu.add(scrollPane, BorderLayout.CENTER); - popupMenu.setPopupSize(calcWidth(), calcHeight(items.getSize())); //TODO: Eradicate this evil + Dimension pref = completionList.getPreferredSize(); + popupMenu.setPopupSize(pref.width + classIcon.getIconWidth(), + Math.min(pref.height + 2, MAX_HEIGHT)); popupMenu.setFocusable(false); // TODO: Update JavaDoc to completionList.getSelectedValue() + JavaTextArea textarea = editor.getJavaTextArea(); popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0) + location.y); //log("Suggestion shown: " + System.currentTimeMillis()); } - /* - private void styleScrollPane() { - String laf = UIManager.getLookAndFeel().getID(); - if (!laf.equals("Nimbus") && !laf.equals("Windows")) return; - - String thumbColor = null; - if (laf.equals("Nimbus")) { - UIDefaults defaults = new UIDefaults(); - defaults.put("PopupMenu.contentMargins", new InsetsUIResource(0, 0, 0, 0)); - defaults.put("ScrollPane[Enabled].borderPainter", new Painter() { - public void paint(Graphics2D g, JComponent t, int w, int h) {} - }); - popupMenu.putClientProperty("Nimbus.Overrides", defaults); - scrollPane.putClientProperty("Nimbus.Overrides", defaults); - thumbColor = "nimbusBlueGrey"; - } else if (laf.equals("Windows")) { - thumbColor = "ScrollBar.thumbShadow"; + /** + * Render an icon and store it in the cache, or return the cached + * version if available to avoid re-rendering several SVG icons + * each time a completion is triggered. + */ + static private ImageIcon renderIcon(File dir, String word) { + String hexColor = Theme.get("editor.completion." + word + ".color"); + String cacheColor = iconColors.getOrDefault(word, null); + if (!hexColor.equals(cacheColor)) { + ImageIcon icon = + Toolkit.renderIcon(new File(dir, word + ".svg"), hexColor, ICON_SIZE); + iconColors.put(word, hexColor); + iconCache.put(word, icon); } - - scrollPane.getHorizontalScrollBar().setPreferredSize(new Dimension(Integer.MAX_VALUE, 8)); - scrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(8, Integer.MAX_VALUE)); - scrollPane.getHorizontalScrollBar().setUI(new CompletionScrollBarUI(thumbColor)); - scrollPane.getVerticalScrollBar().setUI(new CompletionScrollBarUI(thumbColor)); + return iconCache.get(word); } - private static class CompletionScrollBarUI extends BasicScrollBarUI { - private String thumbColorName; + /** + * This is private because there's no need to call updateTheme() externally + * because the theme will never be updated *while* the window is open. + */ + private void updateTheme() { + Mode mode = editor.getMode(); + File dir = new File(mode.getFolder(), "theme/completion"); - protected CompletionScrollBarUI(String thumbColorName) { - this.thumbColorName = thumbColorName; - } + classIcon = renderIcon(dir, "class"); + fieldIcon = renderIcon(dir, "field"); + localIcon = renderIcon(dir, "local"); + methodIcon = renderIcon(dir, "method"); - @Override - protected void paintThumb(Graphics g, JComponent c, Rectangle trackBounds) { - g.setColor((Color) UIManager.get(thumbColorName)); - g.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height); - } - - @Override - protected JButton createDecreaseButton(int orientation) { - return createZeroButton(); - } - - @Override - protected JButton createIncreaseButton(int orientation) { - return createZeroButton(); - } - - static private JButton createZeroButton() { - JButton jbutton = new JButton(); - jbutton.setPreferredSize(new Dimension(0, 0)); - jbutton.setMinimumSize(new Dimension(0, 0)); - jbutton.setMaximumSize(new Dimension(0, 0)); - return jbutton; - } + selectionBgColor = new Color(0xffF0F0F0); + textColor = new Color(0xff222222); } - */ public boolean isVisible() { @@ -233,128 +194,25 @@ public class CompletionPanel { } - /** - * Dynamic height of completion panel depending on item count - */ - private int calcHeight(int itemCount) { - int maxHeight = 250; - FontMetrics fm = textarea.getGraphics().getFontMetrics(); - float itemHeight = Math.max((fm.getHeight() + (fm.getDescent()) * 0.5f), - classIcon.getIconHeight() * 1.2f); - - if (horizontalScrollBarVisible) { - itemCount++; - } - - if (itemCount < 4) { - itemHeight *= 1.3f; //Sorry, but it works. - } - - float h = itemHeight * (itemCount); - - if (itemCount >= 4) { - h += itemHeight * 0.3; // a bit of offset - } - - return Math.min(maxHeight, (int) h); // popup menu height - } - - - private int calcWidth() { - int maxWidth = 300; - float min = 0; - FontMetrics fm = textarea.getGraphics().getFontMetrics(); - for (int i = 0; i < completionList.getModel().getSize(); i++) { - float h = fm.stringWidth(completionList.getModel().getElementAt(i).getLabel()); - min = Math.max(min, h); - } - int w = Math.min((int) min, maxWidth); - horizontalScrollBarVisible = (w == maxWidth); - w += classIcon.getIconWidth(); // add icon width too! - w += fm.stringWidth(" "); // a bit of offset - //log("popup width " + w); - return w; // popup menu width - } - - - /** - * Created the popup list to be displayed - * @param position - * @param items - * @return - private JList createSuggestionList(final int position, - final DefaultListModel items) { - - JList list = new JList(items); - //list.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1)); - list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - list.setSelectedIndex(0); - list.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2) { - insertSelection(MOUSE_COMPLETION); - setInvisible(); - } - } - }); - list.setCellRenderer(new CustomListRenderer()); - list.setFocusable(false); - return list; - } - */ - - - /* - // possibly defunct - private boolean updateList(final DefaultListModel items, String newSubword, - final Point location, int position) { - this.subWord = new String(newSubword); - if (subWord.indexOf('.') != -1) - this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1); - insertionPosition = position; - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - scrollPane.getViewport().removeAll(); - completionList.setModel(items); - completionList.setSelectedIndex(0); - scrollPane.setViewportView(completionList); - popupMenu.setPopupSize(calcWidth(), calcHeight(items.getSize())); - //log("Suggestion updated" + System.nanoTime()); - textarea.requestFocusInWindow(); - popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0) - + location.y); - completionList.validate(); - scrollPane.validate(); - popupMenu.validate(); - } - }); - return true; - } - */ - - /** * Inserts the CompletionCandidate chosen from the suggestion list - * @param completionSource - whether being completed via keypress or mouse click. + * @param completionSource - whether being completed via key press or mouse click. * @return true - if code was successfully inserted at the caret position */ protected boolean insertSelection(int completionSource) { if (completionList.getSelectedValue() != null) { try { - // If user types 'abc.', subword becomes '.' and null is returned - String currentSubword = fetchCurrentSubword(); - int currentSubwordLen = - (currentSubword == null) ? 0 : currentSubword.length(); - //logE(currentSubword + " <= subword,len => " + currentSubword.length()); + // If user types 'abc.', sub word becomes '.' and null is returned + String currentSubWord = fetchCurrentSubWord(); + int currentSubWordLen = + (currentSubWord == null) ? 0 : currentSubWord.length(); String selectedSuggestion = completionList.getSelectedValue().getCompletionString(); - if (currentSubword != null) { - selectedSuggestion = selectedSuggestion.substring(currentSubwordLen); + if (currentSubWord != null) { + selectedSuggestion = selectedSuggestion.substring(currentSubWordLen); } else { - currentSubword = ""; + currentSubWord = ""; } String completionString = @@ -376,43 +234,36 @@ public class CompletionPanel { } } - Messages.err(subWord + " <= subword, Inserting suggestion=> " + - selectedSuggestion + " Current sub: " + currentSubword); - if (currentSubword.length() > 0) { - textarea.getDocument().remove(insertionPosition - currentSubwordLen, - currentSubwordLen); + JavaTextArea textArea = editor.getJavaTextArea(); + Messages.err(subWord + " <= sub word, Inserting suggestion=> " + + selectedSuggestion + " Current sub: " + currentSubWord); + if (currentSubWord.length() > 0) { + textArea.getDocument().remove(caretPos - currentSubWordLen, + currentSubWordLen); } - textarea.getDocument().insertString(insertionPosition - currentSubwordLen, + textArea.getDocument().insertString(caretPos - currentSubWordLen, completionString, null); if (selectedSuggestion.endsWith(")") && !selectedSuggestion.endsWith("()")) { // place the caret between '( and first ',' int x = selectedSuggestion.indexOf(','); if (x == -1) { // the case of single param methods, containing no ',' - textarea.setCaretPosition(textarea.getCaretPosition() - 1); // just before ')' + textArea.setCaretPosition(textArea.getCaretPosition() - 1); // just before ')' } else { - textarea.setCaretPosition(insertionPosition + x); + textArea.setCaretPosition(caretPos + x); } } - Messages.log("Suggestion inserted: " + System.currentTimeMillis()); - if (completionList.getSelectedValue().getLabel().contains("...")) { - // log("No hide"); - // Why not hide it? Coz this is the case of - // overloaded methods. See #2755 - } else { + // https://github.com/processing/processing/issues/2755 + if (!completionList.getSelectedValue().getLabel().contains("...")) { setInvisible(); } - if (mouseClickOnOverloadedMethods) { - // See #2755 ((JavaTextArea) editor.getTextArea()).fetchPhrase(); } return true; - } catch (BadLocationException e1) { - e1.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } @@ -422,8 +273,7 @@ public class CompletionPanel { } - private String fetchCurrentSubword() { - //log("Entering fetchCurrentSubword"); + private String fetchCurrentSubWord() { JEditTextArea ta = editor.getTextArea(); int off = ta.getCaretPosition(); //log2("off " + off); @@ -433,34 +283,25 @@ public class CompletionPanel { if (line < 0) return null; String s = ta.getLineText(line); - //log2("lin " + line); - //log2(s + " len " + s.length()); int x = ta.getCaretPosition() - ta.getLineStartOffset(line) - 1, x1 = x - 1; if (x >= s.length() || x < 0) return null; //TODO: Does this check cause problems? Verify. if (Base.DEBUG) System.out.print(" x char: " + s.charAt(x)); - //int xLS = off - getLineStartNonWhiteSpaceOffset(line); - String word = (x < s.length() ? s.charAt(x) : "") + ""; + String word = String.valueOf(s.charAt(x)); if (s.trim().length() == 1) { - // word = "" - // + (keyChar == KeyEvent.CHAR_UNDEFINED ? s.charAt(x - 1) : keyChar); - //word = (x < s.length()?s.charAt(x):"") + ""; word = word.trim(); if (word.endsWith(".")) word = word.substring(0, word.length() - 1); return word; } - //log("fetchCurrentSubword 1 " + word); - if(word.equals(".")) return null; // If user types 'abc.', subword becomes '.' - // if (keyChar == KeyEvent.VK_BACK_SPACE || keyChar == KeyEvent.VK_DELETE) - // ; // accepted these keys - // else if (!(Character.isLetterOrDigit(keyChar) || keyChar == '_' || keyChar == '$')) - // return null; - int i = 0; + // If user types 'abc.', sub word becomes '.' + if (word.equals(".")) return null; + + int i = 0; while (true) { i++; //TODO: currently works on single line only. "a. b()" won't be detected @@ -481,17 +322,13 @@ public class CompletionPanel { break; } } - // if (keyChar != KeyEvent.CHAR_UNDEFINED) - //log("fetchCurrentSubword 2 " + word); if (Character.isDigit(word.charAt(0))) return null; word = word.trim(); - if (word.endsWith(".")) + if (word.endsWith(".")) { word = word.substring(0, word.length() - 1); - //log("fetchCurrentSubword 3 " + word); - //showSuggestionLater(); + } return word; - //} } @@ -565,7 +402,7 @@ public class CompletionPanel { CompletionCandidate cc = (CompletionCandidate) value; switch (cc.getType()) { case CompletionCandidate.LOCAL_VAR: - label.setIcon(localVarIcon); + label.setIcon(localIcon); break; case CompletionCandidate.LOCAL_FIELD: case CompletionCandidate.PREDEF_FIELD: diff --git a/java/src/processing/mode/java/ErrorChecker.java b/java/src/processing/mode/java/ErrorChecker.java index 4f4669cdd..44f9f1944 100644 --- a/java/src/processing/mode/java/ErrorChecker.java +++ b/java/src/processing/mode/java/ErrorChecker.java @@ -29,7 +29,8 @@ import processing.app.Problem; class ErrorChecker { - // Delay delivering error check result after last sketch change #2677 + // Delay delivering error check result after last sketch change + // https://github.com/processing/processing/issues/2677 private final static long DELAY_BEFORE_UPDATE = 650; final private ScheduledExecutorService scheduler; @@ -37,7 +38,8 @@ class ErrorChecker { private volatile long nextUiUpdate = 0; private volatile boolean enabled; - private final Consumer errorHandlerListener = this::handleSketchProblems; + private final Consumer errorHandlerListener = + this::handleSketchProblems; final private JavaEditor editor; final private PreprocService pps; @@ -46,8 +48,9 @@ class ErrorChecker { public ErrorChecker(JavaEditor editor, PreprocService pps) { this.editor = editor; this.pps = pps; + scheduler = Executors.newSingleThreadScheduledExecutor(); - this.enabled = JavaMode.errorCheckEnabled; + enabled = JavaMode.errorCheckEnabled; if (enabled) { pps.registerListener(errorHandlerListener); } @@ -81,9 +84,8 @@ class ErrorChecker { private void handleSketchProblems(PreprocSketch ps) { - Map suggCache = - JavaMode.importSuggestEnabled ? new HashMap<>() : Collections.emptyMap(); + JavaMode.importSuggestEnabled ? new HashMap<>() : Collections.emptyMap(); List iproblems; if (ps.compilationUnit == null) { @@ -99,35 +101,30 @@ class ErrorChecker { problems.addAll(curlyQuoteProblems); } - if (problems.isEmpty()) { // Check for missing braces - List missingBraceProblems = checkForMissingBraces(ps); - problems.addAll(missingBraceProblems); - } - if (problems.isEmpty()) { - AtomicReference searchClassPath = new AtomicReference<>(null); + AtomicReference searchClassPath = + new AtomicReference<>(null); List cuProblems = iproblems.stream() - // Filter Warnings if they are not enabled - .filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled)) - .filter(iproblem -> !(isIgnorableProblem(iproblem))) - // Transform into our Problems - .map(iproblem -> { - JavaProblem p = convertIProblem(iproblem, ps); + // Filter Warnings if they are not enabled + .filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled)) + .filter(iproblem -> !(isIgnorableProblem(iproblem))) + // Transform into our Problems + .map(iproblem -> { + JavaProblem p = convertIProblem(iproblem, ps); - // Handle import suggestions - if (p != null && JavaMode.importSuggestEnabled && isUndefinedTypeProblem(iproblem)) { - ClassPath cp = searchClassPath.updateAndGet(prev -> prev != null ? - prev : new ClassPathFactory().createFromPaths(ps.searchClassPathArray)); - String[] s = suggCache.computeIfAbsent(iproblem.getArguments()[0], - name -> getImportSuggestions(cp, name)); - p.setImportSuggestions(s); - } - - return p; - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + // Handle import suggestions + if (p != null && JavaMode.importSuggestEnabled && isUndefinedTypeProblem(iproblem)) { + ClassPath cp = searchClassPath.updateAndGet(prev -> prev != null ? + prev : new ClassPathFactory().createFromPaths(ps.searchClassPathArray)); + String[] s = suggCache.computeIfAbsent(iproblem.getArguments()[0], + name -> getImportSuggestions(cp, name)); + p.setImportSuggestions(s); + } + return p; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); problems.addAll(cuProblems); } @@ -135,7 +132,7 @@ class ErrorChecker { if (scheduledUiUpdate != null) { scheduledUiUpdate.cancel(true); } - // Update UI after a delay. See #2677 + // https://github.com/processing/processing/issues/2677 long delay = nextUiUpdate - System.currentTimeMillis(); Runnable uiUpdater = () -> { if (nextUiUpdate > 0 && System.currentTimeMillis() >= nextUiUpdate) { @@ -156,7 +153,7 @@ class ErrorChecker { * cause issues when reaching javac. *

          * - * @return True if ignoreable and false otherwise. + * @return True if ignorable and false otherwise. */ static private boolean isIgnorableProblem(IProblem iproblem) { String message = iproblem.getMessage(); @@ -166,14 +163,11 @@ class ErrorChecker { // also produced and is preferred over this one. // (Syntax error, insert ":: IdentifierOrNew" to complete Expression) // See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=405780 - boolean ignorable = message.contains( - "Syntax error, insert \":: IdentifierOrNew\"" - ); + boolean ignorable = + message.contains("Syntax error, insert \":: IdentifierOrNew\""); // It's ok if the file names do not line up during preprocessing. - ignorable = ignorable || message.contains( - "must be defined in its own file" - ); + ignorable |= message.contains("must be defined in its own file"); return ignorable; } @@ -216,6 +210,10 @@ class ErrorChecker { static private final Pattern CURLY_QUOTE_REGEX = Pattern.compile("([“”‘’])", Pattern.UNICODE_CHARACTER_CLASS); + /** + * Check the scrubbed code for curly quotes. + * They are a common copy/paste error, especially on macOS. + */ static private List checkForCurlyQuotes(PreprocSketch ps) { if (ps.compilationUnit == null) { return new ArrayList<>(); @@ -223,7 +221,6 @@ class ErrorChecker { List problems = new ArrayList<>(0); - // Go through the scrubbed code and look for curly quotes (they should not be any) Matcher matcher = CURLY_QUOTE_REGEX.matcher(ps.scrubbedPdeCode); while (matcher.find()) { int pdeOffset = matcher.start(); @@ -240,7 +237,6 @@ class ErrorChecker { problems.add(problem); } - // Go through iproblems and look for problems involving curly quotes List problems2 = new ArrayList<>(0); IProblem[] iproblems = ps.compilationUnit.getProblems(); @@ -277,86 +273,38 @@ class ErrorChecker { } } } - problems.addAll(problems2); - - return problems; - } - - - static private List checkForMissingBraces(PreprocSketch ps) { - List problems = new ArrayList<>(0); - for (int tabIndex = 0; tabIndex < ps.tabStartOffsets.length; tabIndex++) { - int tabStartOffset = ps.tabStartOffsets[tabIndex]; - int tabEndOffset = (tabIndex < ps.tabStartOffsets.length - 1) ? - ps.tabStartOffsets[tabIndex + 1] : ps.scrubbedPdeCode.length(); - int[] braceResult = SourceUtil.checkForMissingBraces(ps.scrubbedPdeCode, tabStartOffset, tabEndOffset); - if (braceResult[0] != 0) { - JavaProblem problem = - new JavaProblem(braceResult[0] < 0 - ? Language.interpolate("editor.status.missing.left_curly_bracket") - : Language.interpolate("editor.status.missing.right_curly_bracket"), - JavaProblem.ERROR, tabIndex, braceResult[1]); - problem.setPDEOffsets(braceResult[3], braceResult[3] + 1); - problems.add(problem); - } - } - - if (problems.isEmpty()) { - return problems; - } - - int problemTabIndex = problems.get(0).getTabIndex(); - - IProblem missingBraceProblem = Arrays.stream(ps.compilationUnit.getProblems()) - .filter(ErrorChecker::isMissingBraceProblem) - // Ignore if it is at the end of file - .filter(p -> p.getSourceEnd() + 1 < ps.javaCode.length()) - // Ignore if the tab number does not match our detected tab number - .filter(p -> problemTabIndex == ps.mapJavaToSketch(p).tabIndex) - .findFirst() - .orElse(null); - - // Prefer ECJ problem, shows location more accurately - if (missingBraceProblem != null) { - JavaProblem p = convertIProblem(missingBraceProblem, ps); - if (p != null) { - problems.clear(); - problems.add(p); - } - } - return problems; } static public String[] getImportSuggestions(ClassPath cp, String className) { className = className.replace("[", "\\[").replace("]", "\\]"); - RegExpResourceFilter regf = new RegExpResourceFilter( + RegExpResourceFilter filter = new RegExpResourceFilter( Pattern.compile(".*"), Pattern.compile("(.*\\$)?" + className + "\\.class", Pattern.CASE_INSENSITIVE)); - String[] resources = cp.findResources("", regf); + String[] resources = cp.findResources("", filter); return Arrays.stream(resources) - // remove ".class" suffix - .map(res -> res.substring(0, res.length() - 6)) - // replace path separators with dots - .map(res -> res.replace('/', '.')) - // replace inner class separators with dots - .map(res -> res.replace('$', '.')) - // sort, prioritize clases from java. package - .map(res -> res.startsWith("classes.") ? res.substring(8) : res) - .sorted((o1, o2) -> { - // put java.* first, should be prioritized more - boolean o1StartsWithJava = o1.startsWith("java"); - boolean o2StartsWithJava = o2.startsWith("java"); - if (o1StartsWithJava != o2StartsWithJava) { - if (o1StartsWithJava) return -1; - return 1; - } - return o1.compareTo(o2); - }) - .toArray(String[]::new); + // remove ".class" suffix + .map(res -> res.substring(0, res.length() - 6)) + // replace path separators with dots + .map(res -> res.replace('/', '.')) + // replace inner class separators with dots + .map(res -> res.replace('$', '.')) + // sort, prioritize classes from java. package + .map(res -> res.startsWith("classes.") ? res.substring(8) : res) + .sorted((o1, o2) -> { + // put java.* first, should be prioritized more + boolean o1StartsWithJava = o1.startsWith("java"); + boolean o2StartsWithJava = o2.startsWith("java"); + if (o1StartsWithJava != o2StartsWithJava) { + if (o1StartsWithJava) return -1; + return 1; + } + return o1.compareTo(o2); + }) + .toArray(String[]::new); } } diff --git a/java/src/processing/mode/java/ExportPrompt.java b/java/src/processing/mode/java/ExportPrompt.java new file mode 100644 index 000000000..856d4d736 --- /dev/null +++ b/java/src/processing/mode/java/ExportPrompt.java @@ -0,0 +1,395 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* +Part of the Processing project - http://processing.org + +Copyright (c) 2012-22 The Processing Foundation +Copyright (c) 2004-12 Ben Fry and Casey Reas +Copyright (c) 2001-04 Massachusetts Institute of Technology + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package processing.mode.java; + +import processing.app.Language; +import processing.app.Platform; +import processing.app.Preferences; +import processing.app.SketchException; +import processing.app.ui.ColorChooser; +import processing.core.PApplet; +import processing.data.StringDict; +import processing.data.StringList; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + + +public class ExportPrompt { + static final String MACOS_EXPORT_WIKI = + "https://github.com/processing/processing4/wiki/Exporting-Applications#macos"; + static final String EXPORT_VARIANTS = "export.application.variants"; + + final JButton exportButton = new JButton(Language.text("prompt.export")); + final JButton cancelButton = new JButton(Language.text("prompt.cancel")); + + List variantButtons; + + final JavaEditor editor; + + static ExportPrompt inst; + + + private ExportPrompt(JavaEditor editor) { + this.editor = editor; + + String pref = Preferences.get(EXPORT_VARIANTS); + if (pref == null) { + pref = Platform.getVariant(); // just add myself + Preferences.set(EXPORT_VARIANTS, pref); + } + StringList selectedVariants = new StringList(pref.split(",")); + + variantButtons = new ArrayList<>(); + for (StringDict.Entry entry : Platform.getSupportedVariants().entries()) { + String variant = entry.key; + JCheckBox button = new JCheckBox(entry.value); // variant name + if (variant.startsWith("macos") && !Platform.isMacOS()) { + button.setEnabled(false); + } else { + button.setSelected(selectedVariants.hasValue(variant)); + } + button.setActionCommand(variant); + button.addActionListener(e -> updateVariants()); + variantButtons.add(button); + } + } + + + protected void updateVariants() { + StringList list = new StringList(); + for (JCheckBox button : variantButtons) { + if (button.isSelected()) { + list.append(button.getActionCommand()); + } + } + Preferences.set(EXPORT_VARIANTS, list.join(",")); + exportButton.setEnabled(anyExportButton()); + } + + + protected boolean anyExportButton() { + for (JCheckBox button : variantButtons) { + if (button.isSelected()) { + return true; + } + } + return false; + } + + + static protected boolean trigger(JavaEditor editor) throws IOException, SketchException { + if (inst == null) { + inst = new ExportPrompt(editor); + } + return inst.trigger(); + } + + + protected boolean trigger() throws IOException, SketchException { + final JDialog dialog = new JDialog(editor, Language.text("export"), true); + + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.add(Box.createVerticalStrut(6)); + + JLabel label = new JLabel(Language.text("export.description"), SwingConstants.CENTER); + label.setBorder(new EmptyBorder(0, 8, 0, 0)); + panel.add(label); + panel.add(Box.createVerticalStrut(12)); + + JPanel platformPanel = new JPanel(); + int half = (variantButtons.size() + 1) / 2; + + Box leftPlatforms = Box.createVerticalBox(); + for (int i = 0; i < half; i++) { + leftPlatforms.add(variantButtons.get(i)); + } + + Box rightPlatforms = Box.createVerticalBox(); + for (int i = half; i < variantButtons.size(); i++) { + rightPlatforms.add(variantButtons.get(i)); + } + + platformPanel.add(leftPlatforms); + platformPanel.add(rightPlatforms); + + platformPanel.setBorder(new TitledBorder(Language.text("export.platforms"))); + platformPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + panel.add(platformPanel); + + int divWidth = platformPanel.getPreferredSize().width; + + final JCheckBox showStopButton = new JCheckBox(Language.text("export.options.show_stop_button")); + showStopButton.setSelected(Preferences.getBoolean("export.application.stop")); + showStopButton.addItemListener(e -> Preferences.setBoolean("export.application.stop", showStopButton.isSelected())); + showStopButton.setEnabled(Preferences.getBoolean("export.application.present")); + showStopButton.setBorder(new EmptyBorder(3, 13, 6, 13)); + + final JCheckBox presentButton = new JCheckBox(Language.text("export.options.present")); + presentButton.setSelected(Preferences.getBoolean("export.application.present")); + presentButton.addItemListener(e -> { + boolean sal = presentButton.isSelected(); + Preferences.setBoolean("export.application.present", sal); + showStopButton.setEnabled(sal); + }); + presentButton.setBorder(new EmptyBorder(3, 13, 3, 13)); + + JPanel presentPanel = new JPanel(); + presentPanel.setLayout(new BoxLayout(presentPanel, BoxLayout.Y_AXIS)); + Box fullScreenBox = Box.createHorizontalBox(); + fullScreenBox.add(presentButton); + + fullScreenBox.add(new ColorPreference("run.present.bgcolor")); + fullScreenBox.add(Box.createHorizontalStrut(10)); + fullScreenBox.add(Box.createHorizontalGlue()); + + presentPanel.add(fullScreenBox); + + Box showStopBox = Box.createHorizontalBox(); + showStopBox.add(showStopButton); + showStopBox.add(new ColorPreference("run.present.stop.color")); + showStopBox.add(Box.createHorizontalStrut(10)); + showStopBox.add(Box.createHorizontalGlue()); + presentPanel.add(showStopBox); + + presentPanel.setBorder(new TitledBorder(Language.text("export.full_screen"))); + presentPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + panel.add(presentPanel); + + // + + JPanel embedPanel = new JPanel(); + embedPanel.setLayout(new BoxLayout(embedPanel, BoxLayout.Y_AXIS)); + + String platformName = null; + if (Platform.isMacOS()) { + platformName = "macOS"; + } else if (Platform.isWindows()) { + platformName = "Windows"; + } else if (Platform.isLinux()) { + platformName = "Linux"; + } + + final boolean embed = + Preferences.getBoolean("export.application.embed_java"); + final String warning1 = + "
          "; + final String warning2a = + "This option will make the " + platformName + " application " + + "larger, but it will be far more likely to work. " + + "Users on other platforms will need to "; + final String warning2b = + "Users will need to "; + final String warning3 = + "" + + "install OpenJDK " + PApplet.javaPlatform + "."; + + // both are needed because they change as the user hits the checkbox + final String embedWarning = warning1 + warning2a + warning3; + final String nopeWarning = warning1 + warning2b + warning3; + + final JLabel warningLabel = new JLabel(embed ? embedWarning : nopeWarning); + warningLabel.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent event) { + Platform.openURL(JavaBuild.JAVA_DOWNLOAD_URL); + } + }); + warningLabel.setBorder(new EmptyBorder(3, 13, 3, 13)); +// warningLabel.putClientProperty("FlatLaf.styleClass", "medium"); + + final JCheckBox embedJavaButton = + new JCheckBox(Language.interpolate("export.include_java", Platform.getPrettyName())); + //new JCheckBox(Language.interpolate("export.include_java", platformName)); + embedJavaButton.setSelected(embed); + embedJavaButton.addItemListener(e -> { + boolean selected = embedJavaButton.isSelected(); + Preferences.setBoolean("export.application.embed_java", selected); + if (selected) { + warningLabel.setText(embedWarning); + } else { + warningLabel.setText(nopeWarning); + } + dialog.pack(); + }); + embedJavaButton.setBorder(new EmptyBorder(3, 13, 3, 13)); + + embedPanel.add(embedJavaButton); + embedPanel.add(warningLabel); + embedPanel.setBorder(new TitledBorder(Language.text("export.embed_java"))); + panel.add(embedPanel); + + // + + if (Platform.isMacOS()) { + JPanel signPanel = new JPanel(); + signPanel.setLayout(new BoxLayout(signPanel, BoxLayout.Y_AXIS)); + signPanel.setBorder(new TitledBorder(Language.text("export.code_signing"))); + + String thePain = + "Applications on macOS must be \u201Csigned\u201D and \u201Cnotarized,\u201D " + + "or they will be reported as damaged or unsafe. "; + + //if (false && new File("/usr/bin/codesign_allocate").exists()) { + if (JavaBuild.isXcodeInstalled()) { + thePain += "
          " + + "This application will be \u201Cself-signed\u201D which means that " + + "macOS may complain that it comes from an unidentified developer. " + + "If the application will not run, try right-clicking the app and " + + "selecting Open from the pop-up menu. " + + "More details at the Exporting Applications wiki page."; + } else { + thePain += + "To create a working macOS application, " + + "click here to install " + + "the Command Line Tools from Apple."; + } + + // Are you f-king serious, Java API developers? + // (Unless it's an HTML component, even with line wrap turned on, + // getPreferredSize() will return the size for just a single line.) +// JLabel area = new JLabel("
          " + thePain + "
          "); + JLabel area = new JLabel("
          " + thePain + "
          "); +// area.putClientProperty("FlatLaf.styleClass", "medium"); + + area.setBorder(new EmptyBorder(3, 13, 3, 13)); + // Using area.setPreferredSize() here doesn't help, + // but setting the div width in CSS above worked. + signPanel.add(area); + signPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + + area.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent event) { + if (JavaBuild.isXcodeInstalled()) { + Platform.openURL(MACOS_EXPORT_WIKI); + + } else { + // Launch the process asynchronously + PApplet.exec("xcode-select", "--install"); + // Reset the installed state so that the message will change. + JavaBuild.resetXcodeInstalled(); + // Close the window so that we can rebuild it with different text + // once they've finished installing the Command Line Tools. + dialog.setVisible(false); + } + } + }); + + panel.add(signPanel); + } + + // + + final JButton[] options = { exportButton, cancelButton }; + + final JOptionPane optionPane = new JOptionPane(panel, + JOptionPane.PLAIN_MESSAGE, + JOptionPane.YES_NO_OPTION, + null, + options, + exportButton); //options[0]); + + dialog.setContentPane(optionPane); + + exportButton.addActionListener(e -> optionPane.setValue(exportButton)); + cancelButton.addActionListener(e -> optionPane.setValue(cancelButton)); + + optionPane.addPropertyChangeListener(e -> { + String prop = e.getPropertyName(); + + if (dialog.isVisible() && + (e.getSource() == optionPane) && + (prop.equals(JOptionPane.VALUE_PROPERTY))) { + // If you were going to check something before + // closing the window, you'd do it here. + dialog.setVisible(false); + } + }); + dialog.pack(); + dialog.setResizable(false); + + // Center the window in the middle of the editor + Rectangle bounds = editor.getBounds(); + dialog.setLocation(bounds.x + (bounds.width - dialog.getSize().width) / 2, + bounds.y + (bounds.height - dialog.getSize().height) / 2); + dialog.setVisible(true); + + Object value = optionPane.getValue(); + if (value.equals(exportButton)) { + return ((JavaMode) editor.getMode()).handleExportApplication(editor.getSketch()); + } else if (value.equals(cancelButton) || value.equals(-1)) { + // closed window by hitting Cancel or ESC + editor.statusNotice(Language.text("export.notice.exporting.cancel")); + } + return false; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + class ColorPreference extends JPanel implements ActionListener { + ColorChooser chooser; + String prefName; + + public ColorPreference(String pref) { + prefName = pref; + + //setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); + setPreferredSize(new Dimension(30, 20)); + setMaximumSize(new Dimension(30, 20)); + + addMouseListener(new MouseAdapter() { + public void mouseReleased(MouseEvent e) { + Color color = Preferences.getColor(prefName); + chooser = new ColorChooser(editor, true, color, Language.text("color_chooser.select"), ColorPreference.this); + chooser.show(); + } + }); + } + + public void paintComponent(Graphics g) { + g.setColor(Preferences.getColor(prefName)); + Dimension size = getSize(); + g.fillRect(0, 0, size.width, size.height); + } + + public void actionPerformed(ActionEvent e) { + Color color = chooser.getColor(); + Preferences.setColor(prefName, color); + //presentColorPanel.repaint(); + repaint(); + chooser.hide(); + } + } +} \ No newline at end of file diff --git a/java/src/processing/mode/java/JavaBuild.java b/java/src/processing/mode/java/JavaBuild.java index 14e11550c..3acef1bef 100644 --- a/java/src/processing/mode/java/JavaBuild.java +++ b/java/src/processing/mode/java/JavaBuild.java @@ -48,8 +48,8 @@ public class JavaBuild { static public final String PACKAGE_REGEX = "(?:^|\\s|;)package\\s+(\\S+)\\;"; - static public final String JAVA_DOWNLOAD_URL = "https://adoptopenjdk.net/"; - static public final String MIN_JAVA_VERSION = "11.0.11"; + static public final String JAVA_DOWNLOAD_URL = "https://adoptium.net/"; + static public final String MIN_JAVA_VERSION = "17"; protected Sketch sketch; protected Mode mode; @@ -107,7 +107,8 @@ public class JavaBuild { this.binFolder = binFolder; // run the preprocessor - String classNameFound = preprocess(srcFolder, sizeWarning); + PreprocessorResult result = preprocess(srcFolder, sizeWarning); + String classNameFound = result.getClassName(); // compile the program. errors will happen as a RunnerException // that will bubble up to whomever called build(). @@ -139,8 +140,8 @@ public class JavaBuild { * @param srcFolder Location to copy all the .java files * @return null if compilation failed, main class name if not */ - public String preprocess(File srcFolder, boolean sizeWarning) throws SketchException { - PdePreprocessor preprocessor = PdePreprocessor.builderFor(sketch.getName()).build(); + public PreprocessorResult preprocess(File srcFolder, boolean sizeWarning) throws SketchException { + PdePreprocessor preprocessor = PdePreprocessor.builderFor(sketch.getMainName()).build(); return preprocess(srcFolder, null, preprocessor, sizeWarning); } @@ -149,13 +150,13 @@ public class JavaBuild { * @param srcFolder location where the .java source files will be placed * @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 + * @return PreprocessorResult object containing the preprocessing results * @throws SketchException details of where the preprocessing failed */ - public String preprocess(File srcFolder, - String packageName, - PdePreprocessor preprocessor, - boolean sizeWarning) throws SketchException { + public PreprocessorResult preprocess(File srcFolder, + String packageName, + PdePreprocessor preprocessor, + boolean sizeWarning) throws SketchException { // make sure the user isn't playing "hide the sketch folder" sketch.ensureExistence(); @@ -231,7 +232,7 @@ public class JavaBuild { srcFolder : new File(srcFolder, packageName.replace('.', '/')); outputFolder.mkdirs(); // Base.openFolder(outputFolder); - final File java = new File(outputFolder, sketch.getName() + ".java"); + final File java = new File(outputFolder, sketch.getMainName() + ".java"); try (PrintWriter stream = PApplet.createWriter(java)) { result = preprocessor.write(stream, bigCode.toString(), codeFolderPackages); } @@ -379,7 +380,7 @@ public class JavaBuild { } } foundMain = preprocessor.hasMain(); - return result.getClassName(); + return result; } @@ -391,6 +392,7 @@ public class JavaBuild { * for libraries that begin with a prefix like javax, since that includes * the OpenGL library, even though we're just returning true here, hrm... */ + @SuppressWarnings("RedundantIfStatement") protected boolean ignorableImport(String pkg) { if (pkg.startsWith("java.")) return true; if (pkg.startsWith("javax.")) return true; @@ -516,7 +518,7 @@ public class JavaBuild { } // If not the preprocessed file at this point, then need to get out - if (!dotJavaFilename.equals(sketch.getName() + ".java")) { + if (!dotJavaFilename.equals(sketch.getMainName() + ".java")) { return null; } @@ -565,66 +567,85 @@ public class JavaBuild { // if name != exportSketchName, then that's weirdness // BUG unfortunately, that can also be a bug in the preproc :( - if (!sketch.getName().equals(foundName)) { + if (!sketch.getMainName().equals(foundName)) { Messages.showWarning("Error during export", - "Sketch name is " + sketch.getName() + " but the sketch\n" + - "name in the code was " + foundName, null); + "Main tab is named " + sketch.getMainName() + " but the\n" + + "sketch name in the code was " + foundName, null); return false; } - File folder = null; - for (String platformName : PConstants.platformNames) { - int platform = Platform.getIndex(platformName); + /* + for (StringDict.Entry entry : Platform.getSupportedVariants().entries()) { + String variant = entry.key; + String name = entry.value; + } + */ + final String hostVariant = Platform.getVariant(); + for (String variant : Preferences.get(ExportPrompt.EXPORT_VARIANTS).split(",")) { // Can only embed Java on the native platform - boolean embedJava = (platform == PApplet.platform) && + boolean embedJava = variant.equals(hostVariant) && Preferences.getBoolean("export.application.embed_java"); - if (Preferences.getBoolean(JavaEditor.EXPORT_PREFIX + platformName)) { - final int bits = Platform.getNativeBits(); - final String arch = Platform.getNativeArch(); - - if (Library.hasMultipleArch(platform, importedLibraries)) { - // removing 32-bit export for 4.0 alpha 3 - /* - // Don't try to export 32-bit on macOS, because it doesn't exist. - if (platform != PConstants.MACOS) { - // export the 32-bit version - folder = new File(sketch.getFolder(), "application." + platformName + "32"); - if (!exportApplication(folder, platform, "32", embedJava && (bits == 32) && ("x86".equals(arch) || "i386".equals(arch)))) { - return false; - } - } - */ - // export the 64-bit version - //folder = new File(sketch.getFolder(), "application." + platformName + "64"); - // No longer including the 64 suffix in 4.0a3 because it's all 64-bit - folder = new File(sketch.getFolder(), "application." + platformName); - if (!exportApplication(folder, platform, "64", embedJava && (bits == 64) && "amd64".equals(arch))) { - return false; - } - /* - if (platform == PConstants.LINUX) { - // export the arm versions as well - folder = new File(sketch.getFolder(), "application.linux-armv6hf"); - if (!exportApplication(folder, platform, "armv6hf", embedJava && (bits == 32) && "arm".equals(arch))) { - return false; - } - folder = new File(sketch.getFolder(), "application.linux-arm64"); - if (!exportApplication(folder, platform, "arm64", embedJava && (bits == 64) && "aarch64".equals(arch))) { - return false; - } - } - */ - } else { // just make a single one for this platform - folder = new File(sketch.getFolder(), "application." + platformName); - if (!exportApplication(folder, platform, "", embedJava)) { - return false; - } - } + File folder = new File(sketch.getFolder(), variant); + if (!exportApplication(folder, variant, embedJava)) { + return false; } } +// File folder = null; +// for (String platformName : PConstants.platformNames) { +//// int platform = Platform.getIndex(platformName); +// +// // Can only embed Java on the native platform +// boolean embedJava = (platform == PApplet.platform) && +// Preferences.getBoolean("export.application.embed_java"); +// +// if (Preferences.getBoolean(JavaEditor.EXPORT_PREFIX + platformName)) { +// final int bits = Platform.getNativeBits(); +// final String arch = Platform.getNativeArch(); +// +// if (Library.hasMultipleArch(platform, importedLibraries)) { +// // removing 32-bit export for 4.0 alpha 3 +// /* +// // Don't try to export 32-bit on macOS, because it doesn't exist. +// if (platform != PConstants.MACOS) { +// // export the 32-bit version +// folder = new File(sketch.getFolder(), "application." + platformName + "32"); +// if (!exportApplication(folder, platform, "32", embedJava && (bits == 32) && ("x86".equals(arch) || "i386".equals(arch)))) { +// return false; +// } +// } +// */ +// // export the 64-bit version +// //folder = new File(sketch.getFolder(), "application." + platformName + "64"); +// // No longer including the 64 suffix in 4.0a3 because it's all 64-bit +// folder = new File(sketch.getFolder(), "application." + platformName); +// if (!exportApplication(folder, platform, "64", embedJava && (bits == 64) && "amd64".equals(arch))) { +// return false; +// } +// /* +// if (platform == PConstants.LINUX) { +// // export the arm versions as well +// folder = new File(sketch.getFolder(), "application.linux-armv6hf"); +// if (!exportApplication(folder, platform, "armv6hf", embedJava && (bits == 32) && "arm".equals(arch))) { +// return false; +// } +// folder = new File(sketch.getFolder(), "application.linux-arm64"); +// if (!exportApplication(folder, platform, "arm64", embedJava && (bits == 64) && "aarch64".equals(arch))) { +// return false; +// } +// } +// */ +// } else { // just make a single one for this platform +// folder = new File(sketch.getFolder(), "application." + platformName); +// if (!exportApplication(folder, platform, "", embedJava)) { +// return false; +// } +// } +// } +// } + return true; // all good } @@ -633,26 +654,23 @@ public class JavaBuild { * Export to application without GUI. Also called by the Commander. */ protected boolean exportApplication(File destFolder, - int exportPlatform, String exportVariant, boolean embedJava) throws IOException, SketchException { - // TODO this should probably be a dialog box instead of a warning - // on the terminal. And the message should be written better than this. - // http://code.google.com/p/processing/issues/detail?id=884 for (Library library : importedLibraries) { - if (!library.supportsArch(exportPlatform, exportVariant)) { - String pn = PConstants.platformNames[exportPlatform]; + if (library.isUnsupported(exportVariant)) { Messages.showWarning("Quibbles 'n Bits", - "The application." + pn + exportVariant + - " folder will not be created\n" + - "because no " + exportVariant + " version of " + - library.getName() + " is available for " + pn, null); + "The application will not be exported for\n" + + Platform.getSupportedVariants().get(exportVariant) + + " because " + library.getName() + "\n" + + "does not support " + exportVariant + ".", null); return true; // don't cancel all exports for this, just move along } } - /// prep the output directory + /// getting started + + int exportPlatform = Platform.getIndex(exportVariant); mode.prepareExportFolder(destFolder); @@ -692,8 +710,14 @@ public class JavaBuild { File macosFolder = new File(contentsFolder, "MacOS"); macosFolder.mkdirs(); // This is an unsigned copy of the app binary (see build/build.xml) - Util.copyFile(mode.getContentFile("application/mac-app-stub"), - new File(contentsFolder, "MacOS/" + sketch.getName())); + File stubFile = + mode.getContentFile("application/stub-" + exportVariant); + File execFile = + new File(contentsFolder, "MacOS/" + sketch.getMainName()); + Util.copyFile(stubFile, execFile); + if (!execFile.setExecutable(true)) { + throw new IOException("Could not make " + execFile + " executable."); + } File pkgInfo = new File(contentsFolder, "PkgInfo"); PrintWriter writer = PApplet.createWriter(pkgInfo); @@ -738,7 +762,7 @@ public class JavaBuild { /// create the main .jar file FileOutputStream zipOutputFile = - new FileOutputStream(new File(jarFolder, sketch.getName() + ".jar")); + new FileOutputStream(new File(jarFolder, sketch.getMainName() + ".jar")); ZipOutputStream zos = new ZipOutputStream(zipOutputFile); // add the manifest file so that the .jar can be double-clickable @@ -780,18 +804,18 @@ public class JavaBuild { zos.flush(); zos.close(); - jarList.append(sketch.getName() + ".jar"); + jarList.append(sketch.getMainName() + ".jar"); /// add contents of 'library' folders to the export for (Library library : importedLibraries) { // add each item from the library folder / export list to the output - for (File exportFile : library.getApplicationExports(exportPlatform, exportVariant)) { + for (File exportFile : library.getApplicationExports(exportVariant)) { String exportName = exportFile.getName(); if (!exportFile.exists()) { System.err.println(exportFile.getName() + - " is mentioned in export.txt, but it's " + - "a big fat lie and does not exist."); + " is mentioned in export.txt," + + " but it does not exist."); } else if (exportFile.isDirectory()) { Util.copyDir(exportFile, new File(jarFolder, exportName)); @@ -831,7 +855,7 @@ public class JavaBuild { // compared to the machine being used to build/export the sketch // https://github.com/processing/processing/pull/4406 if (Preferences.getBoolean("run.options.memory") && - !exportVariant.equals("armv6hf")) { + !exportVariant.contains("arm")) { runOptions.append("-Xms" + Preferences.get("run.options.memory.initial") + "m"); runOptions.append("-Xmx" + Preferences.get("run.options.memory.maximum") + "m"); } @@ -852,7 +876,8 @@ public class JavaBuild { } } - /// macosx: write out Info.plist (template for classpath, etc) + + /// macOS: write out Info.plist (template for classpath, etc) if (exportPlatform == PConstants.MACOS) { StringBuilder runOptionsXML = new StringBuilder(); @@ -863,52 +888,43 @@ public class JavaBuild { runOptionsXML.append('\n'); } - + // read the template, first checking whether the sketch has its own copy String PLIST_TEMPLATE = "Info.plist.tmpl"; File plistTemplate = new File(sketch.getFolder(), PLIST_TEMPLATE); if (!plistTemplate.exists()) { plistTemplate = mode.getContentFile("application/" + PLIST_TEMPLATE); } + + // substitute variables from the template with their values + StringBuilder sb = new StringBuilder(Util.loadFile(plistTemplate)); + replaceTags(sb, "jvm_runtime", jvmRuntime); + replaceTags(sb, "jvm_version", MIN_JAVA_VERSION); + replaceTags(sb, "jvm_options_list", runOptionsXML.toString()); + replaceTags(sb, "sketch", sketch.getMainName()); + replaceTags(sb, "lsuipresentationmode", + Preferences.getBoolean("export.application.present") ? "4" : "0"); + replaceTags(sb, "lsarchitecturepriority", + exportVariant.substring("macos-".length())); + + // write the plist file File plistFile = new File(dotAppFolder, "Contents/Info.plist"); PrintWriter pw = PApplet.createWriter(plistFile); - - String[] lines = PApplet.loadStrings(plistTemplate); - for (int i = 0; i < lines.length; i++) { - if (lines[i].contains("@@")) { - StringBuilder sb = new StringBuilder(lines[i]); - int index = 0; - while ((index = sb.indexOf("@@jvm_runtime@@")) != -1) { - sb.replace(index, index + "@@jvm_runtime@@".length(), - jvmRuntime); - } - while ((index = sb.indexOf("@@jvm_options_list@@")) != -1) { - sb.replace(index, index + "@@jvm_options_list@@".length(), - runOptionsXML.toString()); - } - while ((index = sb.indexOf("@@sketch@@")) != -1) { - sb.replace(index, index + "@@sketch@@".length(), - sketch.getName()); - } - while ((index = sb.indexOf("@@lsuipresentationmode@@")) != -1) { - sb.replace(index, index + "@@lsuipresentationmode@@".length(), - Preferences.getBoolean("export.application.present") ? "4" : "0"); - } - - lines[i] = sb.toString(); - } - // explicit newlines to avoid Windows CRLF - pw.print(lines[i] + "\n"); - } + pw.print(sb); pw.flush(); pw.close(); // attempt to code sign if the Xcode tools appear to be installed - if (Platform.isMacOS() && isXcodeInstalled()) { - if (embedJava) { - ProcessHelper.ffs("codesign", "--force", "--sign", "-", jdkPath); + String appPath = dotAppFolder.getAbsolutePath(); + if (Platform.isMacOS()) { + if (isXcodeInstalled()) { +// if (embedJava) { +// ProcessHelper.ffs("codesign", "--force", "--sign", "--deep", "-", jdkPath); +// } + ProcessHelper.ffs("codesign", "--force", "--deep", "--sign", "-", appPath); + } else { + System.err.println("Xcode not installed, install it and manually sign this app:"); + System.err.println("codesign --force --deep --sign - " + appPath); } - String appPath = dotAppFolder.getAbsolutePath(); - ProcessHelper.ffs("codesign", "--force", "--sign", "-", appPath); } } else if (exportPlatform == PConstants.WINDOWS) { @@ -944,7 +960,7 @@ public class JavaBuild { config.addChild("icon").setContent(iconFile.getAbsolutePath()); XML clazzPath = config.addChild("classPath"); - clazzPath.addChild("mainClass").setContent(sketch.getName()); + clazzPath.addChild("mainClass").setContent(sketch.getMainName()); for (String jarName : jarList) { clazzPath.addChild("cp").setContent("lib/" + jarName); } @@ -956,13 +972,6 @@ 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); @@ -998,7 +1007,7 @@ public class JavaBuild { //" -Djava.ext.dirs=\"$APPDIR/java/lib/ext\"" + //" -Djna.nosys=true" + " -cp \"" + exportClassPath + "\"" + - " " + sketch.getName() + " \"$@\"\n"); + " " + sketch.getMainName() + " \"$@\"\n"); pw.flush(); pw.close(); @@ -1025,7 +1034,7 @@ public class JavaBuild { } } // move the .java file from the preproc there too - String preprocFilename = sketch.getName() + ".java"; + String preprocFilename = sketch.getMainName() + ".java"; File preprocFile = new File(srcFolder, preprocFilename); if (preprocFile.exists()) { Util.copyFile(preprocFile, new File(sourceFolder, preprocFilename)); @@ -1039,6 +1048,15 @@ public class JavaBuild { } + private void replaceTags(StringBuilder sb, String replacing, String replacement) { + int index = 0; + String tag = "@@" + replacing + "@@"; + while ((index = sb.indexOf(tag)) != -1) { + sb.replace(index, index + tag.length(), replacement); + } + } + + // This is a workaround until a more complete solution is found. public Library findJavaFX() { for (Library library : getImportedLibraries()) { @@ -1071,19 +1089,26 @@ public class JavaBuild { static protected boolean isXcodeInstalled() { if (xcodeInstalled == null) { + // Note that xcode-select is *not* an xcrun tool: it's part of the OS. + // pkgutil --file-info /usr/bin/xcode-select + // https://stackoverflow.com/a/32752859/18247494 + StringList stdout = new StringList(); + StringList stderr = new StringList(); + int result = PApplet.exec(stdout, stderr, "/usr/bin/xcode-select", "-p"); + + // Returns 0 if installed, 2 if not (-1 if exception) // http://stackoverflow.com/questions/15371925 - Process p = PApplet.launch("xcode-select", "-p"); - int result = -1; - try { - result = p.waitFor(); - } catch (InterruptedException ignored) { } - // returns 0 if installed, 2 if not (-1 if exception) xcodeInstalled = (result == 0); } return xcodeInstalled; } + static protected void resetXcodeInstalled() { + xcodeInstalled = null; // give them another chance + } + + /** * Run the launch4j build.xml file through ant to create the exe. * Most of this code was lifted from Android mode. @@ -1130,10 +1155,8 @@ public class JavaBuild { // Send a "build finished" event to the build listeners for this project. p.fireBuildFinished(e); - String out = new String(outb.toByteArray()); - String err = new String(errb.toByteArray()); - System.out.println(out); - System.err.println(err); + System.out.println(outb); + System.err.println(errb); } return false; } @@ -1146,7 +1169,7 @@ public class JavaBuild { String contents = "Manifest-Version: 1.0\n" + "Created-By: Processing " + Base.getVersionName() + "\n" + - "Main-Class: " + sketch.getName() + "\n"; // TODO not package friendly + "Main-Class: " + sketch.getMainName() + "\n"; // TODO not package friendly zos.write(contents.getBytes()); zos.closeEntry(); } diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java index 33531a56c..efb37b180 100644 --- a/java/src/processing/mode/java/JavaEditor.java +++ b/java/src/processing/mode/java/JavaEditor.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org -Copyright (c) 2012-19 The Processing Foundation +Copyright (c) 2012-22 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -31,7 +31,6 @@ import java.util.List; import java.util.Map; import javax.swing.*; -import javax.swing.border.*; import javax.swing.event.*; import javax.swing.text.BadLocationException; import javax.swing.text.Document; @@ -176,7 +175,7 @@ public class JavaEditor extends Editor { protected JEditTextArea createTextArea() { - return new JavaTextArea(new PdeTextAreaDefaults(mode), this); + return new JavaTextArea(new PdeTextAreaDefaults(), this); } @@ -274,7 +273,7 @@ public class JavaEditor extends Editor { JMenu menu = new JMenu(Language.text("menu.help")); JMenuItem item; - // macosx already has its own about menu + // macOS already has its own about menu if (!Platform.isMacOS()) { item = new JMenuItem(Language.text("menu.help.about")); item.addActionListener(e -> new About(JavaEditor.this)); @@ -284,21 +283,24 @@ public class JavaEditor extends Editor { item = new JMenuItem(Language.text("menu.help.welcome")); item.addActionListener(e -> { try { - new Welcome(base, Preferences.getSketchbookPath().equals(Preferences.getOldSketchbookPath())); + new Welcome(base); + //new Welcome(base, Preferences.getSketchbookPath().equals(Preferences.getOldSketchbookPath())); } catch (IOException ioe) { Messages.showWarning("Unwelcome Error", "Please report this error to\n" + - "https://github.com/processing/processing/issues", ioe); + "https://github.com/processing/processing4/issues", ioe); } }); menu.add(item); item = new JMenuItem(Language.text("menu.help.environment")); - item.addActionListener(e -> showReference("environment/index.html")); + //item.addActionListener(e -> showReference("environment/index.html")); + item.addActionListener(e -> Platform.openURL("https://processing.org/environment/")); menu.add(item); item = new JMenuItem(Language.text("menu.help.reference")); - item.addActionListener(e -> showReference("index.html")); + //item.addActionListener(e -> showReference("index.html")); + item.addActionListener(e -> Platform.openURL("https://processing.org/reference/")); menu.add(item); item = Toolkit.newJMenuItemShift(Language.text("menu.help.find_in_reference"), 'F'); @@ -314,102 +316,69 @@ public class JavaEditor extends Editor { menu.addSeparator(); final JMenu libRefSubmenu = new JMenu(Language.text("menu.help.libraries_reference")); - // Populate only when sub-menu is opened, to avoid having spurious menu - // options if a library is deleted, or a missing menu option if a library is added - libRefSubmenu.addMenuListener(new MenuListener() { - @Override - public void menuSelected(MenuEvent e) { - // Adding this in case references are included in a core library, - // or other core libraries are included in the future - boolean isCoreLibMenuItemAdded = - addLibReferencesToSubMenu(mode.coreLibraries, libRefSubmenu); + // Adding this in case references are included in a core library, + // or other core libraries are included in the future + boolean isCoreLibMenuItemAdded = + addLibReferencesToSubMenu(mode.coreLibraries, libRefSubmenu); - if (isCoreLibMenuItemAdded && !mode.contribLibraries.isEmpty()) { - libRefSubmenu.addSeparator(); - } + if (isCoreLibMenuItemAdded && !mode.contribLibraries.isEmpty()) { + libRefSubmenu.addSeparator(); + } - boolean isContribLibMenuItemAdded = - addLibReferencesToSubMenu(mode.contribLibraries, libRefSubmenu); + boolean isContribLibMenuItemAdded = + addLibReferencesToSubMenu(mode.contribLibraries, libRefSubmenu); - if (!isContribLibMenuItemAdded && !isCoreLibMenuItemAdded) { - JMenuItem emptyMenuItem = new JMenuItem(Language.text("menu.help.empty")); - emptyMenuItem.setEnabled(false); - emptyMenuItem.setFocusable(false); - emptyMenuItem.setFocusPainted(false); - libRefSubmenu.add(emptyMenuItem); + if (!isContribLibMenuItemAdded && !isCoreLibMenuItemAdded) { + JMenuItem emptyMenuItem = new JMenuItem(Language.text("menu.help.empty")); + emptyMenuItem.setEnabled(false); + emptyMenuItem.setFocusable(false); + emptyMenuItem.setFocusPainted(false); + libRefSubmenu.add(emptyMenuItem); - } else if (!isContribLibMenuItemAdded && !mode.coreLibraries.isEmpty()) { - //re-populate the menu to get rid of terminal separator - libRefSubmenu.removeAll(); - addLibReferencesToSubMenu(mode.coreLibraries, libRefSubmenu); - } - } - - @Override - public void menuDeselected(MenuEvent e) { - libRefSubmenu.removeAll(); - } - - @Override - public void menuCanceled(MenuEvent e) { - menuDeselected(e); - } - }); + } else if (!isContribLibMenuItemAdded && !mode.coreLibraries.isEmpty()) { + //re-populate the menu to get rid of terminal separator + libRefSubmenu.removeAll(); + addLibReferencesToSubMenu(mode.coreLibraries, libRefSubmenu); + } menu.add(libRefSubmenu); final JMenu toolRefSubmenu = new JMenu(Language.text("menu.help.tools_reference")); - // Populate only when sub-menu is opened, to avoid having spurious menu - // options if a tool is deleted, or a missing menu option if a library is added - toolRefSubmenu.addMenuListener(new MenuListener() { + boolean coreToolMenuItemAdded; + boolean contribToolMenuItemAdded; - @Override - public void menuSelected(MenuEvent e) { - boolean coreToolMenuItemAdded; - boolean contribToolMenuItemAdded; + List contribTools = base.getToolContribs(); + // Adding this in in case a reference folder is added for MovieMaker, or in case + // other core tools are introduced later + coreToolMenuItemAdded = addToolReferencesToSubMenu(base.getCoreTools(), toolRefSubmenu); - List contribTools = base.getToolContribs(); - // Adding this in in case a reference folder is added for MovieMaker, or in case - // other core tools are introduced later - coreToolMenuItemAdded = addToolReferencesToSubMenu(base.getCoreTools(), toolRefSubmenu); + if (coreToolMenuItemAdded && !contribTools.isEmpty()) + toolRefSubmenu.addSeparator(); - if (coreToolMenuItemAdded && !contribTools.isEmpty()) - toolRefSubmenu.addSeparator(); + contribToolMenuItemAdded = addToolReferencesToSubMenu(contribTools, toolRefSubmenu); - contribToolMenuItemAdded = addToolReferencesToSubMenu(contribTools, toolRefSubmenu); - - if (!contribToolMenuItemAdded && !coreToolMenuItemAdded) { - toolRefSubmenu.removeAll(); // in case a separator was added - final JMenuItem emptyMenuItem = new JMenuItem(Language.text("menu.help.empty")); - emptyMenuItem.setEnabled(false); - emptyMenuItem.setBorderPainted(false); - emptyMenuItem.setFocusable(false); - emptyMenuItem.setFocusPainted(false); - toolRefSubmenu.add(emptyMenuItem); - } - else if (!contribToolMenuItemAdded && !contribTools.isEmpty()) { - // re-populate the menu to get rid of terminal separator - toolRefSubmenu.removeAll(); - addToolReferencesToSubMenu(base.getCoreTools(), toolRefSubmenu); - } - } - - @Override - public void menuDeselected(MenuEvent e) { - toolRefSubmenu.removeAll(); - } - - @Override - public void menuCanceled(MenuEvent e) { - menuDeselected(e); - } - }); + if (!contribToolMenuItemAdded && !coreToolMenuItemAdded) { + toolRefSubmenu.removeAll(); // in case a separator was added + final JMenuItem emptyMenuItem = new JMenuItem(Language.text("menu.help.empty")); + emptyMenuItem.setEnabled(false); + emptyMenuItem.setBorderPainted(false); + emptyMenuItem.setFocusable(false); + emptyMenuItem.setFocusPainted(false); + toolRefSubmenu.add(emptyMenuItem); + } + else if (!contribToolMenuItemAdded && !contribTools.isEmpty()) { + // re-populate the menu to get rid of terminal separator + toolRefSubmenu.removeAll(); + addToolReferencesToSubMenu(base.getCoreTools(), toolRefSubmenu); + } menu.add(toolRefSubmenu); menu.addSeparator(); + /* item = new JMenuItem(Language.text("menu.help.online")); item.setEnabled(false); menu.add(item); + */ item = new JMenuItem(Language.text("menu.help.getting_started")); item.addActionListener(e -> Platform.openURL(Language.text("menu.help.getting_started.url"))); @@ -508,424 +477,33 @@ public class JavaEditor extends Editor { * Handler for Sketch → Export Application */ public void handleExportApplication() { -// toolbar.activate(JavaToolbar.EXPORT); - if (handleExportCheckModified()) { statusNotice(Language.text("export.notice.exporting")); try { - if (exportApplicationPrompt()) { + if (ExportPrompt.trigger(this)) { Platform.openFolder(sketch.getFolder()); statusNotice(Language.text("export.notice.exporting.done")); - //} else { + //} else { // error message will already be visible // or there was no error, in which case it was canceled. } + } catch (SketchException se) { + EventQueue.invokeLater(() -> statusError(se)); + } catch (Exception e) { statusNotice(Language.text("export.notice.exporting.error")); e.printStackTrace(); } } -// toolbar.deactivate(JavaToolbar.EXPORT); } - // Can't be .windows because that'll be stripped off as a per-platform pref - static final String EXPORT_PREFIX = "export.application.platform_"; - static final String EXPORT_MACOSX = EXPORT_PREFIX + "macosx"; - static final String EXPORT_WINDOWS = EXPORT_PREFIX + "windows"; - static final String EXPORT_LINUX = EXPORT_PREFIX + "linux"; - - final JButton exportButton = new JButton(Language.text("prompt.export")); - final JButton cancelButton = new JButton(Language.text("prompt.cancel")); - - final JCheckBox windowsButton = new JCheckBox("Windows"); - final JCheckBox macosButton = new JCheckBox("Mac OS X"); - final JCheckBox linuxButton = new JCheckBox("Linux"); - - - protected void updateExportButton() { - exportButton.setEnabled(windowsButton.isSelected() || - macosButton.isSelected() || - linuxButton.isSelected()); - } - - - protected boolean exportApplicationPrompt() throws IOException, SketchException { - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - panel.add(Box.createVerticalStrut(6)); - -// Box panel = Box.createVerticalBox(); -// Box labelBox = Box.createHorizontalBox(); -// String msg = "Click Export to Application to create a standalone, " + -// "double-clickable application for the selected plaforms."; -// String msg = "Export to Application creates a standalone, \n" + -// "double-clickable application for the selected plaforms."; - String line1 = Language.text("export.description.line1"); - String line2 = Language.text("export.description.line2"); - //String line2 = "standalone application for the current plaform."; - JLabel label1 = new JLabel(line1, SwingConstants.CENTER); - JLabel label2 = new JLabel(line2, SwingConstants.CENTER); - label1.setAlignmentX(Component.LEFT_ALIGNMENT); - label2.setAlignmentX(Component.LEFT_ALIGNMENT); - panel.add(label1); - panel.add(label2); - // The longer line is different between Windows and OS X. -// int wide = Math.max(label1.getPreferredSize().width, -// label2.getPreferredSize().width); - panel.add(Box.createVerticalStrut(12)); - -// final JCheckBox windowsButton = new JCheckBox("Windows"); -// final JCheckBox macosxButton = new JCheckBox("Mac OS X"); -// final JCheckBox linuxButton = new JCheckBox("Linux"); - - windowsButton.setSelected(Preferences.getBoolean(EXPORT_WINDOWS)); - windowsButton.addItemListener(e -> { - Preferences.setBoolean(EXPORT_WINDOWS, windowsButton.isSelected()); - updateExportButton(); - }); - - // Only possible to export OS X applications on OS X - if (!Platform.isMacOS()) { - // Make sure they don't have a previous 'true' setting for this - Preferences.setBoolean(EXPORT_MACOSX, false); - } - macosButton.setSelected(Preferences.getBoolean(EXPORT_MACOSX)); - macosButton.addItemListener(e -> { - Preferences.setBoolean(EXPORT_MACOSX, macosButton.isSelected()); - updateExportButton(); - }); - if (!Platform.isMacOS()) { - macosButton.setEnabled(false); - macosButton.setToolTipText(Language.text("export.tooltip.macosx")); - } - - linuxButton.setSelected(Preferences.getBoolean(EXPORT_LINUX)); - linuxButton.addItemListener(e -> { - Preferences.setBoolean(EXPORT_LINUX, linuxButton.isSelected()); - updateExportButton(); - }); - - updateExportButton(); // do the initial enable/disable based on prefs.txt - - JPanel platformPanel = new JPanel(); - //platformPanel.setLayout(new BoxLayout(platformPanel, BoxLayout.X_AXIS)); - platformPanel.add(windowsButton); - platformPanel.add(Box.createHorizontalStrut(6)); - platformPanel.add(macosButton); - platformPanel.add(Box.createHorizontalStrut(6)); - platformPanel.add(linuxButton); - platformPanel.setBorder(new TitledBorder(Language.text("export.platforms"))); - //Dimension goodIdea = new Dimension(wide, platformPanel.getPreferredSize().height); - //platformPanel.setMaximumSize(goodIdea); -// wide = Math.max(wide, platformPanel.getPreferredSize().width); - platformPanel.setAlignmentX(Component.LEFT_ALIGNMENT); - panel.add(platformPanel); - int divWidth = platformPanel.getPreferredSize().width; - - //int indent = new JCheckBox().getPreferredSize().width; - int indent = 0; - - final JCheckBox showStopButton = new JCheckBox(Language.text("export.options.show_stop_button")); - showStopButton.setSelected(Preferences.getBoolean("export.application.stop")); - showStopButton.addItemListener(e -> Preferences.setBoolean("export.application.stop", showStopButton.isSelected())); - showStopButton.setEnabled(Preferences.getBoolean("export.application.present")); - showStopButton.setBorder(new EmptyBorder(3, 13 + indent, 6, 13)); - - final JCheckBox presentButton = new JCheckBox(Language.text("export.options.present")); - presentButton.setSelected(Preferences.getBoolean("export.application.present")); - presentButton.addItemListener(e -> { - boolean sal = presentButton.isSelected(); - Preferences.setBoolean("export.application.present", sal); - showStopButton.setEnabled(sal); - }); - presentButton.setBorder(new EmptyBorder(3, 13, 3, 13)); - - JPanel presentPanel = new JPanel(); - presentPanel.setLayout(new BoxLayout(presentPanel, BoxLayout.Y_AXIS)); - Box fullScreenBox = Box.createHorizontalBox(); - fullScreenBox.add(presentButton); - - /* - //run.present.stop.color -// presentColorPanel = new JTextField(); -// presentColorPanel.setFocusable(false); -// presentColorPanel.setEnabled(false); - presentColorPanel = new JPanel() { - public void paintComponent(Graphics g) { - g.setColor(Preferences.getColor("run.present.bgcolor")); - Dimension size = getSize(); - g.fillRect(0, 0, size.width, size.height); - } - }; - presentColorPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); -// presentColorPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED)); - presentColorPanel.setMaximumSize(new Dimension(30, 20)); - fullScreenBox.add(presentColorPanel); - */ - fullScreenBox.add(new ColorPreference("run.present.bgcolor")); - //presentPanel.add(fullScreenButton); - fullScreenBox.add(Box.createHorizontalStrut(10)); - fullScreenBox.add(Box.createHorizontalGlue()); - - presentPanel.add(fullScreenBox); - -// presentColorPanel.addMouseListener(new MouseAdapter() { -// public void mousePressed(MouseEvent e) { -// new ColorListener("run.present.bgcolor"); -// } -// }); - - Box showStopBox = Box.createHorizontalBox(); - showStopBox.add(showStopButton); - showStopBox.add(new ColorPreference("run.present.stop.color")); - showStopBox.add(Box.createHorizontalStrut(10)); - showStopBox.add(Box.createHorizontalGlue()); - presentPanel.add(showStopBox); - - //presentPanel.add(showStopButton); -// presentPanel.add(Box.createHorizontalStrut(10)); -// presentPanel.add(Box.createHorizontalGlue()); - presentPanel.setBorder(new TitledBorder(Language.text("export.full_screen"))); -// wide = Math.max(wide, platformPanel.getPreferredSize().width); - presentPanel.setAlignmentX(Component.LEFT_ALIGNMENT); - panel.add(presentPanel); - -// Dimension good; -// good = new Dimension(wide, label1.getPreferredSize().height); -// label1.setMaximumSize(good); -// good = new Dimension(wide, label2.getPreferredSize().height); -// label2.setMaximumSize(good); -// good = new Dimension(wide, presentPanel.getPreferredSize().height); - - // - - JPanel embedPanel = new JPanel(); - embedPanel.setLayout(new BoxLayout(embedPanel, BoxLayout.Y_AXIS)); - - String platformName = null; - if (Platform.isMacOS()) { - platformName = "macOS"; - } else if (Platform.isWindows()) { - platformName = "Windows (" + Platform.getNativeBits() + "-bit)"; - } else if (Platform.isLinux()) { - platformName = "Linux (" + Platform.getNativeBits() + "-bit)"; - } - - final boolean embed = - Preferences.getBoolean("export.application.embed_java"); - final String warning1 = - "
          "; - final String warning2a = - "Embedding Java will make the " + platformName + " application " + - "larger, but it will be far more likely to work. " + - "Users on other platforms will need to "; - final String warning2b = - "Users will need to "; - final String warning3 = - "" + - "install OpenJDK " + PApplet.javaPlatform + "."; //"
           "; - - // both are needed because they change as the user hits the checkbox - final String embedWarning = warning1 + warning2a + warning3; - final String nopeWarning = warning1 + warning2b + warning3; - - final JLabel warningLabel = new JLabel(embed ? embedWarning : nopeWarning); - warningLabel.addMouseListener(new MouseAdapter() { - public void mousePressed(MouseEvent event) { - Platform.openURL(JavaBuild.JAVA_DOWNLOAD_URL); - } - }); - warningLabel.setBorder(new EmptyBorder(3, 13 + indent, 3, 13)); - - final JCheckBox embedJavaButton = - //new JCheckBox(Language.text("export.embed_java.for") + " " + platformName); - new JCheckBox(Language.interpolate("export.include_java", platformName)); - embedJavaButton.setSelected(embed); - embedJavaButton.addItemListener(e -> { - boolean selected = embedJavaButton.isSelected(); - Preferences.setBoolean("export.application.embed_java", selected); - if (selected) { - warningLabel.setText(embedWarning); - } else { - warningLabel.setText(nopeWarning); - } - }); - embedJavaButton.setBorder(new EmptyBorder(3, 13, 3, 13)); - - embedPanel.add(embedJavaButton); - embedPanel.add(warningLabel); - embedPanel.setBorder(new TitledBorder(Language.text("export.embed_java"))); - panel.add(embedPanel); - - // - - if (Platform.isMacOS()) { - JPanel signPanel = new JPanel(); - signPanel.setLayout(new BoxLayout(signPanel, BoxLayout.Y_AXIS)); - signPanel.setBorder(new TitledBorder(Language.text("export.code_signing"))); - - // gatekeeper: http://support.apple.com/kb/ht5290 - // for developers: https://developer.apple.com/developer-id/ - final String APPLE_URL = "https://developer.apple.com/developer-id/"; - String thePain = - //"" + - "In recent versions of macOS, Apple has introduced the \u201CGatekeeper\u201D system, " + - "which makes it more difficult to run applications like those exported from Processing. "; - - if (new File("/usr/bin/codesign_allocate").exists()) { - thePain += - "This application will be \u201Cself-signed\u201D which means that Finder may report that the " + - "application is from an \u201Cunidentified developer\u201D. If the application will not " + - "run, try right-clicking the app and selecting Open from the pop-up menu. Or you can visit " + - "System Preferences \u2192 Security & Privacy and select Allow apps downloaded from: anywhere. "; - } else { - thePain += - "Gatekeeper requires applications to be \u201Csigned\u201D, or they will be reported as damaged. " + - "To prevent this message, install Xcode (and the Command Line Tools) from the App Store. "; - } - thePain += - "To avoid the messages entirely, manually code sign your app. " + - "For more information: " + APPLE_URL + ""; - - // xattr -d com.apple.quarantine thesketch.app - - //signPanel.add(new JLabel(thePain)); - //JEditorPane area = new JEditorPane("text/html", thePain); - //JTextPane area = new JEditorPane("text/html", thePain); - -// JTextArea area = new JTextArea(thePain); -// area.setBackground(null); -// area.setFont(new Font("Dialog", Font.PLAIN, 10)); -// area.setLineWrap(true); -// area.setWrapStyleWord(true); - // Are you f-king serious, Java API developers? - JLabel area = new JLabel("
          " + thePain + "
          "); - - area.setBorder(new EmptyBorder(3, 13, 3, 13)); -// area.setPreferredSize(new Dimension(embedPanel.getPreferredSize().width, 100)); -// area.setPreferredSize(new Dimension(300, 200)); - signPanel.add(area); -// signPanel.add(Box.createHorizontalGlue()); - signPanel.setAlignmentX(Component.LEFT_ALIGNMENT); - - area.addMouseListener(new MouseAdapter() { - public void mousePressed(MouseEvent event) { - Platform.openURL("https://developer.apple.com/developer-id/"); - } - }); - - panel.add(signPanel); - } - - // - - //String[] options = { Language.text("prompt.export"), Language.text("prompt.cancel") }; - final JButton[] options = { exportButton, cancelButton }; - - final JOptionPane optionPane = new JOptionPane(panel, - JOptionPane.PLAIN_MESSAGE, - JOptionPane.YES_NO_OPTION, - null, - options, - exportButton); //options[0]); - - - final JDialog dialog = new JDialog(this, Language.text("export"), true); - dialog.setContentPane(optionPane); - - exportButton.addActionListener(e -> optionPane.setValue(exportButton)); - - cancelButton.addActionListener(e -> optionPane.setValue(cancelButton)); - - optionPane.addPropertyChangeListener(e -> { - String prop = e.getPropertyName(); - - if (dialog.isVisible() && - (e.getSource() == optionPane) && - (prop.equals(JOptionPane.VALUE_PROPERTY))) { - // If you were going to check something before - // closing the window, you'd do it here. - dialog.setVisible(false); - } - }); - dialog.pack(); -// System.out.println("after pack: " + panel.getPreferredSize()); -// dialog.setSize(optionPane.getPreferredSize()); - dialog.setResizable(false); - - // Center the window in the middle of the editor - Rectangle bounds = getBounds(); - dialog.setLocation(bounds.x + (bounds.width - dialog.getSize().width) / 2, - bounds.y + (bounds.height - dialog.getSize().height) / 2); - dialog.setVisible(true); - - Object value = optionPane.getValue(); - if (value.equals(exportButton)) { - return jmode.handleExportApplication(sketch); - } else if (value.equals(cancelButton) || value.equals(-1)) { - // closed window by hitting Cancel or ESC - statusNotice(Language.text("export.notice.exporting.cancel")); - } - return false; - } - - - class ColorPreference extends JPanel implements ActionListener { - ColorChooser chooser; - String prefName; - - public ColorPreference(String pref) { - prefName = pref; - - setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); - setPreferredSize(new Dimension(30, 20)); - setMaximumSize(new Dimension(30, 20)); - - addMouseListener(new MouseAdapter() { - public void mouseReleased(MouseEvent e) { - Color color = Preferences.getColor(prefName); - chooser = new ColorChooser(JavaEditor.this, true, color, Language.text("color_chooser.select"), ColorPreference.this); - chooser.show(); - } - }); - } - - public void paintComponent(Graphics g) { - g.setColor(Preferences.getColor(prefName)); - Dimension size = getSize(); - g.fillRect(0, 0, size.width, size.height); - } - - public void actionPerformed(ActionEvent e) { - Color color = chooser.getColor(); - Preferences.setColor(prefName, color); - //presentColorPanel.repaint(); - repaint(); - chooser.hide(); - } - } - - -// protected void selectColor(String prefName) { -// Color color = Preferences.getColor(prefName); -// final ColorChooser chooser = new ColorChooser(JavaEditor.this, true, color, -// "Select", new ActionListener() { -// -// @Override -// public void actionPerformed(ActionEvent e) { -// Preferences.setColor(prefName, c.getColor()); -// } -// }); -// } - - /** * Checks to see if the sketch has been modified, and if so, * asks the user to save the sketch or cancel the export. * This prevents issues where an incomplete version of the sketch * would be exported, and is a fix for - * Bug 157 + * Bug 157 */ protected boolean handleExportCheckModified() { if (sketch.isReadOnly()) { @@ -1203,7 +781,7 @@ public class JavaEditor extends Editor { } - public void showReference(String filename) { + public void showReference(String name) { if (useReferenceServer == null) { File referenceZip = new File(mode.getFolder(), "reference.zip"); if (referenceZip.exists()) { @@ -1221,12 +799,18 @@ public class JavaEditor extends Editor { } if (useReferenceServer) { - String url = referenceServer.getPrefix() + "reference/" + filename; + String url = referenceServer.getPrefix() + "reference/" + name; Platform.openURL(url); } else { - File file = new File(mode.getReferenceFolder(), filename); - showReferenceFile(file); + File file = new File(mode.getReferenceFolder(), name); + if (file.exists()) { + showReferenceFile(file); + } else { + // Offline reference (temporarily) removed in 4.0 beta 9 + // https://github.com/processing/processing4/issues/524 + Platform.openURL("https://processing.org/reference/" + name); + } } } @@ -2173,19 +1757,20 @@ public class JavaEditor extends Editor { frmImportSuggest.setVisible(true); } - /** - * Checks if the sketch contains java tabs. If it does, the editor ain't - * built for it, yet. Also, user should really start looking at a full IDE - * like Eclipse. Disable compilation check and some more features. - */ - private boolean checkForJavaTabs() { - for (SketchCode code : getSketch().getCode()) { - if (code.getExtension().equals("java")) { - return true; - } - } - return false; - } + +// /** +// * Checks if the sketch contains java tabs. If it does, the editor ain't +// * built for it, yet. Also, user should really start looking at a full IDE +// * like Eclipse. Disable compilation check and some more features. +// */ +// private boolean checkForJavaTabs() { +// for (SketchCode code : getSketch().getCode()) { +// if (code.getExtension().equals("java")) { +// return true; +// } +// } +// return false; +// } @Override diff --git a/java/src/processing/mode/java/JavaInputHandler.java b/java/src/processing/mode/java/JavaInputHandler.java index 8d42f114c..56d6447a7 100644 --- a/java/src/processing/mode/java/JavaInputHandler.java +++ b/java/src/processing/mode/java/JavaInputHandler.java @@ -147,7 +147,7 @@ public class JavaInputHandler extends PdeInputHandler { //if ((event.getModifiers() & InputEvent.SHIFT_MASK) != 0) { if (event.isShiftDown()) { // if shift is down, the user always expects an outdent - // http://code.google.com/p/processing/issues/detail?id=458 + // https://github.com/processing/processing/issues/497 editor.handleOutdent(); } else if (textarea.isSelectionActive()) { @@ -242,7 +242,7 @@ public class JavaInputHandler extends PdeInputHandler { if (braceCount > 0) { int sel = textarea.getSelectionStart(); // sel - tabSize will be -1 if start/end parens on the same line - // http://dev.processing.org/bugs/show_bug.cgi?id=484 + // https://download.processing.org/bugzilla/484.html if (sel - tabSize >= 0) { textarea.select(sel - tabSize, sel); String s = spaces(tabSize); @@ -257,7 +257,7 @@ public class JavaInputHandler extends PdeInputHandler { } else { // Enter/Return was being consumed by somehow even if false // was returned, so this is a band-aid to simply fire the event again. - // http://dev.processing.org/bugs/show_bug.cgi?id=1073 + // https://download.processing.org/bugzilla/1073.html textarea.setSelectedText(String.valueOf(c)); } // mark this event as already handled (all but ignored) diff --git a/java/src/processing/mode/java/JavaMode.java b/java/src/processing/mode/java/JavaMode.java index 5f0d1bd00..b708467f8 100644 --- a/java/src/processing/mode/java/JavaMode.java +++ b/java/src/processing/mode/java/JavaMode.java @@ -82,12 +82,8 @@ public class JavaMode extends Mode { public String[] getIgnorable() { - return new String[] { - "applet", - "application.macosx", - "application.windows", - "application.linux" - }; + // folder names for exported applications + return Platform.getSupportedVariants().keyArray(); } diff --git a/java/src/processing/mode/java/JavaTextArea.java b/java/src/processing/mode/java/JavaTextArea.java index 647e1c45f..1dedbf3fc 100644 --- a/java/src/processing/mode/java/JavaTextArea.java +++ b/java/src/processing/mode/java/JavaTextArea.java @@ -517,8 +517,8 @@ public class JavaTextArea extends PdeTextArea { new Point(offsetToX(getCaretLine(), position - getLineStartOffset(getCaretLine())), lineToY(getCaretLine()) + getPainter().getLineHeight()); - suggestion = new CompletionPanel(this, position, subWord, - listModel, location, getJavaEditor()); + suggestion = new CompletionPanel(getJavaEditor(), position, subWord, + listModel, location); requestFocusInWindow(); } catch (Exception e) { diff --git a/java/src/processing/mode/java/JavaTextAreaPainter.java b/java/src/processing/mode/java/JavaTextAreaPainter.java index 9a951647d..56d729c56 100644 --- a/java/src/processing/mode/java/JavaTextAreaPainter.java +++ b/java/src/processing/mode/java/JavaTextAreaPainter.java @@ -98,7 +98,7 @@ public class JavaTextAreaPainter extends PdeTextAreaPainter { // update n position and width, and draw it int lineStartChar = textArea.getLineStartOffset(n.line); int x = textArea.offsetToX(n.line, n.newStartChar - lineStartChar); - int y = textArea.lineToY(n.line) + fm.getHeight() + 1; + int y = textArea.lineToY(n.line) + fontMetrics.getHeight() + 1; int end = textArea.offsetToX(n.line, n.newEndChar - lineStartChar); n.setPos(x, y); n.setWidth(end - x); @@ -109,7 +109,7 @@ public class JavaTextAreaPainter extends PdeTextAreaPainter { for (ColorControlBox cBox: colorBoxes.get(currentTab)) { int lineStartChar = textArea.getLineStartOffset(cBox.getLine()); int x = textArea.offsetToX(cBox.getLine(), cBox.getCharIndex() - lineStartChar); - int y = textArea.lineToY(cBox.getLine()) + fm.getDescent(); + int y = textArea.lineToY(cBox.getLine()) + fontMetrics.getDescent(); cBox.setPos(x, y+1); cBox.draw(g2d); } @@ -253,15 +253,15 @@ public class JavaTextAreaPainter extends PdeTextAreaPainter { int lineStartChar = textArea.getLineStartOffset(n.line); int x = textArea.offsetToX(n.line, n.newStartChar - lineStartChar); int end = textArea.offsetToX(n.line, n.newEndChar - lineStartChar); - int y = textArea.lineToY(n.line) + fm.getHeight() + 1; - n.initInterface(x, y, end-x, fm.getHeight()); + int y = textArea.lineToY(n.line) + fontMetrics.getHeight() + 1; + n.initInterface(x, y, end-x, fontMetrics.getHeight()); } for (ColorControlBox cBox : colorBoxes.get(tab)) { int lineStartChar = textArea.getLineStartOffset(cBox.getLine()); int x = textArea.offsetToX(cBox.getLine(), cBox.getCharIndex() - lineStartChar); - int y = textArea.lineToY(cBox.getLine()) + fm.getDescent(); - cBox.initInterface(this, x, y+1, fm.getHeight()-2, fm.getHeight()-2); + int y = textArea.lineToY(cBox.getLine()) + fontMetrics.getDescent(); + cBox.initInterface(this, x, y+1, fontMetrics.getHeight()-2, fontMetrics.getHeight()-2); } } @@ -340,7 +340,7 @@ public class JavaTextAreaPainter extends PdeTextAreaPainter { private void showHideColorBoxes(int y) { - // display the box if the mouse if in the same line. + // display the box if the mouse is in the same line. // always keep the color box of the color selector. int currentTab = getCurrentCodeIndex(); diff --git a/java/src/processing/mode/java/PreprocService.java b/java/src/processing/mode/java/PreprocService.java index 8678d410b..4d19f7a64 100644 --- a/java/src/processing/mode/java/PreprocService.java +++ b/java/src/processing/mode/java/PreprocService.java @@ -344,7 +344,7 @@ public class PreprocService { JavaMode javaMode = (JavaMode) editor.getMode(); Sketch sketch = result.sketch = editor.getSketch(); - String className = sketch.getName(); + String className = sketch.getMainName(); StringBuilder workBuffer = new StringBuilder(); @@ -358,11 +358,7 @@ public class PreprocService { tabStartsList.append(workBuffer.length()); tabLineStarts.add(numLines); - StringBuilder newPiece = new StringBuilder(); - newPiece.append(getSketchTabContents(sc)); - newPiece.append('\n'); - - String newPieceBuilt = newPiece.toString(); + String newPieceBuilt = getSketchTabContents(sc) + '\n'; numLines += SourceUtil.getCount(newPieceBuilt, "\n"); workBuffer.append(newPieceBuilt); } else if (sc.isExtension("java")) { @@ -389,7 +385,7 @@ public class PreprocService { // Core and default imports PdePreprocessor preProcessor = - editor.createPreprocessor(editor.getSketch().getName()); + editor.createPreprocessor(editor.getSketch().getMainName()); if (coreAndDefaultImports == null) { coreAndDefaultImports = buildCoreAndDefaultImports(preProcessor); } @@ -403,7 +399,7 @@ public class PreprocService { } // TODO: convert unicode escapes to chars - + // TODO: This appears no longer to be needed. SourceUtil.scrubCommentsAndStrings(workBuffer); result.scrubbedPdeCode = workBuffer.toString(); @@ -474,13 +470,13 @@ public class PreprocService { // Create intermediate AST for advanced preprocessing // Wait on .java tabs due to speed since they don't go through preproc. - CompileResults parseableCompile = compileInMemory( + CompileResults parsableCompile = compileInMemory( parsableStage, className, result.classPathArray, false ); - CompilationUnit parsableCU = parseableCompile.getCompilationUnit(); + CompilationUnit parsableCU = parsableCompile.getCompilationUnit(); // Prepare advanced transforms which operate on AST TextTransform toCompilable = new TextTransform(parsableStage); @@ -493,13 +489,13 @@ public class PreprocService { OffsetMapper compilableMapper = toCompilable.getMapper(); // Create compilable AST to get syntax problems - CompileResults compileableCompile = compileInMemory( + CompileResults compilableCompile = compileInMemory( compilableStage, className, result.classPathArray, false ); - CompilationUnit compilableCU = compileableCompile.getCompilationUnit(); + CompilationUnit compilableCU = compilableCompile.getCompilationUnit(); // Get syntax problems from compilable AST result.hasSyntaxErrors |= @@ -521,8 +517,7 @@ public class PreprocService { compilableStage, className, result.classPathArray, - javaFiles, - true + javaFiles ); } @@ -607,20 +602,18 @@ public class PreprocService { * @param className The name of the class enclosing the sketch. * @param classPathArray List of classpath entries. * @param javaFiles Information about the java files. - * @param resolveBindings Flag indicating if compilation should happen with - * binding resolution. * @return The results of compilation with binding. */ private CompileResults compileFromDisk(String sketchSource, - String className, String[] classPathArray, - List javaFiles, boolean resolveBindings) { + String className, String[] classPathArray, + List javaFiles) { ProcessingASTRequester astRequester; List temporaryFilesList = new ArrayList<>(); Map javaFileMapping = new HashMap<>(); // Prepare parser - setupParser(resolveBindings, className, classPathArray); + setupParser(true, className, classPathArray); // Write compilable processing file Path mainTemporaryFile = createTemporaryFile(sketchSource); @@ -657,14 +650,14 @@ public class PreprocService { // Return return new CompileResults( - astRequester.getMainCompilationUnit(), - astRequester.getProblems(), - javaFileMapping + astRequester.getMainCompilationUnit(), + astRequester.getProblems(), + javaFileMapping ); } /** - * Setup the parser compiler options and optionally bindings information. + * Set up the parser compiler options and optionally bindings information. * * @param resolveBindings Flag indicating if bindings should be resolved / * checked for things like missing types. @@ -723,12 +716,12 @@ public class PreprocService { } /** - * JDT AST requestor for compileFromDisk. + * JDT AST requester for compileFromDisk. * *

          - * Abstract syntax tree requestor for the JDT useful for compileFromDisk where + * Abstract syntax tree requester for the JDT useful for compileFromDisk where * there may be ".java" tabs in addition to the single combined ".pde" source. - * This requestor will collect problems from all files but hold onto the AST + * This requester will collect problems from all files but hold onto the AST * for the sketch's combined PDE code (not ".java" tabs). *

          */ @@ -758,10 +751,7 @@ public class PreprocService { if (source.equals(mainSource)) { mainCompilationUnit = ast; } - - for (IProblem problem : ast.getProblems()) { - problems.add(problem); - } + Collections.addAll(problems, ast.getProblems()); } /** @@ -787,7 +777,7 @@ public class PreprocService { /** * Data structure holding the results of compilation. */ - private class CompileResults { + static private class CompileResults { private final CompilationUnit compilationUnit; private final List problems; private final Map javaFileMapping; @@ -845,9 +835,9 @@ public class PreprocService { /** * SketchCode (tab of sketch) which is a ".java" tab. */ - private class JavaSketchCode { - private SketchCode sketchCode; - private int tabIndex; + static private class JavaSketchCode { + private final SketchCode sketchCode; + private final int tabIndex; /** * Create a new record of a ".java" tab inside a sketch. @@ -877,7 +867,6 @@ public class PreprocService { public int getTabIndex() { return tabIndex; } - } /// IMPORTS ----------------------------------------------------------------- @@ -957,6 +946,7 @@ public class PreprocService { static { Map compilerOptions = new HashMap<>(); + // TODO: VERSION_17 if using new language features. Requires new JDT. compilerOptions.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_11); compilerOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_11); compilerOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_11); diff --git a/java/src/processing/mode/java/Rename.java b/java/src/processing/mode/java/Rename.java index 4b4bf1966..7f2139b0b 100644 --- a/java/src/processing/mode/java/Rename.java +++ b/java/src/processing/mode/java/Rename.java @@ -25,6 +25,7 @@ import javax.swing.JOptionPane; import javax.swing.JRootPane; import javax.swing.JTextField; import javax.swing.WindowConstants; +import javax.swing.border.EmptyBorder; import javax.swing.text.BadLocationException; import org.eclipse.jdt.core.dom.ASTNode; @@ -83,7 +84,7 @@ class Rename { } }); Box windowBox = Box.createVerticalBox(); - Toolkit.setBorder(windowBox); + windowBox.setBorder(new EmptyBorder(20, 20, 20, 20)); final int GAP = Toolkit.zoom(5); { // old name diff --git a/java/src/processing/mode/java/SourceUtil.java b/java/src/processing/mode/java/SourceUtil.java index 5570455ec..bd464b869 100644 --- a/java/src/processing/mode/java/SourceUtil.java +++ b/java/src/processing/mode/java/SourceUtil.java @@ -13,10 +13,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import processing.mode.java.TextTransform.Edit; -import processing.mode.java.preproc.PdePreprocessor; + public class SourceUtil { + // No longer needed with use of ANTLR in the preprocessor service. + private static final boolean PERFORM_SOURCE_UTIL_TRANSFORMS = false; public static final Pattern IMPORT_REGEX = Pattern.compile("(?:^|;)\\s*(import\\s+(?:(static)\\s+)?((?:\\w+\\s*\\.)*)\\s*(\\S+)\\s*;)", @@ -36,6 +38,7 @@ public class SourceUtil { return result; } + /* public static List parseProgramImports(CharSequence source, List outImports) { List result = new ArrayList<>(); @@ -52,7 +55,7 @@ public class SourceUtil { } return result; } - + */ // Positive lookahead and lookbehind are needed to match all type constructors @@ -82,7 +85,6 @@ public class SourceUtil { } - public static final Pattern HEX_LITERAL_REGEX = Pattern.compile("(?<=^|\\W)(#[A-Fa-f0-9]{6})(?=\\W|$)"); @@ -101,54 +103,12 @@ public class SourceUtil { } - - public static List insertImports(List imports) { - List result = new ArrayList<>(); - for (ImportStatement imp : imports) { - result.add(Edit.insert(0, imp.getFullSourceLine() + "\n")); - } - return result; - } - - public static List wrapSketch(PdePreprocessor.Mode mode, String className, int sourceLength) { - - List edits = new ArrayList<>(); - - StringBuilder b = new StringBuilder(); - - // Header - if (mode != PdePreprocessor.Mode.JAVA) { - b.append("\npublic class ").append(className).append(" extends PApplet {\n"); - if (mode == PdePreprocessor.Mode.STATIC) { - b.append("public void setup() {\n"); - } - } - - edits.add(Edit.insert(0, b.toString())); - - // Reset builder - b.setLength(0); - - // Footer - if (mode != PdePreprocessor.Mode.JAVA) { - if (mode == PdePreprocessor.Mode.STATIC) { - // no noLoop() here so it does not tell you - // "can't invoke noLoop() on obj" when you type "obj." - b.append("\n}"); - } - b.append("\n}\n"); - } - - edits.add(Edit.insert(sourceLength, b.toString())); - - return edits; - } - - // Verifies that whole input String is floating point literal. Can't be used for searching. // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-DecimalFloatingPointLiteral public static final Pattern FLOATING_POINT_LITERAL_VERIFIER; static { + // TODO lots of "Unnecessary non-capturing group" sequences here, + // but not touching until someone can look more closely. final String DIGITS = "(?:[0-9]|[0-9][0-9_]*[0-9])"; final String EXPONENT_PART = "(?:[eE][+-]?" + DIGITS + ")"; FLOATING_POINT_LITERAL_VERIFIER = Pattern.compile( @@ -163,6 +123,10 @@ public class SourceUtil { Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED; public static List preprocessAST(CompilationUnit cu) { + if (!PERFORM_SOURCE_UTIL_TRANSFORMS) { + return new ArrayList<>(); + } + final List edits = new ArrayList<>(); // Walk the tree @@ -242,13 +206,19 @@ public class SourceUtil { } + /* static public String scrubCommentsAndStrings(String p) { StringBuilder sb = new StringBuilder(p); scrubCommentsAndStrings(sb); return sb.toString(); } + */ + static public void scrubCommentsAndStrings(StringBuilder p) { + if (!PERFORM_SOURCE_UTIL_TRANSFORMS) { + return; + } final int length = p.length(); @@ -318,7 +288,7 @@ public class SourceUtil { } else { // Exiting block int blockEnd = i; - if (prevState == IN_BLOCK_COMMENT && i < length) blockEnd--; // preserve star in '*/' + if (prevState == IN_BLOCK_COMMENT && i < length) blockEnd--; // preserve star in '*/' for (int j = blockStart; j < blockEnd; j++) { char c = p.charAt(j); if (c != '\n' && c != '\r') p.setCharAt(j, ' '); @@ -328,7 +298,6 @@ public class SourceUtil { prevState = state; } - } diff --git a/java/src/processing/mode/java/debug/Debugger.java b/java/src/processing/mode/java/debug/Debugger.java index c4183a09b..013679320 100644 --- a/java/src/processing/mode/java/debug/Debugger.java +++ b/java/src/processing/mode/java/debug/Debugger.java @@ -125,7 +125,7 @@ public class Debugger { debugItem = Toolkit.newJMenuItem(Language.text("menu.debug.enable"), 'D'); debugItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - toggleEnabled(); + editor.toggleDebug(); } }); debugMenu.add(debugItem); diff --git a/java/src/processing/mode/java/debug/VariableInspector.java b/java/src/processing/mode/java/debug/VariableInspector.java index 682d7e8c5..619e4d47b 100644 --- a/java/src/processing/mode/java/debug/VariableInspector.java +++ b/java/src/processing/mode/java/debug/VariableInspector.java @@ -2,7 +2,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-15 The Processing Foundation + Copyright (c) 2012-22 The Processing Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 @@ -21,7 +21,6 @@ package processing.mode.java.debug; import java.awt.*; -import java.awt.image.BufferedImage; import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -39,7 +38,7 @@ import com.sun.jdi.Value; import processing.app.Language; import processing.app.Messages; -import processing.app.Mode; +import processing.app.ui.Toolkit; import processing.mode.java.JavaEditor; @@ -116,107 +115,14 @@ public class VariableInspector extends JDialog { // If it'll fit, place it to the right of the editor window setLocation(x, editor.getY() + VERTICAL_OFFSET); } - - /* - bgColor = mode.getColor("buttons.bgcolor"); - statusFont = mode.getFont("buttons.status.font"); - statusColor = mode.getColor("buttons.status.color"); -// modeTitle = mode.getTitle().toUpperCase(); - modeTitle = mode.getTitle(); - modeTextFont = mode.getFont("mode.button.font"); - modeButtonColor = mode.getColor("mode.button.color"); - */ } - /* - Container createToolbar() { - final Mode mode = editor.getMode(); - Box box = Box.createHorizontalBox(); - - continueButton = - new EditorButton(mode, "theme/debug/continue", - Language.text("toolbar.debug.continue")) { - @Override - public void actionPerformed(ActionEvent e) { - Logger.getLogger(VariableInspector.class.getName()).log(Level.INFO, "Invoked 'Continue' toolbar button"); - editor.debugger.continueDebug(); - } - }; - box.add(continueButton); - box.add(Box.createHorizontalStrut(GAP)); - - stepButton = - new EditorButton(mode, "theme/debug/step", - Language.text("toolbar.debug.step"), - Language.text("toolbar.debug.step_into")) { - @Override - public void actionPerformed(ActionEvent e) { - if (isShiftDown()) { - Logger.getLogger(VariableInspector.class.getName()).log(Level.INFO, "Invoked 'Step Into' toolbar button"); - editor.debugger.stepInto(); - } else { - Logger.getLogger(VariableInspector.class.getName()).log(Level.INFO, "Invoked 'Step' toolbar button"); - editor.debugger.stepOver(); - } - } - }; - box.add(stepButton); - box.add(Box.createHorizontalStrut(GAP)); - - breakpointButton = - new EditorButton(mode, "theme/debug/breakpoint", - Language.text("toolbar.debug.toggle_breakpoints")) { - @Override - public void actionPerformed(ActionEvent e) { - Logger.getLogger(VariableInspector.class.getName()).log(Level.INFO, "Invoked 'Toggle Breakpoint' toolbar button"); - editor.debugger.toggleBreakpoint(); - } - }; - box.add(breakpointButton); - box.add(Box.createHorizontalStrut(GAP)); - - JLabel label = new JLabel(); - box.add(label); - continueButton.setRolloverLabel(label); - stepButton.setRolloverLabel(label); - breakpointButton.setRolloverLabel(label); - - // the rest is all gaps - box.add(Box.createHorizontalGlue()); - box.setBorder(new EmptyBorder(GAP, GAP, GAP, GAP)); - - // prevent the toolbar from getting taller than its default - box.setMaximumSize(new Dimension(getMaximumSize().width, getPreferredSize().height)); - return box; - } - */ - - Container createScrollPane() { JScrollPane scrollPane = new JScrollPane(); tree = new Outline(); scrollPane.setViewportView(tree); - /* - GroupLayout layout = new GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGap(0, 400, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(scrollPane, - GroupLayout.DEFAULT_SIZE, - 400, Short.MAX_VALUE))); - layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGap(0, 300, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(scrollPane, - GroupLayout.Alignment.TRAILING, - GroupLayout.DEFAULT_SIZE, - 300, Short.MAX_VALUE))); - pack(); - */ - // setup Outline rootNode = new DefaultMutableTreeNode("root"); builtins = new DefaultMutableTreeNode("Processing"); @@ -256,85 +162,16 @@ public class VariableInspector extends JDialog { } - /* - protected void activateContinue() { - } - - - protected void deactivateContinue() { - } - - - protected void activateStep() { - } - - - protected void deactivateStep() { - } - */ - - - /* - // Keeps the debug window adjacent the editor at all times. - class EditorFollower implements ComponentListener { - - @Override - public void componentShown(ComponentEvent e) { - if (editor.isDebuggerEnabled()) { -// updateBounds(); - setVisible(true); - } - } - - @Override - public void componentHidden(ComponentEvent e) { - if (isVisible()) { - setVisible(false); - } - } - - @Override - public void componentResized(ComponentEvent e) { - if (isVisible()) { - updateBounds(); - } - } - - @Override - public void componentMoved(ComponentEvent e) { - if (isVisible()) { - updateBounds(); - } - } - } - - - private void updateBounds() { - setBounds(editor.getX() + editor.getWidth(), - editor.getY() + VERTICAL_OFFSET, - getPreferredSize().width, - editor.getHeight() - VERTICAL_OFFSET*2); - } - - - public void setVisible(boolean visible) { - if (visible) { - updateBounds(); - } - super.setVisible(visible); - } - */ - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * Model for a Outline Row (excluding the tree column). Column 0 is "Value". - * Column 1 is "Type". Handles setting and getting values. TODO: Maybe use a - * TableCellRenderer instead of this to also have a different icon based on - * expanded state. See: - * http://kickjava.com/src/org/netbeans/swing/outline/DefaultOutlineCellRenderer.java.htm + * Column 1 is "Type". Handles setting and getting values. + * + * TODO: Maybe use a TableCellRenderer instead of this to also have a + * different icon based on expanded state, for instance: + * http://kickjava.com/src/org/netbeans/swing/outline/DefaultOutlineCellRenderer.java.htm */ protected class VariableRowModel implements RowModel { final String column0 = Language.text("debugger.value"); @@ -458,50 +295,40 @@ public class VariableInspector extends JDialog { * Handles icons, text color and tool tips. */ class OutlineRenderer implements RenderDataProvider { - Icon[][] icons; + static final String ENABLED_COLOR = "#000000"; + static final String DISABLED_COLOR = "#808080"; static final int ICON_SIZE = 16; + // Indices correspond to VariableNode.TYPE_OBJECT...TYPE_SHORT + final String[] TYPE_NAMES = { + "object", "array", "integer", "float", "boolean", + "char", "string", "long", "double", "byte", "short" + }; + final int TYPE_COUNT = TYPE_NAMES.length; + + Icon[] enabledIcons; + Icon[] disabledIcons; + + OutlineRenderer() { - icons = loadIcons("theme/variables-1x.png"); - } + enabledIcons = new Icon[TYPE_COUNT]; + disabledIcons = new Icon[TYPE_COUNT]; - /** - * Load multiple icons (horizontal) with multiple states (vertical) from - * a single file. - * - * @param fileName file path in the mode folder. - * @return a nested array (first index: icon, second index: state) or - * null if the file wasn't found. - */ - private ImageIcon[][] loadIcons(String fileName) { - Mode mode = editor.getMode(); - File file = mode.getContentFile(fileName); - if (!file.exists()) { - Messages.log(getClass().getName(), "icon file not found: " + file.getAbsolutePath()); - return null; + for (int i = 0; i < TYPE_COUNT; i++) { + String type = TYPE_NAMES[i]; + File file = editor.getMode().getContentFile("theme/variables/" + type + ".svg"); + enabledIcons[i] = Toolkit.renderIcon(file, ENABLED_COLOR, ICON_SIZE); + disabledIcons[i] = Toolkit.renderIcon(file, DISABLED_COLOR, ICON_SIZE); } - Image allIcons = mode.loadImage(fileName); - int cols = allIcons.getWidth(null) / ICON_SIZE; - int rows = allIcons.getHeight(null) / ICON_SIZE; - ImageIcon[][] iconImages = new ImageIcon[cols][rows]; - - for (int i = 0; i < cols; i++) { - for (int j = 0; j < rows; j++) { - Image image = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB); - Graphics g = image.getGraphics(); - g.drawImage(allIcons, -i * ICON_SIZE, -j * ICON_SIZE, null); - iconImages[i][j] = new ImageIcon(image); - } - } - return iconImages; } - protected Icon getIcon(int type, int state) { + protected Icon getIcon(int type, boolean enabled) { + Icon[] icons = enabled ? enabledIcons : disabledIcons; if (type < 0 || type > icons.length - 1) { return null; } - return icons[type][state]; + return icons[type]; } @@ -563,7 +390,7 @@ public class VariableInspector extends JDialog { public Icon getIcon(Object o) { VariableNode var = toVariableNode(o); if (var != null) { - return getIcon(var.getType(), tree.isEnabled() ? 0 : 1); + return getIcon(var.getType(), tree.isEnabled()); } if (o instanceof TreeNode) { UIDefaults defaults = UIManager.getDefaults(); @@ -589,13 +416,12 @@ public class VariableInspector extends JDialog { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - // TODO: could probably extend the simpler DefaultTableCellRenderer here /** * Renderer for the value column. Uses an italic font for null values and - * Object values ("instance of ..."). Uses a gray color when tree is not - * enabled. + * Object values ("instance of ..."). Uses a gray color when tree disabled. + * TODO: could probably extend the simpler DefaultTableCellRenderer here */ - protected class ValueCellRenderer extends DefaultOutlineCellRenderer { + static protected class ValueCellRenderer extends DefaultOutlineCellRenderer { public ValueCellRenderer() { super(); @@ -632,7 +458,7 @@ public class VariableInspector extends JDialog { * Editor for the value column. Will show an empty string when editing * String values that are null. */ - protected class ValueCellEditor extends DefaultCellEditor { + static protected class ValueCellEditor extends DefaultCellEditor { public ValueCellEditor() { super(new JTextField()); @@ -720,24 +546,6 @@ public class VariableInspector extends JDialog { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - // removed in 3.0a9, doesn't seem to be used? -// protected static void run(final VariableInspector vi) { -// EventQueue.invokeLater(new Runnable() { -// @Override -// public void run() { -// vi.setVisible(true); -// } -// }); -// } - - - /* - public DefaultMutableTreeNode getRootNode() { - return rootNode; - } - */ - - /** * Unlock the inspector window. Rebuild after this to avoid ... dots in the * trees labels @@ -859,8 +667,6 @@ public class VariableInspector extends JDialog { path = synthesizePath(path); if (path != null) { tree.expandPath(path); - } else { - //System.out.println("couldn't synthesize path"); } } @@ -934,7 +740,7 @@ public class VariableInspector extends JDialog { public interface VariableNodeFilter { /** Check whether the filter accepts a {@link VariableNode}. */ - public boolean accept(VariableNode var); + boolean accept(VariableNode var); } @@ -942,7 +748,7 @@ public class VariableInspector extends JDialog { * A {@link VariableNodeFilter} that accepts Processing built-in variable * names. */ - public class P5BuiltinsFilter implements VariableNodeFilter { + static public class P5BuiltinsFilter implements VariableNodeFilter { protected String[] p5Builtins = { "focused", @@ -972,7 +778,7 @@ public class VariableInspector extends JDialog { * A {@link VariableNodeFilter} that rejects implicit this references. * (Names starting with "this$") */ - public class ThisFilter implements VariableNodeFilter { + static public class ThisFilter implements VariableNodeFilter { @Override public boolean accept(VariableNode var) { @@ -988,7 +794,7 @@ public class VariableInspector extends JDialog { * A {@link VariableNodeFilter} that either rejects this-fields if hidden by * a local, or prefixes its name with "this." */ - public class LocalHidesThisFilter implements VariableNodeFilter { + static public class LocalHidesThisFilter implements VariableNodeFilter { // Reject a this-field if hidden by a local. public static final int MODE_HIDE = 0; // don't show hidden this fields diff --git a/java/src/processing/mode/java/preproc/JavaLexer.g4 b/java/src/processing/mode/java/preproc/JavaLexer.g4 index 27b188cb3..a4f65f892 100644 --- a/java/src/processing/mode/java/preproc/JavaLexer.g4 +++ b/java/src/processing/mode/java/preproc/JavaLexer.g4 @@ -9,7 +9,7 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA @@ -116,6 +116,8 @@ BOOL_LITERAL: 'true' CHAR_LITERAL: '\'' (~['\\\r\n] | EscapeSequence) '\''; STRING_LITERAL: '"' (~["\\\r\n] | EscapeSequence)* '"'; +MULTI_STRING_LIT: '"""' (~[\\] | EscapeSequence)*? '"""'; + NULL_LITERAL: 'null'; // Separators LPAREN: '('; diff --git a/java/src/processing/mode/java/preproc/JavaParser.g4 b/java/src/processing/mode/java/preproc/JavaParser.g4 index 9974cb2bb..0926365d1 100644 --- a/java/src/processing/mode/java/preproc/JavaParser.g4 +++ b/java/src/processing/mode/java/preproc/JavaParser.g4 @@ -294,11 +294,24 @@ literal : integerLiteral | floatLiteral | CHAR_LITERAL - | STRING_LITERAL + | stringLiteral | BOOL_LITERAL | NULL_LITERAL ; +baseStringLiteral + : STRING_LITERAL + ; + +multilineStringLiteral + : MULTI_STRING_LIT + ; + +stringLiteral + : baseStringLiteral + | multilineStringLiteral + ; + integerLiteral : DECIMAL_LITERAL | HEX_LITERAL diff --git a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java index 70918cc5e..163233a1d 100644 --- a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java +++ b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java @@ -55,10 +55,11 @@ public class PdeParseTreeListener extends ProcessingBaseListener { private static final String NO_SMOOTH_METHOD_NAME = "noSmooth"; private static final String PIXEL_DENSITY_METHOD_NAME = "pixelDensity"; private static final String FULLSCREEN_METHOD_NAME = "fullScreen"; + private static final boolean SIMULATE_MULTILINE_STRINGS = true; - private String sketchName; + final private String sketchName; private boolean isTesting; - private TokenStreamRewriter rewriter; + final private TokenStreamRewriter rewriter; private Optional destinationPackageName; private Mode mode = Mode.JAVA; @@ -66,17 +67,18 @@ public class PdeParseTreeListener extends ProcessingBaseListener { private int lineOffset; - private ArrayList coreImports = new ArrayList<>(); - private ArrayList defaultImports = new ArrayList<>(); - private ArrayList codeFolderImports = new ArrayList<>(); - private ArrayList foundImports = new ArrayList<>(); - private ArrayList edits = new ArrayList<>(); + private List coreImports = new ArrayList<>(); + private List defaultImports = new ArrayList<>(); + private List codeFolderImports = new ArrayList<>(); + private List foundImports = new ArrayList<>(); + private List edits = new ArrayList<>(); private String sketchWidth; private String sketchHeight; private String pixelDensity; private String smoothParam; private String sketchRenderer = null; + private String fullscreenArgs = ""; private String sketchOutputFilename = null; private boolean sizeRequiresRewrite = false; @@ -255,7 +257,6 @@ 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() { @@ -289,6 +290,7 @@ public class PdeParseTreeListener extends ProcessingBaseListener { allEdits, sketchWidth, sketchHeight, + sketchRenderer, issues ); } @@ -317,6 +319,31 @@ public class PdeParseTreeListener extends ProcessingBaseListener { footerResult = prepareFooter(rewriter, length); } + /** + * Detect if the user is programming with "mixed" modes. + * + *

          Detect if the user is programming with "mixed" modes where they are + * combining active and static mode features. This may be, for example, a + * method call followed by method definition.

          + * + * @param ctx The context from ANTLR for the mixed modes sketch. + */ + public void enterWarnMixedModes(ProcessingParser.WarnMixedModesContext ctx) { + pdeParseTreeErrorListenerMaybe.ifPresent((listener) -> { + Token token = ctx.getStart(); + int line = token.getLine(); + int charOffset = token.getCharPositionInLine(); + + listener.onError(new PdePreprocessIssue( + line, + charOffset, + PreprocessIssueMessageSimplifier.getLocalStr( + "editor.status.bad.mixed_mode" + ) + )); + }); + } + /** * Endpoint for ANTLR to call when finished parsing a method invocatino. * @@ -325,6 +352,25 @@ public class PdeParseTreeListener extends ProcessingBaseListener { public void exitMethodCall(ProcessingParser.MethodCallContext ctx) { String methodName = ctx.getChild(0).getText(); + // Check if calling on something other than this. + boolean impliedThis = ctx.getParent().getChildCount() == 1; + boolean usesThis; + if (impliedThis) { + usesThis = true; + } else { + String statmentTarget = ctx.getParent().getChild(0).getText(); + boolean explicitThis = statmentTarget.equals("this"); + boolean explicitSuper = statmentTarget.equals("super"); + usesThis = explicitThis || explicitSuper; + } + + // If not using this or super, no rewrite as the user is calling their own + // declaration or instance of PGraphics. + if (!usesThis) { + return; + } + + // If referring to the applet, check for rewrites. if (SIZE_METHOD_NAME.equals(methodName) || FULLSCREEN_METHOD_NAME.equals(methodName)) { handleSizeCall(ctx); } else if (PIXEL_DENSITY_METHOD_NAME.equals(methodName)) { @@ -417,6 +463,29 @@ public class PdeParseTreeListener extends ProcessingBaseListener { } } + /** + * Endpoint for ANTLR to call after parsing a String literal. + * + *

          + * Endpoint for ANTLR to call when finished parsing a string literal, simulating multiline + * strings if configured to do so. + *

          + * + * @param ctx ANTLR context for the literal. + */ + public void exitMultilineStringLiteral(ProcessingParser.MultilineStringLiteralContext ctx) { + String fullLiteral = ctx.getText(); + if (SIMULATE_MULTILINE_STRINGS) { + delete(ctx.start, ctx.stop); + int endIndex = fullLiteral.length() - 3; + String literalContents = fullLiteral.substring(3, endIndex); + String newLiteralContents = literalContents + .replace("\n", "\\n") + .replace("\"", "\\\""); + insertAfter(ctx.stop, "\"" + newLiteralContents + "\""); + } + } + /** * Endpoint for ANTLR to call after parsing a static processing sketch. * @@ -648,18 +717,36 @@ public class PdeParseTreeListener extends ProcessingBaseListener { if (isFullscreen) { sketchWidth = "displayWidth"; - sketchWidth = "displayHeight"; + sketchHeight = "displayHeight"; thisRequiresRewrite = true; sizeIsFullscreen = true; + StringJoiner fullscreenArgsBuilder = new StringJoiner(", "); + + // First arg can be either screen or renderer if (argsContext.getChildCount() > 0) { - sketchRenderer = argsContext.getChild(0).getText(); + String firstArg = argsContext.getChild(0).getText(); + boolean isNumeric = firstArg.matches("\\d+"); + boolean isSpan = firstArg.equals("SPAN"); + boolean isRenderer = !isNumeric && !isSpan; + + fullscreenArgsBuilder.add(firstArg); + if (isRenderer) { + sketchRenderer = firstArg; + } } + + // Second arg can only be screen + if (argsContext.getChildCount() > 2) { + fullscreenArgsBuilder.add(argsContext.getChild(2).getText()); + } + + fullscreenArgs = fullscreenArgsBuilder.toString(); } if (thisRequiresRewrite) { - delete(ctx.start, ctx.stop); + delete(ctx.getParent().start, ctx.getParent().stop); insertAfter(ctx.stop, "/* size commented out by preprocessor */"); sizeRequiresRewrite = true; } @@ -678,8 +765,8 @@ public class PdeParseTreeListener extends ProcessingBaseListener { pixelDensity = argsContext.getChild(0).getText(); - delete(ctx.start, ctx.stop); - insertAfter(ctx.stop, "/* pixelDensity commented out by preprocessor */"); + delete(ctx.getParent().start, ctx.getParent().stop); + insertAfter(ctx.getParent().stop, "/* pixelDensity commented out by preprocessor */"); pixelDensityRequiresRewrite = true; } @@ -694,8 +781,11 @@ public class PdeParseTreeListener extends ProcessingBaseListener { return; // User override of noSmooth. } - delete(ctx.start, ctx.stop); - insertAfter(ctx.stop, "/* noSmooth commented out by preprocessor */"); + delete(ctx.getParent().start, ctx.getParent().stop); + insertAfter( + ctx.getParent().stop, + "/* noSmooth commented out by preprocessor */" + ); noSmoothRequiresRewrite = true; } @@ -716,8 +806,11 @@ public class PdeParseTreeListener extends ProcessingBaseListener { smoothParam = ""; } - delete(ctx.start, ctx.stop); - insertAfter(ctx.stop, "/* smooth commented out by preprocessor */"); + delete(ctx.getParent().start, ctx.getParent().stop); + insertAfter( + ctx.getParent().stop, + "/* smooth commented out by preprocessor */" + ); smoothRequiresRewrite = true; } @@ -746,7 +839,7 @@ public class PdeParseTreeListener extends ProcessingBaseListener { * @return True if setup and false otherwise. */ protected boolean isMethodSetup(ParserRuleContext declaration) { - if (declaration.getChildCount() < 2) { + if (declaration == null || declaration.getChildCount() < 2) { return false; } return declaration.getChild(1).getText().equals("setup"); @@ -1026,7 +1119,6 @@ public class PdeParseTreeListener extends ProcessingBaseListener { RewriteResultBuilder resultBuilder) { headerWriter.addCodeLine("public class " + sketchName + " extends PApplet {"); - headerWriter.addEmptyLine(); } @@ -1081,8 +1173,7 @@ public class PdeParseTreeListener extends ProcessingBaseListener { if (sizeRequiresRewrite) { if (sizeIsFullscreen) { - String fullscreenInner = sketchRenderer == null ? "" : sketchRenderer; - settingsInner.add(String.format("fullScreen(%s);", fullscreenInner)); + settingsInner.add(String.format("fullScreen(%s);", fullscreenArgs)); } else { if (sketchWidth.isEmpty() || sketchHeight.isEmpty()) { @@ -1136,21 +1227,29 @@ public class PdeParseTreeListener extends ProcessingBaseListener { footerWriter.addCodeLine(indent1 + "static public void main(String[] passedArgs) {"); footerWriter.addCode(indent2 + "String[] appletArgs = new String[] { "); + + { // assemble line with applet args - if (Preferences.getBoolean("export.application.fullscreen")) { - footerWriter.addCode("\"" + PApplet.ARGS_FULL_SCREEN + "\", "); + StringJoiner argsJoiner = new StringJoiner(", "); + + boolean shouldFullScreen = Preferences.getBoolean("export.application.present"); + shouldFullScreen = shouldFullScreen || Preferences.getBoolean("export.application.fullscreen"); + if (shouldFullScreen) { + argsJoiner.add("\"" + PApplet.ARGS_FULL_SCREEN + "\""); String bgColor = Preferences.get("run.present.bgcolor"); - footerWriter.addCode("\"" + PApplet.ARGS_BGCOLOR + "=" + bgColor + "\", "); + argsJoiner.add("\"" + PApplet.ARGS_BGCOLOR + "=" + bgColor + "\""); if (Preferences.getBoolean("export.application.stop")) { String stopColor = Preferences.get("run.present.stop.color"); - footerWriter.addCode("\"" + PApplet.ARGS_STOP_COLOR + "=" + stopColor + "\", "); + argsJoiner.add("\"" + PApplet.ARGS_STOP_COLOR + "=" + stopColor + "\""); } else { - footerWriter.addCode("\"" + PApplet.ARGS_HIDE_STOP + "\", "); + argsJoiner.add("\"" + PApplet.ARGS_HIDE_STOP + "\""); } } - footerWriter.addCode("\"" + sketchName + "\""); + + argsJoiner.add("\"" + sketchName + "\""); + footerWriter.addCode(argsJoiner.toString()); } footerWriter.addCodeLine(" };"); diff --git a/java/src/processing/mode/java/preproc/PdePreprocessor.java b/java/src/processing/mode/java/preproc/PdePreprocessor.java index fad1e8c43..eb3f3946b 100644 --- a/java/src/processing/mode/java/preproc/PdePreprocessor.java +++ b/java/src/processing/mode/java/preproc/PdePreprocessor.java @@ -48,23 +48,24 @@ public class PdePreprocessor { /** * The mode that the sketch uses to run. */ - public static enum Mode { + public enum Mode { /** - * Sketch without draw, setup, or settings functions where code is run as if the body of a - * method without any enclosing types. This code will not define its enclosing class or method. + * Sketch without draw, setup, or settings functions where code + * is run as if the body of a method without any enclosing types. + * This code will not define its enclosing class or method. */ STATIC, /** - * Sketch using draw, setup, and / or settings where the code is run as if defining the body - * of a class. This code will not define its enclosing class but it will define its enclosing - * method. + * Sketch using draw, setup, and / or settings where the code is + * run as if defining the body of a class. This code will not define + * its enclosing class, but it will define its enclosing method. */ ACTIVE, /** - * Sketch written like typical Java where the code is run such that it defines the enclosing - * classes itself. + * Sketch written like typical Java where the code is run + * such that it defines the enclosing classes itself. */ JAVA } @@ -143,10 +144,7 @@ public class PdePreprocessor { * @return Information about the preprocessing operation. */ public PreprocessorResult write(Writer outWriter, String inProgram, - Iterable codeFolderPackages) - throws SketchException { - - // Determine inports + Iterable codeFolderPackages) throws SketchException { ArrayList codeFolderImports = new ArrayList<>(); if (codeFolderPackages != null) { for (String item : codeFolderPackages) { @@ -195,7 +193,7 @@ public class PdePreprocessor { listener.setCoreImports(coreImports); listener.setDefaultImports(defaultImports); listener.setCodeFolderImports(codeFolderImports); - listener.setTreeErrorListener((x) -> { treeIssues.add(x); }); + listener.setTreeErrorListener(treeIssues::add); final String finalInProgram = inProgram; ParseTree tree; @@ -203,7 +201,7 @@ public class PdePreprocessor { ProcessingParser parser = new ProcessingParser(tokens); parser.removeErrorListeners(); parser.addErrorListener(new PdeIssueEmitter( - (x) -> { preprocessIssues.add(x); }, + preprocessIssues::add, () -> finalInProgram )); parser.setBuildParseTree(true); @@ -275,7 +273,7 @@ public class PdePreprocessor { */ public static class PdePreprocessorBuilder { - private final String sketchName; + private final String mainName; private Optional tabSize; private Optional isTesting; private Optional parseTreeFactory; @@ -327,10 +325,10 @@ public class PdePreprocessor { * this constructor. *

          * - * @param newSketchName The name of the sketch. + * @param newMainName The name of the sketch. */ - private PdePreprocessorBuilder(String newSketchName) { - sketchName = newSketchName; + private PdePreprocessorBuilder(String newMainName) { + mainName = newMainName; tabSize = Optional.empty(); isTesting = Optional.empty(); parseTreeFactory = Optional.empty(); @@ -365,10 +363,10 @@ public class PdePreprocessor { /** * Specify how the parse tree listener should be built. * - * The ANTLR parse tree listener is where the preprocessing edits are generated and some client - * code (like modes) may need to override some of the preprocessing edit behavior. Specifying - * this factory allows client code to replace the default PdeParseTreeListener that is used - * during preprocessing. + * The ANTLR parse tree listener is where the preprocessing edits are + * generated and some client code (like modes) may need to override some + * preprocessing edit behavior. Specifying this factory allows client code + * to replace the default PdeParseTreeListener used during preprocessing. * * @param newFactory The factory to use in building a parse tree listener. * @return This builder for method chaining. @@ -413,51 +411,43 @@ public class PdePreprocessor { /** * Build the preprocessor. - * - * @return Newly built preproceesor. */ public PdePreprocessor build() { - final int effectiveTabSize = tabSize.orElseGet( - () -> Preferences.getInteger("editor.tabs.size") - ); + final int effectiveTabSize = + tabSize.orElseGet(() -> Preferences.getInteger("editor.tabs.size")); final boolean effectiveIsTesting = isTesting.orElse(false); - ParseTreeListenerFactory effectiveFactory = parseTreeFactory.orElse( - PdeParseTreeListener::new - ); + ParseTreeListenerFactory effectiveFactory = + parseTreeFactory.orElse(PdeParseTreeListener::new); - List effectiveDefaultImports = defaultImports.orElseGet( - () -> Arrays.asList(BASE_DEFAULT_IMPORTS) - ); + List effectiveDefaultImports = + defaultImports.orElseGet(() -> Arrays.asList(BASE_DEFAULT_IMPORTS)); - List effectiveCoreImports = coreImports.orElseGet( - () -> Arrays.asList(BASE_CORE_IMPORTS) - ); + List effectiveCoreImports = + coreImports.orElseGet(() -> Arrays.asList(BASE_CORE_IMPORTS)); return new PdePreprocessor( - sketchName, - effectiveTabSize, - effectiveIsTesting, - effectiveFactory, - effectiveDefaultImports, - effectiveCoreImports, - destinationPackage + mainName, + effectiveTabSize, + effectiveIsTesting, + effectiveFactory, + effectiveDefaultImports, + effectiveCoreImports, + destinationPackage ); } - } /** * Factory which creates parse tree traversal listeners. * - * The ANTLR parse tree listener is where the preprocessing edits are generated and some client - * code (like modes) may need to override some of the preprocessing edit behavior. Specifying - * this factory allows client code to replace the default PdeParseTreeListener that is used - * during preprocessing. + * The ANTLR parse tree listener is where the preprocessing edits are + * generated and some client code (like modes) may need to override some + * preprocessing edit behavior. Specifying this factory allows client code + * to replace the default PdeParseTreeListener used during preprocessing. */ - public static interface ParseTreeListenerFactory { - + public interface ParseTreeListenerFactory { /** * Create a new processing listener. * @@ -467,9 +457,8 @@ public class PdePreprocessor { * @param packageName The optional package name for generated code. * @return The newly created listener. */ - PdeParseTreeListener build(CommonTokenStream tokens, String sketchName, int tabSize, - Optional packageName); - + PdeParseTreeListener build(CommonTokenStream tokens, String sketchName, + int tabSize, Optional packageName); } @@ -479,37 +468,36 @@ public class PdePreprocessor { */ /** - * Utility function to substitute non ascii characters for escaped unicode character sequences. + * Utility function to substitute non-ASCII characters for escaped unicode character sequences. * - * @param program The program source in which to execute the replace. + * @param program The program source in which to execute the replacement * @return The program source after replacement. */ private static String substituteUnicode(String program) { // check for non-ascii chars (these will be/must be in unicode format) - char p[] = program.toCharArray(); + char[] p = program.toCharArray(); int unicodeCount = 0; - for (int i = 0; i < p.length; i++) { - if (p[i] > 127) + for (char value : p) { + if (value > 127) unicodeCount++; } if (unicodeCount == 0) return program; // if non-ascii chars are in there, convert to unicode escapes - // add unicodeCount * 5.. replacing each unicode char + // add unicodeCount * 5... replacing each unicode char // with six digit uXXXX sequence (xxxx is in hex) // (except for nbsp chars which will be a replaced with a space) int index = 0; - char p2[] = new char[p.length + unicodeCount * 5]; - for (int i = 0; i < p.length; i++) { - if (p[i] < 128) { - p2[index++] = p[i]; - } else if (p[i] == 160) { // unicode for non-breaking space + char[] p2 = new char[p.length + unicodeCount * 5]; + for (char value : p) { + if (value < 128) { + p2[index++] = value; + } else if (value == 160) { // unicode for non-breaking space p2[index++] = ' '; } else { - int c = p[i]; p2[index++] = '\\'; p2[index++] = 'u'; - char str[] = Integer.toHexString(c).toCharArray(); + char[] str = Integer.toHexString(value).toCharArray(); // add leading zeros, so that the length is 4 //for (int i = 0; i < 4 - str.length; i++) p2[index++] = '0'; for (int m = 0; m < 4 - str.length; m++) @@ -520,5 +508,4 @@ public class PdePreprocessor { } return new String(p2, 0, index); } - } diff --git a/java/src/processing/mode/java/preproc/PreprocessorResult.java b/java/src/processing/mode/java/preproc/PreprocessorResult.java index 680f75bbf..972abf1ac 100644 --- a/java/src/processing/mode/java/preproc/PreprocessorResult.java +++ b/java/src/processing/mode/java/preproc/PreprocessorResult.java @@ -42,6 +42,7 @@ public class PreprocessorResult { private final List preprocessIssues; private final String sketchWidth; private final String sketchHeight; + private final String sketchRenderer; /** * Create a new PreprocessorResult indicating that there were issues in preprocessing. @@ -67,10 +68,11 @@ public class PreprocessorResult { * @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; + * @param newSketchRenderer The renderer of the sketch. */ public PreprocessorResult(PdePreprocessor.Mode newProgramType, int newHeaderOffset, String newClassName, List newImportStatements, - List newEdits, String newSketchWidth, String newSketchHeight) { + List newEdits, String newSketchWidth, String newSketchHeight, String newSketchRenderer) { if (newClassName == null) { throw new RuntimeException("Could not find main class"); @@ -85,6 +87,7 @@ public class PreprocessorResult { sketchWidth = newSketchWidth; sketchHeight = newSketchHeight; + sketchRenderer = newSketchRenderer; } /** @@ -98,10 +101,11 @@ public class PreprocessorResult { * @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; + * @param newSketchRenderer The renderer of the sketch. */ public PreprocessorResult(PdePreprocessor.Mode newProgramType, int newHeaderOffset, String newClassName, List newImportStatements, - List newEdits, String newSketchWidth, String newSketchHeight, + List newEdits, String newSketchWidth, String newSketchHeight, String newSketchRenderer, List newPreprocessIssues) { if (newClassName == null) { @@ -117,6 +121,7 @@ public class PreprocessorResult { sketchWidth = newSketchWidth; sketchHeight = newSketchHeight; + sketchRenderer = newSketchRenderer; } /** @@ -134,6 +139,7 @@ public class PreprocessorResult { sketchWidth = null; sketchHeight = null; + sketchRenderer = null; } /** @@ -210,4 +216,14 @@ public class PreprocessorResult { public String getSketchHeight() { return sketchHeight; } + + /** + * Get the user provided renderer of this sketch. + * + * @return The renderer of the sketch or null if none + * given. + */ + public String getSketchRenderer() { + return sketchRenderer; + } } diff --git a/java/src/processing/mode/java/preproc/Processing.g4 b/java/src/processing/mode/java/preproc/Processing.g4 index 2b822f9d9..e32f05787 100644 --- a/java/src/processing/mode/java/preproc/Processing.g4 +++ b/java/src/processing/mode/java/preproc/Processing.g4 @@ -1,17 +1,18 @@ /** - * Based on Java 1.7 grammar for ANTLR 4, see Java.g4 + * Based on Java 1.7 grammar for ANTLR 4, see Java.g4 * - * - changes main entry point to reflect sketch types 'static' | 'active' - * - adds support for type converter functions like "int()" - * - adds pseudo primitive type "color" - * - adds HTML hex notation with hash symbol: #ff5522 + * - changes main entry point to reflect sketch types 'static' | 'active' + * - adds support for type converter functions like "int()" + * - adds pseudo primitive type "color" + * - adds HTML hex notation with hash symbol: #ff5522 + * - allow color to appear as part of qualified names (like in imports) */ grammar Processing; @lexer::members { - public static final int WHITESPACE = 1; - public static final int COMMENTS = 2; + public static final int WHITESPACE = 1; + public static final int COMMENTS = 2; } // import Java grammar @@ -19,37 +20,45 @@ import JavaParser; // main entry point, select sketch type processingSketch - : javaProcessingSketch - | staticProcessingSketch - | activeProcessingSketch + : javaProcessingSketch + | staticProcessingSketch + | activeProcessingSketch +// | warnMixedModes ; // java mode, is a compilation unit javaProcessingSketch - : packageDeclaration? importDeclaration* typeDeclaration+ EOF + : packageDeclaration? importDeclaration* typeDeclaration+ EOF ; +// No method declarations, just statements staticProcessingSketch - : (importDeclaration | blockStatement)* EOF + : (importDeclaration | blockStatement)* EOF ; // active mode, has function definitions activeProcessingSketch - : (importDeclaration | classBodyDeclaration)* EOF - ; + : (importDeclaration | classBodyDeclaration)* EOF + ; + +// User incorrectly mixing modes. Included to allow for kind error message. +warnMixedModes + : (importDeclaration | classBodyDeclaration | blockStatement)* blockStatement classBodyDeclaration (importDeclaration | classBodyDeclaration | blockStatement)* + | (importDeclaration | classBodyDeclaration | blockStatement)* classBodyDeclaration blockStatement (importDeclaration | classBodyDeclaration | blockStatement)* + ; variableDeclaratorId - : warnTypeAsVariableName - | IDENTIFIER ('[' ']')* + : warnTypeAsVariableName + | IDENTIFIER ('[' ']')* ; // bug #93 // https://github.com/processing/processing/issues/93 // prevent from types being used as variable names warnTypeAsVariableName - : primitiveType ('[' ']')* { - notifyErrorListeners("Type names are not allowed as variable names: "+$primitiveType.text); - } + : primitiveType ('[' ']')* { + notifyErrorListeners("Type names are not allowed as variable names: "+$primitiveType.text); + } ; // catch special API function calls that we are interested in @@ -63,30 +72,34 @@ methodCall // these are primitive type names plus "()" // "color" is a special Processing primitive (== int) functionWithPrimitiveTypeName - : ( 'boolean' - | 'byte' - | 'char' - | 'float' - | 'int' - | 'color' - ) '(' expressionList? ')' - ; + : ( 'boolean' + | 'byte' + | 'char' + | 'float' + | 'int' + | 'color' + ) '(' expressionList? ')' + ; // adding support for "color" primitive primitiveType - : BOOLEAN - | CHAR - | BYTE - | SHORT - | INT - | LONG - | FLOAT - | DOUBLE - | colorPrimitiveType - ; + : BOOLEAN + | CHAR + | BYTE + | SHORT + | INT + | LONG + | FLOAT + | DOUBLE + | colorPrimitiveType + ; colorPrimitiveType - : 'color' + : 'color' + ; + +qualifiedName + : (IDENTIFIER | colorPrimitiveType) ('.' (IDENTIFIER | colorPrimitiveType))* ; // added HexColorLiteral @@ -94,7 +107,7 @@ literal : integerLiteral | floatLiteral | CHAR_LITERAL - | STRING_LITERAL + | stringLiteral | BOOL_LITERAL | NULL_LITERAL | hexColorLiteral @@ -103,28 +116,29 @@ literal // As parser rule so this produces a separate listener // for us to alter its value. hexColorLiteral - : HexColorLiteral - ; + : HexColorLiteral + ; // add color literal notations for // #ff5522 HexColorLiteral - : '#' (HexDigit HexDigit)? HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit - ; + : '#' (HexDigit HexDigit)? HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit + ; // hide but do not remove whitespace and comments -WS : [ \t\r\n\u000C]+ -> channel(1) +WS : [ \t\r\n\u000C]+ -> channel(1) ; COMMENT - : '/*' .*? '*/' -> channel(2) + : '/*' .*? '*/' -> channel(2) ; LINE_COMMENT - : '//' ~[\r\n]* -> channel(2) + : '//' ~[\r\n]* -> channel(2) ; -CHAR_LITERAL: '\'' (~['\\\r\n] | EscapeSequence)* '\''; // A bit nasty but let JDT tackle invalid chars - +CHAR_LITERAL + : '\'' (~['\\\r\n] | EscapeSequence)* '\'' // A bit nasty but let JDT tackle invalid chars + ; diff --git a/java/src/processing/mode/java/runner/Runner.java b/java/src/processing/mode/java/runner/Runner.java index d94ebc222..2617b1d03 100644 --- a/java/src/processing/mode/java/runner/Runner.java +++ b/java/src/processing/mode/java/runner/Runner.java @@ -24,7 +24,6 @@ package processing.mode.java.runner; import processing.app.*; import processing.app.exec.StreamRedirectThread; -import processing.app.ui.Toolkit; import processing.core.*; import processing.data.StringList; import processing.mode.java.JavaBuild; @@ -33,6 +32,7 @@ import processing.mode.java.JavaEditor; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Point; +import java.awt.Toolkit; import java.io.*; import java.net.ConnectException; import java.net.InetAddress; @@ -108,20 +108,11 @@ public class Runner implements MessageConsumer { } // Make sure all the imported libraries will actually run with this setup. - int bits = Platform.getNativeBits(); +// int bits = Platform.getNativeBits(); String variant = Platform.getVariant(); - for (Library library : build.getImportedLibraries()) { - if (!library.supportsArch(PApplet.platform, variant)) { + if (library.isUnsupported(variant)) { sketchErr.println(library.getName() + " does not run on this architecture: " + variant); - int opposite = (bits == 32) ? 64 : 32; - if (Platform.isMacOS()) { - 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."); - } } } } @@ -351,12 +342,15 @@ public class Runner implements MessageConsumer { // No longer needed / doesn't seem to do anything differently //params.append("-Dcom.apple.mrj.application.apple.menu.about.name=" + // build.getSketchClassName()); - } else if (Platform.isWindows()) { + } + /* + if (Platform.isWindows()) { // 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"); } + */ // sketch.libraryPath might be "" // librariesClassPath will always have sep char prepended @@ -463,7 +457,7 @@ public class Runner implements MessageConsumer { Point editorLocation = editor.getLocation(); params.append(PApplet.ARGS_EDITOR_LOCATION + "=" + editorLocation.x + "," + editorLocation.y); - } else { +// } else { // The sketch's main() will set a location centered on the new // display. It has to happen in main() because the width/height // of the sketch are not known here. @@ -496,9 +490,17 @@ public class Runner implements MessageConsumer { // removed for 3.0a6 because it would break the args passed to sketches. params.append(PApplet.ARGS_SKETCH_FOLDER + "=" + build.getSketchPath()); + /* if (Toolkit.zoom(100) >= 200) { // Use 100 to bypass possible rounding in zoom() params.append(PApplet.ARGS_DENSITY + "=2"); } + */ + if (Platform.isWindows()) { + // Pass the DPI setting to the app to avoid using the helper app. + int dpi = Toolkit.getDefaultToolkit().getScreenResolution(); + int uiScale = PApplet.constrain(dpi / 96, 1, 2); + params.append(PApplet.ARGS_UI_SCALE + "=" + uiScale); + } params.append(build.getSketchClassName()); } @@ -647,7 +649,7 @@ public class Runner implements MessageConsumer { // System.out.println("in here"); // Bug #852 tracked to this next line in the code. - // http://dev.processing.org/bugs/show_bug.cgi?id=852 + // https://download.processing.org/bugzilla/852.html errThread.join(); // Make sure output is forwarded // System.out.println("and then"); outThread.join(); // before we exit @@ -740,7 +742,7 @@ public class Runner implements MessageConsumer { final PrintStream err) { if (exceptionClass.equals("java.lang.OutOfMemoryError")) { if (message.contains("exceeds VM budget")) { - // TODO this is a kludge for Android, since there's no memory preference + // TODO this is a kludge for Android, since there is no memory preference listener.statusError("OutOfMemoryError: This code attempts to use more memory than available."); err.println("An OutOfMemoryError means that your code is either using up too much memory"); err.println("because of a bug (e.g. creating an array that's too large, or unintentionally"); @@ -755,13 +757,8 @@ public class Runner implements MessageConsumer { err.println("you can increase the memory available to your sketch using the Preferences window."); } } else if (exceptionClass.equals("java.lang.UnsatisfiedLinkError")) { - listener.statusError("A library used by this sketch is not installed properly."); - if (PApplet.platform == PConstants.LINUX) { - err.println(message); - } - err.println("A library relies on native code that's not available."); - err.println("Or only works properly when the sketch is run as a " + - ((Platform.getNativeBits() == 32) ? "64-bit" : "32-bit") + " application."); + err.println("A library used by this sketch relies on native code that is not available."); + err.println(message); } else if (exceptionClass.equals("java.lang.StackOverflowError")) { listener.statusError("StackOverflowError: This sketch is attempting too much recursion."); @@ -788,9 +785,9 @@ public class Runner implements MessageConsumer { } - // TODO: This may be called more than one time per error in the VM, - // presumably because exceptions might be wrapped inside others, - // and this will fire for both. + // TODO This may be called more than one time per error in the VM, + // presumably because exceptions may be wrapped inside others, + // and this will fire for both. protected void reportException(String message, ObjectReference or, ThreadReference thread) { listener.statusError(findException(message, or, thread)); } @@ -908,7 +905,7 @@ public class Runner implements MessageConsumer { // made synchronized for 0087 // attempted to remove synchronized for 0136 to fix bug #775 (no luck tho) - // http://dev.processing.org/bugs/show_bug.cgi?id=775 + // https://download.processing.org/bugzilla/775.html synchronized public void message(String s) { // System.out.println("M" + s.length() + ":" + s.trim()); // + "MMM" + s.length()); diff --git a/java/src/processing/mode/java/tweak/SketchParser.java b/java/src/processing/mode/java/tweak/SketchParser.java index d2a85e050..2dfedd732 100644 --- a/java/src/processing/mode/java/tweak/SketchParser.java +++ b/java/src/processing/mode/java/tweak/SketchParser.java @@ -115,7 +115,7 @@ public class SketchParser { // for every number found: // save its type (int/float), name, value and position in code. - Pattern p = Pattern.compile("[\\[\\{<>(),\\t\\s\\+\\-\\/\\*^%!|&=?:~]\\d+\\.?\\d*"); + Pattern p = Pattern.compile("[\\[\\{<>(),\\t\\s\\+\\-\\/\\*^%!|&=?:~]\\d+(_+\\d+)*(\\.\\d+(_+\\d+)*)?"); // Matches any "delimiting" character, and then a decimal number. for (int i = 0; i < codeTabs.length; i++) { List handles = new ArrayList<>(); allHandles.add(handles); @@ -185,7 +185,7 @@ public class SketchParser { continue; int line = countLines(c.substring(0, start)) - 1; // zero based - String value = c.substring(start, end); + String value = c.substring(start, end).replace("_", ""); if (value.contains(".") || forceFloat) { // consider this as a float String name = varPrefix + "_float[" + floatVarCount +"]"; @@ -211,7 +211,7 @@ public class SketchParser { private void addAllHexNumbers() { // for every number found: // save its type (int/float), name, value and position in code. - Pattern p = Pattern.compile("[\\[\\{<>(),\\t\\s\\+\\-\\/\\*^%!|&=?:~]0x[A-Fa-f0-9]+"); + Pattern p = Pattern.compile("[\\[\\{<>(),\\t\\s\\+\\-\\/\\*^%!|&=?:~]0x[A-Fa-f0-9]+(_+[A-Fa-f0-9]+)*"); for (int i = 0; i < codeTabs.length; i++) { String c = codeTabs[i]; Matcher m = p.matcher(c); @@ -248,7 +248,7 @@ public class SketchParser { } int line = countLines(c.substring(0, start)) - 1; // zero based - String value = c.substring(start, end); + String value = c.substring(start, end).replace("_", ""); String name = varPrefix + "_int[" + intVarCount + "]"; Handle handle; try { diff --git a/java/test/lib/hamcrest-core-1.3.jar b/java/test/lib/hamcrest-core-1.3.jar new file mode 100644 index 000000000..9d5fe16e3 Binary files /dev/null and b/java/test/lib/hamcrest-core-1.3.jar differ diff --git a/java/test/lib/junit-4.13.2.jar b/java/test/lib/junit-4.13.2.jar new file mode 100644 index 000000000..6da55d8b8 Binary files /dev/null and b/java/test/lib/junit-4.13.2.jar differ diff --git a/java/test/lib/junit-4.8.1.jar b/java/test/lib/junit-4.8.1.jar deleted file mode 100644 index 524cd65ce..000000000 Binary files a/java/test/lib/junit-4.8.1.jar and /dev/null differ diff --git a/java/test/processing/mode/java/ParserTests.java b/java/test/processing/mode/java/ParserTests.java index 34e8add92..f322b8e8b 100644 --- a/java/test/processing/mode/java/ParserTests.java +++ b/java/test/processing/mode/java/ParserTests.java @@ -11,8 +11,10 @@ import java.io.IOException; import java.util.Optional; import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Test; +import processing.app.Preferences; import processing.app.SketchException; import processing.mode.java.preproc.PreprocessorResult; import processing.mode.java.preproc.PdePreprocessIssueException; @@ -25,6 +27,11 @@ public class ParserTests { ProcessingTestUtil.init(); } + @Before + public void before() { + Preferences.setBoolean("export.application.fullscreen", false); + } + static void expectRecognitionException(final String id, final int expectedLine) { @@ -325,6 +332,11 @@ public class ParserTests { expectGood("fullscreen", true); } + @Test + public void fullscreenArg() { + expectGood("fullscreen_arg", true); + } + @Test public void customMain() { expectGood("custommain", true); @@ -375,6 +387,11 @@ public class ParserTests { expectGood("smoothnoparam"); } + @Test + public void testSmoothThis() { + expectGood("smoothnoparamthis"); + } + @Test public void testSmoothWithParam() { expectGood("smoothparam"); @@ -385,4 +402,45 @@ public class ParserTests { expectGood("smoothparamstatic"); } + @Test + public void testColorInImport() { + expectGood("colorimport"); + } + + @Test + public void testPGraphicsStandalone() { + expectGood("pgraphics"); + } + + @Test + public void testSizeThis() { + expectGood("sizethis"); + } + + @Test + public void testMixing() { + expectRunnerException("mixing", 1); + } + + @Test + public void testSizeClass() { + expectGood("sizeclass"); + } + + @Test + public void testMultilineString() { + expectGood("multilinestr"); + } + + @Test + public void testMultilineStringClass() { + expectGood("multilinestrclass"); + } + + @Test + public void testMultiMultilineString() { + Preferences.setBoolean("export.application.fullscreen", true); + expectGood("fullscreen_export"); + } + } diff --git a/java/test/resources/colorimport.expected b/java/test/resources/colorimport.expected new file mode 100644 index 000000000..939d21609 --- /dev/null +++ b/java/test/resources/colorimport.expected @@ -0,0 +1,37 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import test.color; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class colorimport extends PApplet { + + public void setup() { + + +boolean test = true; +int c1 = color(255, 255, 255); +int c2 = test ? 0xFFA011CD : 0xC0C0C0C0; + + noLoop(); + } + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "colorimport" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/colorimport.pde b/java/test/resources/colorimport.pde new file mode 100644 index 000000000..2da84c3a1 --- /dev/null +++ b/java/test/resources/colorimport.pde @@ -0,0 +1,5 @@ +import test.color; + +boolean test = true; +color c1 = color(255, 255, 255); +color c2 = test ? #A011CD : #C0C0C0C0; diff --git a/java/test/resources/fullscreen_arg.expected b/java/test/resources/fullscreen_arg.expected new file mode 100644 index 000000000..d8aba4453 --- /dev/null +++ b/java/test/resources/fullscreen_arg.expected @@ -0,0 +1,33 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class fullscreen_arg extends PApplet { + + public void setup() { +/* size commented out by preprocessor */; + + noLoop(); + } + + public void settings() { fullScreen(FX2D, 2); } + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "fullscreen_arg" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/fullscreen_arg.pde b/java/test/resources/fullscreen_arg.pde new file mode 100644 index 000000000..2b1232659 --- /dev/null +++ b/java/test/resources/fullscreen_arg.pde @@ -0,0 +1 @@ +fullScreen(FX2D, 2); diff --git a/java/test/resources/fullscreen_export.expected b/java/test/resources/fullscreen_export.expected new file mode 100644 index 000000000..8fa8afe5d --- /dev/null +++ b/java/test/resources/fullscreen_export.expected @@ -0,0 +1,39 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class fullscreen_export extends PApplet { + +int x = 0; + + public void setup() { + background(0); + noStroke(); + fill(102); +} + + public void draw() { + rect(x, height*0.2f, 1, height*0.6f); + x = x + 2; +} + + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "--full-screen", "--bgcolor=null", "--hide-stop", "fullscreen_export" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/fullscreen_export.pde b/java/test/resources/fullscreen_export.pde new file mode 100644 index 000000000..343c4a96f --- /dev/null +++ b/java/test/resources/fullscreen_export.pde @@ -0,0 +1,12 @@ +int x = 0; + +void setup() { + background(0); + noStroke(); + fill(102); +} + +void draw() { + rect(x, height*0.2, 1, height*0.6); + x = x + 2; +} diff --git a/java/test/resources/fullscreen_noarg.expected b/java/test/resources/fullscreen_noarg.expected new file mode 100644 index 000000000..a55f46361 --- /dev/null +++ b/java/test/resources/fullscreen_noarg.expected @@ -0,0 +1,33 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class fullscreen_noarg extends PApplet { + + public void setup() { +/* size commented out by preprocessor */; + + noLoop(); + } + + public void settings() { fullScreen(); } + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "fullscreen_noarg" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/fullscreen_noarg.pde b/java/test/resources/fullscreen_noarg.pde new file mode 100644 index 000000000..2cc577698 --- /dev/null +++ b/java/test/resources/fullscreen_noarg.pde @@ -0,0 +1 @@ +fullScreen(); diff --git a/java/test/resources/mixing.pde b/java/test/resources/mixing.pde new file mode 100644 index 000000000..48dc2686a --- /dev/null +++ b/java/test/resources/mixing.pde @@ -0,0 +1,8 @@ +size(100, 100); +int radius = 10; + +ellispe(radus, radus); + +void setup() { + +} diff --git a/java/test/resources/multilinestr.expected b/java/test/resources/multilinestr.expected new file mode 100644 index 000000000..830b12e39 --- /dev/null +++ b/java/test/resources/multilinestr.expected @@ -0,0 +1,35 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class multilinestr extends PApplet { + + public void setup() { +String testOldStyle = "line1\"\nline 2 \"\"\nline 3"; +String testMultiline = "\nline4 \"\nline 5 \"\"\nline 6\nline 7"; + +println(testOldStyle); +println(testMultiline); + + noLoop(); + } + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "multilinestr" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/multilinestr.pde b/java/test/resources/multilinestr.pde new file mode 100644 index 000000000..4c4c8dbea --- /dev/null +++ b/java/test/resources/multilinestr.pde @@ -0,0 +1,9 @@ +String testOldStyle = "line1\"\nline 2 \"\"\nline 3"; +String testMultiline = """ +line4 " +line 5 "" +line 6 +line 7"""; + +println(testOldStyle); +println(testMultiline); diff --git a/java/test/resources/multilinestrclass.expected b/java/test/resources/multilinestrclass.expected new file mode 100644 index 000000000..5a5a8093d --- /dev/null +++ b/java/test/resources/multilinestrclass.expected @@ -0,0 +1,43 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class multilinestrclass extends PApplet { + +class TestClass { + final String testMultiline1 = "\nline1 \"\nline 2 \"\"\nline 3\nline 4"; + + public String getStr() { + return testMultiline1; + } +} + + + public void setup() { + TestClass test = new TestClass(); + println(test.getStr()); +} + + public void draw() { +} + + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "multilinestrclass" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/multilinestrclass.pde b/java/test/resources/multilinestrclass.pde new file mode 100644 index 000000000..a8e83e840 --- /dev/null +++ b/java/test/resources/multilinestrclass.pde @@ -0,0 +1,20 @@ +class TestClass { + final String testMultiline1 = """ +line1 " +line 2 "" +line 3 +line 4"""; + + String getStr() { + return testMultiline1; + } +} + + +void setup() { + TestClass test = new TestClass(); + println(test.getStr()); +} + +void draw() { +} diff --git a/java/test/resources/multimultilinestr.expected b/java/test/resources/multimultilinestr.expected new file mode 100644 index 000000000..df0d8e41f --- /dev/null +++ b/java/test/resources/multimultilinestr.expected @@ -0,0 +1,35 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class multimultilinestr extends PApplet { + + public void setup() { +String testMultiline1 = "\nline1 \"\nline 2 \"\"\nline 3\nline 4"; +final String testMultiline2 = "\nline5 \"\nline 6 \"\"\nline 7\nline 8"; + +println(testMultiline1); +println(testMultiline2); + + noLoop(); + } + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "multimultilinestr" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/multimultilinestr.pde b/java/test/resources/multimultilinestr.pde new file mode 100644 index 000000000..3e4274663 --- /dev/null +++ b/java/test/resources/multimultilinestr.pde @@ -0,0 +1,14 @@ +String testMultiline1 = """ +line1 " +line 2 "" +line 3 +line 4"""; + +final String testMultiline2 = """ +line5 " +line 6 "" +line 7 +line 8"""; + +println(testMultiline1); +println(testMultiline2); diff --git a/java/test/resources/pgraphics.expected b/java/test/resources/pgraphics.expected new file mode 100644 index 000000000..2432ab7a6 --- /dev/null +++ b/java/test/resources/pgraphics.expected @@ -0,0 +1,37 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class pgraphics extends PApplet { + +PGraphics gfx; + + public void setup() { + + gfx = createGraphics(width, height); + gfx.smooth(); +} + + public void draw() { +} + + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "pgraphics" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/pgraphics.pde b/java/test/resources/pgraphics.pde new file mode 100644 index 000000000..b0a765813 --- /dev/null +++ b/java/test/resources/pgraphics.pde @@ -0,0 +1,10 @@ +PGraphics gfx; + +void setup() { + + gfx = createGraphics(width, height); + gfx.smooth(); +} + +void draw() { +} diff --git a/java/test/resources/sizeclass.expected b/java/test/resources/sizeclass.expected new file mode 100644 index 000000000..d93e31c71 --- /dev/null +++ b/java/test/resources/sizeclass.expected @@ -0,0 +1,50 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class sizeclass extends PApplet { + +// Thanks StanLepunK: https://github.com/processing/processing4/issues/317 +Truc truc = new Truc(); + + public void setup() { + /* size commented out by preprocessor */; + truc.size(1,1); // problem >>> error \u00e0 "." + // func(); +} + + public void draw() { + truc.size(1,1); // no problem +} + + public void func() { + truc.size(1,1); // no problem +} + +class Truc { + public void size(int x, int y) { + } +} + + + public void settings() { size(200, 200); } + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "sizeclass" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/sizeclass.pde b/java/test/resources/sizeclass.pde new file mode 100644 index 000000000..65d3d15df --- /dev/null +++ b/java/test/resources/sizeclass.pde @@ -0,0 +1,21 @@ +// Thanks StanLepunK: https://github.com/processing/processing4/issues/317 +Truc truc = new Truc(); + +void setup() { + size(200,200); + truc.size(1,1); // problem >>> error à "." + // func(); +} + +void draw() { + truc.size(1,1); // no problem +} + +void func() { + truc.size(1,1); // no problem +} + +class Truc { + void size(int x, int y) { + } +} diff --git a/java/test/resources/sizethis.expected b/java/test/resources/sizethis.expected new file mode 100644 index 000000000..5ff336a61 --- /dev/null +++ b/java/test/resources/sizethis.expected @@ -0,0 +1,45 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import processing.pdf.*; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class sizethis extends PApplet { + + + + public void setup() { + /* size commented out by preprocessor */; +} + + public void draw() { + // Draw something good here + line(0, 0, width/2, height); + + // Exit the program + println("Finished."); + exit(); +} + + + public void settings() { size(400,400,PDF,"filename.pdf"); } + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "sizethis" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/sizethis.pde b/java/test/resources/sizethis.pde new file mode 100644 index 000000000..5a1ecf638 --- /dev/null +++ b/java/test/resources/sizethis.pde @@ -0,0 +1,14 @@ +import processing.pdf.*; + +void setup() { + this.size(400, 400, PDF, "filename.pdf"); +} + +void draw() { + // Draw something good here + line(0, 0, width/2, height); + + // Exit the program + println("Finished."); + exit(); +} diff --git a/java/test/resources/smoothnoparamthis.expected b/java/test/resources/smoothnoparamthis.expected new file mode 100644 index 000000000..b22457aee --- /dev/null +++ b/java/test/resources/smoothnoparamthis.expected @@ -0,0 +1,42 @@ +import processing.core.*; +import processing.data.*; +import processing.event.*; +import processing.opengl.*; + +import java.util.HashMap; +import java.util.ArrayList; +import java.io.File; +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public class smoothnoparamthis extends PApplet { + + public void setup(){ + /* size commented out by preprocessor */; + /* smooth commented out by preprocessor */; +} + + public void draw(){ + background(0); + fill(255,0,0); + ellipse(100,100,100,100); + fill(0,255,0); + ellipse(150,150,100,100); +} + + + public void settings() { size(300, 300, P2D); +smooth(); } + + static public void main(String[] passedArgs) { + String[] appletArgs = new String[] { "smoothnoparamthis" }; + if (passedArgs != null) { + PApplet.main(concat(appletArgs, passedArgs)); + } else { + PApplet.main(appletArgs); + } + } +} diff --git a/java/test/resources/smoothnoparamthis.pde b/java/test/resources/smoothnoparamthis.pde new file mode 100644 index 000000000..a6cd900fa --- /dev/null +++ b/java/test/resources/smoothnoparamthis.pde @@ -0,0 +1,12 @@ +void setup(){ + size(300,300, P2D); + this.smooth(); +} + +void draw(){ + background(0); + fill(255,0,0); + ellipse(100,100,100,100); + fill(0,255,0); + ellipse(150,150,100,100); +} diff --git a/java/theme/completion/class.svg b/java/theme/completion/class.svg new file mode 100644 index 000000000..e67048b07 --- /dev/null +++ b/java/theme/completion/class.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/java/theme/completion/class_obj-1x.png b/java/theme/completion/class_obj-1x.png deleted file mode 100644 index 7ea06bb70..000000000 Binary files a/java/theme/completion/class_obj-1x.png and /dev/null differ diff --git a/java/theme/completion/class_obj-2x.png b/java/theme/completion/class_obj-2x.png deleted file mode 100644 index 0f7b25331..000000000 Binary files a/java/theme/completion/class_obj-2x.png and /dev/null differ diff --git a/java/theme/completion/field.svg b/java/theme/completion/field.svg new file mode 100644 index 000000000..60e3f8715 --- /dev/null +++ b/java/theme/completion/field.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/java/theme/completion/field_default_obj-1x.png b/java/theme/completion/field_default_obj-1x.png deleted file mode 100644 index 10f8e61c5..000000000 Binary files a/java/theme/completion/field_default_obj-1x.png and /dev/null differ diff --git a/java/theme/completion/field_default_obj-2x.png b/java/theme/completion/field_default_obj-2x.png deleted file mode 100644 index 093ae9c8a..000000000 Binary files a/java/theme/completion/field_default_obj-2x.png and /dev/null differ diff --git a/java/theme/completion/field_protected_obj-1x.png b/java/theme/completion/field_protected_obj-1x.png deleted file mode 100644 index 4858d93e6..000000000 Binary files a/java/theme/completion/field_protected_obj-1x.png and /dev/null differ diff --git a/java/theme/completion/field_protected_obj-2x.png b/java/theme/completion/field_protected_obj-2x.png deleted file mode 100644 index bdd9ab600..000000000 Binary files a/java/theme/completion/field_protected_obj-2x.png and /dev/null differ diff --git a/java/theme/completion/local.svg b/java/theme/completion/local.svg new file mode 100644 index 000000000..05c54a30c --- /dev/null +++ b/java/theme/completion/local.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/java/theme/completion/method.svg b/java/theme/completion/method.svg new file mode 100644 index 000000000..a124da972 --- /dev/null +++ b/java/theme/completion/method.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/java/theme/completion/methpub_obj-1x.png b/java/theme/completion/methpub_obj-1x.png deleted file mode 100644 index 7e9e3aeee..000000000 Binary files a/java/theme/completion/methpub_obj-1x.png and /dev/null differ diff --git a/java/theme/completion/methpub_obj-2x.png b/java/theme/completion/methpub_obj-2x.png deleted file mode 100644 index cf64cb75b..000000000 Binary files a/java/theme/completion/methpub_obj-2x.png and /dev/null differ diff --git a/java/theme/debug/breakpoint-enabled-1x.png b/java/theme/debug/breakpoint-enabled-1x.png deleted file mode 100644 index db540e8b2..000000000 Binary files a/java/theme/debug/breakpoint-enabled-1x.png and /dev/null differ diff --git a/java/theme/debug/breakpoint-enabled-2x.png b/java/theme/debug/breakpoint-enabled-2x.png deleted file mode 100644 index 426ddbf75..000000000 Binary files a/java/theme/debug/breakpoint-enabled-2x.png and /dev/null differ diff --git a/java/theme/debug/continue-enabled-1x.png b/java/theme/debug/continue-enabled-1x.png deleted file mode 100644 index f43171df4..000000000 Binary files a/java/theme/debug/continue-enabled-1x.png and /dev/null differ diff --git a/java/theme/debug/continue-enabled-2x.png b/java/theme/debug/continue-enabled-2x.png deleted file mode 100644 index 49c3687b9..000000000 Binary files a/java/theme/debug/continue-enabled-2x.png and /dev/null differ diff --git a/java/theme/debug/step-enabled-1x.png b/java/theme/debug/step-enabled-1x.png deleted file mode 100644 index 2217cd8b3..000000000 Binary files a/java/theme/debug/step-enabled-1x.png and /dev/null differ diff --git a/java/theme/debug/step-enabled-2x.png b/java/theme/debug/step-enabled-2x.png deleted file mode 100644 index b4e0f329d..000000000 Binary files a/java/theme/debug/step-enabled-2x.png and /dev/null differ diff --git a/java/theme/variables-1x.png b/java/theme/variables-1x.png deleted file mode 100644 index b20038b48..000000000 Binary files a/java/theme/variables-1x.png and /dev/null differ diff --git a/java/theme/variables-2x.png b/java/theme/variables-2x.png deleted file mode 100644 index 134701d1e..000000000 Binary files a/java/theme/variables-2x.png and /dev/null differ diff --git a/java/theme/variables/array.svg b/java/theme/variables/array.svg new file mode 100644 index 000000000..a0aa0b523 --- /dev/null +++ b/java/theme/variables/array.svg @@ -0,0 +1,4 @@ + + + + diff --git a/java/theme/variables/boolean.svg b/java/theme/variables/boolean.svg new file mode 100644 index 000000000..cc606e1b0 --- /dev/null +++ b/java/theme/variables/boolean.svg @@ -0,0 +1,4 @@ + + + + diff --git a/java/theme/variables/byte.svg b/java/theme/variables/byte.svg new file mode 100644 index 000000000..91f4e8e5a --- /dev/null +++ b/java/theme/variables/byte.svg @@ -0,0 +1,4 @@ + + + + diff --git a/java/theme/variables/char.svg b/java/theme/variables/char.svg new file mode 100644 index 000000000..c2877c3e9 --- /dev/null +++ b/java/theme/variables/char.svg @@ -0,0 +1,4 @@ + + + + diff --git a/java/theme/variables/double.svg b/java/theme/variables/double.svg new file mode 100644 index 000000000..d448f4e2b --- /dev/null +++ b/java/theme/variables/double.svg @@ -0,0 +1,4 @@ + + + + diff --git a/java/theme/variables/float.svg b/java/theme/variables/float.svg new file mode 100644 index 000000000..5ae8f6199 --- /dev/null +++ b/java/theme/variables/float.svg @@ -0,0 +1,4 @@ + + + + diff --git a/java/theme/variables/integer.svg b/java/theme/variables/integer.svg new file mode 100644 index 000000000..e96e43b87 --- /dev/null +++ b/java/theme/variables/integer.svg @@ -0,0 +1,4 @@ + + + + diff --git a/java/theme/variables/long.svg b/java/theme/variables/long.svg new file mode 100644 index 000000000..a37a78360 --- /dev/null +++ b/java/theme/variables/long.svg @@ -0,0 +1,4 @@ + + + + diff --git a/java/theme/variables/object.svg b/java/theme/variables/object.svg new file mode 100644 index 000000000..e59c849aa --- /dev/null +++ b/java/theme/variables/object.svg @@ -0,0 +1,4 @@ + + + + diff --git a/java/theme/variables/short.svg b/java/theme/variables/short.svg new file mode 100644 index 000000000..6f6d486fc --- /dev/null +++ b/java/theme/variables/short.svg @@ -0,0 +1,4 @@ + + + + diff --git a/java/theme/variables/string.svg b/java/theme/variables/string.svg new file mode 100644 index 000000000..44f75d290 --- /dev/null +++ b/java/theme/variables/string.svg @@ -0,0 +1,4 @@ + + + + diff --git a/todo.txt b/todo.txt index 0cba770ee..80759e758 100755 --- a/todo.txt +++ b/todo.txt @@ -1,168 +1,714 @@ -1277 (4.0b2) -X remove translated URLs that are not actually translated -X entries in the Help menu were going to ancient links -X https://github.com/processing/processing4/issues/250 -X Switch to getModifiersEx() in processing.app -X https://github.com/processing/processing4/issues/67 -X deal with getFontMetrics() deprecation warning in EditorToolbar +1287 (4.0.2) +X using Java 17 in more syntax +X fix potential casting problem with Platform and DefaultPlatform +X should be unreachable, but fixed anyway +X bring back getCodeIndex() for GUI Builder Tool +X https://github.com/processing/processing4/issues/545 -_ make the call on the launcher setting for Linux +contribs +X Catalan translation for Processing 4.0.1 +X https://github.com/processing/processing4/issues/550 +X https://github.com/processing/processing4/pull/554 + +docs +X added a "Translations" page to the wiki +X https://github.com/processing/processing4/wiki/Translations +X https://github.com/processing/processing/wiki/Localization +X explanation of how to create a naming.json file +X https://github.com/processing/processing4/wiki/Naming-Sketches + + +before 4.0.2 +_ if naming scheme produces a sketch w/ the same name, what happens? +_ probably a crash (or infinite loop?) need to check +_ temp folders owned by one user can't be overwritten by another +_ this was on Linux; curious if Windows has an issue too? +_ overwrite with -Djava.io.tmpdir=/path/to/tmpdir +_ maybe we should use java.io.tmpdir -> processing -> $USER +_ https://github.com/processing/processing4/issues/549 +_ "Show Sketch Folder" for libraries needs to treat the sketch as Untitled +_ and with that, switch to another directory +_ https://github.com/processing/processing4/issues/548 + + +_ set more build.xml files to use Java 17 +_ remove target.path from build/build.xml +_ maybe simpler way to write version? sheesh + + +_ args passed to main() aren't working +_ (there was a bug report for this already?) +_ were the run() and runSketch() methods merged w/o realizing loss of args? +_ runSketch() probably needs to pass applet args and sketch args separately +_ though this can't be done w/o breaking Python + + +contrib +X Fix vertical placement of top elements in the Manager window +X https://github.com/processing/processing4/issues/520 +X https://github.com/processing/processing4/pull/539 + + +known issues +_ mixed mode warning now broken +_ https://github.com/processing/processing4/issues/519 +_ tests also turned off in build.xml because this is causing it to fail +_ 'color' in imports shows up as an error in the editor +_ but code still compiles and runs +_ https://github.com/processing/processing4/issues/521 +_ need to make it possible to use module jars +_ https://github.com/processing/processing4/issues/522 +_ https://github.com/processing/processing4-javafx/issues/15 + + +finalizing a release +_ submit to Microsoft +_ https://www.microsoft.com/en-us/wdsi/filesubmission +_ https://aka.ms/wdsi +_ https://www.microsoft.com/security/oblog/2018/08/16/partnering-with-the-industry-to-minimize-false-positives/ + + +. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + +priorities/would like to fix soon (but not necessary for 4.0 final) +_ write a local.properties file when there's more than one file in the sketch +_ this will fix the majority of cases where sketch name is accidentally detached +_ for single .pde sketches, it's not a problem because it will find the file +_ but for multiple files, it needs to identify the main +_ 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 +_ add warnings to the old wiki pages with links to the newer versions +o change the license from GPL +_ make notes about the decision in the FAQ +_ Remove “Disable HiDPI Scaling” preference on Windows? +_ document it in the troubleshooting doc, ask people to report +_ put it into the Preferences page? +_ adding a file to an untitled sketch should require a save +_ editor breakpoints out of the .pde files +_ really a drag for version control +_ https://github.com/processing/processing/issues/5848 +_ or at least avoid the multiple +_ https://github.com/processing/processing4/issues/330 +_ Cannot type quotes with an international keyboard on MacOS +_ fixed by turning on "Enable complex text input" +_ https://github.com/processing/processing4/issues/322 +_ loadShape(), show a warning when using style instead of presentation attributes +_ apt package for easier rpi install (especially) +_ show relative Library/Mode/Tool usage/popularity in the manager +_ Open Sketch Folder not working on Linux with java.awt.Desktop quirks +_ make the call on the launcher setting for Linux _ https://github.com/processing/processing4/issues/247 +_ Find in Reference disabled for various keywords (draw, for, if, catch, while) +_ https://github.com/processing/processing/issues/5562 +X https://github.com/processing/processing/pull/5642 (closed) +_ ui scaling in the PDE is important for classes/seeing at a distance +_ some interface zoom now conflicting with the Java 9+ defaults +_ things look terrible (tiny fonts) on Windows, adjusting has weird effects +_ resolve in DefaultPlatform and Toolkit, disable ui in PreferencesFrame +_ proxy trouble with p5? since adding the system proxy? +_ this can be a problem in school environments (where proxies show up) +_ really difficult to test, however, without a proxy setup +_ https://github.com/processing/processing/pull/3251/files +_ larger problem thread https://github.com/processing/processing/issues/3891 +_ ideen2011.blogspot.de/2011/08/java-proxyselector-usesystemproxies-and.html +_ malformed proxy issues http://stackoverflow.com/q/376101 +_ docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html +_ https://github.com/processing/processing/issues/1476#issuecomment-23229990 +_ https://github.com/processing/processing4/wiki/Preferences#proxies +_ right-click/ctrl-click for jump to declaration causes hang +_ https://github.com/processing/processing4/issues/527 +_ https://github.com/processing/processing4/issues/551 + +design +_ completion mode pop-up not following dark and light mode conventions +_ tool tips for errors/warnings not picking up correct colors +_ update console.scrollbar.thumb.rollover.color and console.scrollbar.thumb.pressed.color +_ currently just using .enabled.color because they weren't in ColorSet -highest -_ update the README.md in the root for alpha 6 and beta 1 -_ update https://github.com/processing/processing4/wiki/Changes-in-4.0 -_ clean up dist files, 'bin' and 'src' from Java Mode are included, iml files, etc +organize this mess +_ implement support for Java “modules” and clean up JavaFX-specific workarounds +_ https://github.com/processing/processing4/issues/212 +_ modules and classpath ugh https://stackoverflow.com/a/46289257 +_ also the Crusty2D renderer for use with macOS hack stuff +_ actually need separate alpha/1.5/2.x versions of this +_ errors inside setup() aren't coming through at all? +_ seen in Eclipse; have to turn on the debugger... same as #4703? +_ mode list does not update after changing sketchbook folder +_ already reported? +_ make "Could not get the settings folder" message more helpful +_ https://github.com/processing/processing/issues/5744 +_ need to check the locations it'd be writing to, and see if available +_ then tell the user which folder to fix +_ already fairly close, the problem is that they get a "report this" dialog box +_ but the useful information is in the actual exception message +_ import suggestions box needs design review +_ https://github.com/processing/processing/issues/3407 +_ show compiling/startup animation after clicking run +_ or maybe show an animation for starting up +_ rather than post-compile +_ https://github.com/processing/processing/issues/136 +_ save() and saveAs() need to be refactored +_ https://github.com/processing/processing/issues/3843 +_ move Library to LibraryContribution and into contrib? +_ improve error message when creating a tab with the same name +_ right now it's generic, based on "a file exists" +_ don't allow users to create 'blah.java' when 'blah.pde' already in sketch +_ detect changes in case with libraries +_ https://github.com/processing/processing/issues/4507 +_ modes are being loaded multiple times, which can cause trouble +_ jeditsyntax is a mess of old-style getModifiers() +_ would like to switch this over, but needs to be tested a lot -_ what to double-click when opening p5 projects (.pdp?) -_ lack of a project file makes this a little bit of a headache -_ dropping a sketch folder onto the PDE should also be implemented -_ refresh option for sketchbook (bottom of window) -_ import option for sketchbook (button to select files/folders/etc) + +//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// + +PDE - Processing Development Environment + + +PDE / Dialogs + +_ option to suppress warning dialogs +_ starting with the one about modifying the sketch name for spaces +_ also the "some features will be disabled" with .java tabs +_ https://github.com/processing/processing/issues/46 +_ add "don't warn me about this" for sketch renaming +_ make sure renamed version doesn't exist already +_ prompt user before nuking applet or application folders +_ along with a "don't ask me later" + + +PDE / Compiler & Preprocessor + +medium (bugs/features) +_ preprocessor throws error when calling color() on a PGraphics +_ https://github.com/processing/processing/issues/3762 +_ omitting a semicolon shows the error on the line after it +_ npe if library is removed before compile +_ always check library folders to make sure they're still valid +_ with additional .pde files, don't allow static mode in front tab +_ e.g. no setup()/draw() block +_ don't allow "for (blah; blah; blah) ;" +_ or if (blah blah blah) ; +_ it's never useful. students can use { } if they want an empty block + +low (features) +_ copy running code from /tmp/buildXXxxx on crash of p5 +_ should probably make a way to save/recover code +_ make the buildXxxx folders relate to time/date? +_ link out to further documentation (e.g. AIOOBE, NPE) + +low (common errors around reserved names/class naming) +_ Saving sketch with the same name as a class or primitive breaks sketch +_ https://github.com/processing/processing/issues/196 +_ don't allow people to override methods like paint() +_ make them final? just improve the error messages? +_ https://github.com/processing/processing/issues/1058 +_ Processing chokes if a sketch defines a class with same name as the sketch +_ https://github.com/processing/processing/issues/196 +_ don't allow goofy case versions of reserved words +_ keypressed should maybe throw an error +_ https://github.com/processing/processing/issues/44 +_ "unexpected token" on anonymous instance of parameterized Comparator +_ https://github.com/processing/processing/issues/533 + + +PDE / Editor + +_ 'recent' menu paths can get enormous +_ active editor not being set null +_ in Base.nextEditorLocation(), changed to "editors.size() == 0" +_ instead of (activeEditor == null), but that's papering over a problem +_ where the active editor is not being set null +_ renaming RGB (.pde) to Rgb.java says "a file named RGB.pde already exists" +_ improve update check message "a new release (1.0.1) is available" +_ be more descriptive, use a second line in latest.txt +_ maybe just include the full text of the update message there? +_ go through other sketch-opening menus to check for disappearing sketches +_ deal with isManagingFocus() warning in the editor src +_ strange NullPointerException problem prevents launch +_ some kind of NPE in handleOpenInternal and friends +_ appears to be a synchronization problem with the loading +_ blank sketch opened even if another opened by double-click +_ add a 150 ms or more lag before opening the untitled window +_ https://github.com/processing/processing/issues/218 +_ editors opening up at the same time on load? +_ either synchronize the open (at a minimum) +_ or wait for mac handlers to register an open event +_ can also cause problems with opening multiple copies of same sketch +_ after fixing name of sketch, ensure sketch of that name does not exist +_ add auto-save to the editor +_ https://github.com/processing/processing/issues/131 +_ implement better method of showing curly brace closure +_ https://github.com/processing/processing/issues/94 +_ setModified() getting called on Windows (probably Linux) for key cmds + + +PDE / Completion + +_ text overlapping in the code completion popup +_ appears to be a bug in the text width calculations in JLabel +_ applyMatrix() sometimes ... and sometimes not +_ apparently "overloaded" methods are show as methodName(...) until selected +_ but the actual behavior seems inconsistent at best + + +PDE / Editor Toolbar (Buttons) + +_ run button issues (unconfirmed) +_ does it unhighlight after compile or runtime errors? +_ also when using draw() instead of loop() +_ applet needs to notify runner that it has terminated +_ check 'finished' via objectreference? +_ EditorToolbar has two TODO items for open menu handling + + +PDE / Export + +_ make possible to embed java for all platforms +_ straightforward to download from adoptium +_ export application ubuntu -> windows not working (2.2.1) +_ https://github.com/processing/processing/issues/2698 +_ might be something with libraries (native or otherwise) +_ "error during export" message, but no error message contents come through +_ e.g. https://github.com/processing/processing/issues/4792 + + +PDE / Libraries + +_ alternate handling of duplicate library conflicts +_ https://github.com/processing/processing/pull/5126 +_ Add a means to specify packages to import in library.properties +_ https://github.com/processing/processing/issues/2134 + + +PDE / Examples + +_ examples handling is less than ideal +_ examples not mentioned in warning dialog when installing +_ doesn't show in examples window for p5jsMode +_ the extra "Contributed Examples" subfolder is awkward +_ perhaps more important, examples need to specify their Mode +_ not ideal for Android since there's some crossover, but ok for Example sets +_ otherwise impossible to know how to populate the Examples window +_ several "Could not find a matching .properties file" with clocks pdex +_ examples window is per-Mode, but that doesn't seem helpful +_ just like sketchbook, you can open one from the other +_ or maybe just contribs show everywhere? hrm +_ make sure pdex/pdez files working, also on Windows +_ contrib examples are just too convoluted to double-click +_ folders inside folders, bleh +_ what Mode are they for? +_ pdez links for contribs, not just the .zip +_ fix the code so that the contribs manager will handle it +_ though that's bad for earlier contribs, hrm +_ document pdez and pdex files so that folks can use them _ load examples from pdez files _ 250 example (folders), would cut ~8-900 files to just those 250 _ https://github.com/processing/processing/issues/182 _ examples window sketches should load in proper environment -_ if Java Mode is in front, and user double-clicks another Mode's entry, what happens? +_ test for what happens: +_ if Java Mode is in front, and user double-clicks another Mode's entry _ write build.xml file to automatically update the examples _ when adding a new library, its examples aren't added to the list until restart -_ key shortcuts broken on Katherine's laptop -_ try with a clean user account -X double-check on other Big Sur machines (tried on VMware, was fine) -_ auto-complete not triggering, workaround as pref? -_ Auto-completion: [ ] Disabled [ ] Show on Ctrl-Space [ ] Always show -_ https://github.com/processing/processing/issues/5691 +PDE / Manager (4.x notes) -_ IDE cursor position is wrong if font size is changed in preferences on macOS -_ probably related to second displays, need to hook one up and test -_ https://github.com/processing/processing4/issues/194 - -_ 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 -_ sort out ui.font plus the other fonts inside theme.txt -_ Toolkit.getSansFont() will use the internal ProcessingSansPro font -_ which means it gets used inside ManagerFrame, -_ and prevents the theme from updating the font -_ while that font could exist in theme.txt, -_ getSansFont() also handles the language fallback version -_ which perhaps, that pref should come from the translations: i.e. an override setting -_ 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 -_ Space Grotesk and Mono? -_ https://fonts.google.com/specimen/Space+Mono -_ https://fonts.google.com/specimen/Space+Grotesk - -_ Implement support for Java “modules” and clean up JavaFX-specific workarounds -_ https://github.com/processing/processing4/issues/212 - -_ when lib downloads (batik) go dead, fallback to the download.processing.org version -_ or for now, tell users how to do it manually - -windows/scaling -_ 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 -_ was looking crunchy on low-dpi screen set to 125% -_ was this due to the args change in alpha 5? +_ remove dorky loading.gif (used in ContributionTab and UpdateContributionTab) +_ should be a better way to implement this +_ if no internet available, install buttons disabled, but not clear why broken +_ also if update check disabled, user isn't notified that contribs unavailable +_ currently no indication that contrib download failed +_ so the 'try again' won't show up; everything looks fine +_ (was this for the contrib listing? or an actual contrib?) +_ ContributionTab.updateContributionListing() +_ this one shouldn't be in a single tab +_ and it should include all contribs (method from Base to get in one swoop?) +_ this is also likely the cause of per-Mode weirdness +_ Base.setUpdatesAvailable() is called from half a dozen places!? +_ how to handle contrib manager download/retry/etc and showing progress +_ ListPanel.configureStatusColumnLabel() getting null DetailPanel objects +_ seems to be out of sync/unavailable data based on more recent contrib changes? +_ ManagerFrame.downloadAndUpdateContributionListing() +_ should not even be run, because the contribs load on startup +_ but ContributionTab.tryAgainButton needs to be able to request re-download +_ ManagerFrame.makeAndShowTab() +_ this one looks like it's gonna get called multiple times +_ holy s*t ContributionTableModel is using all of the contribs +_ after failed update of Mode, the button is no longer available +_ have to click something else, then click back on the Mode line again +_ when starting in p5jsMode, contrib manager shows no green checks for libs +_ tricky because those aren't gonna show up in 'Add Library' +_ which is another case for a separate 'contrib manager' thing +_ maybe that's the update button? when no updates, it's still a button? +_ after opening Editor window, Processing freezes until contribs are loaded +_ when opening manager, animation runs briefly then freezes +_ DetailPanel setContribution() being called 4x for each contrib on startup +_ during install of contrib, progress is halting during the install +_ probably too much happening on the EDT that should not be +_ optimize ContributionTab addListener() call in constructor +X no longer calling addListener() on every single entry, whcih helps +_ but what else might that change be breaking? +_ StatusPanel seems to be recreated entirely +_ StatusPanel being reset twice on each click +_ is checking for previous, but apparently that's not working +_ AvailableContribution.install() needs status handler +_ can either be the StatusPanel *or* EditorStatus *or* console (during startup) +_ needs to be EditorStatus when double-clicking a .pdex file +_ this is also why Base.handleOpen() does statusError() calls during pdex load +_ add indicator for the stats of libraries +_ https://download.processing.org/stats +_ add rank for libraries/modes/tools (use unicode chars?) +_ probably not sort by default to avoid confusion +_ lots of rewriting to use SwingWorker +_ https://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html +_ obvious places where this needs to happen, but the code is working for now +_ needs a few hours work to untangle all of it, but worth the regressions? +_ get rid of IgnorableException +_ contrib/mode/lib/example List objects in Base +_ which of these should be Set objects? +_ JProgressBar in StatusPanel is shared by all installing contribs +_ which could make for weird behavior if installs before something has finished +_ work to prevent multiple from colliding +_ (i.e. get rid of ContribProgress class) +X disable hashCode() implementation in Contribution +X was getting in the way of comparisons +_ but need to compare the entries differently so there aren't dupes +_ i.e. it was doing a name comparison, so need to swap by name +_ after library/tool changes, rebuild Help > Library/Tool Reference submenus +_ could not write to temporary directory (virus checker problems) +_ https://github.com/processing/processing/issues/4757 -design -_ Update graphics and visual design elements for 4.x -_ https://github.com/processing/processing4/issues/48 -_ welcome screen is a mess -_ need icons for .pde, .pdex, .pdez -_ core/src/icon-NN.png should be the exported application icon -_ currently it's the p5 icon since the export just looks black -_ also lib/icons has the pde icon set and the foundation icon (for contribs) -_ use svg images for res-indep icons/gui? -_ https://stackoverflow.com/a/2495712 -_ (built and tested a version of this code if we want to use it) +PDE / Manager (3.x notes) + +_ vertical centering of the search box in the manager +_ “could not move the contribution to the backup folder” message while updating +_ problem is that any sketch that uses a library, the lib is stuck as "in use" +_ https://github.com/processing/processing/issues/4973 +_ issues with updating modes +_ https://github.com/processing/processing/issues/5424 +_ examples window not updating on install +_ open examples window +_ mode > add mode > libraries > install video +_ did not update the examples window, had to restart pde +_ was able to save over the video capture examples b/c they were a library +_ lib examples not properly marked as read-only +_ "Could not find a examples in the downloaded file" is a poorly worded message +_ 'version' should be x.y or x.y.z, not some extra long string +_ enforce this by disallowing spaces? on the import script? +_ Progress bar height on macOS is too thin +_ https://github.com/processing/processing/issues/4734 +_ proper error handling when downloading contribs listing +_ https://github.com/processing/processing/issues/4732 +_ update CM entries when sketchbook location changes +_ https://github.com/processing/processing/issues/3927 +_ ugly white gap at the top of scroll bar +_ the table header doesn't extend far enough +_ scrolling "past" top/bottom causes the screen to jiggle (OS X and Trackpad) +_ looks like ContributionListing.getScrollableUnitIncrement() returns early +_ Examples window closes and re-opens during library install/remove +_ https://github.com/processing/processing/issues/3304 +_ several TODO items listed in ContributionPanel +_ something to set min/max versions that are supported by a library +_ ability to cancel a download/install +_ we shouldn't use .properties extension for modes, et al +_ because a .properties file is iso8859-1 +_ make note that .properties file *must* be utf-8 +_ if not it'll make things gross (andre sier flob library) +_ why wasn't Library moved to LibraryContribution? +_ or that LibraryContribution needs to be a wrapper around it? +_ send info on 'check for updates' so we know about libs/modes/etc? +_ how to disclose to users? +_ only send for items that are part of the public list +_ otherwise we're sending private libraries/installs +_ although this won't pick up old libraries not on the new system +_ classpath conflicts.. +_ getPackageList.. from Library... maybe others? +_ really need to make sure that a weird core.jar isn't being imported +_ coffeescript was doing this and breaking the pde +_ contrib library examples are not read-only +_ another point for doing .zip files to prevent overwriting +_ add BookContribution? +_ will offer to open the contrib manager even though it's already open +_ though this was looking at libraries, and there may have been modes +_ is there an "all" view for the mgr that shows libs, modes, etc? +_ update button only showing up when item is selected feels awkward +_ "update all" would be useful +_ "Update 4 items" as a button name +_ new libraries not picked up when changing sketchbook location +_ make sure contrib manager can run w/o a network connection +_ or if a bad document comes through, it can recover +_ alternating blue/white backgrounds aren't updated after changing filter +_ just need to call a repaint() after a filter change? +_ check with Casey about coloring for error messages +_ test on Windows and Linux +_ font size for "Downloading" on progress bar is too large +_ but changing the size breaks the vertical centering +_ wheel mouse is super jumpy +_ something about unit increment in ContributionListPanel +_ arrow keys up/down move scroll bar, not selection +_ fonts/etc need to be set in one place where they can be edited +_ move styling to separate constants that are more accessible -before 4.x final -_ turn off javafx web if not using -_ SVG library, show a warning when using style instead of presentation attributes -_ Welcome screen or not? -_ set a new preference for it, so people see it -_ just skip the welcome screen on Windows hidpi dipslays? -_ better to launch a web browser -_ also use id/ask for login to help understand community? -_ change help menu links to go to newer FAQ and the rest -_ release files: macosx -> macos, linux64 -> linux, windows64 -> windows -_ 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? -_ Export Application can use nicer names, the libs thing is trickier -_ better means of understanding Library/Mode/Tool usage -_ 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 +PDE / Preferences + +_ need to do the Mode prefs separately +_ since we can't have per-Mode constants (the pref names) in PreferencesFrame +_ Editor.applyFrame() may not have a valid 'editor' object to work with +_ if windows closed, and prefs altered, NPE thrown +_ make sure editor isn't trying to apply prefs when no editor is open +_ (on mac os x, due to the change for no windows open) +_ clear up prefs so that multiple editors don't trash each other's prefs +_ when are prefs saved? could instead save whenever changes are made +_ and then if the file gets modified, it'll put up an error message +_ also, this may be part of why other sketches aren't reloading properly +_ simple prefs implementation to set key/value pairs using a JTable +_ https://github.com/processing/processing/issues/5425 +_ prefs window doesn't swap ok/cancel properly for mac vs. windows/linux +_ don't bother having a "cancel" for the prefs window +_ make prefs dialog modal? + + +PDE / Runner + +_ if RuntimeException thrown, needs to check if it's a wrapped exception +_ for instance, if there's a crash inside makeGraphics() +_ this inside the handling that comes from the JVM, which makes it tricky +_ draw mode apps do not shut off the run button when finished +_ need to talk to VM and read when 'finished' var is set +_ does closing the window call stop()? +_ need to make sure hitting stop button and closing window explicitly call +_ set finished to true, then join() the animation thread +_ dispose handlers not called when stop button pressed +_ https://github.com/processing/processing/issues/4445 +_ need to set dock icon title on osx +_ changing number of screens between run causes things to show up off-screen +_ so when running, check to make sure that things are out of the area + + +PDE / Debug + +_ mouse events (i.e. toggle breakpoint) seem to be firing twice +_ wonder if this is mousePressed and mouseClicked? +_ this may also be the cause of the double entries in files +_ fix background color for selected lines in VariableInspector +_ https://github.com/processing/processing/issues/3925 +_ deactivate step, continue, stop when not running? +_ disable the debug toggle menu while running +_ otherwise could run in one mode, then try to stop in another + + +PDE / Multiple Monitors + +_ changing modes brings the PDE back on the second screen +_ the Find window (also the save windows) also have the same problem + + +PDE / Tweak + +_ TweakMode listener mess in JavaTextArea +_ https://github.com/processing/processing/issues/4605 + + +PDE / Sketch & Sketchbook + +_ Saving sketch with the same name as a class +_ https://github.com/processing/processing/pull/4033 +_ fix up the code for sketchbook building +_ is it time to make the internal representation into a tree object +_ and then build the GUI versions separately (on the EDT) +_ Base.checkSketchFolder() is used to identify sketches for the sketchbook frame +_ but it assumes that it's folder name + default extension as the name for it +_ that should be moved into the Mode class as a "is this folder a sketch" call +_ (so that Modes can work differently too) +_ Mode.rebuildLibraryList() called too many times on startup? +_ and when sketches saved as well? +_ makes saving *really* slow with a lot of libraries +_ New/Rename/Save As is reloading the whole sketchbook, argh +_ improve start time by populating sketchbook/libraries on threads +_ https://github.com/processing/processing/issues/2945 +_ Large number of files in sketchbook folder can cause slow startup +_ and/or errors with launch4j +_ https://github.com/processing/processing/issues/1190 +_ error that sketch is read-only can't be canceled +_ hitting cancel (or ESC?) still brings up the save dialog +_ ArticulatePrint-070103a.pde from ArticulatePrint-070103a loads ok +_ but when opening the sketch, it leaves out the file +_ because there's a dash in the name +_ and instead only loads StemCell.pde +_ show progress dialog during export and save +_ hitting ESC on "create this, move file, continue" opened anyway + + +PDE / Commander + _ command line complaints _ https://github.com/processing/processing/issues/6129 -_ editor breakpoints out of the .pde files -_ https://github.com/processing/processing/issues/5848 -_ or at least avoid the multiple -_ remove jai_imageio.jar from MovieMaker -_ also clean out the other unused class files and src - -decisions before final 4.0 release -_ can we compress jdk/Contents/Home/legal into a single zip? -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 -_ 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 -_ 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 - -after initial 4.0 release -_ startup is so incredibly slow -X the splash screen comes up fairly quickly, so what gives? -_ lots of EDT thread stuff, particularly with loading contribs - - -discuss with Sam -_ JSSC binaries for windows_32 and windows_64 appear to be macOS files -_ https://github.com/processing/processing4/issues/119 - - -would like to fix +_ emacs style errors in commander aren't quite right +_ https://github.com/processing/processing/issues/2158 _ better command line support/basic language server support? _ make it easier to use with other editors -_ detach sketch name and folder name (use sketch.properties) -_ better for git, etc -_ single file thing is long gone -_ introduce the idea of 'scraps' (ala gist) that are just single page blobs -windows -_ go back to including a .bat file? + +//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// + + +TOOLS / Movie Maker + +_ downloader for ffmpeg instead of including in download +_ would save ~20-25 MB in the download +_ should prompt the user before starting the download (not nice to use bandwidth) +_ need to disable creation of movies until the download has finished +_ handle failed downloads as well +_ make sure there are no signing problems on macOS after downloading + + +TOOLS / Ideas + +_ eclipse import/export +_ simple mechanism to export to eclipse +_ don't worry about subversion stuff, just export with libraries +_ something clever to import back from eclipse +_ could keep the .svn files in with the libs and all +_ then when exporting for eclipse, people can update as necessary +_ -> dan et al say better to do fixed versions and have it work simply +_ import sketch from url (takes a zip from archive sketch) +_ archive sketch direct to bug report +_ shared code +_ Integrator / FloatThing / CameraStuff -- Update +_ to pull code from a local folder +_ update will update classes from shared in the current folder + + +TOOLS / Auto Format + +_ Reas: comments go nasty when auto-formatted +_ Switch block cases not indented +_ https://github.com/processing/processing/issues/1042 +_ do a better job of maintaining cursor +_ only auto-format a particular section of code +_ set the 'tabs' var based on how many spaces on previous line +_ http://processing.org/discourse/yabb/YaBB.cgi?board=Proce55ing_Software;action=display;num=1087227217 + + +LIBRARIES / General + +_ need to unpack InvocationTargetException in xxxxxxEvent calls +_ http://processing.org/discourse/yabb_beta/YaBB.cgi?board=VideoCamera;action=display;num=1116850328#3 +_ go through libraries and clean things up +protected void finalize() throws Throwable { + try { + close(); + } catch (Exception e) { + // do something + } finally { + super.finalize(); + // more code can be written here as per need of application + } +} + + +LIBRARIES / Net + +_ modernize Client/Server code to use synchronized lists +_ do we let people use the public vars in Server and Client? +_ are they documented? + + +MODES / Python 2 + +X add JavaFX handler +X opening an example throws an NPE +_ update Export to Application to use the new window +_ add changes for the eawt mess? (too tricky) +_ https://github.com/jdf/processing.py/issues/398 +_ submit a PR + + + +//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// + + +DIST + +How the environment gets packed up, downloaded, and installed. + + +DIST / General + +_ when lib downloads (batik) go dead, fallback to download.processing.org +_ or for now, tell users how to do it manually +_ batik and ant have both broken the build when old versions were removed +_ move processing-java inside the Java Mode? +_ make a Tool that installs it for all platforms, not just OS X +_ not really part of the 'build' anymore +_ line ending issues +_ doesn't really help on Windows since we use Cygwin +_ but it would be helpful for people not using it (ant/other LF issues) +_ https://help.github.com/articles/dealing-with-line-endings +_ http://stackoverflow.com/questions/170961/whats-the-best-crlf-handling-strategy-with-git +_ add means to move changes from desktop to Android (and vice-versa) +_ add bootclasspath for 1.6 +_ would require --install for command line on OS X +_ and unpacking the JVM earlier in the build process +_ not sure if it's worth the addl complexity +_ add additional caveats about incompletion to javadoc +_ drag and drop +_ including the number of items added to sketch would be especially nice +_ implement automatic update +_ https://github.com/processing/processing/issues/100 +_ need .pde document icons +_ need .psk file icon +_ need exported application icons +_ need more comprehensive list of 'known bugs' +_ need more comprehensive list of 'known suggestions' +_ write notes about running p5 on another platforms +_ this was some feedback from running on bsd: +_ /usr/local/jdk1.3.1/bin/java -cp lib:lib/build:lib/pde.jar:lib/kjc.jar:lib/oro.jar:java/lib/ext/comm.jar PdeBase +_ need to use the 1.3 vm, and get a fake platform name +_ otherwise, goes looking for lib/pde_.properties or something +_ about box +_ bring up information about gpl, lgpl, and ibmpl +_ jedit syntax is under mit license +_ http://www.opensource.org/licenses/mit-license.php +_ add proper copyright and license information for all included projects +_ https://github.com/processing/processing/issues/224 +_ write up guidelines for modes +_ i.e. don't mess with Sketch menu, put it in the mode menu +_ p5 assets need to be licensed differently from the source + + +DIST / Windows + +_ implement document icons for pdez etc on Windows +_ Resolve “Successfully created” and “illegal reflective access” errors from launch4j +_ https://github.com/processing/processing4/issues/137 +_ windows batch file would be a good idea (debugging is hard) +_ how to leave the window open when running a script +_ if files removed by Defender, the launcher may not know +_ would be good to auto-generate this from the config (avoid double updates) _ .\java\bin\java.exe -cp lib/pde.jar;core/library/core.jar;lib/jna.jar;lib/jna-platform.jar;lib/antlr-4.7.2-complete.jar;lib/ant.jar;lib/ant-launcher.jar -Djna.nosys=true -Dsun.java2d.uiScale.enabled=false -Djna.boot.library.path=lib -Djna.nounpack=true -Dsun.java2d.d3d=false -Dsun.java2d.ddoffscreen=false -Dsun.java2d.noddraw=true processing.app.Base +_ "Required files could not be found" when trying to run from the .zip file +_ https://github.com/processing/processing/issues/5022 +_ use an installer instead? +_ does launching p5 from inside the .zip folder cause it to quit immediately? +_ how can we provide an error message here? +_ extra warning for Eclipse Adoptium "certain features turned off" +_ need a better way to handle this/warn users/avoid it altogether _ launch4j doesn't work from folders with non-native charsets _ anything in CP1252 on an English Windows system is fine _ but anything else reports "font sadness" b/c it's using the system JRE @@ -175,166 +721,24 @@ _ https://github.com/processing/processing/issues/4736 _ related: https://github.com/processing/processing/issues/3543 _ windows anti-malware leaves browser stuck at 100% _ https://github.com/processing/processing/issues/5893 -_ temp folders (particularly on Windows) -_ a whole section down below about this _ sign releases on Windows to avoid SmartScreen warnings/errors _ https://github.com/processing/processing4/issues/25 +_ https://www.microsoft.com/security/ +blog/2018/08/16/partnering-with-the-industry-to-minimize-false-positives/ +_ https://shop.certum.eu/open-source-code-signing-code.html +_ how to handle double-clicked files on windows? +_ big deal for psk and others +_ this may already work with SingleInstance stuff -_ i18n support for Modes -_ https://github.com/processing/processing/commit/0ed2fc139c3c5dfe0a1702ed8348987b3c6a5c9d -_ update installation guide for Linux -_ https://github.com/processing/processing-docs/issues/645 +DIST / macOS - -probably past 4.x -_ language server refactoring -_ https://github.com/processing/processing4/issues/117 -_ 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 -_ then tell the user which folder to fix - - -contrib -_ Saving sketch with the same name as a class -_ https://github.com/processing/processing/pull/4033 -_ Pasting text into PDE results in "Clipboard does not contain a string" -_ https://github.com/processing/processing/issues/3651 -_ https://github.com/processing/processing/pull/4040 - - -high -_ run button not deactivating -_ https://github.com/processing/processing/issues/5786 -_ Find in Reference disabled for various keywords (draw, for, if, catch, while) -_ https://github.com/processing/processing/issues/5562 -_ https://github.com/processing/processing/pull/5642 -_ errors inside setup() aren't coming through at all? -_ seen in Eclipse; have to turn on the debugger... same as #4703? - - -temp -_ inside Sketch, makeTempFolder() would be the place to modify the location -_ perhaps make a 'temp' inside the sketchbook folder? -_ on startup, check to see if there are a lot of files, remove them? -_ or maybe auto-delete once older than 24 hours? -_ also don't search it when walking the sketchbook -_ untitled folders are stored in temp folder -_ add a note about temp dir to the bug on windows temp dirs -_ move away from using a temp dir at all for sketches -_ -Djava.io.tmpdir=Z:\temp -_ clean up /tmp folders used during build -_ https://github.com/processing/processing/issues/1896 -_ clean Windows temp folders -_ https://github.com/processing/processing/issues/1896 -_ could not write to temporary directory (virus checker problems) -_ https://github.com/processing/processing/issues/4757 - - -modes -_ sketch.properties not being written if initial mode is p5.js? -_ when creating a sketch within non-Java mode, should write the settings file -_ so that it re-loads in the proper environment -_ remove sketch.properties when moving back to the default? -_ or can we not do this, because it's used to set the 'next' mode -_ allow modes to specify their own base file name -_ need to move "is this a sketch?" handling into Mode -_ fix extension check for other modes -_ https://github.com/processing/processing/issues/3980 -_ mode list does not update after changing sketchbook folder -_ already reported? - - -teaching -_ teacher wants user input on the console -_ https://github.com/processing/processing/issues/5779 -_ did we lose settings.path because it was too buggy? -_ https://github.com/processing/processing/issues/3948 -_ proxy trouble with p5? since adding the system proxy? -_ https://github.com/processing/processing/pull/3251/files -_ larger problem thread https://github.com/processing/processing/issues/3891 -_ ideen2011.blogspot.de/2011/08/java-proxyselector-usesystemproxies-and.html -_ malformed proxy issues http://stackoverflow.com/q/376101 -_ docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html -_ https://github.com/processing/processing/issues/1476#issuecomment-23229990 - - -_ "Required files could not be found" when trying to run from the .zip file -_ https://github.com/processing/processing/issues/5022 -_ use an installer instead? - -_ "error during export" message, but no error message contents come through -_ e.g. https://github.com/processing/processing/issues/4792 - -_ library compilations not ordered properly w/ sorting -_ do we still support library compilations? that was from 2016 -_ https://github.com/processing/processing/issues/4630 - - -needs review -_ createPreprocessor() added to JavaEditor, creating a mess -_ https://github.com/processing/processing/commit/2ecdc36ac7c680eb36e271d17ad80b657b3ae6a0 -_ patch to core classpath by Manindra? - - -gui -_ mouse events (i.e. toggle breakpoint) seem to be firing twice -_ spacing of the updates number/circle in the lower right is off -_ https://github.com/processing/processing/issues/4094 -_ https://github.com/processing/processing/pull/4097 -_ solution is to create a sprite sheet as a psd that'll have better type -_ no way we're gonna fix the sizing and spacing for all platforms -_ more than a certain amount should just be 10+, +, or whatever -_ text gutter doesn't seem to be hidpi -X or is it b/c screen not quite 2x? (nope) -_ Tooltip over variable decl has wrong style and content -_ make all tooltips run through our style -_ https://github.com/processing/processing/issues/3940 -_ import suggestions box needs design review -_ https://github.com/processing/processing/issues/3407 -_ fix background color for selected lines in VariableInspector -_ https://github.com/processing/processing/issues/3925 -_ implement 2x versions of the icons for the debugger window/variable inspector -_ https://github.com/processing/processing/issues/3921 -_ different design of squiggly line -_ easy to do inside JavaTextAreaPainter.paintSquiggle() -_ build custom scroll bar since the OS versions are so ugly -_ see notes in the 'dialogs' section below, implement our own option panes? -_ tiny trail of dots when moving the selection bar up/down on retina -_ need 'actively pressed' version of 'play' and 'stop' -_ could do rollover as well, but do other apps use them? -_ iTunes has no rollover state but has a 'down' state -_ swaps to stop after release -_ deactivate step, continue, stop when not running? -_ disable the debug toggle menu while running -_ otherwise could run in one mode, then try to stop in another -_ recent menu gets huge with other p5 versions on osx -_ show compiling/startup animation after clicking run -_ or maybe show an animation for starting up -_ rather than post-compile -_ https://github.com/processing/processing/issues/136 - - -fonts -_ fonts are still really ugly (on non-retina) -_ may need to drop use of Source Sans -_ what do these do, and are we doing it already? -_ System.setProperty("awt.useSystemAAFontSettings","on"); -_ System.setProperty("swing.aatext", "true"); -_ how are we going to handle fonts for other languages? -_ two new fonts have been added, other languages will need more -_ need a decent sans with with Unicode coverage -_ i.e. https://github.com/processing/processing/pull/3025 -_ Implement fallback fonts so we can use Source et al with CJK/Greek/Arabic -_ https://github.com/processing/processing/issues/5023 - - -pde/build +appbundler +_ appbundler broken when built on something later than Mojave +_ https://github.com/processing/processing4/issues/284 +_ https://github.com/google/filament/pull/1959/files +_ also need separate appbundler versions because Monterey build won't run on x86_64 +_ https://github.com/processing/processing4/issues/364 _ fix appbundler problems due to rollback _ https://github.com/processing/processing/issues/3790 _ the rollback re-introduces two bugs (serial export and scrolling) @@ -343,107 +747,86 @@ _ https://github.com/processing/processing/commits/master/build/macosx/appbund _ https://github.com/processing/processing/commits/master/build/macosx/appbundler/native/main.m _ https://github.com/processing/processing/commit/fa27b983e76fdbc5c4c1451a1f0d854c652b1639 _ https://bitbucket.org/infinitekind/appbundler -_ unsupported java version when trying ant run with 7u65 -_ no helpful message about how to automatically download 8u51 -_ ignore-tools in build.xml not being called for some reason -_ when variables used in size(), getting exceptions instead of any warning -_ https://github.com/processing/processing/issues/3311 -_ crashed on startup w/ JavaScript mode as default b/c PdeKeyListener not found -_ because it's in the other ClassLoader, can no longer rely on it -_ remove JavaMode.errorLogsEnabled and JavaEditor.writeErrorsToFile() -_ continue clearing out ProgressFrame -_ also hook up the statusNotice() when done -_ 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 -_ save() and saveAs() need to be refactored -_ https://github.com/processing/processing/issues/3843 -_ clean out the repo -_ https://github.com/processing/processing/issues/1898 -_ search the source for 'applet' references (i.e. SVG docs) -_ PreferencesFrame is a misnomer (not a frame itself) -_ change to PreferencesDialog, and make it a dialog? -_ move Library to LibraryContribution and into contrib? +_ update appbundler? https://github.com/TheInfiniteKind/appbundler +_ might be broken though https://github.com/TheInfiniteKind/appbundler/issues/70 +_ symlink https://github.com/TheInfiniteKind/appbundler/issues/1 +_ startup chatter (from appbundler?) +_ Processing[25059:13082813] int launch(char *, int, char **) Launchpath +_ Find a long-term solution for OS X bundler to address signing/symlink issues +_ https://github.com/processing/processing/issues/2967 +_ don't re-copy JRE into work folder if already exists + +other +_ Opening a sketch from the Desktop folder can result in several macOS access prompts +_ https://github.com/processing/processing4/issues/523 +_ if a sketch is in the Desktop folder, try triggering macos access dialog once +_ do it once for the root, then see if it allows the folders inside as well +_ (opening p5jsMode sketch on Desktop causes a zillion dialog boxes) +_ https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/DesigningYourSandbox/DesigningYourSandbox.html +_ https://stackoverflow.com/questions/12153504/accessing-the-desktop-in-a-sandboxed-app +_ reliable getLibraryFolder() and getDocumentsFolder() methods in MacPlatform +_ https://github.com/processing/processing4/issues/9 +_ disable "notifications" prompt on startup for macOS +_ https://github.com/processing/processing4/issues/234 +_ 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 +_ Help Menu disabled on OS X (looks like a JVM bug) +_ https://github.com/processing/processing/issues/4353#issuecomment-237715947 +_ still broken in 11.0.8 +_ Java bug prevents us from setting the dock name of a sketch run from the PDE +_ https://github.com/processing/processing/issues/5045 +_ client properties +_ https://developer.apple.com/library/mac/technotes/tn2007/tn2196.html +_ built-in images: http://nadeausoftware.com/articles/2008/12/mac_java_tip_how_access_mac_specific_nsimage_icons +_ Update QuickLook plugin for Processing 3 +_ https://github.com/processing/processing/issues/3261 +_ more OS X-specific hackery for improved appearance +_ https://developer.apple.com/library/mac/technotes/tn2007/tn2196.html +_ possible better option for doing retina? +_ g.getFontRenderContext().getTransform().equals(AffineTransform.getScaleInstance(2.0, 2.0)) +_ LWJGL forum discussion +_ http://lwjgl.org/forum/index.php/topic,4711.225.html +_ change cmd line for OS X to use symlink? +_ otherwise updates are going to require reinstall.. +_ or that it's gonna need to parse and say "update command line?" +_ we're breaking some mac human interface guidelines +_ should be using a menu factory to create menubar for all sub-windows +_ http://developer.apple.com/technotes/tn/tn2042.html +_ and the general warning dialogs are just ass ugly +_ (i.e. we really need those replacements for JOptionPane) +_ Exiting a sketch with Command-Q or File > Quit doesn't call stop() on OS X +_ https://github.com/processing/processing/issues/186 -from the todo list -_ reas: comments go nasty when auto-formatted -_ reas: code coloring sometimes disappears -_ me: undo not in the correct location -_ drop XP support (but improve Windows 8 support? ouch) -_ improve error message when creating a tab with the same name -_ right now it's generic, based on "a file exists" -_ don't allow users to create 'blah.java' when 'blah.pde' already in sketch +DIST / Linux + +_ implement more document icons on Linux +_ Need better method than reflection for setting application name +_ https://github.com/processing/processing4/issues/349 +_ update install.sh for .pdex and .pdez file associations +_ https://github.com/processing/processing4/issues/239 +_ update installation guide for Linux +_ https://github.com/processing/processing-docs/issues/645 -sketchbook -_ Mode.rebuildLibraryList() called too many times on startup? -_ and when sketches saved as well? -_ makes saving *really* slow with a lot of libraries -_ New/Rename/Save As is reloading the whole sketchbook, argh -_ improve start time by populating sketchbook/libraries on threads -_ https://github.com/processing/processing/issues/2945 - -help me -_ Add support for localizing contributions -_ https://github.com/processing/processing/pull/2833 -_ https://github.com/processing/processing/issues/3154 -_ https://github.com/processing/processing/pull/3337 -_ export application ubuntu -> windows not working (2.2.1) -_ https://github.com/processing/processing/issues/2698 -_ might be something with libraries (native or otherwise) - - -medium -_ detect changes in case with libraries -_ https://github.com/processing/processing/issues/4507 -_ Library path mismatch between processing-java and export -_ https://github.com/processing/processing/issues/4493 -_ remove toolbar menu references and code to rebuild -_ fix single instance server on OS X to load double-clicked files -_ when run from Eclipse, the single instance thing punts -_ 'recent' menu needs to recognize the p5 app folder -_ also should recognize the user's home dir -_ possibility of libraries folder inside a particular sketch? -_ add font fixes to the rest of the API -_ https://github.com/processing/processing/commit/eaff673d173b2d27f276cf5c59e3abf6c0fab86b -_ g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, -_ RenderingHints.VALUE_FRACTIONALMETRICS_ON); -_ should default to the local Java on Windows and Linux -_ have export apps default to the local JRE -_ Linux is probably using the system JRE if available -_ launch4j may be all set, but double-check -_ um, no--we should use the embedded version, b/c who knows what happens -_ use platformDelete() to remove untitled sketches? -_ would allow us to use the /tmp folder -_ change to using platformDelete() instead of Base.removeDir() where possible -_ verify that the OS X version uses the real call -_ and doesn't just look for .Trash -_ getCoreLibrary() is breaking OpenGL -_ "new Library()" constructor needs to go back to private -_ add .bat file to lib on windows so that we can get better debugging info -_ changing modes brings the PDE back on the second screen -_ the Find window (also the save windows) also have the same problem -_ move old Google Code SVN back to processing.org -_ then cull out the old branches/tags from the Github repo -_ and/or start bundling separate source downloads -_ look through all isPopupTrigger() code -_ make sure both press/release are implemented -_ emacs style errors in commander aren't quite right -_ https://github.com/processing/processing/issues/2158 -_ add documentation for how to run mode development from Eclipse -_ implementation/changes from JDF -_ modes are being loaded multiple times, which can cause trouble -_ add minimum version required (or max version?) to libraries/modes/etc - - -. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// DOC / Misc +_ thread safety with color() et al +_ https://github.com/processing/processing4/issues/431#issuecomment-1057979636 +_ thread safety for drawing functions in general +_ words on how key/mouse events are queued to the end of draw so they're safe +_ make reference build process part of dist +_ https://github.com/processing/processing-docs/issues/85 +_ separate ant target, but only require them for dist +_ as separate targets, folks can build explicitly if they'd like +_ processing-docs/java_generate/ReferenceGenerator/processingrefBuild.sh _ find in reference for copy() (on image) tries to open PVector.copy() _ might need disambiguation pages? _ if a reference page is missing, throws a bunch of exceptions @@ -464,26 +847,18 @@ _ also note that ellipse() et al use beginShape() _ and that java2d should complain if people try it _ method to go from function name to the included examples where used? _ encourage use of set() instead of point() in the drawing api -_ http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1114204116 -_ other projects on which p5 is built -_ no longer oro matcher and jikes -_ add: quaqua, jna, registry stuff, .. ? +_ https://forum.processing.org/beta/num_1114204116.html _ noLoop() isn't the same as "finished", though it's sometimes used that way _ to finish, use exit() (though that will make the window close) _ add this to ref once halt() is finished _ errors in pdf/index.html examples _ probably remove these, move the useful ones to the examples folder _ that way people will read the reference -_ i can't fix these: -An unexpected error has been detected by HotSpot Virtual Machine: -# EXCEPTION_ACCESS_VIOLATION -all i can do is find things that might trigger them, but it's more likely -than not to be a java problem, and not something that can be fixed _ explanation of how we see libraries in the PDE _ We're making an assumption that the idea of a "library" refers to a block of Java-based code. The reasoning is that from within the PDE, we anticipate that it will be Java-compatible versions of other languages (such as Jython, JRuby, or Scala) as opposed to more generic languages. In the future we could open this up, but we'd rather not complicate things for the moment. _ add to troubleshooting -_ problem with big floats: http://processing.org/discourse/yabb/YaBB.cgi?board=Syntax;action=display;num=1061302380 -_ problem with small floats: http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Programs;action=display;num=1115500448 +_ problem with big floats: https://forum.processing.org/alpha/board_Syntax_action_display_num_1061302380.html +_ problem with small floats: https://forum.processing.org/beta/num_1115500448.html _ console stored in prefs location _ console may be useful for debugging @@ -557,22 +932,18 @@ _ write a threading example _ show an example of how to handle callback into the app X or handle this some more intelligent way, super.stop() is needed. X registerDispose() does the trick -_ http://processing.org/discourse/yabb/YaBB.cgi?board=Syntax;action=display;num=1083574943 -_ http://processing.org/discourse/yabb/YaBB.cgi?board=Syntax;action=display;num=1067383998 +_ https://forum.processing.org/alpha/board_Syntax_action_display_num_1083574943.html +_ https://forum.processing.org/alpha/board_Syntax_action_display_num_1067383998.html _ scripts: how to make a long setup() sleep so that things don't lock way up _ write an example that uses HashMap (or Hashtable) _ write an example that uses ArrayList properly _ get xml library example in there _ simple method for having a clickable region or sprite with rollover -_ post to web example +_ POST to web url example _ particularly for uploading image data _ along with php script to handle receive _ this is in hacks, but -_ http://processing.org/discourse/yabb_beta/YaBB.cgi?board=VideoCamera;action=display;num=1117194066#7 - - -DOC / Hacks - +_ (broken link) http://processing.org/discourse/yabb_beta/YaBB.cgi?board=VideoCamera;action=display;num=1117194066#7 _ write a timer class/example library _ how do i add gui to a sketch? _ don't use awt components @@ -580,9 +951,7 @@ _ how to use swing and embed components inside p5 _ explain how to integrate code with swing _ use a separate environment, call init(), use noLoop(), redraw() _ use JPopupMenu.setDefaultLightWeightPopupEnabled(false); for zorder -_ http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Integrate;action=display;num=1147670059 -_ write documentation on general use of processing.core -_ note that applet is itself a Component +_ https://forum.processing.org/beta/num_1147670059.html _ eclipse _ document how to properly integrate with eclipse _ add runtime args to opengl library so that it sets path properly @@ -597,518 +966,15 @@ _ how to do this with subclipse instructions? //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// -PDE - Processing Development Environment - - -PDE / Dialogs - -_ two-tiered dialogs for everything - use big font/little font style throughout -_ http://javagraphics.blogspot.com/2008/06/joptionpane-making-alternative.html -_ option to suppress warning dialogs -_ starting with the one about modifying the sketch name for spaces -_ also the "some features will be disabled" with .java tabs -_ https://github.com/processing/processing/issues/46 -_ add "don't warn me about this" for sketch renaming -_ make sure renamed version doesn't exist already -_ prompt user before nuking applet or application folders -_ along with a "don't ask me later" -_ use macosx dialogs for all of the editor stuff -_ see about doing the same on windows, linux? -_ the others seem to respond ok to the lucida grande since they use defaults -_ vista style dialogs -_ http://msdn.microsoft.com/en-us/library/bb328626.aspx -_ confirmation dialogs (save and don't save.. who'd a thunk it) -_ http://msdn.microsoft.com/en-us/library/aa511273.aspx -_ http://i.msdn.microsoft.com/Aa511273.Confirmations03(en-us,MSDN.10).png - - -PDE / Compiler & Preprocessor - -medium (bugs/features) -_ preprocessor throws error when calling color() on a PGraphics -_ https://github.com/processing/processing/issues/3762 -_ omitting a semicolon shows the error on the line after it -_ npe if library is removed before compile -_ always check library folders to make sure they're still valid -_ with additional .pde files, don't allow static mode in front tab -_ e.g. no setup()/draw() block -_ don't allow "for (blah; blah; blah) ;" -_ or if (blah blah blah) ; -_ it's never useful. students can use { } if they want an empty block - -low (features) -_ copy running code from /tmp/buildXXxxx on crash of p5 -_ should probably make a way to save/recover code -_ make the buildXxxx folders relate to time/date? -_ link out to further documentation (e.g. AIOOBE, NPE) - -low (common errors around reserved names/class naming) -_ Saving sketch with the same name as a class or primitive breaks sketch -_ https://github.com/processing/processing/issues/196 -_ don't allow people to override methods like paint() -_ make them final? just improve the error messages? -_ https://github.com/processing/processing/issues/1058 -_ Processing chokes if a sketch defines a class with same name as the sketch -_ https://github.com/processing/processing/issues/196 -_ don't allow goofy case versions of reserved words -_ keypressed should maybe throw an error -_ https://github.com/processing/processing/issues/44 -_ "unexpected token" on anonymous instance of parameterized Comparator -_ https://github.com/processing/processing/issues/533 - - -PDE / Editor - -_ 'recent' menu doesn't respect examples folder of other p5 versions -_ could write that into the file, that it's an example -_ or write the path as shown in the PDE to the file as simpler -_ 'recent' menu paths can get enormous -_ active editor not being set null -_ in Base.nextEditorLocation(), changed to "editors.size() == 0" -_ instead of (activeEditor == null), but that's papering over a problem -_ where the active editor is not being set null -_ renaming RGB (.pde) to Rgb.java says "a file named RGB.pde already exists" -_ improve update check message "a new release (1.0.1) is available" -_ be more descriptive, use a second line in latest.txt -_ maybe just include the full text of the update message there? -_ go through other sketch-opening menus to check for disappearing sketches -_ deal with isManagingFocus() warning in the editor src -_ strange NullPointerException problem prevents launch -_ some kind of NPE in handleOpenInternal and friends -_ appears to be a synchronization problem with the loading -_ blank sketch opened even if another opened by double-click -_ add a 150 ms or more lag before opening the untitled window -_ https://github.com/processing/processing/issues/218 -_ editors opening up at the same time on load? -_ either synchronize the open (at a minimum) -_ or wait for mac handlers to register an open event -_ can also cause problems with opening multiple copies of same sketch -_ after fixing name of sketch, ensure sketch of that name does not exist -_ add auto-save to the editor -_ https://github.com/processing/processing/issues/131 -_ implement better method of showing curly brace closure -_ https://github.com/processing/processing/issues/94 -_ setModified() getting called on Windows (probably Linux) for key cmds - - -PDE / Editor Toolbar (Buttons) - -_ run button issues (unconfirmed) -_ does it unhighlight after compile or runtime errors? -_ also when using draw() instead of loop() -_ applet needs to notify runner that it has terminated -_ check 'finished' via objectreference? -_ EditorToolbar has two TODO items for open menu handling - - -PDE / Export - -_ if the lib folder goes missing from export, give an error -_ also any .jar files that are missing, give an error -_ showing more debug messages (command line?) -_ when exporting to application (or applet) don't copy .java files from folder -_ (they'll be copied as source files later) -_ make .java files and friends go to correct locations on export (app) -_ warn on export when people call their sketches things like Server -_ warn if someone extends PApplet but mis-names the sketch -_ or don't allow it to be exported -_ add bug reference to the faq once added to the db -X or at least add a note about this to the faq -_ show error when no main() is included but class extends PApplet -_ error can happen or be checked -_ exporting application copies .java files -_ .java files are copied to the root folder as well as the source folder - - -PDE / Libraries - -_ alternate handling of duplicate library conflicts -_ https://github.com/processing/processing/pull/5126 -_ Add a means to specify packages to import in library.properties -_ https://github.com/processing/processing/issues/2134 -_ need to deal with classpath conflicts -_ avoid garbage that people have installed on their machines -_ antlr.jar in the classpath will cause trouble.. -_ http://processing.org/discourse/yabb_beta/YaBB.cgi?board=SoftwareBugs;action=display;num=1138652326 -_ jogl jar files, or jogl install will cause trouble -_ /System/Library/Java/Extensions or /Library/Java/Extensions -_ the /System one contains qtjava -_ the /Library one is empty (by default), maybe skip it? -_ classpath conflicts also problem with PDE itself -_ catch antlr conflicts, and put up an error message -_ http://dev.processing.org/bugs/show_bug.cgi?id=1225 (no Google issue) -_ make sure there aren't library jar files named the same thing -_ i.e. if one library has db.jar, then that's gonna kill another db.jar -_ when the files are copied over -_ java.ext.dirs for /System/Library/Java/Extensions -_ http://java.sun.com/j2se/1.5.0/docs/guide/extensions/spec.html -_ can set java.ext.dirs to something else -_ on osx, just ignore anything in /Library/Java/Extensions (but not others)? -_ native lib stuff, use native.txt in lib folder, then: -_ String osName = System.getProperty("os.name"); -_ String osArch = System.getProperty("os.arch"); -_ http://stackoverflow.com/questions/1611357/how-to-make-a-jar-file-that-include-dll-files -_ need better error messages for broken api / library troubles -_ e.g. ocd is broken in 0125 because of method signature changes - - -PDE / Manager - -_ Manager fails to complete install of PythonMode when no windows open -_ https://github.com/processing/processing/issues/5309 -_ Python Mode not downloading? -_ https://github.com/processing/processing/issues/5918 -_ an incompatible Mode prevents the PDE from quitting after last window is closed -_ https://github.com/processing/processing/issues/5112 -_ “could not move the contribution to the backup folder” message while updating -_ problem is that any sketch that uses a library, the lib is stuck as "in use" -_ https://github.com/processing/processing/issues/4973 -_ issues with updating modes -_ https://github.com/processing/processing/issues/5424 -_ examples window not updating on install -_ open examples window -_ mode > add mode > libraries > install video -_ did not update the examples window, had to restart pde -_ was able to save over the video capture examples b/c they were a library -_ lib examples not properly marked as read-only -_ "Could not find a examples in the downloaded file" is a poorly worded message -_ 'version' should be x.y or x.y.z, not some extra long string -_ enforce this by disallowing spaces? on the import script? -_ Progress bar height on macOS is too thin -_ https://github.com/processing/processing/issues/4734 -_ proper error handling when downloading contribs listing -_ https://github.com/processing/processing/issues/4732 -_ update CM entries when sketchbook location changes -_ https://github.com/processing/processing/issues/3927 -_ ugly white gap at the top of scroll bar -_ the table header doesn't extend far enough -_ scrolling "past" top/bottom causes the screen to jiggle (OS X and Trackpad) -_ looks like ContributionListing.getScrollableUnitIncrement() returns early -_ Examples window closes and re-opens during library install/remove -_ https://github.com/processing/processing/issues/3304 -_ CM - Icon instead of an "X" for the "could not connect" message -_ https://github.com/processing/processing/issues/3706 -_ several TODO items listed in ContributionPanel -_ something to set min/max versions that are supported by a library -_ ability to cancel a download/install -_ we shouldn't use .properties extension for modes, et al -_ because a .properties file is iso8859-1 -_ make note that .properties file *must* be utf-8 -_ if not it'll make things gross (andre sier flob library) -_ why wasn't Library moved to LibraryContribution? -_ or that LibraryContribution needs to be a wrapper around it? -_ send info on 'check for updates' so we know about libs/modes/etc? -_ how to disclose to users? -_ only send for items that are part of the public list -_ otherwise we're sending private libraries/installs -_ although this won't pick up old libraries not on the new system -_ classpath conflicts.. -_ getPackageList.. from Library... maybe others? -_ really need to make sure that a weird core.jar isn't being imported -_ coffeescript was doing this and breaking the pde -_ contrib library examples are not read-only -_ another point for doing .zip files to prevent overwriting -_ add BookContribution? -_ will offer to open the contrib manager even though it's already open -_ though this was looking at libraries, and there may have been modes -_ is there an "all" view for the mgr that shows libs, modes, etc? -_ update button only showing up when item is selected feels awkward -_ "update all" would be useful -_ "Update 4 items" as a button name -_ new libraries not picked up when changing sketchbook location -_ make sure contrib manager can run w/o a network connection -_ or if a bad document comes through, it can recover -_ alternating blue/white backgrounds aren't updated after changing filter -_ just need to call a repaint() after a filter change? -_ check with Casey about coloring for error messages -_ test on Windows and Linux -_ font size for "Downloading" on progress bar is too large -_ but changing the size breaks the vertical centering -_ highlight color seems to be incorrect? -_ after installing, the item in the manager list doesn't change color -_ wheel mouse is super jumpy -_ something about unit increment in ContributionListPanel -_ arrow keys up/down move scroll bar, not selection -_ fonts/etc need to be set in one place where they can be edited -_ move styling to separate constants that are more accessible - - -PDE / Preferences - -_ Editor.applyFrame() may not have a valid 'editor' object to work with -_ if windows closed, and prefs altered, NPE thrown -_ make sure editor isn't trying to apply prefs when no editor is open -_ (on mac os x, due to the change for no windows open) -_ clear up prefs so that multiple editors don't trash each other's prefs -_ when are prefs saved? could instead save whenever changes are made -_ and then if the file gets modified, it'll put up an error message -_ also, this may be part of why other sketches aren't reloading properly -_ simple prefs implementation to set key/value pairs using a JTable -_ https://github.com/processing/processing/issues/5425 -_ prefs window doesn't swap ok/cancel properly for mac vs. windows/linux -_ don't bother having a "cancel" for the prefs window -_ make prefs dialog modal? - - -PDE / Runner - -_ if RuntimeException thrown, needs to check if it's a wrapped exception -_ for instance, if there's a crash inside makeGraphics() -_ this inside the handling that comes from the JVM, which makes it tricky -_ draw mode apps do not shut off the run button when finished -_ need to talk to VM and read when 'finished' var is set -_ does closing the window call stop()? -_ need to make sure hitting stop button and closing window explicitly call -_ set finished to true, then join() the animation thread -_ dispose handlers not called when stop button pressed -_ https://github.com/processing/processing/issues/4445 -_ need to set dock icon title on osx - - -PDE / Tweak - -_ TweakMode listener mess in JavaTextArea -_ https://github.com/processing/processing/issues/4605 - - -PDE / Sketch & Sketchbook - -_ Large number of files in sketchbook folder can cause slow startup -_ and/or errors with launch4j -_ https://github.com/processing/processing/issues/1190 -_ error that sketch is read-only can't be canceled -_ hitting cancel (or ESC?) still brings up the save dialog -_ ArticulatePrint-070103a.pde from ArticulatePrint-070103a loads ok -_ but when opening the sketch, it leaves out the file -_ because there's a dash in the name -_ and instead only loads StemCell.pde -_ show progress dialog during export and save -_ hitting ESC on "create this, move file, continue" opened anyway - - - -//////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// - - - -TOOLS / General - -_ create default tools folder (just like libraries) -_ for tools, maybe don't run on event thread? (makes the gui hang) -_ but instead, things that affect gui need to be called w/ invokeLater? - - -TOOLS / Ideas - -_ eclipse import/export -_ simple mechanism to export to eclipse -_ don't worry about subversion stuff, just export with libraries -_ something clever to import back from eclipse -_ could keep the .svn files in with the libs and all -_ then when exporting for eclipse, people can update as necessary -_ -> dan et al say better to do fixed versions and have it work simply -_ import sketch from url (takes a zip from archive sketch) -_ archive sketch direct to bug report -_ shared code -_ Integrator / FloatThing / CameraStuff -- Update -_ to pull code from a local folder -_ update will update classes from shared in the current folder - - -TOOLS / Auto Format - -_ Switch block cases not indented -_ https://github.com/processing/processing/issues/1042 -_ do a better job of maintaining cursor -_ only auto-format a particular section of code -_ set the 'tabs' var based on how many spaces on previous line -_ http://processing.org/discourse/yabb/YaBB.cgi?board=Proce55ing_Software;action=display;num=1087227217 - - - -//////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// - - -LIBRARIES / General - -_ need to unpack InvocationTargetException in xxxxxxEvent calls -_ http://processing.org/discourse/yabb_beta/YaBB.cgi?board=VideoCamera;action=display;num=1116850328#3 - - -LIBRARIES / Net - -_ modernize Client/Server code to use synchronized lists -_ do we let people use the public vars in Server and Client? -_ are they documented? - - - -//////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// - - -DIST - -How the environment gets packed up, downloaded, and installed. - - -DIST / General - -_ move processing-java inside the Java Mode? -_ make a Tool that installs it for all platforms, not just OS X -_ not really part of the 'build' anymore -_ line ending issues -_ doesn't really help on Windows since we use Cygwin -_ but it would be helpful for people not using it (ant/other LF issues) -_ https://help.github.com/articles/dealing-with-line-endings -_ http://stackoverflow.com/questions/170961/whats-the-best-crlf-handling-strategy-with-git -_ add means to move changes from desktop to Android (and vice-versa) -_ add bootclasspath for 1.6 -_ would require --install for command line on OS X -_ and unpacking the JVM earlier in the build process -_ not sure if it's worth the addl complexity -_ add additional caveats about incompletion to javadoc -_ drag and drop -_ including the number of items added to sketch would be especially nice -_ implement automatic update -_ https://github.com/processing/processing/issues/100 -_ need .pde document icons -_ need .psk file icon -_ need exported application icons -_ need more comprehensive list of 'known bugs' -_ need more comprehensive list of 'known suggestions' -_ write notes about running p5 on another platforms -_ this was some feedback from running on bsd: -_ /usr/local/jdk1.3.1/bin/java -cp lib:lib/build:lib/pde.jar:lib/kjc.jar:lib/oro.jar:java/lib/ext/comm.jar PdeBase -_ need to use the 1.3 vm, and get a fake platform name -_ otherwise, goes looking for lib/pde_.properties or something -_ about box -_ bring up information about gpl, lgpl, and ibmpl -_ jedit syntax is under mit license -_ http://www.opensource.org/licenses/mit-license.php -_ add proper copyright and license information for all included projects -_ https://github.com/processing/processing/issues/224 -_ write up guidelines for modes -_ i.e. don't mess with Sketch menu, put it in the mode menu -_ p5 assets need to be licensed differently from the source - - -DIST / Windows - -_ Resolve “Successfully created” chatter and “illegal reflective access” errors from launch4j -_ https://github.com/processing/processing4/issues/137 -_ does launching p5 from inside the .zip folder cause it to quit immediately? -_ how can we provide an error message here? -_ how to handle double-clicked files on windows? -_ big deal for psk and others -_ this may already work with SingleInstance stuff - - -DIST / macOS - -appbundler -_ update appbundler? https://github.com/TheInfiniteKind/appbundler -_ might be broken though https://github.com/TheInfiniteKind/appbundler/issues/70 -_ symlink https://github.com/TheInfiniteKind/appbundler/issues/1 -_ startup chatter (from appbundler?) -_ Processing[25059:13082813] int launch(char *, int, char **) Launchpath -_ Find a long-term solution for OS X bundler to address signing/symlink issues -_ https://github.com/processing/processing/issues/2967 -_ appbundler improvements -_ don't re-copy JRE into work folder if already exists -_ implement a splash screen - -others -_ reliable getLibraryFolder() and getDocumentsFolder() methods in MacPlatform -_ https://github.com/processing/processing4/issues/9 -_ disable "notifications" prompt on startup for macOS -_ https://github.com/processing/processing4/issues/234 -_ 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 -_ Help Menu disabled on OS X (looks like a JVM bug) -_ https://github.com/processing/processing/issues/4353#issuecomment-237715947 -_ still broken in 11.0.8 -_ Java bug prevents us from setting the dock name of a sketch run from the PDE -_ https://github.com/processing/processing/issues/5045 -_ client properties -_ https://developer.apple.com/library/mac/technotes/tn2007/tn2196.html -_ built-in images: http://nadeausoftware.com/articles/2008/12/mac_java_tip_how_access_mac_specific_nsimage_icons -_ Update QuickLook plugin for Processing 3 -_ https://github.com/processing/processing/issues/3261 -_ more OS X-specific hackery for improved appearance -_ https://developer.apple.com/library/mac/technotes/tn2007/tn2196.html -_ possible better option for doing retina? -_ g.getFontRenderContext().getTransform().equals(AffineTransform.getScaleInstance(2.0, 2.0)) -_ LWJGL forum discussion -_ http://lwjgl.org/forum/index.php/topic,4711.225.html -_ change cmd line for OS X to use symlink? -_ otherwise updates are going to require reinstall.. -_ or that it's gonna need to parse and say "update command line?" -_ we're breaking some mac human interface guidelines -_ should be using a menu factory to create menubar for all sub-windows -_ http://developer.apple.com/technotes/tn/tn2042.html -_ and the general warning dialogs are just ass ugly -_ (i.e. we really need those replacements for JOptionPane) -_ Exiting a sketch with Command-Q or File > Quit doesn't call stop() on OS X -_ https://github.com/processing/processing/issues/186 - - -DIST / Linux - -_ update install.sh for .pdex and .pdez file associations -_ https://github.com/processing/processing4/issues/239 - - - -//////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// - - FUTURE Notes for some indefinite later release... -_ make reference build process part of dist -_ https://github.com/processing/processing-docs/issues/85 -_ separate ant target, but only require them for dist -_ as separate targets, folks can build explicitly if they'd like -_ processing-docs/java_generate/ReferenceGenerator/processingrefBuild.sh -_ remove reference.zip from main repo -_ nurbs or other architecture stuff _ force indentation - implement an option for beginners especially _ indents and loops _ or make the area light up gray as it's being worked on -_ get rid of static mode? only setup/draw mode? or create project that way? -_ pro: simplify internal code, fixes some of the worst errors -_ con: people love it, most of the books use it heavily -_ add === for String.equals() -_ might be problematic since it might be opposite the javascript meaning -_ jer: it's a decent time to start talking abt references/vars -_ Blank sketch opened even if opening an existing sketch by double-clicking -_ https://github.com/processing/processing/issues/218 -_ changing number of screens between run causes things to show up off-screen -_ so when running, check to make sure that things are out of the area -_ improve the speed of file copying -_ use FileChannels, see FileInputStream.getChannel(), -_ and use transferFrom() or transferTo().) -_ could also use FileUtils in Apache's common io -_ http://commons.apache.org/io/api-release/index.html -_ go through libraries and clean things up -protected void finalize() throws Throwable { - try { - close(); - } catch (Exception e) { - // do something - } finally { - super.finalize(); - // more code can be written here as per need of application - } -} +_ language server refactoring +_ https://github.com/processing/processing4/issues/117 +_ https://theia-ide.org/ +_ https://medium.com/ballerina-techblog/implementing-a-language-server-how-hard-can-it-be-part-2-fa65a741aa23