diff --git a/.classpath b/.classpath
index 64e8893d55a..574e1c7f83d 100644
--- a/.classpath
+++ b/.classpath
@@ -4,7 +4,6 @@
-
@@ -16,17 +15,17 @@
-
+
-
+
-
-
-
-
+
+
+
+
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000000..e1a034449f9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1 @@
+Please read the Issues section of the Contributing Rules at the "Contributing" link to the right before submitting an issue report.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000000..0257f89eea7
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,17 @@
+### All Submissions:
+
+* [ ] Have you followed the guidelines in our Contributing document?
+* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/arduino/Arduino/pulls?q=) for the same update/change?
+
+
+
+### New Feature Submissions:
+
+1. [ ] Does your submission pass tests?
+2. [ ] Have you lint your code locally prior to submission?
+
+### Changes to Core Features:
+
+* [ ] Have you added an explanation of what your changes do and why you'd like us to include them?
+* [ ] Have you written new tests for your core changes, as applicable?
+* [ ] Have you successfully ran tests with your changes
diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml
new file mode 100644
index 00000000000..0a2e0a343e6
--- /dev/null
+++ b/.github/workflows/ant.yml
@@ -0,0 +1,34 @@
+name: Java CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ - name: Build with Ant
+ working-directory: ./build
+ run: |
+ sed -i 's###' build.xml
+ ant clean dist
+ - name: Install X virtual framebuffer
+ run: sudo apt-get install -y xvfb
+ - name: Run tests
+ working-directory: ./app
+ run: xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" ant test -Drunning-from-github-action=1
+ - name: Publish results
+ uses: actions/upload-artifact@v1
+ with:
+ name: html-results
+ path: app/test-bin/results/html/
+ - name: Cleanup xvfb
+ uses: bcomnes/cleanup-xvfb@v1
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d4b6b1e8dd7..27e2e104f28 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,102 +1,80 @@
-## Contributing guide
-This document serves as a checklist before contributing to this repository. It includes includes links to read up on if topics are unclear to you.
-
-This guide mainly focuses on the proper use of Git. It has some overlap with the more general information found in the [Development Policy File](https://github.com/arduino/Arduino/wiki/Development-Policy).
-
-### 1. Before using the issue tracker
-To report a bug or a small enhancement please use the [issue tracker](http://github.com/arduino/Arduino/issues). But check the following boxes before posting an issue:
-
-- [ ] `Your issue is NOT a question about an Arduino sketch.` Sketch questions are handled on the [Arduino Forum](http://forum.arduino.cc/).
-- [ ] `Check if your issue has already been resolved in the` [hourly build](http://www.arduino.cc/en/Main/Software#hourly)
-- [ ] `Your issue is not a duplicate.` So search for similar open and closed issues and pull-requests.
-- [ ] `Make sure you are working on the right repository. See the table below.`
-
-| Repositories | Projects |
-|---|---|
-|[Arduino](https://github.com/arduino/Arduino) | Arduino IDE, arduino.cc (but not the Arduino Playground), Library Manager |
-|[Arduino Playground](http://forum.arduino.cc/index.php?board=24.0) | This is a publicly editable wiki. Please either make the edit yourself or create a post |
-|[Arduino Forum](https://github.com/arduino/forum-issues) | Issues about the Arduino Forum |
-| [Libraries for Arduino IDE](https://github.com/arduino-libraries) | Changing libraries for the IDE |
-| [Arduino-builder](https://github.com/arduino/arduino-builder)| |
-|[Arduino Web Editor](https://github.com/arduino/arduino-create-agent) | |
-|[Arduino SAMD Boards](https://github.com/arduino/ArduinoCore-samd)|Zero, MKR1000, MKRZero, etc. |
-|[Arduino SAM Boards](https://github.com/arduino/ArduinoCore-sam)| Due |
-|[Arduino AVR Boards toolchain (avr-gcc)](https://github.com/arduino/toolchain-avr)| |
-|[Arduino's build of AVRDUDE](https://github.com/arduino/avrdude-build-script)||
-|Third party repository | for third party libraries, hardware packages or sketches |
-
-### 2. Posting the issue
-When you have checked the previous boxes. Please consider the following points before posting the issue.
-
-- [ ] `Describe the issue based on the behaviour you were expecting`
-- [ ] `Post complete error messages using markdown code fencing:` [Markdown Code Fencing Example](https://guides.github.com/features/mastering-markdown/#examples)
-- [ ] `Provide a full set of steps necessary to reproduce the issue`
-- [ ] `Demonstration code should be complete, correct and the minimum amount necessary to reproduce the issue`
-- [ ] `Library Manager submissions: make sure your library meets all the requirements listed in the` [Library Manager FAQ](https://github.com/arduino/Arduino/wiki/Library-Manager-FAQ)
-
-### 3. Pull Requests
-Before starting to work on bigger topics like modifying the API or changes with backward compatibility trade-offs please discuss them in the [mailing list](https://groups.google.com/a/arduino.cc/forum/#!forum/developers) first.
-
-### 4. Commit messages
-An easy to read pull request will speed up the merging process. Your commit messages need to be logically separate. And containing enough information on their own. When this is done consistently your pull request will have an easy to read log of changes.
-
-Your commits need to be [atomic](https://www.freshconsulting.com/atomic-commits/) which allows the repository to remain flexible after merging.
-
-If you did not read the following 7 points before or just want to fresh up. Please read up on them on this [website](https://chris.beams.io/posts/git-commit) by Chris Beams.
-
-1. Separate subject from body with a blank line
-2. Limit the subject line (first line) to 50 characters
-3. Capitalize the subject line
-4. Do not end the subject line with a period `(.)`
-5. Use the imperative mood in the subject line.
-This should be in the written as giving an instruction for example "Fixed save-as bug" (it shows what the PR achieves when merging it)
-6. Wrap body at 72 characters
-7. Use the body to explain what, why and how
-
-If your pull request fixes, closes or resolves an issue please reference it in the body with the following [syntax](https://help.github.com/articles/closing-issues-via-commit-messages/). Also see the last lines of the following example.
-
-A general example with these 7 guidelines in mind is shown below (from the same website of [Chris Beams](https://chris.beams.io/posts/git-commit)):
-```
-Summarize changes in around 50 characters or less
-
-More detailed explanatory text, if necessary. Wrap it to about 72
-characters or so. In some contexts, the first line is treated as the
-subject of the commit and the rest of the text as the body. The
-blank line separating the summary from the body is critical (unless
-you omit the body entirely); various tools like `log`, `shortlog`
-and `rebase` can get confused if you run the two together.
-
-Explain the problem that this commit is solving. Focus on why you
-are making this change as opposed to how (the code explains that).
-Are there side effects or other unintuitive consequences of this
-change? Here's the place to explain them.
-
-Further paragraphs come after blank lines.
-
- - Bullet points are okay, too
-
- - Typically a hyphen or asterisk is used for the bullet, preceded
- by a single space, with blank lines in between, but conventions
- vary here
-
-If you use an issue tracker, put references to them at the bottom,
-like this:
-
-Resolves: #123
-See also: #456, #789
-```
-
-### 5. Rebasing pull requests
-When different people are working on the Arduino project simultaneously, pull requests can go stale quickly. A "stale" pull request is one that is no longer up to date with the latest merges in the project. It needs to be updated before it can be merged.
-
-Most often pull requests become stale when merge conflicts occur. This happens when two pull requests both modify similar lines in the same file and one gets merged, the unmerged request will now have a merge conflict and needs updating.
-
-When your pull request is stale, you will need to rebase your branch on the current master branch before you can merge it without conflicts.
-
-More information about rebasing can be found at the repository of [edX](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request).
-
-### 6. Merged!
-When your pull request is merged please update the documentation if the changes require it:
-
-- [ ] Edit appropiate [Wiki pages](https://github.com/arduino/Arduino/wiki/_pages)
-- [ ] Submit an [issue report](https://github.com/arduino/Arduino/issues/new) requesting changes to the [arduino.cc reference pages](https://www.arduino.cc/en/Reference/HomePage)
+# Contributing Rules
+Thanks for your interest in contributing to this free open source project! Arduino welcomes help from the community. There are several ways you can get involved:
+
+| Type of contribution | Contribution method |
+|-|-|
+| - Support request - Question - Problem with your Arduino - Discussion | Post on the [Arduino Forum](http://forum.arduino.cc) |
+| - Bug report - [Arduino website](https://www.arduino.cc/) issue or improvement - Feature request | Issue report (read the [issue guidelines](#issues)) |
+| - Bug fix - Enhancement | Pull Request (read the [pull request guidelines](#pull-requests)) |
+| Translations for the Arduino IDE | [transifex](https://www.transifex.com/mbanzi/arduino-ide-15/) |
+| Translations for the [Language Reference](https://www.arduino.cc/reference) | [Reference repositories](https://github.com/arduino?q=reference-) |
+| Monetary | - [Donate](https://www.arduino.cc/en/Main/Contribute) - [Buy official products](https://store.arduino.cc) |
+
+
+## Issues
+- Do you need help or have a question about using Arduino? Support requests should be made to the appropriate section of the [Arduino forum](http://forum.arduino.cc) rather than an issue report. **Issue reports are to be used to report bugs or make feature requests only.**
+- Check if your issue has already been resolved in the [hourly build](http://www.arduino.cc/en/Main/Software#hourly).
+- Submit issue reports to the correct repository:
+
+| Issue topic | Report at |
+|-|-|
+| Arduino IDE, arduino.cc (but not the Arduino Forum), Library Manager additions | [arduino/Arduino](https://github.com/arduino/Arduino/issues) |
+| [Language Reference](https://www.arduino.cc/reference) | [Reference repositories](https://github.com/arduino?q=reference-) |
+| Arduino Forum | [arduino/forum-issues](https://github.com/arduino/forum-issues/issues) |
+| Arduino libraries | [arduino-libraries](https://github.com/arduino-libraries) |
+| arduino-builder | [arduino/arduino-builder](https://github.com/arduino/arduino-builder/issues) |
+| [Arduino Web Editor](https://create.arduino.cc/editor) | [**Create > Editor** section of the Arduino Forum](http://forum.arduino.cc/index.php?board=101.0) |
+| Arduino AVR Boards (Uno, Mega, Leonardo, etc.) | [arduino/ArduinoCore-avr](https://github.com/arduino/ArduinoCore-avr/issues) |
+| Arduino SAMD Boards (Zero, MKR1000, MKRZero, etc.) | [arduino/ArduinoCore-samd](https://github.com/arduino/ArduinoCore-samd/issues) |
+| Arduino SAM Boards (Due) | [arduino/ArduinoCore-sam](https://github.com/arduino/ArduinoCore-sam/issues) |
+| AVR Toolchain for Arduino | [arduino/toolchain-avr](https://github.com/arduino/toolchain-avr/issues) |
+| Arduino's build of AVRDUDE | [arduino/avrdude-build-script](https://github.com/arduino/avrdude-build-script/issues) |
+| Security vulnerability | See: [Coordinated Vulnerability Disclosure Policy](https://github.com/arduino/arduino-cvd-policy) |
+| 3rd party libraries, hardware, or sketches | Report issues to the author of the software, *not* Arduino. |
+
+When you're not sure where your issue belongs, report it at [arduino/Arduino](https://github.com/arduino/Arduino) and we'll move it to where it belongs (but remember: Only bug reports and feature requests, do not ask for help with your own code there).
+
+- Search [existing pull requests and issues](https://github.com/arduino/Arduino/issues?q=) to be sure it hasn't already been reported. If you have additional information to provide about an existing issue then please comment on that issue. If you simply want to express your support then use the [Reactions feature](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments).
+- State the newest version of the Arduino IDE you have verified the issue with and which operating system you are using.
+- The issue title should be concise yet descriptive. Vague titles make it difficult to know the purpose of the issue when looking through the list of reports and may cause your issue to not be given proper attention.
+- Describe the issue and what behavior you were expecting. Post complete error messages using [Markdown code fencing](https://guides.github.com/features/mastering-markdown/#examples), three backticks before and after the error message:
+ ````
+ ```
+ my error message here
+ ```
+ ````
+- Provide a full set of steps necessary to reproduce the issue. Demonstration code should be complete, correct, and simplified to the minimum amount of code necessary to reproduce the issue. Please use [Markdown code fencing](https://guides.github.com/features/mastering-markdown/#examples), three backticks before and after the code:
+ ````
+ ```
+ my code here
+ ```
+ ````
+- Be responsive. We may need you to provide more information, please respond as soon as possible.
+- If you find a solution to your problem update your issue report with an explanation of how you were able to fix it and close the issue.
+- Library Manager submissions: make sure your library meets all the requirements listed in the [Library Manager FAQ](https://github.com/arduino/Arduino/wiki/Library-Manager-FAQ).
+
+
+## Pull Requests
+Pull requests are an easy and effective way to submit a proposal for a change to the content of one of Arduino's repositories. The Arduino team can merge your change with a single click! You can find more information about pull requests [here](https://help.github.com/articles/creating-a-pull-request/).
+- Big changes, changes to the API, or changes with backward compatibility trade-offs should be first discussed in the [Arduino Developers Mailing List](https://groups.google.com/a/arduino.cc/forum/#!forum/developers).
+- Search [existing pull requests](https://github.com/arduino/Arduino/pulls?q=) to see if one has already been submitted for this change. Search the [issues](https://github.com/arduino/Arduino/issues?q=is%3Aissue) to see if there has been a discussion on this topic and whether your pull request can close any issues.
+- Code formatting should be consistent with the style used in the existing code.
+- Don't leave commented out code. A record of this code is already preserved in the commit history.
+- Note that the Arduino core libraries support many boards and processors. When fixing or adding functionality for one of them, it's easy to break something on the others. Please test your changes on as many processors as possible. Even if you don't have a particular board, try compiling your patch for it to make sure that you haven't introduced any errors.
+- All commits must be atomic. This means that the commit completely accomplishes a single task. Each commit should result in fully functional code. Multiple tasks should not be combined in a single commit, but a single task should not be split over multiple commits (e.g. one commit per file modified is not a good practice). For more information see http://www.freshconsulting.com/atomic-commits.
+- Each pull request should address a single bug fix or enhancement. This may consist of multiple commits. If you have multiple, unrelated fixes or enhancements to contribute, submit them as separate pull requests.
+- Commit messages:
+ - Use the [imperative mood](http://chris.beams.io/posts/git-commit/#imperative) in the title. For example: "Apply editor.indent preference"
+ - Capitalize the title.
+ - Do not end the title with a period.
+ - Separate title from the body with a blank line. If you're committing via GitHub or GitHub Desktop this will be done automatically.
+ - Wrap body at 72 characters.
+ - Completely explain the purpose of the commit. Include a rationale for the change, any caveats, side-effects, etc.
+ - If your pull request fixes an issue in the issue tracker, use the [closes/fixes/resolves syntax](https://help.github.com/articles/closing-issues-via-commit-messages) in the body to indicate this.
+ - See http://chris.beams.io/posts/git-commit for more tips on writing good commit messages.
+- Pull request title and description should follow the same guidelines as commit messages.
+- Rebasing pull requests is OK and encouraged. After submitting your pull request some changes may be requested. Rather than adding unnecessary extra commits to the pull request, you can squash these changes into the existing commit and then do a force push to your fork. When you do a force push to your fork, the PR will be updated with your new changes, so there is no need to open a new PR to make changes. Leave a comment on the pull request thread to explain that the history has been changed. This will help to keep the commit history of the repository clean.
+- After your pull request is merged please update the documentation if the changes require it:
+ - Edit appropriate [Wiki pages](https://github.com/arduino/Arduino/wiki/_pages).
+ - Submit an [issue report](https://github.com/arduino/Arduino/issues/new) requesting changes to the [arduino.cc reference pages](https://www.arduino.cc/en/Reference/HomePage).
+- For more contributing guidelines, see the [Arduino Development Policy](https://github.com/arduino/Arduino/wiki/Development-Policy).
diff --git a/README.md b/README.md
index 0bc3e34fb3a..1fd42861444 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,73 @@
-Arduino
-========
+
+
+
-* Arduino is an open-source physical computing platform based on a simple I/O
+Arduino is an open-source physical computing platform based on a simple I/O
board and a development environment that implements the Processing/Wiring
language. Arduino can be used to develop stand-alone interactive objects or
can be connected to software on your computer (e.g. Flash, Processing and MaxMSP).
The boards can be assembled by hand or purchased preassembled; the open-source
-IDE can be downloaded for free at https://www.arduino.cc/en/Main/Software
+IDE can be downloaded for free at [https://arduino.cc](https://www.arduino.cc/en/Main/Software)
-* For more information, see the website at: https://www.arduino.cc/
-or the forums at: https://forum.arduino.cc/
-You can also follow Arduino on Twitter at: https://twitter.com/arduino or
-like Arduino on Facebook at: https://www.facebook.com/official.arduino
+## More info at
-* To report a *bug* in the software or to request *a simple enhancement* go to:
-https://github.com/arduino/Arduino/issues
+- [Our website](https://www.arduino.cc/)
-* More complex requests and technical discussion should go on the Arduino Developers
-mailing list:
-https://groups.google.com/a/arduino.cc/forum/#!forum/developers
+- [The forums](https://forum.arduino.cc/)
-* If you're interested in modifying or extending the Arduino software, we strongly
-suggest discussing your ideas on the Developers mailing list *before* starting
-to work on them. That way you can coordinate with the Arduino Team and others,
+- Follow us on [Twitter](https://twitter.com/arduino)
+- And like us at [Facebook](https://www.facebook.com/official.arduino)
+
+## Bug reports and technical discussions
+
+- To report a *bug* in the software or to request *a simple enhancement* go to [Github Issues](https://github.com/arduino/Arduino/issues)
+
+- More complex requests and technical discussion should go on the [Arduino Developers
+mailing list](https://groups.google.com/a/arduino.cc/forum/#!forum/developers)
+
+- If you're interested in modifying or extending the Arduino software, we strongly
+suggest discussing your ideas on the
+[Developers mailing list](https://groups.google.com/a/arduino.cc/forum/#!forum/developers)
+ *before* starting to work on them.
+That way you can coordinate with the Arduino Team and others,
giving your work a higher chance of being integrated into the official release
-https://groups.google.com/a/arduino.cc/forum/#!forum/developers
-Installation
-------------
-Detailed instructions for installation in popular operating systems.
-For Linux: https://www.arduino.cc/en/Guide/Linux (see also the Arduino playground page https://playground.arduino.cc/Learning/Linux)
-For macOS X: https://www.arduino.cc/en/Guide/MacOSX
-For Windows: https://www.arduino.cc/en/Guide/Windows
+## Installation
+
+Detailed instructions for installation in popular operating systems can be found at:
+
+- [Linux](https://www.arduino.cc/en/Guide/Linux) (see also the [Arduino playground](https://playground.arduino.cc/Learning/Linux))
+- [macOS](https://www.arduino.cc/en/Guide/MacOSX)
+- [Windows](https://www.arduino.cc/en/Guide/Windows)
+
+## Contents of this repository
+
+This repository contains just the code for the Arduino IDE itself.
+Originally, it also contained the AVR and SAM Arduino core and libraries
+(i.e. the code that is compiled as part of a sketch and runs on the
+actual Arduino device), but those have been moved into their own
+repositories. They are still automatically downloaded as part of the
+build process and included in built releases, though.
+
+The repositories for these extra parts can be found here:
+- Non-core specific Libraries are listed under:
+ (and also a few other places, see `build/build.xml`).
+
+- The AVR core can be found at:
+
+- Other cores are not included by default but installed through the
+ board manager. Their repositories can also be found under
+ .
+
+## Building and testing
+
+Instructions for building the IDE and running unit tests can be found on
+the wiki:
+-
+-
+
+## Credits
-Credits
---------
Arduino is an open source project, supported by many.
The Arduino team is composed of Massimo Banzi, David Cuartielles, Tom Igoe
@@ -43,8 +76,8 @@ and David A. Mellis.
Arduino uses
[GNU avr-gcc toolchain](https://gcc.gnu.org/wiki/avr-gcc),
[GCC ARM Embedded toolchain](https://launchpad.net/gcc-arm-embedded),
-[avr-libc](http://www.nongnu.org/avr-libc/),
-[avrdude](http://www.nongnu.org/avrdude/),
+[avr-libc](https://www.nongnu.org/avr-libc/),
+[avrdude](https://www.nongnu.org/avrdude/),
[bossac](http://www.shumatech.com/web/products/bossa),
[openOCD](http://openocd.org/)
and code from [Processing](https://www.processing.org)
diff --git a/app/.classpath b/app/.classpath
index 315612244ef..bb2bf7417c6 100644
--- a/app/.classpath
+++ b/app/.classpath
@@ -4,7 +4,6 @@
-
@@ -30,20 +29,19 @@
-
-
-
-
-
+
+
+
-
+
+
+
-
-
+
@@ -53,4 +51,8 @@
+
+
+
+
diff --git a/app/build.xml b/app/build.xml
index cc38670adc6..fa3223642ff 100644
--- a/app/build.xml
+++ b/app/build.xml
@@ -1,5 +1,5 @@
-
+
@@ -80,6 +80,10 @@
includeAntRuntime="false"
debug="true"
classpathref="class.path" />
+
+
+
+
@@ -105,7 +109,20 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -117,9 +134,20 @@
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -127,6 +155,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/lib/RSyntaxTextArea.License.txt b/app/lib/RSyntaxTextArea.License.txt
new file mode 100644
index 00000000000..f0f2d4c743e
--- /dev/null
+++ b/app/lib/RSyntaxTextArea.License.txt
@@ -0,0 +1,24 @@
+Copyright (c) 2012, Robert Futrell
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the author nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/app/lib/commons-io-2.6.jar b/app/lib/commons-io-2.6.jar
new file mode 100644
index 00000000000..00556b119d4
Binary files /dev/null and b/app/lib/commons-io-2.6.jar differ
diff --git a/app/lib/commons-lang3-3.3.2.jar b/app/lib/commons-lang3-3.3.2.jar
deleted file mode 100644
index 2ce08ae99d1..00000000000
Binary files a/app/lib/commons-lang3-3.3.2.jar and /dev/null differ
diff --git a/app/lib/commons-lang3-3.8.1.jar b/app/lib/commons-lang3-3.8.1.jar
new file mode 100644
index 00000000000..2c65ce67d5c
Binary files /dev/null and b/app/lib/commons-lang3-3.8.1.jar differ
diff --git a/app/lib/jackson-annotations-2.6.3.jar b/app/lib/jackson-annotations-2.6.3.jar
deleted file mode 100644
index 9bb53e875cc..00000000000
Binary files a/app/lib/jackson-annotations-2.6.3.jar and /dev/null differ
diff --git a/app/lib/jackson-annotations-2.9.5.jar b/app/lib/jackson-annotations-2.9.5.jar
new file mode 100644
index 00000000000..98ea0047791
Binary files /dev/null and b/app/lib/jackson-annotations-2.9.5.jar differ
diff --git a/app/lib/jackson-core-2.6.3.jar b/app/lib/jackson-core-2.6.3.jar
deleted file mode 100644
index e1ec916dddc..00000000000
Binary files a/app/lib/jackson-core-2.6.3.jar and /dev/null differ
diff --git a/app/lib/jackson-core-2.9.5.jar b/app/lib/jackson-core-2.9.5.jar
new file mode 100644
index 00000000000..b70d1ef1ac8
Binary files /dev/null and b/app/lib/jackson-core-2.9.5.jar differ
diff --git a/app/lib/jackson-databind-2.6.3.jar b/app/lib/jackson-databind-2.6.3.jar
deleted file mode 100644
index e60f9fe1a7c..00000000000
Binary files a/app/lib/jackson-databind-2.6.3.jar and /dev/null differ
diff --git a/app/lib/jackson-databind-2.9.5.jar b/app/lib/jackson-databind-2.9.5.jar
new file mode 100644
index 00000000000..7a951509890
Binary files /dev/null and b/app/lib/jackson-databind-2.9.5.jar differ
diff --git a/app/lib/jackson-module-mrbean-2.6.3.jar b/app/lib/jackson-module-mrbean-2.6.3.jar
deleted file mode 100644
index 69cc60a5808..00000000000
Binary files a/app/lib/jackson-module-mrbean-2.6.3.jar and /dev/null differ
diff --git a/app/lib/jmdns-3.5.1.jar b/app/lib/jmdns-3.5.1.jar
deleted file mode 100644
index f9527c104c0..00000000000
Binary files a/app/lib/jmdns-3.5.1.jar and /dev/null differ
diff --git a/app/lib/jmdns-3.5.5.jar b/app/lib/jmdns-3.5.5.jar
new file mode 100644
index 00000000000..a8b65ff2ec7
Binary files /dev/null and b/app/lib/jmdns-3.5.5.jar differ
diff --git a/app/lib/jssc-2.8.0-arduino1.jar b/app/lib/jssc-2.8.0-arduino1.jar
deleted file mode 100755
index 3cf257ea634..00000000000
Binary files a/app/lib/jssc-2.8.0-arduino1.jar and /dev/null differ
diff --git a/app/lib/jssc-2.8.0-arduino4.jar b/app/lib/jssc-2.8.0-arduino4.jar
new file mode 100644
index 00000000000..623a3833bce
Binary files /dev/null and b/app/lib/jssc-2.8.0-arduino4.jar differ
diff --git a/app/lib/jtouchbar-1.0.0.jar b/app/lib/jtouchbar-1.0.0.jar
new file mode 100644
index 00000000000..2c473bec881
Binary files /dev/null and b/app/lib/jtouchbar-1.0.0.jar differ
diff --git a/app/lib/jtouchbar.LICENSE.MIT.txt b/app/lib/jtouchbar.LICENSE.MIT.txt
new file mode 100644
index 00000000000..f4fa99707a0
--- /dev/null
+++ b/app/lib/jtouchbar.LICENSE.MIT.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 thizzer.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/app/lib/log4j-api-2.12.0.jar b/app/lib/log4j-api-2.12.0.jar
new file mode 100644
index 00000000000..93f770d64a9
Binary files /dev/null and b/app/lib/log4j-api-2.12.0.jar differ
diff --git a/app/lib/log4j-core-2.12.0.jar b/app/lib/log4j-core-2.12.0.jar
new file mode 100644
index 00000000000..fbab720635d
Binary files /dev/null and b/app/lib/log4j-core-2.12.0.jar differ
diff --git a/app/lib/rsyntaxtextarea-2.6.1.jar b/app/lib/rsyntaxtextarea-2.6.1.jar
deleted file mode 100644
index b834e2d412b..00000000000
Binary files a/app/lib/rsyntaxtextarea-2.6.1.jar and /dev/null differ
diff --git a/app/lib/rsyntaxtextarea-3.0.3-SNAPSHOT.jar b/app/lib/rsyntaxtextarea-3.0.3-SNAPSHOT.jar
new file mode 100644
index 00000000000..e1844494aca
Binary files /dev/null and b/app/lib/rsyntaxtextarea-3.0.3-SNAPSHOT.jar differ
diff --git a/app/lib/rsyntaxtextarea-BSD.txt b/app/lib/rsyntaxtextarea-BSD.txt
deleted file mode 100644
index 3a6e638904a..00000000000
--- a/app/lib/rsyntaxtextarea-BSD.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-Copyright (c) 2012, Robert Futrell
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the author nor the names of its contributors may
- be used to endorse or promote products derived from this software
- without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/app/src/cc/arduino/ConsoleOutputStream.java b/app/src/cc/arduino/ConsoleOutputStream.java
index 452c190b414..6334f11e7e2 100644
--- a/app/src/cc/arduino/ConsoleOutputStream.java
+++ b/app/src/cc/arduino/ConsoleOutputStream.java
@@ -52,7 +52,7 @@
*/
public class ConsoleOutputStream extends ByteArrayOutputStream {
- private final SimpleAttributeSet attributes;
+ private SimpleAttributeSet attributes;
private final PrintStream printStream;
private final Timer timer;
@@ -73,6 +73,10 @@ public ConsoleOutputStream(SimpleAttributeSet attributes, PrintStream printStrea
timer.setRepeats(false);
}
+ public void setAttibutes(SimpleAttributeSet attributes) {
+ this.attributes = attributes;
+ }
+
public void setCurrentEditorConsole(EditorConsole console) {
this.editorConsole = console;
}
diff --git a/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java b/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java
index 3dcda927274..d28d735e35f 100644
--- a/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java
+++ b/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java
@@ -29,9 +29,6 @@
package cc.arduino.contributions;
-import cc.arduino.contributions.filters.BuiltInPredicate;
-import cc.arduino.contributions.filters.InstalledPredicate;
-import cc.arduino.contributions.packages.ContributedPackage;
import cc.arduino.contributions.packages.ContributedPlatform;
import processing.app.Base;
import processing.app.BaseNoGui;
@@ -39,8 +36,8 @@
import processing.app.PreferencesData;
import javax.swing.*;
-import java.util.Collection;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
import static processing.app.I18n.tr;
@@ -67,13 +64,21 @@ private void builtInPackageIsNewerCheck() throws InterruptedException {
return;
}
- List contributedPlatforms = BaseNoGui.indexer.getPackages().stream().map(ContributedPackage::getPlatforms).flatMap(Collection::stream).collect(Collectors.toList());
+ List contributedPlatforms = BaseNoGui.indexer
+ .getPackages().stream() //
+ .map(pack -> pack.getPlatforms()) //
+ .flatMap(platfs -> platfs.stream()) //
+ .collect(Collectors.toList());
- List installedBuiltInPlatforms = contributedPlatforms.stream().filter(new InstalledPredicate()).filter(new BuiltInPredicate()).collect(Collectors.toList());
- if (installedBuiltInPlatforms.size() != 1) {
+ Optional mayInstalledBuiltIn = contributedPlatforms
+ .stream() //
+ .filter(p -> p.isInstalled()) //
+ .filter(p -> p.isBuiltIn()) //
+ .findFirst();
+ if (!mayInstalledBuiltIn.isPresent()) {
return;
}
- final ContributedPlatform installedBuiltIn = installedBuiltInPlatforms.get(0);
+ final ContributedPlatform installedBuiltIn = mayInstalledBuiltIn.get();
ContributedPlatform installedNotBuiltIn = BaseNoGui.indexer.getInstalled(installedBuiltIn.getParentPackage().getName(), installedBuiltIn.getArchitecture());
if (installedNotBuiltIn == null) {
@@ -84,7 +89,7 @@ private void builtInPackageIsNewerCheck() throws InterruptedException {
Thread.sleep(100);
}
- if (VersionHelper.valueOf(installedBuiltIn.getParsedVersion()).greaterThan(VersionHelper.valueOf(installedNotBuiltIn.getParsedVersion()))) {
+ if (VersionComparator.greaterThan(installedBuiltIn.getParsedVersion(), installedNotBuiltIn.getParsedVersion())) {
SwingUtilities.invokeLater(() -> {
PreferencesData.setInteger("builtin_platform_is_newer", BaseNoGui.REVISION);
assert base.hasActiveEditor();
diff --git a/app/src/cc/arduino/contributions/ContributionsSelfCheck.java b/app/src/cc/arduino/contributions/ContributionsSelfCheck.java
index ed9f638c8a6..96fd987b099 100644
--- a/app/src/cc/arduino/contributions/ContributionsSelfCheck.java
+++ b/app/src/cc/arduino/contributions/ContributionsSelfCheck.java
@@ -29,32 +29,34 @@
package cc.arduino.contributions;
+import cc.arduino.UpdatableBoardsLibsFakeURLsHandler;
import cc.arduino.contributions.libraries.LibraryInstaller;
import cc.arduino.contributions.libraries.filters.UpdatableLibraryPredicate;
import cc.arduino.contributions.packages.ContributionInstaller;
import cc.arduino.contributions.packages.filters.UpdatablePlatformPredicate;
import cc.arduino.view.NotificationPopup;
-import processing.app.Base;
-import processing.app.BaseNoGui;
-import processing.app.Editor;
-import processing.app.I18n;
+import org.apache.logging.log4j.LogManager;
+import processing.app.*;
import javax.swing.*;
import javax.swing.event.HyperlinkListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
+import java.net.URL;
import java.util.TimerTask;
import static processing.app.I18n.tr;
-public class ContributionsSelfCheck extends TimerTask {
+public class ContributionsSelfCheck extends TimerTask implements NotificationPopup.OptionalButtonCallbacks {
private final Base base;
private final HyperlinkListener hyperlinkListener;
private final ContributionInstaller contributionInstaller;
private final LibraryInstaller libraryInstaller;
private final ProgressListener progressListener;
+ private final String boardsManagerURL = "http://boardsmanager/DropdownUpdatableCoresItem";
+ private final String libraryManagerURL = "http://librarymanager/DropdownUpdatableLibrariesItem";
private volatile boolean cancelled;
private volatile NotificationPopup notificationPopup;
@@ -73,25 +75,49 @@ public void run() {
updateContributionIndex();
updateLibrariesIndex();
- long updatablePlatforms = BaseNoGui.indexer.getPackages().stream()
- .flatMap(pack -> pack.getPlatforms().stream())
- .filter(new UpdatablePlatformPredicate()).count();
+ boolean updatablePlatforms = checkForUpdatablePlatforms();
- long updatableLibraries = BaseNoGui.librariesIndexer.getInstalledLibraries().stream()
- .filter(new UpdatableLibraryPredicate())
- .count();
+ boolean updatableLibraries = checkForUpdatableLibraries();
- if (updatableLibraries <= 0 && updatablePlatforms <= 0) {
+ if (!updatableLibraries && !updatablePlatforms) {
return;
}
- String text;
- if (updatableLibraries > 0 && updatablePlatforms <= 0) {
- text = I18n.format(tr("Updates available for some of your {0}libraries{1}"), "", "");
- } else if (updatableLibraries <= 0 && updatablePlatforms > 0) {
- text = I18n.format(tr("Updates available for some of your {0}boards{1}"), "", "");
+ boolean setAccessible = PreferencesData.getBoolean("ide.accessible");
+ final String text;
+ final String button1Name;
+ final String button2Name;
+ String openAnchorBoards = "";
+ String closeAnchorBoards = "";
+ String openAnchorLibraries = "";
+ String closeAnchorLibraries = "";
+
+ // if accessibility mode and board updates are available set the button name and clear the anchors
+ if(setAccessible && updatablePlatforms) {
+ button1Name = tr("Boards");
+ openAnchorBoards = "";
+ closeAnchorBoards = "";
+ }
+ else { // when not accessibility mode or no boards to update no button is needed
+ button1Name = null;
+ }
+
+ // if accessibility mode and libraries updates are available set the button name and clear the anchors
+ if (setAccessible && updatableLibraries) {
+ button2Name = tr("Libraries");
+ openAnchorLibraries = "";
+ closeAnchorLibraries = "";
+ }
+ else { // when not accessibility mode or no libraries to update no button is needed
+ button2Name = null;
+ }
+
+ if (updatableLibraries && !updatablePlatforms) {
+ text = I18n.format(tr("Updates available for some of your {0}libraries{1}"), openAnchorLibraries, closeAnchorLibraries);
+ } else if (!updatableLibraries && updatablePlatforms) {
+ text = I18n.format(tr("Updates available for some of your {0}boards{1}"), openAnchorBoards, closeAnchorBoards);
} else {
- text = I18n.format(tr("Updates available for some of your {0}boards{1} and {2}libraries{3}"), "", "", "", "");
+ text = I18n.format(tr("Updates available for some of your {0}boards{1} and {2}libraries{3}"), openAnchorBoards, closeAnchorBoards, openAnchorLibraries, closeAnchorLibraries);
}
if (cancelled) {
@@ -100,7 +126,13 @@ public void run() {
SwingUtilities.invokeLater(() -> {
Editor ed = base.getActiveEditor();
- notificationPopup = new NotificationPopup(ed, hyperlinkListener, text);
+ boolean accessibleIde = PreferencesData.getBoolean("ide.accessible");
+ if (accessibleIde) {
+ notificationPopup = new NotificationPopup(ed, hyperlinkListener, text, false, this, button1Name, button2Name);
+ }
+ else { // if not accessible view leave it the same
+ notificationPopup = new NotificationPopup(ed, hyperlinkListener, text);
+ }
if (ed.isFocused()) {
notificationPopup.begin();
return;
@@ -126,6 +158,35 @@ public void windowGainedFocus(WindowEvent evt) {
});
}
+ private void goToManager(String link) {
+ try {
+ ((UpdatableBoardsLibsFakeURLsHandler) hyperlinkListener).openBoardLibManager(new URL(link));
+ }
+ catch (Exception e){
+ LogManager.getLogger(ContributionsSelfCheck.class).warn("Exception while attempting to go to board manager", e);
+ }
+ }
+ // callback for boards button
+ public void onOptionalButton1Callback() {
+ goToManager(boardsManagerURL);
+ }
+
+ // callback for libraries button
+ public void onOptionalButton2Callback() {
+ goToManager(libraryManagerURL);
+ }
+
+ static boolean checkForUpdatablePlatforms() {
+ return BaseNoGui.indexer.getPackages().stream()
+ .flatMap(pack -> pack.getPlatforms().stream())
+ .anyMatch(new UpdatablePlatformPredicate());
+ }
+
+ static boolean checkForUpdatableLibraries() {
+ return BaseNoGui.librariesIndexer.getIndex().getLibraries().stream()
+ .anyMatch(new UpdatableLibraryPredicate());
+ }
+
@Override
public boolean cancel() {
cancelled = true;
diff --git a/app/src/cc/arduino/contributions/filters/NoopPredicate.java b/app/src/cc/arduino/contributions/filters/NoopPredicate.java
deleted file mode 100644
index 47580917e89..00000000000
--- a/app/src/cc/arduino/contributions/filters/NoopPredicate.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * This file is part of Arduino.
- *
- * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
- *
- * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * As a special exception, you may use this file as part of a free software
- * library without restriction. Specifically, if other files instantiate
- * templates or use macros or inline functions from this file, or you compile
- * this file and link it with other files to produce an executable, this
- * file does not by itself cause the resulting executable to be covered by
- * the GNU General Public License. This exception does not however
- * invalidate any other reasons why the executable file might be covered by
- * the GNU General Public License.
- */
-
-package cc.arduino.contributions.filters;
-
-import java.util.function.Predicate;
-
-public class NoopPredicate implements Predicate {
-
- @Override
- public boolean test(T input) {
- return true;
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof NoopPredicate;
- }
-
-
-}
diff --git a/app/src/cc/arduino/contributions/libraries/LibraryByTypeComparator.java b/app/src/cc/arduino/contributions/libraries/LibraryByTypeComparator.java
deleted file mode 100644
index dd11773c8b9..00000000000
--- a/app/src/cc/arduino/contributions/libraries/LibraryByTypeComparator.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * This file is part of Arduino.
- *
- * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
- *
- * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * As a special exception, you may use this file as part of a free software
- * library without restriction. Specifically, if other files instantiate
- * templates or use macros or inline functions from this file, or you compile
- * this file and link it with other files to produce an executable, this
- * file does not by itself cause the resulting executable to be covered by
- * the GNU General Public License. This exception does not however
- * invalidate any other reasons why the executable file might be covered by
- * the GNU General Public License.
- */
-
-package cc.arduino.contributions.libraries;
-
-import java.util.Comparator;
-
-public class LibraryByTypeComparator implements Comparator {
-
- private final LibraryTypeComparator libraryTypeComparator;
-
- public LibraryByTypeComparator() {
- this(new LibraryTypeComparator());
- }
-
- public LibraryByTypeComparator(LibraryTypeComparator libraryTypeComparator) {
- this.libraryTypeComparator = libraryTypeComparator;
- }
-
- @Override
- public int compare(ContributedLibrary o1, ContributedLibrary o2) {
- if (o1.getTypes() == null) {
- return 1;
- }
- if (o2.getTypes() == null) {
- return -1;
- }
- return libraryTypeComparator.compare(o1.getTypes().get(0), o2.getTypes().get(0));
- }
-
-}
diff --git a/app/src/cc/arduino/contributions/libraries/LibraryOfSameTypeComparator.java b/app/src/cc/arduino/contributions/libraries/LibraryOfSameTypeComparator.java
index 4a87e8bd090..74bd3767518 100644
--- a/app/src/cc/arduino/contributions/libraries/LibraryOfSameTypeComparator.java
+++ b/app/src/cc/arduino/contributions/libraries/LibraryOfSameTypeComparator.java
@@ -31,18 +31,23 @@
import java.util.Comparator;
-public class LibraryOfSameTypeComparator implements Comparator {
+import processing.app.packages.UserLibrary;
+
+public class LibraryOfSameTypeComparator implements Comparator {
@Override
- public int compare(ContributedLibrary o1, ContributedLibrary o2) {
- if (o1.getTypes() == null) {
+ public int compare(UserLibrary o1, UserLibrary o2) {
+ if (o1.getTypes().isEmpty() && o2.getTypes().isEmpty()) {
+ return 0;
+ }
+ if (o1.getTypes().isEmpty()) {
return 1;
}
- if (o2.getTypes() == null) {
+ if (o2.getTypes().isEmpty()) {
return -1;
}
if (!o1.getTypes().get(0).equals(o2.getTypes().get(0))) {
- return 0;
+ return o1.getTypes().get(0).compareTo(o2.getTypes().get(0));
}
return o1.getName().compareTo(o2.getName());
}
diff --git a/app/src/cc/arduino/contributions/libraries/filters/CategoryPredicate.java b/app/src/cc/arduino/contributions/libraries/filters/CategoryPredicate.java
deleted file mode 100644
index 3d857327315..00000000000
--- a/app/src/cc/arduino/contributions/libraries/filters/CategoryPredicate.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * This file is part of Arduino.
- *
- * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
- *
- * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * As a special exception, you may use this file as part of a free software
- * library without restriction. Specifically, if other files instantiate
- * templates or use macros or inline functions from this file, or you compile
- * this file and link it with other files to produce an executable, this
- * file does not by itself cause the resulting executable to be covered by
- * the GNU General Public License. This exception does not however
- * invalidate any other reasons why the executable file might be covered by
- * the GNU General Public License.
- */
-
-package cc.arduino.contributions.libraries.filters;
-
-import cc.arduino.contributions.libraries.ContributedLibrary;
-
-import java.util.function.Predicate;
-
-public class CategoryPredicate implements Predicate {
-
- private final String category;
-
- public CategoryPredicate(String category) {
- this.category = category;
- }
-
- @Override
- public boolean test(ContributedLibrary input) {
- return input.getCategory() != null && category.equals(input.getCategory());
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof CategoryPredicate && ((CategoryPredicate) obj).category.equals(category);
- }
-
-}
diff --git a/app/src/cc/arduino/contributions/libraries/filters/InstalledLibraryPredicate.java b/app/src/cc/arduino/contributions/libraries/filters/InstalledLibraryPredicate.java
deleted file mode 100644
index a606f823b17..00000000000
--- a/app/src/cc/arduino/contributions/libraries/filters/InstalledLibraryPredicate.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * This file is part of Arduino.
- *
- * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
- *
- * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * As a special exception, you may use this file as part of a free software
- * library without restriction. Specifically, if other files instantiate
- * templates or use macros or inline functions from this file, or you compile
- * this file and link it with other files to produce an executable, this
- * file does not by itself cause the resulting executable to be covered by
- * the GNU General Public License. This exception does not however
- * invalidate any other reasons why the executable file might be covered by
- * the GNU General Public License.
- */
-
-package cc.arduino.contributions.libraries.filters;
-
-import cc.arduino.contributions.filters.InstalledPredicate;
-import cc.arduino.contributions.libraries.ContributedLibrary;
-import processing.app.BaseNoGui;
-
-import java.util.List;
-import java.util.function.Predicate;
-
-public class InstalledLibraryPredicate implements Predicate {
-
- @Override
- public boolean test(ContributedLibrary input) {
- if (input.isInstalled()) {
- return true;
- }
-
- List libraries = BaseNoGui.librariesIndexer.getIndex().find(input.getName());
-
- return libraries.stream()
- .filter(new InstalledPredicate())
- .count() > 0;
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof InstalledLibraryPredicate;
- }
-
-}
diff --git a/app/src/cc/arduino/contributions/libraries/filters/OnlyUpstreamReleasePredicate.java b/app/src/cc/arduino/contributions/libraries/filters/OnlyUpstreamReleasePredicate.java
deleted file mode 100644
index ff3e678854a..00000000000
--- a/app/src/cc/arduino/contributions/libraries/filters/OnlyUpstreamReleasePredicate.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * This file is part of Arduino.
- *
- * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
- *
- * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * As a special exception, you may use this file as part of a free software
- * library without restriction. Specifically, if other files instantiate
- * templates or use macros or inline functions from this file, or you compile
- * this file and link it with other files to produce an executable, this
- * file does not by itself cause the resulting executable to be covered by
- * the GNU General Public License. This exception does not however
- * invalidate any other reasons why the executable file might be covered by
- * the GNU General Public License.
- */
-
-package cc.arduino.contributions.libraries.filters;
-
-import cc.arduino.contributions.libraries.ContributedLibrary;
-import processing.app.packages.UserLibrary;
-
-import java.util.function.Predicate;
-
-public class OnlyUpstreamReleasePredicate implements Predicate {
-
- @Override
- public boolean test(ContributedLibrary input) {
- return !(input instanceof UserLibrary);
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof OnlyUpstreamReleasePredicate;
- }
-}
diff --git a/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java b/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java
index 32165643b4a..e96f1759423 100644
--- a/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java
+++ b/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java
@@ -29,32 +29,34 @@
package cc.arduino.contributions.libraries.filters;
+import java.util.List;
+import java.util.function.Predicate;
+
import cc.arduino.contributions.VersionComparator;
import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.LibrariesIndexer;
import processing.app.BaseNoGui;
-import processing.app.packages.UserLibrary;
-
-import java.util.List;
-import java.util.function.Predicate;
public class UpdatableLibraryPredicate implements Predicate {
- private final VersionComparator versionComparator;
+ LibrariesIndexer librariesIndexer;
public UpdatableLibraryPredicate() {
- this.versionComparator = new VersionComparator();
+ librariesIndexer = BaseNoGui.librariesIndexer;
+ }
+
+ public UpdatableLibraryPredicate(LibrariesIndexer indexer) {
+ librariesIndexer = indexer;
}
@Override
- public boolean test(ContributedLibrary contributedLibrary) {
- String libraryName = contributedLibrary.getName();
- UserLibrary installed = BaseNoGui.librariesIndexer.getInstalledLibraries().getByName(libraryName);
- if (installed == null) {
+ public boolean test(ContributedLibrary lib) {
+ if (!lib.isLibraryInstalled()) {
return false;
}
- List libraries = BaseNoGui.librariesIndexer.getIndex().find(libraryName);
- return libraries.stream()
- .filter(library -> versionComparator.greaterThan(library.getParsedVersion(), installed.getParsedVersion()))
- .count() > 0;
+ String libraryName = lib.getName();
+ List libraries = librariesIndexer.getIndex().find(libraryName);
+ ContributedLibrary latest = libraries.stream().reduce(VersionComparator::max).get();
+ return !latest.isLibraryInstalled();
}
}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryReleases.java b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryReleases.java
deleted file mode 100644
index a1eb1424de7..00000000000
--- a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryReleases.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * This file is part of Arduino.
- *
- * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
- *
- * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * As a special exception, you may use this file as part of a free software
- * library without restriction. Specifically, if other files instantiate
- * templates or use macros or inline functions from this file, or you compile
- * this file and link it with other files to produce an executable, this
- * file does not by itself cause the resulting executable to be covered by
- * the GNU General Public License. This exception does not however
- * invalidate any other reasons why the executable file might be covered by
- * the GNU General Public License.
- */
-
-package cc.arduino.contributions.libraries.ui;
-
-import cc.arduino.contributions.DownloadableContributionBuiltInAtTheBottomComparator;
-import cc.arduino.contributions.filters.InstalledPredicate;
-import cc.arduino.contributions.libraries.ContributedLibrary;
-import cc.arduino.contributions.ui.FilteredAbstractTableModel;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class ContributedLibraryReleases {
-
- private final ContributedLibrary library;
- private final List releases;
- private final List versions;
-
- private ContributedLibrary selected;
-
- public ContributedLibraryReleases(ContributedLibrary library) {
- this.library = library;
- this.versions = new LinkedList<>();
- this.releases = new LinkedList<>();
- this.selected = null;
- add(library);
- }
-
- public ContributedLibrary getLibrary() {
- return library;
- }
-
- public List getReleases() {
- return releases;
- }
-
- public boolean shouldContain(ContributedLibrary lib) {
- return lib.getName().equals(library.getName());
- }
-
- public void add(ContributedLibrary library) {
- releases.add(library);
- String version = library.getParsedVersion();
- if (version != null) {
- versions.add(version);
- }
- selected = getLatest();
- }
-
- public ContributedLibrary getInstalled() {
- List installedReleases = releases.stream().filter(new InstalledPredicate()).collect(Collectors.toList());
- Collections.sort(installedReleases, new DownloadableContributionBuiltInAtTheBottomComparator());
-
- if (installedReleases.isEmpty()) {
- return null;
- }
-
- return installedReleases.get(0);
- }
-
- public ContributedLibrary getLatest() {
- return FilteredAbstractTableModel.getLatestOf(releases);
- }
-
- public ContributedLibrary getSelected() {
- return selected;
- }
-
- public void select(ContributedLibrary value) {
- for (ContributedLibrary plat : releases) {
- if (plat == value) {
- selected = plat;
- return;
- }
- }
- }
-}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryReleasesComparator.java b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryReleasesComparator.java
index 0395a16f666..11436b2ccfb 100644
--- a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryReleasesComparator.java
+++ b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryReleasesComparator.java
@@ -30,8 +30,11 @@
package cc.arduino.contributions.libraries.ui;
import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import java.util.Arrays;
import java.util.Comparator;
+import java.util.List;
public class ContributedLibraryReleasesComparator implements Comparator {
@@ -43,12 +46,14 @@ public ContributedLibraryReleasesComparator(String firstType) {
@Override
public int compare(ContributedLibraryReleases o1, ContributedLibraryReleases o2) {
- ContributedLibrary lib1 = o1.getLibrary();
- ContributedLibrary lib2 = o2.getLibrary();
+ ContributedLibrary lib1 = o1.getLatest();
+ ContributedLibrary lib2 = o2.getLatest();
+
+ List types1 = lib1.getTypes();
+ List types2 = lib2.getTypes();
+ if (types1 == null) types1 = Arrays.asList();
+ if (types2 == null) types2 = Arrays.asList();
- if (lib1.getTypes() == null || lib2.getTypes() == null) {
- return compareName(lib1, lib2);
- }
if (lib1.getTypes().contains(firstType) && lib2.getTypes().contains(firstType)) {
return compareName(lib1, lib2);
}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellEditor.java b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellEditor.java
index f2299ac52b5..7c2ecff383f 100644
--- a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellEditor.java
+++ b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellEditor.java
@@ -36,17 +36,15 @@
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Optional;
import javax.swing.JComboBox;
import javax.swing.JTable;
import cc.arduino.contributions.DownloadableContributionVersionComparator;
import cc.arduino.contributions.VersionComparator;
-import cc.arduino.contributions.filters.BuiltInPredicate;
-import cc.arduino.contributions.filters.InstalledPredicate;
import cc.arduino.contributions.libraries.ContributedLibrary;
-import cc.arduino.contributions.libraries.filters.OnlyUpstreamReleasePredicate;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
import cc.arduino.contributions.ui.InstallerTableCell;
import cc.arduino.utils.ReverseComparator;
@@ -76,64 +74,58 @@ public Component getTableCellEditorComponent(JTable table, Object value,
ContributedLibrary lib = (ContributedLibrary) chooser.getSelectedItem();
onInstall(lib, editorValue.getInstalled());
});
- editorCell.versionToInstallChooser.addItemListener(e -> editorValue
- .select((ContributedLibrary) editorCell.versionToInstallChooser
- .getSelectedItem()));
+ editorCell.versionToInstallChooser.addActionListener(e -> {
+ editorValue.select((ContributedLibrary) editorCell.versionToInstallChooser.getSelectedItem());
+ if (editorCell.versionToInstallChooser.getSelectedIndex() != 0) {
+ InstallerTableCell.dropdownSelected(true);
+ }
+ });
setEnabled(true);
- final ContributedLibrary installed = editorValue.getInstalled();
-
- List releases = editorValue.getReleases().stream()
- .filter(new OnlyUpstreamReleasePredicate())
- .collect(Collectors.toList());
- List uninstalledReleases = releases.stream()
- .filter(new InstalledPredicate().negate()).collect(Collectors.toList());
-
- List installedBuiltIn = releases.stream()
- .filter(new InstalledPredicate()).filter(new BuiltInPredicate())
- .collect(Collectors.toList());
+ final Optional mayInstalled = editorValue.getInstalled();
- if (installed != null && !installedBuiltIn.contains(installed)) {
- uninstalledReleases.addAll(installedBuiltIn);
+ List releases = editorValue.getReleases();
+ List notInstalled = new LinkedList<>(releases);
+ if (mayInstalled.isPresent()) {
+ notInstalled.remove(editorValue.getInstalled().get());
}
- Collections.sort(uninstalledReleases, new ReverseComparator<>(
+ Collections.sort(notInstalled, new ReverseComparator<>(
new DownloadableContributionVersionComparator()));
editorCell.downgradeChooser.removeAllItems();
editorCell.downgradeChooser.addItem(tr("Select version"));
- final List uninstalledPreviousReleases = new LinkedList<>();
- final List uninstalledNewerReleases = new LinkedList<>();
+ final List notInstalledPrevious = new LinkedList<>();
+ final List notInstalledNewer = new LinkedList<>();
- final VersionComparator versionComparator = new VersionComparator();
- uninstalledReleases.stream().forEach(input -> {
- if (installed == null
- || versionComparator.greaterThan(installed.getParsedVersion(),
- input.getParsedVersion())) {
- uninstalledPreviousReleases.add(input);
+ notInstalled.stream().forEach(input -> {
+ if (!mayInstalled.isPresent()
+ || VersionComparator.greaterThan(mayInstalled.get(), input)) {
+ notInstalledPrevious.add(input);
} else {
- uninstalledNewerReleases.add(input);
+ notInstalledNewer.add(input);
}
});
- uninstalledNewerReleases.forEach(editorCell.downgradeChooser::addItem);
- uninstalledPreviousReleases.forEach(editorCell.downgradeChooser::addItem);
+ notInstalledNewer.forEach(editorCell.downgradeChooser::addItem);
+ notInstalledPrevious.forEach(editorCell.downgradeChooser::addItem);
editorCell.downgradeChooser
- .setVisible(installed != null
- && (!uninstalledPreviousReleases.isEmpty()
- || uninstalledNewerReleases.size() > 1));
+ .setVisible(mayInstalled.isPresent()
+ && (!notInstalledPrevious.isEmpty()
+ || notInstalledNewer.size() > 1));
editorCell.downgradeButton
- .setVisible(installed != null
- && (!uninstalledPreviousReleases.isEmpty()
- || uninstalledNewerReleases.size() > 1));
+ .setVisible(mayInstalled.isPresent()
+ && (!notInstalledPrevious.isEmpty()
+ || notInstalledNewer.size() > 1));
editorCell.versionToInstallChooser.removeAllItems();
- uninstalledReleases.forEach(editorCell.versionToInstallChooser::addItem);
+ notInstalled.forEach(editorCell.versionToInstallChooser::addItem);
editorCell.versionToInstallChooser
- .setVisible(installed == null && uninstalledReleases.size() > 1);
+ .setVisible(!mayInstalled.isPresent() && notInstalled.size() > 1);
+ editorCell.setForeground(Color.BLACK);
editorCell.setBackground(new Color(218, 227, 227)); // #dae3e3
return editorCell;
}
@@ -152,7 +144,7 @@ protected void onRemove(ContributedLibrary selected) {
}
protected void onInstall(ContributedLibrary selected,
- ContributedLibrary installed) {
+ Optional mayInstalled) {
// Empty
}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellJPanel.java b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellJPanel.java
index 5a7c091683a..4f8c15f5642 100644
--- a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellJPanel.java
+++ b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellJPanel.java
@@ -3,20 +3,12 @@
import static processing.app.I18n.format;
import static processing.app.I18n.tr;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.Insets;
-
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JTable;
-import javax.swing.JTextPane;
+import java.awt.*;
+import java.util.Optional;
+
+import javax.swing.*;
import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
import javax.swing.event.HyperlinkEvent;
import javax.swing.text.Document;
import javax.swing.text.html.HTMLDocument;
@@ -24,12 +16,15 @@
import cc.arduino.contributions.DownloadableContributionVersionComparator;
import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
import cc.arduino.contributions.ui.InstallerTableCell;
import processing.app.Base;
+import processing.app.PreferencesData;
import processing.app.Theme;
public class ContributedLibraryTableCellJPanel extends JPanel {
+ final JButton moreInfoButton;
final JButton installButton;
final Component installButtonPlaceholder;
final JComboBox downgradeChooser;
@@ -38,12 +33,22 @@ public class ContributedLibraryTableCellJPanel extends JPanel {
final JPanel buttonsPanel;
final JPanel inactiveButtonsPanel;
final JLabel statusLabel;
+ final JTextPane description;
+ final TitledBorder titledBorder;
+ private final String moreInfoLbl = tr("More info");
public ContributedLibraryTableCellJPanel(JTable parentTable, Object value,
boolean isSelected) {
super();
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ // Actual title set below
+ titledBorder = BorderFactory.createTitledBorder("");
+ titledBorder.setTitleFont(getFont().deriveFont(Font.BOLD));
+ setBorder(titledBorder);
+
+ moreInfoButton = new JButton(moreInfoLbl);
+ moreInfoButton.setVisible(false);
installButton = new JButton(tr("Install"));
int width = installButton.getPreferredSize().width;
installButtonPlaceholder = Box.createRigidArea(new Dimension(width, 1));
@@ -52,25 +57,37 @@ public ContributedLibraryTableCellJPanel(JTable parentTable, Object value,
downgradeChooser = new JComboBox();
downgradeChooser.addItem("-");
- downgradeChooser.setMaximumSize(downgradeChooser.getPreferredSize());
- downgradeChooser.addItemListener(e -> {
+ downgradeChooser.setMaximumSize(new Dimension((int)downgradeChooser.getPreferredSize().getWidth() + 50, (int)downgradeChooser.getPreferredSize().getHeight()));
+ downgradeChooser.setMinimumSize(new Dimension((int)downgradeChooser.getPreferredSize().getWidth() + 50, (int)downgradeChooser.getPreferredSize().getHeight()));
+ downgradeChooser.addActionListener(e -> {
Object selectVersionItem = downgradeChooser.getItemAt(0);
- boolean disableDowngrade = (e.getItem() == selectVersionItem);
+ boolean disableDowngrade = (downgradeChooser.getSelectedItem() == selectVersionItem);
downgradeButton.setEnabled(!disableDowngrade);
+ if (!disableDowngrade) {
+ InstallerTableCell.dropdownSelected(true);
+ }
});
versionToInstallChooser = new JComboBox();
versionToInstallChooser.addItem("-");
versionToInstallChooser
- .setMaximumSize(versionToInstallChooser.getPreferredSize());
+ .setMaximumSize(new Dimension((int)versionToInstallChooser.getPreferredSize().getWidth() + 50, (int)versionToInstallChooser.getPreferredSize().getHeight()));
+ versionToInstallChooser
+ .setMinimumSize(new Dimension((int)versionToInstallChooser.getPreferredSize().getWidth() + 50, (int)versionToInstallChooser.getPreferredSize().getHeight()));
- makeNewDescription();
+ description = makeNewDescription();
+ add(description);
buttonsPanel = new JPanel();
buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.X_AXIS));
buttonsPanel.setOpaque(false);
buttonsPanel.add(Box.createHorizontalStrut(7));
+ if (PreferencesData.getBoolean("ide.accessible")) {
+ buttonsPanel.add(moreInfoButton);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(Box.createHorizontalStrut(15));
+ }
buttonsPanel.add(downgradeChooser);
buttonsPanel.add(Box.createHorizontalStrut(5));
buttonsPanel.add(downgradeButton);
@@ -103,23 +120,23 @@ public ContributedLibraryTableCellJPanel(JTable parentTable, Object value,
add(Box.createVerticalStrut(15));
ContributedLibraryReleases releases = (ContributedLibraryReleases) value;
- JTextPane description = makeNewDescription();
// FIXME: happens on macosx, don't know why
if (releases == null)
return;
ContributedLibrary selected = releases.getSelected();
- ContributedLibrary installed = releases.getInstalled();
+ titledBorder.setTitle(selected.getName());
+ Optional mayInstalled = releases.getInstalled();
boolean installable, upgradable;
- if (installed == null) {
+ if (!mayInstalled.isPresent()) {
installable = true;
upgradable = false;
} else {
installable = false;
upgradable = new DownloadableContributionVersionComparator()
- .compare(selected, installed) > 0;
+ .compare(selected, mayInstalled.get()) > 0;
}
if (installable) {
installButton.setText(tr("Install"));
@@ -133,7 +150,7 @@ public ContributedLibraryTableCellJPanel(JTable parentTable, Object value,
String name = selected.getName();
String author = selected.getAuthor();
// String maintainer = selectedLib.getMaintainer();
- String website = selected.getWebsite();
+ final String website = selected.getWebsite();
String sentence = selected.getSentence();
String paragraph = selected.getParagraph();
// String availableVer = selectedLib.getVersion();
@@ -144,8 +161,8 @@ public ContributedLibraryTableCellJPanel(JTable parentTable, Object value,
String desc = "";
// Library name...
- desc += format("{0}", name);
- if (installed != null && installed.isReadOnly()) {
+// desc += format("{0}", name);
+ if (mayInstalled.isPresent() && mayInstalled.get().isIDEBuiltIn()) {
desc += " Built-In ";
}
@@ -156,8 +173,8 @@ public ContributedLibraryTableCellJPanel(JTable parentTable, Object value,
}
// ...version.
- if (installed != null) {
- String installedVer = installed.getParsedVersion();
+ if (mayInstalled.isPresent()) {
+ String installedVer = mayInstalled.get().getParsedVersion();
if (installedVer == null) {
desc += " " + tr("Version unknown");
} else {
@@ -166,7 +183,7 @@ public ContributedLibraryTableCellJPanel(JTable parentTable, Object value,
}
desc += "";
- if (installed != null) {
+ if (mayInstalled.isPresent()) {
desc += " INSTALLED";
}
@@ -180,12 +197,13 @@ public ContributedLibraryTableCellJPanel(JTable parentTable, Object value,
desc += " ";
}
if (author != null && !author.isEmpty()) {
- desc += format("More info", website);
+ desc = setButtonOrLink(moreInfoButton, desc, moreInfoLbl, website);
}
desc += "";
description.setText(desc);
- description.setBackground(Color.WHITE);
+ // copy description to accessibility context for screen readers to use
+ description.getAccessibleContext().setAccessibleDescription(desc);
// for modelToView to work, the text area has to be sized. It doesn't
// matter if it's visible or not.
@@ -195,20 +213,29 @@ public ContributedLibraryTableCellJPanel(JTable parentTable, Object value,
InstallerTableCell
.setJTextPaneDimensionToFitContainedText(description,
parentTable.getBounds().width);
+ }
- if (isSelected) {
- setBackground(parentTable.getSelectionBackground());
- setForeground(parentTable.getSelectionForeground());
- } else {
- setBackground(parentTable.getBackground());
- setForeground(parentTable.getForeground());
+ // same function as in ContributedPlatformTableCellJPanel - is there a utils file this can move to?
+ private String setButtonOrLink(JButton button, String desc, String label, String url) {
+ boolean accessibleIDE = PreferencesData.getBoolean("ide.accessible");
+ String retString = desc;
+
+ if (accessibleIDE) {
+ button.setVisible(true);
+ button.addActionListener(e -> {
+ Base.openURL(url);
+ });
+ }
+ else {
+ // if not accessible IDE, keep link the same EXCEPT that now the link text is translated!
+ retString += format("{1} ", url, label);
}
+
+ return retString;
}
+ // TODO Make this a method of Theme
private JTextPane makeNewDescription() {
- if (getComponentCount() > 0) {
- remove(0);
- }
JTextPane description = new JTextPane();
description.setInheritsPopupMenu(true);
Insets margin = description.getMargin();
@@ -234,7 +261,6 @@ private JTextPane makeNewDescription() {
}
});
// description.addKeyListener(new DelegatingKeyListener(parentTable));
- add(description, 0);
return description;
}
@@ -243,4 +269,13 @@ public void setButtonsVisible(boolean enabled) {
buttonsPanel.setVisible(enabled);
inactiveButtonsPanel.setVisible(!enabled);
}
+
+ public void setForeground(Color c) {
+ super.setForeground(c);
+ // The description is not opaque, so copy our foreground color to it.
+ if (description != null)
+ description.setForeground(c);
+ if (titledBorder != null)
+ titledBorder.setTitleColor(c);
+ }
}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellRenderer.java b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellRenderer.java
index bc4b3ffd940..d107f90208a 100644
--- a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellRenderer.java
+++ b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellRenderer.java
@@ -46,6 +46,7 @@ public Component getTableCellRendererComponent(JTable table, Object value,
value, isSelected);
cell.setButtonsVisible(false);
+ cell.setForeground(Color.BLACK);
if (row % 2 == 0) {
cell.setBackground(new Color(236, 241, 241)); // #ecf1f1
} else {
diff --git a/app/src/cc/arduino/contributions/libraries/ui/DropdownAllLibraries.java b/app/src/cc/arduino/contributions/libraries/ui/DropdownAllLibraries.java
new file mode 100644
index 00000000000..ce50aca1432
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/DropdownAllLibraries.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.ui.DropdownItem;
+
+import java.util.function.Predicate;
+
+import static processing.app.I18n.tr;
+
+public class DropdownAllLibraries implements DropdownItem {
+
+ public String toString() {
+ return tr("All");
+ }
+
+ @Override
+ public Predicate getFilterPredicate() {
+ return x -> true;
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/DropdownInstalledLibraryItem.java b/app/src/cc/arduino/contributions/libraries/ui/DropdownInstalledLibraryItem.java
index bcd328ba057..e5b42e3b37a 100644
--- a/app/src/cc/arduino/contributions/libraries/ui/DropdownInstalledLibraryItem.java
+++ b/app/src/cc/arduino/contributions/libraries/ui/DropdownInstalledLibraryItem.java
@@ -29,28 +29,27 @@
package cc.arduino.contributions.libraries.ui;
-import cc.arduino.contributions.libraries.ContributedLibrary;
-import cc.arduino.contributions.libraries.filters.InstalledLibraryPredicate;
-import cc.arduino.contributions.ui.DropdownItem;
+import static processing.app.I18n.tr;
import java.util.function.Predicate;
-import static processing.app.I18n.tr;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.ui.DropdownItem;
-public class DropdownInstalledLibraryItem implements DropdownItem {
+public class DropdownInstalledLibraryItem implements DropdownItem {
public String toString() {
return tr("Installed");
}
@Override
- public Predicate getFilterPredicate() {
- return new InstalledLibraryPredicate();
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof DropdownInstalledLibraryItem;
+ public Predicate getFilterPredicate() {
+ return new Predicate() {
+ @Override
+ public boolean test(ContributedLibraryReleases t) {
+ return t.getInstalled().isPresent();
+ }
+ };
}
}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfCategoryItem.java b/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfCategoryItem.java
index a17a375995e..0d07b3ccf03 100644
--- a/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfCategoryItem.java
+++ b/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfCategoryItem.java
@@ -30,14 +30,14 @@
package cc.arduino.contributions.libraries.ui;
import cc.arduino.contributions.libraries.ContributedLibrary;
-import cc.arduino.contributions.libraries.filters.CategoryPredicate;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
import cc.arduino.contributions.ui.DropdownItem;
import java.util.function.Predicate;
import static processing.app.I18n.tr;
-public class DropdownLibraryOfCategoryItem implements DropdownItem {
+public class DropdownLibraryOfCategoryItem implements DropdownItem {
private final String category;
@@ -50,13 +50,14 @@ public String toString() {
}
@Override
- public Predicate getFilterPredicate() {
- return new CategoryPredicate(category);
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof DropdownLibraryOfCategoryItem && ((DropdownLibraryOfCategoryItem) obj).category.equals(category);
+ public Predicate getFilterPredicate() {
+ return new Predicate() {
+ @Override
+ public boolean test(ContributedLibraryReleases rel) {
+ ContributedLibrary lib = rel.getLatest();
+ return category.equals(lib.getCategory());
+ }
+ };
}
}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfTypeItem.java b/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfTypeItem.java
index 08e35ef2024..28f44a01894 100644
--- a/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfTypeItem.java
+++ b/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfTypeItem.java
@@ -29,15 +29,15 @@
package cc.arduino.contributions.libraries.ui;
-import cc.arduino.contributions.libraries.ContributedLibrary;
-import cc.arduino.contributions.libraries.filters.TypePredicate;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
import cc.arduino.contributions.ui.DropdownItem;
+import java.util.List;
import java.util.function.Predicate;
import static processing.app.I18n.tr;
-public class DropdownLibraryOfTypeItem implements DropdownItem {
+public class DropdownLibraryOfTypeItem implements DropdownItem {
private final String type;
@@ -50,13 +50,14 @@ public String toString() {
}
@Override
- public Predicate getFilterPredicate() {
- return new TypePredicate(type);
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof DropdownLibraryOfTypeItem && ((DropdownLibraryOfTypeItem) obj).type.equals(type);
+ public Predicate getFilterPredicate() {
+ return new Predicate() {
+ @Override
+ public boolean test(ContributedLibraryReleases lib) {
+ List types = lib.getLatest().getTypes();
+ return types != null && types.contains(type);
+ }
+ };
}
}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/DropdownUpdatableLibrariesItem.java b/app/src/cc/arduino/contributions/libraries/ui/DropdownUpdatableLibrariesItem.java
index e71cab89d03..2c75498f822 100644
--- a/app/src/cc/arduino/contributions/libraries/ui/DropdownUpdatableLibrariesItem.java
+++ b/app/src/cc/arduino/contributions/libraries/ui/DropdownUpdatableLibrariesItem.java
@@ -30,18 +30,28 @@
package cc.arduino.contributions.libraries.ui;
import cc.arduino.contributions.libraries.ContributedLibrary;
-import cc.arduino.contributions.libraries.filters.UpdatableLibraryPredicate;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
import cc.arduino.contributions.ui.DropdownItem;
+import java.util.Optional;
import java.util.function.Predicate;
import static processing.app.I18n.tr;
-public class DropdownUpdatableLibrariesItem implements DropdownItem {
+public class DropdownUpdatableLibrariesItem implements DropdownItem {
@Override
- public Predicate getFilterPredicate() {
- return new UpdatableLibraryPredicate();
+ public Predicate getFilterPredicate() {
+ return new Predicate() {
+ @Override
+ public boolean test(ContributedLibraryReleases lib) {
+ Optional mayInstalled = lib.getInstalled();
+ if (!mayInstalled.isPresent()) {
+ return false;
+ }
+ return !lib.getLatest().equals(mayInstalled.get());
+ }
+ };
}
@Override
@@ -49,9 +59,4 @@ public String toString() {
return tr("Updatable");
}
- @Override
- public boolean equals(Object obj) {
- return obj instanceof DropdownUpdatableLibrariesItem;
- }
-
}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/LibrariesIndexTableModel.java b/app/src/cc/arduino/contributions/libraries/ui/LibrariesIndexTableModel.java
index 40bd655ad8e..ceed4562f8d 100644
--- a/app/src/cc/arduino/contributions/libraries/ui/LibrariesIndexTableModel.java
+++ b/app/src/cc/arduino/contributions/libraries/ui/LibrariesIndexTableModel.java
@@ -30,6 +30,7 @@
package cc.arduino.contributions.libraries.ui;
import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
import cc.arduino.contributions.packages.ContributedPlatform;
import cc.arduino.contributions.ui.FilteredAbstractTableModel;
import processing.app.BaseNoGui;
@@ -38,11 +39,10 @@
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
-import java.util.stream.Stream;
@SuppressWarnings("serial")
public class LibrariesIndexTableModel
- extends FilteredAbstractTableModel {
+ extends FilteredAbstractTableModel {
private final List contributions = new ArrayList<>();
@@ -50,12 +50,12 @@ public class LibrariesIndexTableModel
private final Class>[] columnTypes = { ContributedPlatform.class };
- Predicate selectedCategoryFilter = null;
+ Predicate selectedCategoryFilter = null;
String selectedFilters[] = null;
public void updateIndexFilter(String filters[],
- Stream> additionalFilters) {
- selectedCategoryFilter = additionalFilters.reduce(Predicate::and).get();
+ Predicate additionalFilter) {
+ selectedCategoryFilter = additionalFilter;
selectedFilters = filters;
update();
}
@@ -87,17 +87,6 @@ private boolean stringContainsAll(String string, String filters[]) {
return true;
}
- private void addContribution(ContributedLibrary lib) {
- for (ContributedLibraryReleases contribution : contributions) {
- if (!contribution.shouldContain(lib))
- continue;
- contribution.add(lib);
- return;
- }
-
- contributions.add(new ContributedLibraryReleases(lib));
- }
-
@Override
public int getColumnCount() {
return columnNames.length;
@@ -150,17 +139,23 @@ public void update() {
fireTableDataChanged();
}
- private void applyFilterToLibrary(ContributedLibrary lib) {
+ private boolean filterCondition(ContributedLibraryReleases lib) {
if (selectedCategoryFilter != null && !selectedCategoryFilter.test(lib)) {
- return;
+ return false;
}
- String compoundTargetSearchText = lib.getName() + "\n" + lib.getParagraph()
- + "\n" + lib.getSentence();
+ ContributedLibrary latest = lib.getLatest();
+ String compoundTargetSearchText = latest.getName() + " "
+ + latest.getParagraph() + " "
+ + latest.getSentence();
+ if (latest.getProvidesIncludes() != null) {
+ compoundTargetSearchText += " " + latest.getProvidesIncludes();
+ }
if (!stringContainsAll(compoundTargetSearchText, selectedFilters)) {
- return;
+ return false;
}
- addContribution(lib);
+
+ return true;
}
public void updateLibrary(ContributedLibrary lib) {
@@ -190,12 +185,26 @@ public void updateLibrary(ContributedLibrary lib) {
fireTableRowsDeleted(row, row);
}
+ private List rebuildContributionsFromIndex() {
+ List res = new ArrayList<>();
+ BaseNoGui.librariesIndexer.getIndex().getLibraries(). //
+ forEach(lib -> {
+ for (ContributedLibraryReleases contribution : res) {
+ if (!contribution.shouldContain(lib))
+ continue;
+ contribution.add(lib);
+ return;
+ }
+
+ res.add(new ContributedLibraryReleases(lib));
+ });
+ return res;
+ }
+
private void updateContributions() {
+ List all = rebuildContributionsFromIndex();
contributions.clear();
- BaseNoGui.librariesIndexer.getIndex().getLibraries()
- .forEach(this::applyFilterToLibrary);
- BaseNoGui.librariesIndexer.getInstalledLibraries()
- .forEach(this::applyFilterToLibrary);
+ all.stream().filter(this::filterCondition).forEach(contributions::add);
Collections.sort(contributions,
new ContributedLibraryReleasesComparator("Arduino"));
}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/LibraryManagerUI.java b/app/src/cc/arduino/contributions/libraries/ui/LibraryManagerUI.java
index 4c94700197f..69ab10006c9 100644
--- a/app/src/cc/arduino/contributions/libraries/ui/LibraryManagerUI.java
+++ b/app/src/cc/arduino/contributions/libraries/ui/LibraryManagerUI.java
@@ -35,10 +35,12 @@
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
-import java.util.function.Predicate;
+import java.util.List;
+import java.util.Optional;
import javax.swing.Box;
import javax.swing.JComboBox;
@@ -46,11 +48,11 @@
import javax.swing.JOptionPane;
import javax.swing.table.TableCellRenderer;
-import cc.arduino.contributions.DownloadableContribution;
import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
import cc.arduino.contributions.libraries.LibraryInstaller;
import cc.arduino.contributions.libraries.LibraryTypeComparator;
-import cc.arduino.contributions.ui.DropdownAllItem;
+import cc.arduino.contributions.libraries.ui.MultiLibraryInstallDialog.Result;
import cc.arduino.contributions.ui.DropdownItem;
import cc.arduino.contributions.ui.FilteredAbstractTableModel;
import cc.arduino.contributions.ui.InstallerJDialog;
@@ -60,17 +62,20 @@
import processing.app.BaseNoGui;
@SuppressWarnings("serial")
-public class LibraryManagerUI extends InstallerJDialog {
+public class LibraryManagerUI extends InstallerJDialog {
private final JComboBox typeChooser;
private final LibraryInstaller installer;
- private Predicate typeFilter;
@Override
protected FilteredAbstractTableModel createContribModel() {
return new LibrariesIndexTableModel();
}
+ private LibrariesIndexTableModel getContribModel() {
+ return (LibrariesIndexTableModel) contribModel;
+ }
+
@Override
protected TableCellRenderer createCellRenderer() {
return new ContributedLibraryTableCellRenderer();
@@ -80,11 +85,11 @@ protected TableCellRenderer createCellRenderer() {
protected InstallerTableCell createCellEditor() {
return new ContributedLibraryTableCellEditor() {
@Override
- protected void onInstall(ContributedLibrary selectedLibrary, ContributedLibrary installedLibrary) {
- if (selectedLibrary.isReadOnly()) {
- onRemovePressed(installedLibrary);
+ protected void onInstall(ContributedLibrary selectedLibrary, Optional mayInstalledLibrary) {
+ if (mayInstalledLibrary.isPresent() && selectedLibrary.isIDEBuiltIn()) {
+ onRemovePressed(mayInstalledLibrary.get());
} else {
- onInstallPressed(selectedLibrary, installedLibrary);
+ onInstallPressed(selectedLibrary);
}
}
@@ -113,71 +118,60 @@ public LibraryManagerUI(Frame parent, LibraryInstaller installer) {
}
protected final ActionListener typeChooserActionListener = new ActionListener() {
-
@Override
public void actionPerformed(ActionEvent event) {
- DropdownItem selected = (DropdownItem) typeChooser.getSelectedItem();
- if (typeFilter == null || !typeFilter.equals(selected)) {
- typeFilter = selected.getFilterPredicate();
+ DropdownItem selected = (DropdownItem) typeChooser.getSelectedItem();
+ previousRowAtPoint = -1;
+ if (selected != null && extraFilter != selected.getFilterPredicate()) {
+ extraFilter = selected.getFilterPredicate();
if (contribTable.getCellEditor() != null) {
contribTable.getCellEditor().stopCellEditing();
}
- updateIndexFilter(filters, categoryFilter, typeFilter);
+ updateIndexFilter(filters, categoryFilter.and(extraFilter));
}
}
};
- @Override
- public void updateIndexFilter(String[] filters, Predicate... additionalFilters) {
- if (additionalFilters.length == 1) {
- additionalFilters = new Predicate[]{additionalFilters[0], typeFilter};
- }
- super.updateIndexFilter(filters, additionalFilters);
- }
+ private Collection oldCategories = new ArrayList<>();
+ private Collection oldTypes = new ArrayList<>();
public void updateUI() {
- DropdownItem previouslySelectedCategory = (DropdownItem) categoryChooser.getSelectedItem();
- DropdownItem previouslySelectedType = (DropdownItem) typeChooser.getSelectedItem();
-
- categoryChooser.removeActionListener(categoryChooserActionListener);
- typeChooser.removeActionListener(typeChooserActionListener);
+ // Check if categories or types have changed
+ Collection categories = BaseNoGui.librariesIndexer.getIndex().getCategories();
+ List types = new LinkedList<>(BaseNoGui.librariesIndexer.getIndex().getTypes());
+ Collections.sort(types, new LibraryTypeComparator());
- categoryFilter = null;
- categoryChooser.removeAllItems();
+ if (categories.equals(oldCategories) && types.equals(oldTypes)) {
+ return;
+ }
+ oldCategories = categories;
+ oldTypes = types;
// Load categories
- categoryChooser.addItem(new DropdownAllItem());
- Collection categories = BaseNoGui.librariesIndexer.getIndex().getCategories();
+ categoryFilter = x -> true;
+ categoryChooser.removeActionListener(categoryChooserActionListener);
+ categoryChooser.removeAllItems();
+ categoryChooser.addItem(new DropdownAllLibraries());
for (String category : categories) {
categoryChooser.addItem(new DropdownLibraryOfCategoryItem(category));
}
-
categoryChooser.setEnabled(categoryChooser.getItemCount() > 1);
-
categoryChooser.addActionListener(categoryChooserActionListener);
- if (previouslySelectedCategory != null) {
- categoryChooser.setSelectedItem(previouslySelectedCategory);
- } else {
- categoryChooser.setSelectedIndex(0);
- }
+ categoryChooser.setSelectedIndex(0);
- typeFilter = null;
+ // Load types
+ extraFilter = x -> true;
+ typeChooser.removeActionListener(typeChooserActionListener);
typeChooser.removeAllItems();
- typeChooser.addItem(new DropdownAllItem());
+ typeChooser.addItem(new DropdownAllLibraries());
typeChooser.addItem(new DropdownUpdatableLibrariesItem());
typeChooser.addItem(new DropdownInstalledLibraryItem());
- java.util.List types = new LinkedList<>(BaseNoGui.librariesIndexer.getIndex().getTypes());
- Collections.sort(types, new LibraryTypeComparator());
for (String type : types) {
typeChooser.addItem(new DropdownLibraryOfTypeItem(type));
}
typeChooser.setEnabled(typeChooser.getItemCount() > 1);
typeChooser.addActionListener(typeChooserActionListener);
- if (previouslySelectedType != null) {
- typeChooser.setSelectedItem(previouslySelectedType);
- } else {
- typeChooser.setSelectedIndex(0);
- }
+ typeChooser.setSelectedIndex(0);
filterField.setEnabled(contribModel.getRowCount() > 0);
}
@@ -208,6 +202,10 @@ protected void onUpdatePressed() {
setProgressVisible(true, "");
installer.updateIndex(this::setProgress);
onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
@@ -219,14 +217,35 @@ protected void onUpdatePressed() {
installerThread.start();
}
- public void onInstallPressed(final ContributedLibrary lib, final ContributedLibrary replaced) {
+ public void onInstallPressed(final ContributedLibrary lib) {
+ List deps = BaseNoGui.librariesIndexer.getIndex().resolveDependeciesOf(lib);
+ boolean depsInstalled = deps.stream().allMatch(l -> l.getInstalledLibrary().isPresent() || l.getName().equals(lib.getName()));
+ Result installDeps;
+ if (!depsInstalled) {
+ MultiLibraryInstallDialog dialog;
+ dialog = new MultiLibraryInstallDialog(this, lib, deps);
+ dialog.setLocationRelativeTo(this);
+ dialog.setVisible(true);
+ installDeps = dialog.getInstallDepsResult();
+ if (installDeps == Result.CANCEL)
+ return;
+ } else {
+ installDeps = Result.NONE;
+ }
clearErrorMessage();
installerThread = new Thread(() -> {
try {
setProgressVisible(true, tr("Installing..."));
- installer.install(lib, replaced, this::setProgress);
- onIndexesUpdated(); // TODO: Do a better job in refreshing only the needed element
- //getContribModel().updateLibrary(lib);
+ if (installDeps == Result.ALL) {
+ installer.install(deps, this::setProgress);
+ } else {
+ installer.install(lib, this::setProgress);
+ }
+ onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
@@ -253,8 +272,11 @@ public void onRemovePressed(final ContributedLibrary lib) {
try {
setProgressVisible(true, tr("Removing..."));
installer.remove(lib, this::setProgress);
- onIndexesUpdated(); // TODO: Do a better job in refreshing only the needed element
- //getContribModel().updateLibrary(lib);
+ onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
@@ -269,5 +291,4 @@ public void onRemovePressed(final ContributedLibrary lib) {
protected void onIndexesUpdated() throws Exception {
// Empty
}
-
-}
+}
\ No newline at end of file
diff --git a/app/src/cc/arduino/contributions/libraries/ui/MultiLibraryInstallDialog.java b/app/src/cc/arduino/contributions/libraries/ui/MultiLibraryInstallDialog.java
new file mode 100644
index 00000000000..75f7703f430
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/MultiLibraryInstallDialog.java
@@ -0,0 +1,177 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2017 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import static processing.app.I18n.format;
+import static processing.app.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Insets;
+import java.awt.Window;
+import java.awt.event.WindowEvent;
+import java.util.List;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JTextPane;
+import javax.swing.WindowConstants;
+import javax.swing.border.EmptyBorder;
+import javax.swing.text.Document;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.StyleSheet;
+
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.UnavailableContributedLibrary;
+import processing.app.Base;
+import processing.app.Theme;
+
+public class MultiLibraryInstallDialog extends JDialog {
+
+ enum Result {
+ ALL, NONE, CANCEL
+ }
+
+ private Result result = Result.CANCEL;
+
+ public MultiLibraryInstallDialog(Window parent, ContributedLibrary lib,
+ List dependencies) {
+ super(parent, format(tr("Dependencies for library {0}:{1}"), lib.getName(),
+ lib.getParsedVersion()),
+ ModalityType.APPLICATION_MODAL);
+ Container pane = getContentPane();
+ pane.setLayout(new BorderLayout());
+
+ pane.add(Box.createHorizontalStrut(10), BorderLayout.WEST);
+ pane.add(Box.createHorizontalStrut(10), BorderLayout.EAST);
+
+ {
+ JButton cancel = new JButton(tr("Cancel"));
+ cancel.addActionListener(ev -> {
+ result = Result.CANCEL;
+ setVisible(false);
+ });
+
+ JButton all = new JButton(tr("Install all"));
+ all.addActionListener(ev -> {
+ result = Result.ALL;
+ setVisible(false);
+ });
+
+ JButton none = new JButton(format(tr("Install '{0}' only"), lib.getName()));
+ none.addActionListener(ev -> {
+ result = Result.NONE;
+ setVisible(false);
+ });
+
+ Box buttonsBox = Box.createHorizontalBox();
+ buttonsBox.add(all);
+ buttonsBox.add(Box.createHorizontalStrut(5));
+ buttonsBox.add(none);
+ buttonsBox.add(Box.createHorizontalStrut(5));
+ buttonsBox.add(cancel);
+
+ JPanel buttonsPanel = new JPanel();
+ buttonsPanel.setBorder(new EmptyBorder(7, 10, 7, 10));
+ buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.Y_AXIS));
+ buttonsPanel.add(buttonsBox);
+
+ pane.add(buttonsPanel, BorderLayout.SOUTH);
+ }
+
+ {
+ String libName = format("{0}:{1}", lib.getName(),
+ lib.getParsedVersion());
+ String desc = format(tr("The library {0} needs some other library dependencies currently not installed:"),
+ libName);
+ desc += "
";
+ for (ContributedLibrary l : dependencies) {
+ if (l.getName().equals(lib.getName()))
+ continue;
+ if (l.getInstalledLibrary().isPresent())
+ continue;
+ if (l instanceof UnavailableContributedLibrary)
+ continue;
+ desc += format("- {0} ", l.getName());
+ }
+ desc += " ";
+ desc += tr("Would you like to install also all the missing dependencies?");
+
+ JTextPane textArea = makeNewDescription();
+ textArea.setContentType("text/html");
+ textArea.setText(desc);
+
+ JPanel libsList = new JPanel();
+ libsList.setLayout(new BoxLayout(libsList, BoxLayout.Y_AXIS));
+ libsList.add(textArea);
+ libsList.setBorder(new EmptyBorder(7, 7, 7, 7));
+ pane.add(libsList, BorderLayout.NORTH);
+ }
+
+ pack();
+ setResizable(false);
+ setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+
+ WindowEvent closing = new WindowEvent(this, WindowEvent.WINDOW_CLOSING);
+ Base.registerWindowCloseKeys(getRootPane(), e -> dispatchEvent(closing));
+ }
+
+ // TODO Make this a method of Theme
+ private JTextPane makeNewDescription() {
+ JTextPane description = new JTextPane();
+ description.setInheritsPopupMenu(true);
+ Insets margin = description.getMargin();
+ margin.bottom = 0;
+ description.setMargin(margin);
+ description.setContentType("text/html");
+ Document doc = description.getDocument();
+ if (doc instanceof HTMLDocument) {
+ HTMLDocument html = (HTMLDocument) doc;
+ StyleSheet s = html.getStyleSheet();
+ s.addRule("body { margin: 0; padding: 0;"
+ + "font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;"
+ + "color: black;" + "font-size: " + 15 * Theme.getScale() / 100
+ + "; }");
+ }
+ description.setOpaque(false);
+ description.setBorder(new EmptyBorder(4, 7, 7, 7));
+ description.setHighlighter(null);
+ description.setEditable(false);
+ add(description, 0);
+ return description;
+ }
+
+ public Result getInstallDepsResult() {
+ return result;
+ }
+}
diff --git a/app/src/cc/arduino/contributions/packages/filters/CategoryPredicate.java b/app/src/cc/arduino/contributions/packages/filters/CategoryPredicate.java
index a6261404109..c756e014f1b 100644
--- a/app/src/cc/arduino/contributions/packages/filters/CategoryPredicate.java
+++ b/app/src/cc/arduino/contributions/packages/filters/CategoryPredicate.java
@@ -46,9 +46,4 @@ public boolean test(ContributedPlatform input) {
return input.getCategory() != null && category.equals(input.getCategory());
}
- @Override
- public boolean equals(Object obj) {
- return obj instanceof CategoryPredicate && ((CategoryPredicate) obj).category.equals(category);
- }
-
}
diff --git a/app/src/cc/arduino/contributions/packages/filters/UpdatablePlatformPredicate.java b/app/src/cc/arduino/contributions/packages/filters/UpdatablePlatformPredicate.java
index 34a62ad7574..019b5118eee 100644
--- a/app/src/cc/arduino/contributions/packages/filters/UpdatablePlatformPredicate.java
+++ b/app/src/cc/arduino/contributions/packages/filters/UpdatablePlatformPredicate.java
@@ -38,12 +38,6 @@
public class UpdatablePlatformPredicate implements Predicate {
- private final VersionComparator versionComparator;
-
- public UpdatablePlatformPredicate() {
- this.versionComparator = new VersionComparator();
- }
-
@Override
public boolean test(ContributedPlatform contributedPlatform) {
String packageName = contributedPlatform.getParentPackage().getName();
@@ -56,7 +50,7 @@ public boolean test(ContributedPlatform contributedPlatform) {
List platforms = BaseNoGui.indexer.getIndex().findPlatforms(packageName, architecture);
return platforms.stream()
- .filter(platform -> versionComparator.greaterThan(platform.getParsedVersion(), installed.getParsedVersion()))
+ .filter(platform -> VersionComparator.greaterThan(platform.getParsedVersion(), installed.getParsedVersion()))
.count() > 0;
}
}
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformReleases.java b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformReleases.java
index 3c356749e7b..3545b1ff42b 100644
--- a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformReleases.java
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformReleases.java
@@ -34,8 +34,6 @@
import java.util.List;
import java.util.stream.Collectors;
-import cc.arduino.contributions.DownloadableContributionBuiltInAtTheBottomComparator;
-import cc.arduino.contributions.filters.InstalledPredicate;
import cc.arduino.contributions.packages.ContributedPackage;
import cc.arduino.contributions.packages.ContributedPlatform;
@@ -72,10 +70,9 @@ public void add(ContributedPlatform platform) {
public ContributedPlatform getInstalled() {
List installedReleases = releases.stream()
- .filter(new InstalledPredicate()).collect(Collectors.toList());
- Collections
- .sort(installedReleases,
- new DownloadableContributionBuiltInAtTheBottomComparator());
+ .filter(p -> p.isInstalled()) //
+ .collect(Collectors.toList());
+ Collections.sort(installedReleases, ContributedPlatform.BUILTIN_AS_LAST);
if (installedReleases.isEmpty()) {
return null;
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellEditor.java b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellEditor.java
index cc3cfbb63f5..7fe221fa340 100644
--- a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellEditor.java
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellEditor.java
@@ -42,8 +42,6 @@
import cc.arduino.contributions.DownloadableContributionVersionComparator;
import cc.arduino.contributions.VersionComparator;
-import cc.arduino.contributions.filters.BuiltInPredicate;
-import cc.arduino.contributions.filters.InstalledPredicate;
import cc.arduino.contributions.packages.ContributedPlatform;
import cc.arduino.contributions.ui.InstallerTableCell;
import cc.arduino.utils.ReverseComparator;
@@ -74,20 +72,25 @@ public Component getTableCellEditorComponent(JTable table, Object _value,
.getSelectedItem();
onInstall(selected, value.getInstalled());
});
- cell.versionToInstallChooser.addItemListener(e -> value
- .select((ContributedPlatform) cell.versionToInstallChooser
- .getSelectedItem()));
+ cell.versionToInstallChooser.addActionListener(e -> {
+ value.select((ContributedPlatform) cell.versionToInstallChooser.getSelectedItem());
+ if (cell.versionToInstallChooser.getSelectedIndex() != 0) {
+ InstallerTableCell.dropdownSelected(true);
+ }
+ });
setEnabled(true);
final ContributedPlatform installed = value.getInstalled();
List releases = new LinkedList<>(value.releases);
- List uninstalledReleases = releases.stream()
- .filter(new InstalledPredicate().negate()).collect(Collectors.toList());
+ List uninstalledReleases = releases.stream() //
+ .filter(p -> !p.isInstalled()) //
+ .collect(Collectors.toList());
- List installedBuiltIn = releases.stream()
- .filter(new InstalledPredicate()).filter(new BuiltInPredicate())
+ List installedBuiltIn = releases.stream() //
+ .filter(p -> p.isInstalled()) //
+ .filter(p -> p.isBuiltIn()) //
.collect(Collectors.toList());
if (installed != null && !installedBuiltIn.contains(installed)) {
@@ -103,10 +106,9 @@ public Component getTableCellEditorComponent(JTable table, Object _value,
final List uninstalledPreviousReleases = new LinkedList<>();
final List uninstalledNewerReleases = new LinkedList<>();
- final VersionComparator versionComparator = new VersionComparator();
uninstalledReleases.stream().forEach(input -> {
if (installed == null
- || versionComparator.greaterThan(installed.getParsedVersion(),
+ || VersionComparator.greaterThan(installed.getParsedVersion(),
input.getParsedVersion())) {
uninstalledPreviousReleases.add(input);
} else {
@@ -127,7 +129,8 @@ public Component getTableCellEditorComponent(JTable table, Object _value,
cell.versionToInstallChooser
.setVisible(installed == null && uninstalledReleases.size() > 1);
- cell.update(table, _value, true, !installedBuiltIn.isEmpty());
+ cell.update(table, _value, !installedBuiltIn.isEmpty());
+ cell.setForeground(Color.BLACK);
cell.setBackground(new Color(218, 227, 227)); // #dae3e3
return cell;
}
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellJPanel.java b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellJPanel.java
index 75464f3d0ee..75e26783dfe 100644
--- a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellJPanel.java
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellJPanel.java
@@ -32,20 +32,11 @@
import static processing.app.I18n.format;
import static processing.app.I18n.tr;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.Insets;
-
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JTable;
-import javax.swing.JTextPane;
+import java.awt.*;
+
+import javax.swing.*;
import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
import javax.swing.event.HyperlinkEvent;
import javax.swing.text.Document;
import javax.swing.text.html.HTMLDocument;
@@ -57,11 +48,14 @@
import cc.arduino.contributions.packages.ContributedPlatform;
import cc.arduino.contributions.ui.InstallerTableCell;
import processing.app.Base;
+import processing.app.PreferencesData;
import processing.app.Theme;
@SuppressWarnings("serial")
public class ContributedPlatformTableCellJPanel extends JPanel {
+ final JButton moreInfoButton;
+ final JButton onlineHelpButton;
final JButton installButton;
final JButton removeButton;
final Component removeButtonPlaceholder;
@@ -72,13 +66,26 @@ public class ContributedPlatformTableCellJPanel extends JPanel {
final JPanel buttonsPanel;
final JPanel inactiveButtonsPanel;
final JLabel statusLabel;
+ final JTextPane description;
+ final TitledBorder titledBorder;
+ private final String moreInfoLbl = tr("More Info");
+ private final String onlineHelpLbl = tr("Online Help");
public ContributedPlatformTableCellJPanel() {
super();
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ // Actual title set by update()
+ titledBorder = BorderFactory.createTitledBorder("");
+ titledBorder.setTitleFont(getFont().deriveFont(Font.BOLD));
+ setBorder(titledBorder);
+
{
installButton = new JButton(tr("Install"));
+ moreInfoButton = new JButton(moreInfoLbl);
+ moreInfoButton.setVisible(false);
+ onlineHelpButton = new JButton(onlineHelpLbl);
+ onlineHelpButton.setVisible(false);
int width = installButton.getPreferredSize().width;
installButtonPlaceholder = Box.createRigidArea(new Dimension(width, 1));
}
@@ -98,6 +105,9 @@ public ContributedPlatformTableCellJPanel() {
Object selectVersionItem = downgradeChooser.getItemAt(0);
boolean disableDowngrade = (e.getItem() == selectVersionItem);
downgradeButton.setEnabled(!disableDowngrade);
+ if (!disableDowngrade) {
+ InstallerTableCell.dropdownSelected(true);
+ }
});
versionToInstallChooser = new JComboBox();
@@ -105,13 +115,21 @@ public ContributedPlatformTableCellJPanel() {
versionToInstallChooser
.setMaximumSize(versionToInstallChooser.getPreferredSize());
- makeNewDescription();
+ description = makeNewDescription();
+ add(description);
buttonsPanel = new JPanel();
buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.X_AXIS));
buttonsPanel.setOpaque(false);
buttonsPanel.add(Box.createHorizontalStrut(7));
+ if (PreferencesData.getBoolean("ide.accessible")) { // only add the buttons if needed
+ buttonsPanel.add(onlineHelpButton);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(moreInfoButton);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(Box.createHorizontalStrut(15));
+ }
buttonsPanel.add(downgradeChooser);
buttonsPanel.add(Box.createHorizontalStrut(5));
buttonsPanel.add(downgradeButton);
@@ -146,11 +164,27 @@ public ContributedPlatformTableCellJPanel() {
add(Box.createVerticalStrut(15));
}
- void update(JTable parentTable, Object value, boolean isSelected,
- boolean hasBuiltInRelease) {
- ContributedPlatformReleases releases = (ContributedPlatformReleases) value;
+ // same function as in ContributedLibraryTableCellJPanel - is there a utils file this can move to?
+ private String setButtonOrLink(JButton button, String desc, String label, String url) {
+ boolean accessibleIDE = PreferencesData.getBoolean("ide.accessible");
+ String retString = desc;
- JTextPane description = makeNewDescription();
+ if (accessibleIDE) {
+ button.setVisible(true);
+ button.addActionListener(e -> {
+ Base.openURL(url);
+ });
+ }
+ else {
+ // if not accessible IDE, keep link the same EXCEPT that now the link text is translated!
+ retString += " " + format("{1} ", url, label);
+ }
+
+ return retString;
+ }
+
+ void update(JTable parentTable, Object value, boolean hasBuiltInRelease) {
+ ContributedPlatformReleases releases = (ContributedPlatformReleases) value;
// FIXME: happens on macosx, don't know why
if (releases == null) {
@@ -158,6 +192,7 @@ void update(JTable parentTable, Object value, boolean isSelected,
}
ContributedPlatform selected = releases.getSelected();
+ titledBorder.setTitle(selected.getName());
ContributedPlatform installed = releases.getInstalled();
boolean removable, installable, upgradable;
@@ -167,7 +202,7 @@ void update(JTable parentTable, Object value, boolean isSelected,
upgradable = false;
} else {
installable = false;
- removable = !installed.isReadOnly() && !hasBuiltInRelease;
+ removable = !installed.isBuiltIn() && !hasBuiltInRelease;
upgradable = new DownloadableContributionVersionComparator()
.compare(selected, installed) > 0;
}
@@ -183,8 +218,8 @@ void update(JTable parentTable, Object value, boolean isSelected,
removeButtonPlaceholder.setVisible(!removable);
String desc = "";
- desc += "" + selected.getName() + "";
- if (installed != null && installed.isReadOnly()) {
+// desc += "" + selected.getName() + "";
+ if (installed != null && installed.isBuiltIn()) {
desc += " Built-In ";
}
@@ -213,21 +248,23 @@ void update(JTable parentTable, Object value, boolean isSelected,
} else if (selected.getParentPackage().getHelp() != null) {
help = selected.getParentPackage().getHelp();
}
+
if (help != null) {
String url = help.getOnline();
if (url != null && !url.isEmpty()) {
- desc += " " + format("Online help ", url);
+ desc = setButtonOrLink(onlineHelpButton, desc, onlineHelpLbl, url);
}
}
String url = selected.getParentPackage().getWebsiteURL();
if (url != null && !url.isEmpty()) {
- desc += " " + format("More info", url);
+ desc = setButtonOrLink(moreInfoButton, desc, moreInfoLbl, url);
}
desc += "";
description.setText(desc);
- description.setBackground(Color.WHITE);
+ // copy description to accessibility context for screen readers to use
+ description.getAccessibleContext().setAccessibleDescription(desc);
// for modelToView to work, the text area has to be sized. It doesn't
// matter if it's visible or not.
@@ -237,20 +274,9 @@ void update(JTable parentTable, Object value, boolean isSelected,
int width = parentTable.getBounds().width;
InstallerTableCell.setJTextPaneDimensionToFitContainedText(description,
width);
-
- if (isSelected) {
- setBackground(parentTable.getSelectionBackground());
- setForeground(parentTable.getSelectionForeground());
- } else {
- setBackground(parentTable.getBackground());
- setForeground(parentTable.getForeground());
- }
}
private JTextPane makeNewDescription() {
- if (getComponentCount() > 0) {
- remove(0);
- }
JTextPane description = new JTextPane();
description.setInheritsPopupMenu(true);
Insets margin = description.getMargin();
@@ -274,7 +300,6 @@ private JTextPane makeNewDescription() {
Base.openURL(e.getDescription());
}
});
- add(description, 0);
return description;
}
@@ -285,4 +310,12 @@ public void setButtonsVisible(boolean enabled) {
inactiveButtonsPanel.setVisible(!enabled);
}
+ public void setForeground(Color c) {
+ super.setForeground(c);
+ // The description is not opaque, so copy our foreground color to it.
+ if (description != null)
+ description.setForeground(c);
+ if (titledBorder != null)
+ titledBorder.setTitleColor(c);
+ }
}
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellRenderer.java b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellRenderer.java
index cc4b1d0c186..b6f6aae015c 100644
--- a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellRenderer.java
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellRenderer.java
@@ -44,8 +44,9 @@ public Component getTableCellRendererComponent(JTable table, Object value,
int column) {
ContributedPlatformTableCellJPanel cell = new ContributedPlatformTableCellJPanel();
cell.setButtonsVisible(false);
- cell.update(table, value, isSelected, false);
+ cell.update(table, value, false);
+ cell.setForeground(Color.BLACK);
if (row % 2 == 0) {
cell.setBackground(new Color(236, 241, 241)); // #ecf1f1
} else {
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributionIndexTableModel.java b/app/src/cc/arduino/contributions/packages/ui/ContributionIndexTableModel.java
index 283df1067c2..7472c62479b 100644
--- a/app/src/cc/arduino/contributions/packages/ui/ContributionIndexTableModel.java
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributionIndexTableModel.java
@@ -39,7 +39,6 @@
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
@SuppressWarnings("serial")
public class ContributionIndexTableModel
@@ -48,12 +47,18 @@ public class ContributionIndexTableModel
private final List contributions = new ArrayList<>();
private final String[] columnNames = { "Description" };
private final Class>[] columnTypes = { ContributedPlatform.class };
+ private Predicate filter;
+ private String[] filters;
public void updateIndexFilter(String[] filters,
- Stream> additionalFilters) {
+ Predicate filter) {
+ this.filter = filter;
+ this.filters = filters;
+ updateContributions();
+ }
+
+ private void updateContributions() {
contributions.clear();
- Predicate filter = additionalFilters
- .reduce(Predicate::and).get();
for (ContributedPackage pack : BaseNoGui.indexer.getPackages()) {
for (ContributedPlatform platform : pack.getPlatforms()) {
String compoundTargetSearchText = platform.getName() + "\n"
@@ -149,6 +154,7 @@ public ContributedPlatform getSelectedRelease(int row) {
}
public void update() {
+ updateContributions();
fireTableDataChanged();
}
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributionManagerUI.java b/app/src/cc/arduino/contributions/packages/ui/ContributionManagerUI.java
index 7e1389637ce..0c949fe1cd3 100644
--- a/app/src/cc/arduino/contributions/packages/ui/ContributionManagerUI.java
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributionManagerUI.java
@@ -29,7 +29,6 @@
package cc.arduino.contributions.packages.ui;
-import cc.arduino.contributions.DownloadableContribution;
import cc.arduino.contributions.packages.ContributedPlatform;
import cc.arduino.contributions.packages.ContributionInstaller;
import cc.arduino.contributions.ui.*;
@@ -41,6 +40,7 @@
import javax.swing.table.TableCellRenderer;
import java.awt.*;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -72,7 +72,7 @@ protected InstallerTableCell createCellEditor() {
@Override
protected void onInstall(ContributedPlatform selected,
ContributedPlatform installed) {
- if (selected.isReadOnly()) {
+ if (selected.isBuiltIn()) {
onRemovePressed(installed, false);
} else {
onInstallPressed(selected, installed);
@@ -92,31 +92,28 @@ public ContributionManagerUI(Frame parent, ContributionInstaller installer) {
this.installer = installer;
}
+ private Collection oldCategories = new ArrayList<>();
+
public void updateUI() {
- DropdownItem previouslySelectedCategory = (DropdownItem) categoryChooser
- .getSelectedItem();
+ // Check if categories have changed
+ Collection categories = BaseNoGui.indexer.getCategories();
+ if (categories.equals(oldCategories)) {
+ return;
+ }
+ oldCategories = categories;
categoryChooser.removeActionListener(categoryChooserActionListener);
-
- categoryFilter = null;
- categoryChooser.removeAllItems();
-
- filterField.setEnabled(getContribModel().getRowCount() > 0);
-
- categoryChooser.addActionListener(categoryChooserActionListener);
-
// Enable categories combo only if there are two or more choices
+ filterField.setEnabled(getContribModel().getRowCount() > 0);
+ categoryFilter = x -> true;
+ categoryChooser.removeAllItems();
categoryChooser.addItem(new DropdownAllCoresItem());
categoryChooser.addItem(new DropdownUpdatableCoresItem());
- Collection categories = BaseNoGui.indexer.getCategories();
for (String s : categories) {
categoryChooser.addItem(new DropdownCoreOfCategoryItem(s));
}
- if (previouslySelectedCategory != null) {
- categoryChooser.setSelectedItem(previouslySelectedCategory);
- } else {
- categoryChooser.setSelectedIndex(0);
- }
+ categoryChooser.addActionListener(categoryChooserActionListener);
+ categoryChooser.setSelectedIndex(0);
}
public void setProgress(Progress progress) {
@@ -147,6 +144,10 @@ public void onUpdatePressed() {
.updateIndex(this::setProgress);
installer.deleteUnknownFiles(downloadedPackageIndexFiles);
onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
@@ -167,11 +168,15 @@ public void onInstallPressed(final ContributedPlatform platformToInstall,
List errors = new LinkedList<>();
try {
setProgressVisible(true, tr("Installing..."));
- if (platformToRemove != null && !platformToRemove.isReadOnly()) {
+ if (platformToRemove != null && !platformToRemove.isBuiltIn()) {
errors.addAll(installer.remove(platformToRemove));
}
errors.addAll(installer.install(platformToInstall, this::setProgress));
onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
@@ -210,6 +215,10 @@ public void onRemovePressed(final ContributedPlatform platform,
setProgressVisible(true, tr("Removing..."));
installer.remove(platform);
onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
diff --git a/app/src/cc/arduino/contributions/packages/ui/DropdownAllCoresItem.java b/app/src/cc/arduino/contributions/packages/ui/DropdownAllCoresItem.java
index 87b671e5a0d..15a7bf531d8 100644
--- a/app/src/cc/arduino/contributions/packages/ui/DropdownAllCoresItem.java
+++ b/app/src/cc/arduino/contributions/packages/ui/DropdownAllCoresItem.java
@@ -29,7 +29,6 @@
package cc.arduino.contributions.packages.ui;
-import cc.arduino.contributions.filters.NoopPredicate;
import cc.arduino.contributions.packages.ContributedPlatform;
import cc.arduino.contributions.ui.DropdownItem;
@@ -45,11 +44,7 @@ public String toString() {
@Override
public Predicate getFilterPredicate() {
- return new NoopPredicate<>();
+ return x -> true;
}
- @Override
- public boolean equals(Object obj) {
- return obj instanceof DropdownAllCoresItem;
- }
}
diff --git a/app/src/cc/arduino/contributions/packages/ui/DropdownCoreOfCategoryItem.java b/app/src/cc/arduino/contributions/packages/ui/DropdownCoreOfCategoryItem.java
index 0b8cd2e9ac4..6de03b587b3 100644
--- a/app/src/cc/arduino/contributions/packages/ui/DropdownCoreOfCategoryItem.java
+++ b/app/src/cc/arduino/contributions/packages/ui/DropdownCoreOfCategoryItem.java
@@ -54,9 +54,4 @@ public Predicate getFilterPredicate() {
return new CategoryPredicate(category);
}
- @Override
- public boolean equals(Object obj) {
- return obj instanceof DropdownCoreOfCategoryItem && ((DropdownCoreOfCategoryItem) obj).category.equals(category);
- }
-
}
diff --git a/app/src/cc/arduino/contributions/packages/ui/DropdownUpdatableCoresItem.java b/app/src/cc/arduino/contributions/packages/ui/DropdownUpdatableCoresItem.java
index e5103a5557c..7f704b388b5 100644
--- a/app/src/cc/arduino/contributions/packages/ui/DropdownUpdatableCoresItem.java
+++ b/app/src/cc/arduino/contributions/packages/ui/DropdownUpdatableCoresItem.java
@@ -49,8 +49,4 @@ public String toString() {
return tr("Updatable");
}
- @Override
- public boolean equals(Object obj) {
- return obj instanceof DropdownUpdatableCoresItem;
- }
}
diff --git a/app/src/cc/arduino/contributions/ui/DropdownAllItem.java b/app/src/cc/arduino/contributions/ui/DropdownAllItem.java
deleted file mode 100644
index ca77faf5bfe..00000000000
--- a/app/src/cc/arduino/contributions/ui/DropdownAllItem.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * This file is part of Arduino.
- *
- * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
- *
- * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * As a special exception, you may use this file as part of a free software
- * library without restriction. Specifically, if other files instantiate
- * templates or use macros or inline functions from this file, or you compile
- * this file and link it with other files to produce an executable, this
- * file does not by itself cause the resulting executable to be covered by
- * the GNU General Public License. This exception does not however
- * invalidate any other reasons why the executable file might be covered by
- * the GNU General Public License.
- */
-
-package cc.arduino.contributions.ui;
-
-import cc.arduino.contributions.DownloadableContribution;
-import cc.arduino.contributions.filters.NoopPredicate;
-
-import java.util.function.Predicate;
-
-import static processing.app.I18n.tr;
-
-public class DropdownAllItem implements DropdownItem {
-
- public String toString() {
- return tr("All");
- }
-
- @Override
- public Predicate getFilterPredicate() {
- return new NoopPredicate<>();
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof DropdownAllItem;
- }
-
-}
diff --git a/app/src/cc/arduino/contributions/ui/FilterJTextField.java b/app/src/cc/arduino/contributions/ui/FilterJTextField.java
index 9dc7fd8d6b5..f4cb3420340 100644
--- a/app/src/cc/arduino/contributions/ui/FilterJTextField.java
+++ b/app/src/cc/arduino/contributions/ui/FilterJTextField.java
@@ -41,6 +41,7 @@ public class FilterJTextField extends JTextField {
private final String filterHint;
private boolean showingHint;
+ private Timer timer;
public FilterJTextField(String hint) {
super(hint);
@@ -48,6 +49,10 @@ public FilterJTextField(String hint) {
showingHint = true;
updateStyle();
+ timer = new Timer(1000, e -> {
+ applyFilter();
+ timer.stop();
+ });
addFocusListener(new FocusListener() {
public void focusLost(FocusEvent focusEvent) {
@@ -68,33 +73,40 @@ public void focusGained(FocusEvent focusEvent) {
getDocument().addDocumentListener(new DocumentListener() {
public void removeUpdate(DocumentEvent e) {
- applyFilter();
+ spawnTimer();
}
public void insertUpdate(DocumentEvent e) {
- applyFilter();
+ spawnTimer();
}
public void changedUpdate(DocumentEvent e) {
- applyFilter();
+
+ }
+ });
+
+ addActionListener(e -> {
+ if (timer.isRunning()) {
+ timer.stop();
}
+ applyFilter();
});
}
- private String lastFilter = "";
+ private void spawnTimer() {
+ if (timer.isRunning()) {
+ timer.stop();
+ }
+ timer.start();
+ }
- private void applyFilter() {
+ public void applyFilter() {
String filter = showingHint ? "" : getText();
filter = filter.toLowerCase();
// Replace anything but 0-9, a-z, or : with a space
filter = filter.replaceAll("[^\\x30-\\x39^\\x61-\\x7a^\\x3a]", " ");
- // Fire event only if the filter is changed
- if (filter.equals(lastFilter))
- return;
-
- lastFilter = filter;
onFilter(filter.split(" "));
}
@@ -112,4 +124,23 @@ private void updateStyle() {
setFont(getFont().deriveFont(Font.PLAIN));
}
}
+
+ @Override
+ public void paste() {
+
+ // Same precondition check as JTextComponent#paste().
+ if (!isEditable() || !isEnabled()) {
+ return;
+ }
+
+ // Disable hint to prevent the focus handler from clearing the pasted text.
+ if (showingHint) {
+ showingHint = false;
+ setText("");
+ updateStyle();
+ }
+
+ // Perform the paste.
+ super.paste();
+ }
}
diff --git a/app/src/cc/arduino/contributions/ui/FilteredAbstractTableModel.java b/app/src/cc/arduino/contributions/ui/FilteredAbstractTableModel.java
index 2e423a47e79..348561c31bf 100644
--- a/app/src/cc/arduino/contributions/ui/FilteredAbstractTableModel.java
+++ b/app/src/cc/arduino/contributions/ui/FilteredAbstractTableModel.java
@@ -37,11 +37,10 @@
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
-import java.util.stream.Stream;
public abstract class FilteredAbstractTableModel extends AbstractTableModel {
- abstract public void updateIndexFilter(String[] filters, Stream> additionalFilters);
+ abstract public void updateIndexFilter(String[] filters, Predicate additionalFilter);
public static T getLatestOf(List contribs) {
contribs = new LinkedList<>(contribs);
diff --git a/app/src/cc/arduino/contributions/ui/InstallerJDialog.java b/app/src/cc/arduino/contributions/ui/InstallerJDialog.java
index 4563b21fdd9..8abff8f3454 100644
--- a/app/src/cc/arduino/contributions/ui/InstallerJDialog.java
+++ b/app/src/cc/arduino/contributions/ui/InstallerJDialog.java
@@ -43,9 +43,12 @@
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.WindowAdapter;
import java.util.function.Predicate;
-import java.util.stream.Stream;
+import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@@ -53,6 +56,7 @@
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
@@ -63,10 +67,10 @@
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
+import javax.swing.text.DefaultEditorKit;
import cc.arduino.contributions.ui.listeners.AbstractKeyListener;
import processing.app.Base;
-import processing.app.Theme;
public abstract class InstallerJDialog extends JDialog {
@@ -77,6 +81,7 @@ public abstract class InstallerJDialog extends JDialog {
protected final FilterJTextField filterField;
protected final JPanel filtersContainer;
// Currently selected category and filters
+ protected Predicate extraFilter = x -> true;
protected Predicate categoryFilter;
protected String[] filters;
protected final String noConnectionErrorMessage;
@@ -88,6 +93,8 @@ public abstract class InstallerJDialog extends JDialog {
private final JButton closeButton;
private final JButton dismissErrorMessageButton;
+ protected int previousRowAtPoint = -1;
+
abstract protected FilteredAbstractTableModel createContribModel();
abstract protected TableCellRenderer createCellRenderer();
@@ -118,17 +125,40 @@ public InstallerJDialog(Frame parent, String title, ModalityType applicationModa
filterField = new FilterJTextField(tr("Filter your search...")) {
@Override
protected void onFilter(String[] _filters) {
+ previousRowAtPoint = -1;
filters = _filters;
if (contribTable.getCellEditor() != null) {
contribTable.getCellEditor().stopCellEditing();
}
updateIndexFilter(filters, categoryFilter);
- if (contribModel.getRowCount() == 1) {
- // TODO: understand why it doesn't work
- //contribTable.addRowSelectionInterval(0, 0);
- }
}
};
+ filterField.getAccessibleContext().setAccessibleDescription(tr("Search Filter"));
+
+ // Add cut/copy/paste contextual menu to the search filter input field.
+ JPopupMenu menu = new JPopupMenu();
+
+ Action cut = new DefaultEditorKit.CutAction();
+ cut.putValue(Action.NAME, tr("Cut"));
+ menu.add(cut);
+
+ Action copy = new DefaultEditorKit.CopyAction();
+ copy.putValue(Action.NAME, tr("Copy"));
+ menu.add(copy);
+
+ Action paste = new DefaultEditorKit.PasteAction();
+ paste.putValue(Action.NAME, tr("Paste"));
+ menu.add(paste);
+
+ filterField.setComponentPopupMenu(menu);
+
+ // Focus the filter field when the window opens.
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowOpened(WindowEvent e) {
+ filterField.requestFocus();
+ }
+ });
filtersContainer = new JPanel();
filtersContainer.setLayout(new BoxLayout(filtersContainer, BoxLayout.X_AXIS));
@@ -150,7 +180,6 @@ protected void onFilter(String[] _filters) {
contribTable.setDragEnabled(false);
contribTable.setIntercellSpacing(new Dimension(0, 1));
contribTable.setShowVerticalLines(false);
- contribTable.setSelectionBackground(Theme.getColor("status.notice.bgcolor"));
contribTable.addKeyListener(new AbstractKeyListener() {
@Override
@@ -167,6 +196,25 @@ public void keyReleased(KeyEvent keyEvent) {
}
});
+ contribTable.addMouseMotionListener(new MouseMotionListener() {
+
+ public void mouseDragged(MouseEvent e) {}
+
+ public void mouseMoved(MouseEvent e) {
+ // avoid firing edits events until the mouse changes cell or the user is back on the cell after selecting a dropdown
+ int rowAtPoint = contribTable.rowAtPoint(e.getPoint());
+ if (!InstallerTableCell.isDropdownSelected() && rowAtPoint != previousRowAtPoint) {
+ contribTable.editCellAt(rowAtPoint, 0);
+ previousRowAtPoint = rowAtPoint;
+ InstallerTableCell.dropdownSelected(false);
+ }
+ if (InstallerTableCell.isDropdownSelected() && rowAtPoint == previousRowAtPoint) {
+ // back to the original cell, can drop dropdown selector lock
+ InstallerTableCell.dropdownSelected(false);
+ }
+ }
+ });
+
{
TableColumnModel tcm = contribTable.getColumnModel();
TableColumn col = tcm.getColumn(0);
@@ -241,9 +289,8 @@ public void keyReleased(KeyEvent keyEvent) {
SwingUtilities.invokeLater(InstallerJDialog.this::onUpdatePressed);
}
- public void updateIndexFilter(String[] filters, Predicate... additionalFilters) {
- Stream> notNullAdditionalFilters = Stream.of(additionalFilters).filter(filter -> filter != null);
- contribModel.updateIndexFilter(filters, notNullAdditionalFilters);
+ public void updateIndexFilter(String[] filters, Predicate additionalFilter) {
+ contribModel.updateIndexFilter(filters, additionalFilter);
}
public void setErrorMessage(String message) {
@@ -282,16 +329,16 @@ private void setErrorMessageVisible(boolean visible) {
}
protected final ActionListener categoryChooserActionListener = new ActionListener() {
-
@Override
public void actionPerformed(ActionEvent event) {
DropdownItem selected = (DropdownItem) categoryChooser.getSelectedItem();
- if (categoryFilter == null || !categoryFilter.equals(selected)) {
+ previousRowAtPoint = -1;
+ if (selected != null && categoryFilter != selected.getFilterPredicate()) {
categoryFilter = selected.getFilterPredicate();
if (contribTable.getCellEditor() != null) {
contribTable.getCellEditor().stopCellEditing();
}
- updateIndexFilter(filters, categoryFilter);
+ updateIndexFilter(filters, categoryFilter.and(extraFilter));
}
}
};
@@ -301,6 +348,7 @@ public void setFilterText(String filterText) {
listener.focusGained(new FocusEvent(filterField, FocusEvent.FOCUS_GAINED));
}
filterField.setText(filterText);
+ filterField.applyFilter();
}
public void selectDropdownItemByClassName(String dropdownItem) {
diff --git a/app/src/cc/arduino/contributions/ui/InstallerTableCell.java b/app/src/cc/arduino/contributions/ui/InstallerTableCell.java
index bbf62ebad70..3f94d3e0d79 100644
--- a/app/src/cc/arduino/contributions/ui/InstallerTableCell.java
+++ b/app/src/cc/arduino/contributions/ui/InstallerTableCell.java
@@ -41,6 +41,16 @@ public abstract class InstallerTableCell extends AbstractCellEditor implements T
abstract public void setEnabled(boolean b);
+ private static boolean dropdownSelected = false;
+
+ public static boolean isDropdownSelected( ) {
+ return dropdownSelected;
+ }
+
+ public static void dropdownSelected(boolean b) {
+ dropdownSelected = b;
+ }
+
abstract public void setStatus(String s);
public static void setJTextPaneDimensionToFitContainedText(JTextPane jTextPane, int width) {
diff --git a/app/src/cc/arduino/contributions/ui/ProgressJProgressBar.java b/app/src/cc/arduino/contributions/ui/ProgressJProgressBar.java
index 12b39742fa3..7c946e4993e 100644
--- a/app/src/cc/arduino/contributions/ui/ProgressJProgressBar.java
+++ b/app/src/cc/arduino/contributions/ui/ProgressJProgressBar.java
@@ -38,8 +38,13 @@ public class ProgressJProgressBar extends JProgressBar {
public void setValue(Progress p) {
setValue((int) p.getProgress());
- if (p.getStatus() != null)
+ if (p.getStatus() != null) {
setString(p.getStatus());
+ // copy status to accessibility context for screen readers to use
+ getAccessibleContext().setAccessibleDescription(p.getStatus());
+ // make status focusable so screen readers can get to it
+ setFocusable(true);
+ }
}
}
diff --git a/app/src/cc/arduino/packages/MonitorFactory.java b/app/src/cc/arduino/packages/MonitorFactory.java
index 83d849dd6ef..3be7723b586 100644
--- a/app/src/cc/arduino/packages/MonitorFactory.java
+++ b/app/src/cc/arduino/packages/MonitorFactory.java
@@ -39,7 +39,7 @@ public AbstractMonitor newMonitor(BoardPort port) {
if ("network".equals(port.getProtocol())) {
if ("yes".equals(port.getPrefs().get("ssh_upload"))) {
// the board is SSH capable
- return new NetworkMonitor(port);
+ return new NetworkMonitor(port);
} else {
// SSH not supported, no monitor support
return null;
diff --git a/app/src/cc/arduino/view/NotificationPopup.java b/app/src/cc/arduino/view/NotificationPopup.java
index 2334d6e14bd..2de079c8525 100644
--- a/app/src/cc/arduino/view/NotificationPopup.java
+++ b/app/src/cc/arduino/view/NotificationPopup.java
@@ -36,12 +36,7 @@
import java.awt.Frame;
import java.awt.Image;
import java.awt.Point;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
+import java.awt.event.*;
import java.util.Timer;
import java.util.TimerTask;
@@ -55,22 +50,46 @@
import javax.swing.event.HyperlinkListener;
import cc.arduino.Constants;
+import processing.app.PreferencesData;
import processing.app.Theme;
-public class NotificationPopup extends JDialog {
+import java.awt.event.KeyEvent;
+
+import static processing.app.I18n.tr;
+public class NotificationPopup extends JDialog {
private Timer autoCloseTimer = new Timer(false);
private boolean autoClose = true;
+ private OptionalButtonCallbacks optionalButtonCallbacks;
+
+ public interface OptionalButtonCallbacks {
+ void onOptionalButton1Callback();
+ void onOptionalButton2Callback();
+ }
public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener,
String message) {
- this(parent, hyperlinkListener, message, true);
+ this(parent, hyperlinkListener, message, true, null, null, null);
}
public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener,
String message, boolean _autoClose) {
+ this(parent, hyperlinkListener, message, _autoClose, null, null, null);
+ }
+
+ public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener,
+ String message, boolean _autoClose, OptionalButtonCallbacks listener, String button1Name, String button2Name) {
super(parent, false);
- autoClose = _autoClose;
+
+ if (!PreferencesData.getBoolean("ide.accessible")) {
+ // often auto-close is too fast for users of screen readers, so don't allow it.
+ autoClose = _autoClose;
+ }
+ else {
+ autoClose = false;
+ }
+ optionalButtonCallbacks = listener;
+
setLayout(new FlowLayout());
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setUndecorated(true);
@@ -90,6 +109,74 @@ public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener,
text.addHyperlinkListener(hyperlinkListener);
add(text);
+ if (button1Name != null) {
+ JButton optionalButton1 = new JButton(tr(button1Name));
+ MouseAdapter button1Action = new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (optionalButtonCallbacks != null) {
+ optionalButtonCallbacks.onOptionalButton1Callback();
+ }
+ }
+ };
+ optionalButton1.addMouseListener(button1Action);
+
+ KeyListener button1Key = new KeyListener() {
+ // Ignore when the key is typed - only act once the key is released
+ public void keyTyped(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ // Ignore when the key is pressed - only act once the key is released
+ public void keyPressed(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ public void keyReleased(KeyEvent e) {
+ int key = e.getKeyCode();
+ if ((key == KeyEvent.VK_ENTER) || (key == KeyEvent.VK_SPACE)) {
+ optionalButtonCallbacks.onOptionalButton1Callback();
+ }
+ }
+ };
+ optionalButton1.addKeyListener(button1Key);
+ add(optionalButton1);
+ }
+
+ if (button2Name != null) {
+ JButton optionalButton2 = new JButton(tr(button2Name));
+ MouseAdapter button2Action = new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (optionalButtonCallbacks != null) {
+ optionalButtonCallbacks.onOptionalButton2Callback();
+ }
+ }
+ };
+ optionalButton2.addMouseListener(button2Action);
+
+ KeyListener button2Key = new KeyListener() {
+ // Ignore when the key is typed - only act once the key is released
+ public void keyTyped(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ // Ignore when the key is pressed - only act once the key is released
+ public void keyPressed(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ public void keyReleased(KeyEvent e) {
+ int key = e.getKeyCode();
+ if ((key == KeyEvent.VK_ENTER) || (key == KeyEvent.VK_SPACE)) {
+ optionalButtonCallbacks.onOptionalButton2Callback();
+ }
+ }
+ };
+ optionalButton2.addKeyListener(button2Key);
+ add(optionalButton2);
+ }
+
Image close = Theme.getThemeImage("close", this, scale(22), scale(22));
JButton closeButton = new JButton(new ImageIcon(close));
closeButton.setBorder(null);
@@ -97,6 +184,26 @@ public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener,
closeButton.setHideActionText(true);
closeButton.setOpaque(false);
closeButton.setBackground(new Color(0, 0, 0, 0));
+ closeButton.getAccessibleContext().setAccessibleDescription(tr("Close"));
+ KeyListener closeKey = new KeyListener() {
+ // Ignore when the key is typed - only act once the key is released
+ public void keyTyped(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ // Ignore when the key is pressed - only act once the key is released
+ public void keyPressed(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ public void keyReleased(KeyEvent e) {
+ int key = e.getKeyCode();
+ if ((key == KeyEvent.VK_ENTER) || (key == KeyEvent.VK_SPACE)) {
+ close();
+ }
+ }
+ };
+ closeButton.addKeyListener(closeKey);
add(closeButton);
MouseAdapter closeOnClick = new MouseAdapter() {
@@ -145,6 +252,7 @@ public void close() {
if (autoClose) {
autoCloseTimer.cancel();
}
+ setModal(false);
dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
}
@@ -158,5 +266,9 @@ public void run() {
}, Constants.NOTIFICATION_POPUP_AUTOCLOSE_DELAY);
}
setVisible(true);
+ if (PreferencesData.getBoolean("ide.accessible")) {
+ requestFocus();
+ setModal(true);
+ }
}
}
diff --git a/app/src/cc/arduino/view/findreplace/FindReplace.java b/app/src/cc/arduino/view/findreplace/FindReplace.java
index 6af1f397bdb..03e7b10947d 100644
--- a/app/src/cc/arduino/view/findreplace/FindReplace.java
+++ b/app/src/cc/arduino/view/findreplace/FindReplace.java
@@ -37,6 +37,9 @@
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
+import javax.swing.JPopupMenu;
+import javax.swing.Action;
+import javax.swing.text.DefaultEditorKit;
import java.util.HashMap;
import java.util.Map;
@@ -161,6 +164,22 @@ private void initComponents() {
searchAllFilesBox.setText(tr("Search all Sketch Tabs"));
+ JPopupMenu menu = new JPopupMenu();
+ Action cut = new DefaultEditorKit.CutAction();
+ cut.putValue(Action.NAME, tr("Cut"));
+ menu.add( cut );
+
+ Action copy = new DefaultEditorKit.CopyAction();
+ copy.putValue(Action.NAME, tr("Copy"));
+ menu.add( copy );
+
+ Action paste = new DefaultEditorKit.PasteAction();
+ paste.putValue(Action.NAME, tr("Paste"));
+ menu.add( paste );
+
+ findField.setComponentPopupMenu( menu );
+ replaceField.setComponentPopupMenu( menu );
+
findButton.setText(tr("Find"));
findButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
diff --git a/app/src/cc/arduino/view/preferences/Preferences.java b/app/src/cc/arduino/view/preferences/Preferences.java
index 4ecbd26c122..005d2f83e54 100644
--- a/app/src/cc/arduino/view/preferences/Preferences.java
+++ b/app/src/cc/arduino/view/preferences/Preferences.java
@@ -38,6 +38,7 @@
import processing.app.I18n;
import processing.app.PreferencesData;
import processing.app.Theme;
+import processing.app.Theme.ZippedTheme;
import processing.app.helpers.FileUtils;
import processing.app.legacy.PApplet;
@@ -46,6 +47,7 @@
import java.awt.event.ItemEvent;
import java.awt.event.WindowEvent;
import java.io.File;
+import java.util.Collection;
import java.util.LinkedList;
import static processing.app.I18n.tr;
@@ -89,7 +91,7 @@ public Preferences(Window parent, Base base) {
Base.registerWindowCloseKeys(getRootPane(), this::cancelButtonActionPerformed);
- showPrerefencesData();
+ showPreferencesData();
}
/**
@@ -130,10 +132,9 @@ private void initComponents() {
enableCodeFoldingBox = new javax.swing.JCheckBox();
verifyUploadBox = new javax.swing.JCheckBox();
externalEditorBox = new javax.swing.JCheckBox();
- cacheCompiledCore = new javax.swing.JCheckBox();
checkUpdatesBox = new javax.swing.JCheckBox();
- updateExtensionBox = new javax.swing.JCheckBox();
saveVerifyUploadBox = new javax.swing.JCheckBox();
+ accessibleIDEBox = new javax.swing.JCheckBox();
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
scaleSpinner = new javax.swing.JSpinner();
@@ -159,6 +160,9 @@ private void initComponents() {
autoProxyUsername = new javax.swing.JTextField();
autoProxyPassword = new javax.swing.JPasswordField();
autoProxyPasswordLabel = new javax.swing.JLabel();
+ comboThemeLabel = new javax.swing.JLabel();
+ comboTheme = new JComboBox();
+ requiresRestartLabel2 = new javax.swing.JLabel();
javax.swing.JPanel jPanel3 = new javax.swing.JPanel();
javax.swing.JButton okButton = new javax.swing.JButton();
javax.swing.JButton cancelButton = new javax.swing.JButton();
@@ -175,7 +179,7 @@ private void initComponents() {
sketchbookLocationLabel.setText(tr("Sketchbook location:"));
sketchbookLocationLabel.setLabelFor(sketchbookLocationField);
-
+
sketchbookLocationField.setColumns(40);
browseButton.setText(I18n.PROMPT_BROWSE);
@@ -188,7 +192,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
comboLanguageLabel.setText(tr("Editor language: "));
requiresRestartLabel.setText(tr(" (requires restart of Arduino)"));
-
+
comboLanguage.getAccessibleContext().setAccessibleName("Editor language (requires restart of Arduino)");
fontSizeLabel.setText(tr("Editor font size: "));
@@ -243,7 +247,7 @@ public void mouseEntered(java.awt.event.MouseEvent evt) {
arduinoNotRunningLabel.setForeground(Color.GRAY);
arduinoNotRunningLabel.setText(tr("(edit only when Arduino is not running)"));
- checkboxesContainer.setLayout(new javax.swing.BoxLayout(checkboxesContainer, javax.swing.BoxLayout.Y_AXIS));
+ checkboxesContainer.setLayout(new GridLayout(0,2));
displayLineNumbersBox.setText(tr("Display line numbers"));
checkboxesContainer.add(displayLineNumbersBox);
@@ -272,18 +276,15 @@ public void mouseEntered(java.awt.event.MouseEvent evt) {
checkboxesContainer.add(externalEditorBox);
- cacheCompiledCore.setText(tr("Aggressively cache compiled core"));
- checkboxesContainer.add(cacheCompiledCore);
-
checkUpdatesBox.setText(tr("Check for updates on startup"));
checkboxesContainer.add(checkUpdatesBox);
- updateExtensionBox.setText(tr("Update sketch files to new extension on save (.pde -> .ino)"));
- checkboxesContainer.add(updateExtensionBox);
-
saveVerifyUploadBox.setText(tr("Save when verifying or uploading"));
checkboxesContainer.add(saveVerifyUploadBox);
+ accessibleIDEBox.setText(tr("Use accessibility features"));
+ checkboxesContainer.add(accessibleIDEBox);
+
jLabel1.setText(tr("Interface scale:"));
jLabel2.setText(tr(" (requires restart of Arduino)"));
@@ -303,6 +304,12 @@ public void itemStateChanged(java.awt.event.ItemEvent evt) {
jLabel3.setText("%");
+ comboThemeLabel.setText(tr("Theme: "));
+
+ comboTheme.getAccessibleContext().setAccessibleName("Theme (requires restart of Arduino)");
+
+ requiresRestartLabel2.setText(tr(" (requires restart of Arduino)"));
+
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
@@ -311,7 +318,7 @@ public void itemStateChanged(java.awt.event.ItemEvent evt) {
.addContainerGap()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
- .addComponent(sketchbookLocationField, javax.swing.GroupLayout.DEFAULT_SIZE, 553, Short.MAX_VALUE)
+ .addComponent(sketchbookLocationField, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(browseButton))
.addComponent(checkboxesContainer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
@@ -341,9 +348,14 @@ public void itemStateChanged(java.awt.event.ItemEvent evt) {
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(comboLanguageLabel)
- .addComponent(fontSizeLabel))
+ .addComponent(fontSizeLabel)
+ .addComponent(comboThemeLabel))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addComponent(comboTheme, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(requiresRestartLabel2))
.addComponent(fontSizeField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(comboLanguage, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
@@ -363,7 +375,7 @@ public void itemStateChanged(java.awt.event.ItemEvent evt) {
.addContainerGap())
);
- jPanel1Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {comboLanguageLabel, comboWarningsLabel, fontSizeLabel, jLabel1, showVerboseLabel});
+ jPanel1Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {comboLanguageLabel, comboWarningsLabel, fontSizeLabel, jLabel1, showVerboseLabel, comboThemeLabel});
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -391,6 +403,11 @@ public void itemStateChanged(java.awt.event.ItemEvent evt) {
.addComponent(autoScaleCheckBox)
.addComponent(jLabel3))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(comboThemeLabel)
+ .addComponent(comboTheme, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(requiresRestartLabel2))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(showVerboseLabel)
.addComponent(verboseCompilationBox)
@@ -610,13 +627,13 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGap(0, 691, Short.MAX_VALUE)
+ .addGap(0, 800, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGap(0, 580, Short.MAX_VALUE)
+ .addGap(0, 400, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
@@ -700,6 +717,7 @@ private void autoScaleCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//
private javax.swing.JCheckBox autoScaleCheckBox;
private javax.swing.JButton browseButton;
private javax.swing.JCheckBox checkUpdatesBox;
+ private javax.swing.JCheckBox accessibleIDEBox;
private javax.swing.JPanel checkboxesContainer;
private javax.swing.JComboBox comboLanguage;
private javax.swing.JLabel comboLanguageLabel;
@@ -709,7 +727,6 @@ private void autoScaleCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//
private javax.swing.JCheckBox enableCodeFoldingBox;
private javax.swing.JButton extendedAdditionalUrlFieldWindow;
private javax.swing.JCheckBox externalEditorBox;
- private javax.swing.JCheckBox cacheCompiledCore;
private javax.swing.JTextField fontSizeField;
private javax.swing.JLabel fontSizeLabel;
private javax.swing.JLabel jLabel1;
@@ -738,10 +755,12 @@ private void autoScaleCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//
private javax.swing.JLabel showVerboseLabel;
private javax.swing.JTextField sketchbookLocationField;
private javax.swing.JLabel sketchbookLocationLabel;
- private javax.swing.JCheckBox updateExtensionBox;
private javax.swing.JCheckBox verboseCompilationBox;
private javax.swing.JCheckBox verboseUploadBox;
private javax.swing.JCheckBox verifyUploadBox;
+ private javax.swing.JComboBox comboTheme;
+ private javax.swing.JLabel comboThemeLabel;
+ private javax.swing.JLabel requiresRestartLabel2;
// End of variables declaration//GEN-END:variables
private java.util.List validateData() {
@@ -770,6 +789,12 @@ private void savePreferencesData() {
Language newLanguage = (Language) comboLanguage.getSelectedItem();
PreferencesData.set("editor.languages.current", newLanguage.getIsoCode());
+ if (comboTheme.getSelectedIndex() == 0) {
+ PreferencesData.set("theme.file", "");
+ } else {
+ PreferencesData.set("theme.file", ((ZippedTheme) comboTheme.getSelectedItem()).getKey());
+ }
+
String newSizeText = fontSizeField.getText();
try {
int newSize = Integer.parseInt(newSizeText.trim());
@@ -804,13 +829,9 @@ private void savePreferencesData() {
PreferencesData.setBoolean("editor.external", externalEditorBox.isSelected());
- PreferencesData.setBoolean("compiler.cache_core", cacheCompiledCore.isSelected());
-
PreferencesData.setBoolean("update.check", checkUpdatesBox.isSelected());
- PreferencesData.setBoolean("editor.update_extension", updateExtensionBox.isSelected());
-
- PreferencesData.setBoolean("editor.save_on_verify", saveVerifyUploadBox.isSelected());
+ PreferencesData.setBoolean("ide.accessible", accessibleIDEBox.isSelected());
PreferencesData.set("boardsmanager.additional.urls", additionalBoardsManagerField.getText().replace("\r\n", "\n").replace("\r", "\n").replace("\n", ","));
@@ -819,13 +840,17 @@ private void savePreferencesData() {
PreferencesData.set(Constants.PREF_PROXY_MANUAL_TYPE, manualProxyTypeButtonGroup.getSelection().getActionCommand());
PreferencesData.set(Constants.PREF_PROXY_MANUAL_HOSTNAME, manualProxyHostName.getText());
PreferencesData.set(Constants.PREF_PROXY_MANUAL_PORT, manualProxyPort.getText());
- PreferencesData.set(Constants.PREF_PROXY_MANUAL_USERNAME, manualProxyUsername.getText());
- PreferencesData.set(Constants.PREF_PROXY_MANUAL_PASSWORD, String.valueOf(manualProxyPassword.getPassword()));
- PreferencesData.set(Constants.PREF_PROXY_AUTO_USERNAME, autoProxyUsername.getText());
- PreferencesData.set(Constants.PREF_PROXY_AUTO_PASSWORD, String.valueOf(autoProxyPassword.getPassword()));
+ if (PreferencesData.get(Constants.PREF_PROXY_TYPE).equals(Constants.PROXY_TYPE_MANUAL)) {
+ PreferencesData.set(Constants.PREF_PROXY_USERNAME, manualProxyUsername.getText());
+ PreferencesData.set(Constants.PREF_PROXY_PASSWORD, String.valueOf(manualProxyPassword.getPassword()));
+ }
+ if (PreferencesData.get(Constants.PREF_PROXY_TYPE).equals(Constants.PROXY_TYPE_AUTO)) {
+ PreferencesData.set(Constants.PREF_PROXY_USERNAME, autoProxyUsername.getText());
+ PreferencesData.set(Constants.PREF_PROXY_PASSWORD, String.valueOf(autoProxyPassword.getPassword()));
+ }
}
- private void showPrerefencesData() {
+ private void showPreferencesData() {
sketchbookLocationField.setText(PreferencesData.get("sketchbook.path"));
String currentLanguageISOCode = PreferencesData.get("editor.languages.current");
@@ -835,6 +860,16 @@ private void showPrerefencesData() {
}
}
+ String selectedTheme = PreferencesData.get("theme.file", "");
+ Collection availablethemes = Theme.getAvailablethemes();
+ comboTheme.addItem(tr("Default theme"));
+ for (ZippedTheme theme : availablethemes) {
+ comboTheme.addItem(theme);
+ if (theme.getKey().equals(selectedTheme)) {
+ comboTheme.setSelectedItem(theme);
+ }
+ }
+
Font editorFont = PreferencesData.getFont("editor.font");
fontSizeField.setText(String.valueOf(editorFont.getSize()));
@@ -866,11 +901,17 @@ private void showPrerefencesData() {
externalEditorBox.setSelected(PreferencesData.getBoolean("editor.external"));
- cacheCompiledCore.setSelected(PreferencesData.get("compiler.cache_core") == null || PreferencesData.getBoolean("compiler.cache_core"));
+ if (PreferencesData.get("compiler.cache_core") == null) {
+ PreferencesData.setBoolean("compiler.cache_core", true);
+ }
checkUpdatesBox.setSelected(PreferencesData.getBoolean("update.check"));
- updateExtensionBox.setSelected(PreferencesData.get("editor.update_extension") == null || PreferencesData.getBoolean("editor.update_extension"));
+ if (PreferencesData.get("editor.update_extension") == null) {
+ PreferencesData.setBoolean("editor.update_extension", true);
+ }
+
+ accessibleIDEBox.setSelected(PreferencesData.getBoolean("ide.accessible"));
saveVerifyUploadBox.setSelected(PreferencesData.getBoolean("editor.save_on_verify"));
@@ -887,16 +928,16 @@ private void showPrerefencesData() {
if (!PreferencesData.get(Constants.PREF_PROXY_PAC_URL, "").isEmpty()) {
autoProxyUsePAC.setSelected(true);
autoProxyPACURL.setText(PreferencesData.get(Constants.PREF_PROXY_PAC_URL));
- autoProxyUsername.setText(PreferencesData.get(Constants.PREF_PROXY_AUTO_USERNAME));
- autoProxyPassword.setText(PreferencesData.get(Constants.PREF_PROXY_AUTO_PASSWORD));
+ autoProxyUsername.setText(PreferencesData.get(Constants.PREF_PROXY_USERNAME));
+ autoProxyPassword.setText(PreferencesData.get(Constants.PREF_PROXY_PASSWORD));
}
} else {
manualProxy.setSelected(true);
manualProxyFieldsSetEnabled(true);
manualProxyHostName.setText(PreferencesData.get(Constants.PREF_PROXY_MANUAL_HOSTNAME));
manualProxyPort.setText(PreferencesData.get(Constants.PREF_PROXY_MANUAL_PORT));
- manualProxyUsername.setText(PreferencesData.get(Constants.PREF_PROXY_MANUAL_USERNAME));
- manualProxyPassword.setText(PreferencesData.get(Constants.PREF_PROXY_MANUAL_PASSWORD));
+ manualProxyUsername.setText(PreferencesData.get(Constants.PREF_PROXY_USERNAME));
+ manualProxyPassword.setText(PreferencesData.get(Constants.PREF_PROXY_PASSWORD));
}
String selectedManualProxyType = PreferencesData.get(Constants.PREF_PROXY_MANUAL_TYPE, Constants.PROXY_MANUAL_TYPE_HTTP);
diff --git a/app/src/log4j2.xml b/app/src/log4j2.xml
new file mode 100644
index 00000000000..64f6b8f063f
--- /dev/null
+++ b/app/src/log4j2.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ %d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}{UTC} %p %c{1.}:%L [%t] %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/processing/app/AbstractMonitor.java b/app/src/processing/app/AbstractMonitor.java
index 52c5b65b56b..b6ba0d7652e 100644
--- a/app/src/processing/app/AbstractMonitor.java
+++ b/app/src/processing/app/AbstractMonitor.java
@@ -17,6 +17,7 @@ public abstract class AbstractMonitor extends JFrame implements ActionListener {
private StringBuffer updateBuffer;
private Timer updateTimer;
+ private Timer portExistsTimer;
private BoardPort boardPort;
@@ -27,6 +28,7 @@ public AbstractMonitor(BoardPort boardPort) {
this.boardPort = boardPort;
addWindowListener(new WindowAdapter() {
+ @Override
public void windowClosing(WindowEvent event) {
try {
closed = true;
@@ -41,6 +43,7 @@ public void windowClosing(WindowEvent event) {
KeyStroke wc = Editor.WINDOW_CLOSE_KEYSTROKE;
getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(wc, "close");
getRootPane().getActionMap().put("close", (new AbstractAction() {
+ @Override
public void actionPerformed(ActionEvent event) {
try {
close();
@@ -71,6 +74,26 @@ public void actionPerformed(ActionEvent event) {
updateTimer = new Timer(33, this); // redraw serial monitor at 30 Hz
updateTimer.start();
+ ActionListener portExists = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent ae) {
+ try {
+ if (Base.getDiscoveryManager().find(boardPort.getAddress()) == null) {
+ if (!closed) {
+ suspend();
+ }
+ } else {
+ if (closed && !Editor.isUploading()) {
+ resume(boardPort);
+ }
+ }
+ } catch (Exception e) {}
+ }
+ };
+
+ portExistsTimer = new Timer(1000, portExists); // check if the port is still there every second
+ portExistsTimer.start();
+
closed = false;
}
@@ -90,6 +113,11 @@ public void suspend() throws Exception {
close();
}
+ public void dispose() {
+ super.dispose();
+ portExistsTimer.stop();
+ }
+
public void resume(BoardPort boardPort) throws Exception {
setBoardPort(boardPort);
@@ -165,6 +193,7 @@ private synchronized String consumeUpdateBuffer() {
return s;
}
+ @Override
public void actionPerformed(ActionEvent e) {
String s = consumeUpdateBuffer();
if (s.isEmpty()) {
@@ -173,4 +202,13 @@ public void actionPerformed(ActionEvent e) {
message(s);
}
}
+
+ /**
+ * Read and apply new values from the preferences, either because
+ * the app is just starting up, or the user just finished messing
+ * with things in the Preferences window.
+ */
+ public void applyPreferences() {
+ // Empty.
+ };
}
diff --git a/app/src/processing/app/AbstractTextMonitor.java b/app/src/processing/app/AbstractTextMonitor.java
index 1602e869a72..00eabb20649 100644
--- a/app/src/processing/app/AbstractTextMonitor.java
+++ b/app/src/processing/app/AbstractTextMonitor.java
@@ -3,14 +3,21 @@
import static processing.app.I18n.tr;
import java.awt.BorderLayout;
+import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseWheelListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.StringTokenizer;
+import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@@ -18,11 +25,13 @@
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.text.DefaultCaret;
+import javax.swing.text.DefaultEditorKit;
import cc.arduino.packages.BoardPort;
@@ -36,25 +45,36 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
protected JButton sendButton;
protected JButton clearButton;
protected JCheckBox autoscrollBox;
- protected JComboBox lineEndings;
- protected JComboBox serialRates;
+ protected JCheckBox addTimeStampBox;
+ protected JComboBox lineEndings;
+ protected JComboBox serialRates;
public AbstractTextMonitor(BoardPort boardPort) {
super(boardPort);
}
-
+
+ @Override
+ public synchronized void addMouseWheelListener(MouseWheelListener l) {
+ super.addMouseWheelListener(l);
+ textArea.addMouseWheelListener(l);
+ }
+
+ @Override
+ public synchronized void addKeyListener(KeyListener l) {
+ super.addKeyListener(l);
+ textArea.addKeyListener(l);
+ textField.addKeyListener(l);
+ }
+
+ @Override
protected void onCreateWindow(Container mainPane) {
- Font consoleFont = Theme.getFont("console.font");
- Font editorFont = PreferencesData.getFont("editor.font");
- Font font = Theme.scale(new Font(consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize()));
mainPane.setLayout(new BorderLayout());
- textArea = new TextAreaFIFO(8000000);
+ textArea = new TextAreaFIFO(8_000_000);
textArea.setRows(16);
textArea.setColumns(40);
textArea.setEditable(false);
- textArea.setFont(font);
// don't automatically update the caret. that way we can manually decide
// whether or not to do so based on the autoscroll checkbox.
@@ -63,7 +83,7 @@ protected void onCreateWindow(Container mainPane) {
scrollPane = new JScrollPane(textArea);
mainPane.add(scrollPane, BorderLayout.CENTER);
-
+
JPanel upperPane = new JPanel();
upperPane.setLayout(new BoxLayout(upperPane, BoxLayout.X_AXIS));
upperPane.setBorder(new EmptyBorder(4, 4, 4, 4));
@@ -71,11 +91,29 @@ protected void onCreateWindow(Container mainPane) {
textField = new JTextField(40);
// textField is selected every time the window is focused
addWindowFocusListener(new WindowAdapter() {
+ @Override
public void windowGainedFocus(WindowEvent e) {
textField.requestFocusInWindow();
}
});
+ // Add cut/copy/paste contextual menu to the text input field.
+ JPopupMenu menu = new JPopupMenu();
+
+ Action cut = new DefaultEditorKit.CutAction();
+ cut.putValue(Action.NAME, tr("Cut"));
+ menu.add(cut);
+
+ Action copy = new DefaultEditorKit.CopyAction();
+ copy.putValue(Action.NAME, tr("Copy"));
+ menu.add(copy);
+
+ Action paste = new DefaultEditorKit.PasteAction();
+ paste.putValue(Action.NAME, tr("Paste"));
+ menu.add(paste);
+
+ textField.setComponentPopupMenu(menu);
+
sendButton = new JButton(tr("Send"));
clearButton = new JButton(tr("Clear output"));
@@ -90,6 +128,7 @@ public void windowGainedFocus(WindowEvent e) {
pane.setBorder(new EmptyBorder(4, 4, 4, 4));
autoscrollBox = new JCheckBox(tr("Autoscroll"), true);
+ addTimeStampBox = new JCheckBox(tr("Show timestamp"), false);
noLineEndingAlert = new JLabel(I18n.format(tr("You've pressed {0} but nothing was sent. Should you select a line ending?"), tr("Send")));
noLineEndingAlert.setToolTipText(noLineEndingAlert.getText());
@@ -98,19 +137,17 @@ public void windowGainedFocus(WindowEvent e) {
minimumSize.setSize(minimumSize.getWidth() / 3, minimumSize.getHeight());
noLineEndingAlert.setMinimumSize(minimumSize);
- lineEndings = new JComboBox(new String[]{tr("No line ending"), tr("Newline"), tr("Carriage return"), tr("Both NL & CR")});
- lineEndings.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent event) {
- PreferencesData.setInteger("serial.line_ending", lineEndings.getSelectedIndex());
- noLineEndingAlert.setForeground(pane.getBackground());
- }
+ lineEndings = new JComboBox<>(new String[]{tr("No line ending"), tr("Newline"), tr("Carriage return"), tr("Both NL & CR")});
+ lineEndings.addActionListener((ActionEvent event) -> {
+ PreferencesData.setInteger("serial.line_ending", lineEndings.getSelectedIndex());
+ noLineEndingAlert.setForeground(pane.getBackground());
});
- if (PreferencesData.get("serial.line_ending") != null) {
- lineEndings.setSelectedIndex(PreferencesData.getInteger("serial.line_ending"));
- }
+ addTimeStampBox.addActionListener((ActionEvent event) ->
+ PreferencesData.setBoolean("serial.show_timestamp", addTimeStampBox.isSelected()));
+
lineEndings.setMaximumSize(lineEndings.getMinimumSize());
- serialRates = new JComboBox();
+ serialRates = new JComboBox<>();
for (String rate : serialRateStrings) {
serialRates.addItem(rate + " " + tr("baud"));
}
@@ -118,6 +155,7 @@ public void actionPerformed(ActionEvent event) {
serialRates.setMaximumSize(serialRates.getMinimumSize());
pane.add(autoscrollBox);
+ pane.add(addTimeStampBox);
pane.add(Box.createHorizontalGlue());
pane.add(noLineEndingAlert);
pane.add(Box.createRigidArea(new Dimension(8, 0)));
@@ -127,26 +165,41 @@ public void actionPerformed(ActionEvent event) {
pane.add(Box.createRigidArea(new Dimension(8, 0)));
pane.add(clearButton);
+ applyPreferences();
+
mainPane.add(pane, BorderLayout.SOUTH);
}
- protected void onEnableWindow(boolean enable)
- {
- textArea.setEnabled(enable);
- clearButton.setEnabled(enable);
+ @Override
+ protected void onEnableWindow(boolean enable) {
+ // never actually disable textArea, so people can
+ // still select & copy text, even when the port
+ // is closed or disconnected
+ textArea.setEnabled(true);
+ if (enable) {
+ // setting these to null for system default
+ // gives a wrong gray background on Windows
+ // so assume black text on white background
+ textArea.setForeground(Color.BLACK);
+ textArea.setBackground(Color.WHITE);
+ } else {
+ // In theory, UIManager.getDefaults() should
+ // give us the system's colors for disabled
+ // windows. But it doesn't seem to work. :(
+ textArea.setForeground(new Color(64, 64, 64));
+ textArea.setBackground(new Color(238, 238, 238));
+ }
+ textArea.invalidate();
scrollPane.setEnabled(enable);
textField.setEnabled(enable);
sendButton.setEnabled(enable);
- autoscrollBox.setEnabled(enable);
- lineEndings.setEnabled(enable);
- serialRates.setEnabled(enable);
}
public void onSendCommand(ActionListener listener) {
textField.addActionListener(listener);
sendButton.addActionListener(listener);
}
-
+
public void onClearCommand(ActionListener listener) {
clearButton.addActionListener(listener);
}
@@ -154,15 +207,59 @@ public void onClearCommand(ActionListener listener) {
public void onSerialRateChange(ActionListener listener) {
serialRates.addActionListener(listener);
}
-
- public void message(final String s) {
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- textArea.append(s);
- if (autoscrollBox.isSelected()) {
- textArea.setCaretPosition(textArea.getDocument().getLength());
- }
+
+ @Override
+ public void message(String msg) {
+ SwingUtilities.invokeLater(() -> updateTextArea(msg));
+ }
+
+ private static final String LINE_SEPARATOR = "\n";
+ private boolean isStartingLine = true;
+
+ protected void updateTextArea(String msg) {
+ if (addTimeStampBox.isSelected()) {
+ textArea.append(addTimestamps(msg));
+ } else {
+ textArea.append(msg);
+ }
+ if (autoscrollBox.isSelected()) {
+ textArea.setCaretPosition(textArea.getDocument().getLength());
+ }
+ }
+
+ @Override
+ public void applyPreferences() {
+
+ // Apply font.
+ Font consoleFont = Theme.getFont("console.font");
+ Font editorFont = PreferencesData.getFont("editor.font");
+ textArea.setFont(Theme.scale(new Font(
+ consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize())));
+
+ // Apply line endings.
+ if (PreferencesData.get("serial.line_ending") != null) {
+ lineEndings.setSelectedIndex(PreferencesData.getInteger("serial.line_ending"));
+ }
+
+ // Apply timestamp visibility.
+ if (PreferencesData.get("serial.show_timestamp") != null) {
+ addTimeStampBox.setSelected(PreferencesData.getBoolean("serial.show_timestamp"));
+ }
+ }
+
+ private String addTimestamps(String text) {
+ String now = new SimpleDateFormat("HH:mm:ss.SSS -> ").format(new Date());
+ final StringBuilder sb = new StringBuilder(text.length() + now.length());
+ StringTokenizer tokenizer = new StringTokenizer(text, LINE_SEPARATOR, true);
+ while (tokenizer.hasMoreTokens()) {
+ if (isStartingLine) {
+ sb.append(now);
}
- });
+ String token = tokenizer.nextToken();
+ sb.append(token);
+ // tokenizer returns "\n" as a single token
+ isStartingLine = token.equals(LINE_SEPARATOR);
+ }
+ return sb.toString();
}
}
diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index 1752e1dc824..1e4819bba34 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -27,7 +27,10 @@
import cc.arduino.UpdatableBoardsLibsFakeURLsHandler;
import cc.arduino.UploaderUtils;
import cc.arduino.contributions.*;
-import cc.arduino.contributions.libraries.*;
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.LibrariesIndexer;
+import cc.arduino.contributions.libraries.LibraryInstaller;
+import cc.arduino.contributions.libraries.LibraryOfSameTypeComparator;
import cc.arduino.contributions.libraries.ui.LibraryManagerUI;
import cc.arduino.contributions.packages.ContributedPlatform;
import cc.arduino.contributions.packages.ContributionInstaller;
@@ -35,10 +38,11 @@
import cc.arduino.contributions.packages.ui.ContributionManagerUI;
import cc.arduino.files.DeleteFilesOnShutdown;
import cc.arduino.packages.DiscoveryManager;
+import cc.arduino.packages.Uploader;
import cc.arduino.view.Event;
import cc.arduino.view.JMenuUtils;
import cc.arduino.view.SplashScreenHelper;
-
+import com.github.zafarkhaja.semver.Version;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
import processing.app.debug.TargetBoard;
@@ -52,6 +56,7 @@
import processing.app.macosx.ThinkDifferent;
import processing.app.packages.LibraryList;
import processing.app.packages.UserLibrary;
+import processing.app.packages.UserLibraryFolder.Location;
import processing.app.syntax.PdeKeywords;
import processing.app.syntax.SketchTextAreaDefaultInputMap;
import processing.app.tools.MenuScroller;
@@ -61,15 +66,16 @@
import java.awt.*;
import java.awt.event.*;
import java.io.*;
-import java.util.*;
import java.util.List;
import java.util.Timer;
+import java.util.*;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static processing.app.I18n.format;
import static processing.app.I18n.tr;
@@ -135,7 +141,7 @@ static public void main(String args[]) throws Exception {
if (OSUtils.isMacOS()) {
System.setProperty("apple.laf.useScreenMenuBar",
String.valueOf(!System.getProperty("os.version").startsWith("10.13")
- || com.apple.eawt.Application.getApplication().isAboutMenuItemPresent()));
+ || isMacOsAboutMenuItemPresent()));
ThinkDifferent.init();
}
@@ -148,6 +154,11 @@ static public void main(String args[]) throws Exception {
}
}
+ @SuppressWarnings("deprecation")
+ public static boolean isMacOsAboutMenuItemPresent() {
+ return com.apple.eawt.Application.getApplication().isAboutMenuItemPresent();
+ }
+
static public void initLogger() {
Handler consoleHandler = new ConsoleLogger();
consoleHandler.setLevel(Level.ALL);
@@ -209,6 +220,26 @@ public Base(String[] args) throws Exception {
parser.parseArgumentsPhase1();
commandLine = !parser.isGuiMode();
+ // This configure the logs root folder
+ if (parser.isGuiMode()) {
+ System.out.println("Set log4j store directory " + BaseNoGui.getSettingsFolder().getAbsolutePath());
+ }
+ System.setProperty("log4j.dir", BaseNoGui.getSettingsFolder().getAbsolutePath());
+
+ BaseNoGui.checkInstallationFolder();
+
+ // If no path is set, get the default sketchbook folder for this platform
+ if (BaseNoGui.getSketchbookPath() == null) {
+ File defaultFolder = getDefaultSketchbookFolderOrPromptForIt();
+ if (BaseNoGui.getPortableFolder() != null)
+ PreferencesData.set("sketchbook.path", BaseNoGui.getPortableSketchbookFolder());
+ else
+ PreferencesData.set("sketchbook.path", defaultFolder.getAbsolutePath());
+ if (!defaultFolder.exists()) {
+ defaultFolder.mkdirs();
+ }
+ }
+
SplashScreenHelper splash;
if (parser.isGuiMode()) {
// Setup all notification widgets
@@ -243,23 +274,11 @@ public Base(String[] args) throws Exception {
untitledFolder = FileUtils.createTempFolder("untitled" + new Random().nextInt(Integer.MAX_VALUE), ".tmp");
DeleteFilesOnShutdown.add(untitledFolder);
- BaseNoGui.checkInstallationFolder();
-
- // If no path is set, get the default sketchbook folder for this platform
- if (BaseNoGui.getSketchbookPath() == null) {
- File defaultFolder = getDefaultSketchbookFolderOrPromptForIt();
- if (BaseNoGui.getPortableFolder() != null)
- PreferencesData.set("sketchbook.path", BaseNoGui.getPortableSketchbookFolder());
- else
- PreferencesData.set("sketchbook.path", defaultFolder.getAbsolutePath());
- if (!defaultFolder.exists()) {
- defaultFolder.mkdirs();
- }
- }
-
splash.splashText(tr("Initializing packages..."));
BaseNoGui.initPackages();
+ parser.getUploadPort().ifPresent(BaseNoGui::selectSerialPort);
+
splash.splashText(tr("Preparing boards..."));
if (!isCommandLine()) {
@@ -277,8 +296,9 @@ public Base(String[] args) throws Exception {
pdeKeywords = new PdeKeywords();
pdeKeywords.reload();
- contributionInstaller = new ContributionInstaller(BaseNoGui.getPlatform(), new GPGDetachedSignatureVerifier());
- libraryInstaller = new LibraryInstaller(BaseNoGui.getPlatform());
+ final GPGDetachedSignatureVerifier gpgDetachedSignatureVerifier = new GPGDetachedSignatureVerifier();
+ contributionInstaller = new ContributionInstaller(BaseNoGui.getPlatform(), gpgDetachedSignatureVerifier);
+ libraryInstaller = new LibraryInstaller(BaseNoGui.getPlatform(), gpgDetachedSignatureVerifier);
parser.parseArgumentsPhase2();
@@ -292,7 +312,7 @@ public Base(String[] args) throws Exception {
if (parser.isInstallBoard()) {
ContributionsIndexer indexer = new ContributionsIndexer(
BaseNoGui.getSettingsFolder(), BaseNoGui.getHardwareFolder(),
- BaseNoGui.getPlatform(), new GPGDetachedSignatureVerifier());
+ BaseNoGui.getPlatform(), gpgDetachedSignatureVerifier);
ProgressListener progressListener = new ConsoleProgressListener();
List downloadedPackageIndexFiles = contributionInstaller.updateIndex(progressListener);
@@ -304,7 +324,12 @@ public Base(String[] args) throws Exception {
ContributedPlatform selected = null;
if (boardToInstallParts.length == 3) {
- selected = indexer.getIndex().findPlatform(boardToInstallParts[0], boardToInstallParts[1], VersionHelper.valueOf(boardToInstallParts[2]).toString());
+ Optional version = VersionHelper.valueOf(boardToInstallParts[2]);
+ if (!version.isPresent()) {
+ System.out.println(format(tr("Invalid version {0}"), boardToInstallParts[2]));
+ System.exit(1);
+ }
+ selected = indexer.getIndex().findPlatform(boardToInstallParts[0], boardToInstallParts[1], version.get().toString());
} else if (boardToInstallParts.length == 2) {
List platformsByName = indexer.getIndex().findPlatforms(boardToInstallParts[0], boardToInstallParts[1]);
Collections.sort(platformsByName, new DownloadableContributionVersionComparator());
@@ -319,11 +344,11 @@ public Base(String[] args) throws Exception {
ContributedPlatform installed = indexer.getInstalled(boardToInstallParts[0], boardToInstallParts[1]);
- if (!selected.isReadOnly()) {
+ if (!selected.isBuiltIn()) {
contributionInstaller.install(selected, progressListener);
}
- if (installed != null && !installed.isReadOnly()) {
+ if (installed != null && !installed.isBuiltIn()) {
contributionInstaller.remove(installed);
}
@@ -337,15 +362,20 @@ public Base(String[] args) throws Exception {
LibrariesIndexer indexer = new LibrariesIndexer(BaseNoGui.getSettingsFolder());
indexer.parseIndex();
- indexer.setSketchbookLibrariesFolder(BaseNoGui.getSketchbookLibrariesFolder());
- indexer.setLibrariesFolders(BaseNoGui.getLibrariesPath());
+ indexer.setLibrariesFolders(BaseNoGui.getLibrariesFolders());
+ indexer.rescanLibraries();
for (String library : parser.getLibraryToInstall().split(",")) {
String[] libraryToInstallParts = library.split(":");
ContributedLibrary selected = null;
if (libraryToInstallParts.length == 2) {
- selected = indexer.getIndex().find(libraryToInstallParts[0], VersionHelper.valueOf(libraryToInstallParts[1]).toString());
+ Optional version = VersionHelper.valueOf(libraryToInstallParts[1]);
+ if (!version.isPresent()) {
+ System.out.println(format(tr("Invalid version {0}"), libraryToInstallParts[1]));
+ System.exit(1);
+ }
+ selected = indexer.getIndex().find(libraryToInstallParts[0], version.get().toString());
} else if (libraryToInstallParts.length == 1) {
List librariesByName = indexer.getIndex().find(libraryToInstallParts[0]);
Collections.sort(librariesByName, new DownloadableContributionVersionComparator());
@@ -358,11 +388,14 @@ public Base(String[] args) throws Exception {
System.exit(1);
}
- ContributedLibrary installed = indexer.getIndex().getInstalled(libraryToInstallParts[0]);
- if (selected.isReadOnly()) {
- libraryInstaller.remove(installed, progressListener);
+ Optional mayInstalled = indexer.getIndex().getInstalled(libraryToInstallParts[0]);
+ if (mayInstalled.isPresent() && selected.isIDEBuiltIn()) {
+ System.out.println(tr(I18n
+ .format("Library {0} is available as built-in in the IDE.\nRemoving the other version {1} installed in the sketchbook...",
+ library, mayInstalled.get().getParsedVersion())));
+ libraryInstaller.remove(mayInstalled.get(), progressListener);
} else {
- libraryInstaller.install(selected, installed, progressListener);
+ libraryInstaller.install(selected, progressListener);
}
}
@@ -446,7 +479,7 @@ public Base(String[] args) throws Exception {
if (!parser.isForceSavePrefs())
PreferencesData.setDoSave(true);
if (handleOpen(file, retrieveSketchLocation(".default"), false) == null) {
- String mess = I18n.format(tr("Failed to open sketch: \"{0}\""), path);
+ String mess = format(tr("Failed to open sketch: \"{0}\""), path);
// Open failure is fatal in upload/verify mode
if (parser.isVerifyOrUploadMode())
showError(null, mess, 2);
@@ -483,6 +516,9 @@ public Base(String[] args) throws Exception {
System.exit(0);
} else if (parser.isGetPrefMode()) {
BaseNoGui.dumpPrefs(parser);
+ } else if (parser.isVersionMode()) {
+ System.out.println("Arduino: " + BaseNoGui.VERSION_NAME_LONG);
+ System.exit(0);
}
}
@@ -590,7 +626,12 @@ private int[] retrieveSketchLocation(String index) {
.get("last.sketch" + index + ".location");
if (locationStr == null)
return defaultEditorLocation();
- return PApplet.parseInt(PApplet.split(locationStr, ','));
+
+ int location[] = PApplet.parseInt(PApplet.split(locationStr, ','));
+ if (location[0] > screen.width || location[1] > screen.height)
+ return defaultEditorLocation();
+
+ return location;
}
protected void storeRecentSketches(SketchController sketch) {
@@ -629,9 +670,6 @@ protected void handleActivated(Editor whichEditor) {
System.err.println(e);
}
}
-
- // set the current window to be the console that's getting output
- EditorConsole.setCurrentEditorConsole(activeEditor.console);
}
protected int[] defaultEditorLocation() {
@@ -742,7 +780,20 @@ protected File createNewUntitled() throws IOException {
if (!newbieFile.createNewFile()) {
throw new IOException();
}
- FileUtils.copyFile(new File(getContentFile("examples"), "01.Basics" + File.separator + "BareMinimum" + File.separator + "BareMinimum.ino"), newbieFile);
+
+ // Initialize the pde file with the BareMinimum sketch.
+ // Apply user-defined tab settings.
+ String sketch = FileUtils.readFileToString(
+ new File(getContentFile("examples"), "01.Basics" + File.separator
+ + "BareMinimum" + File.separator + "BareMinimum.ino"));
+ String currentTab = " ";
+ String newTab = (PreferencesData.getBoolean("editor.tabs.expand")
+ ? StringUtils.repeat(" ",
+ PreferencesData.getInteger("editor.tabs.size"))
+ : "\t");
+ sketch = sketch.replaceAll(
+ "(?<=(^|\n)(" + currentTab + "){0,50})" + currentTab, newTab);
+ FileUtils.writeStringToFile(newbieFile, sketch);
return newbieFile;
}
@@ -906,46 +957,23 @@ public void actionPerformed(ActionEvent actionEvent) {
* @return true if succeeded in closing, false if canceled.
*/
public boolean handleClose(Editor editor) {
- // Check if modified
-// boolean immediate = editors.size() == 1;
- if (!editor.checkModified()) {
- return false;
- }
if (editors.size() == 1) {
- storeScreenDimensions();
- storeSketches();
-
- // This will store the sketch count as zero
- editors.remove(editor);
- try {
- Editor.serialMonitor.close();
- } catch (Exception e) {
- //ignore
+ if (!handleQuit()) {
+ return false;
}
- rebuildRecentSketchesMenuItems();
-
- // Save out the current prefs state
- PreferencesData.save();
-
- // Since this wasn't an actual Quit event, call System.exit()
- System.exit(0);
-
+ // Everything called after handleQuit will only affect OSX
+ editor.setVisible(false);
+ editors.remove(editor);
} else {
// More than one editor window open,
// proceed with closing the current window.
+ // Check if modified
+ if (!editor.checkModified()) {
+ return false;
+ }
editor.setVisible(false);
editor.dispose();
-// for (int i = 0; i < editorCount; i++) {
-// if (editor == editors[i]) {
-// for (int j = i; j < editorCount-1; j++) {
-// editors[j] = editors[j+1];
-// }
-// editorCount--;
-// // Set to null so that garbage collection occurs
-// editors[editorCount] = null;
-// }
-// }
editors.remove(editor);
}
return true;
@@ -968,11 +996,19 @@ public boolean handleQuit() {
// ignore
}
+ // kill uploader (if still alive)
+ UploaderUtils uploaderInstance = new UploaderUtils();
+ Uploader uploader = uploaderInstance.getUploaderByPreferences(false);
+ if (uploader != null && Uploader.programmerPid != null && Uploader.programmerPid.isAlive()) {
+ // kill the stuck programmer
+ Uploader.programmerPid.destroyForcibly();
+ }
+
if (handleQuitEach()) {
// Save out the current prefs state
PreferencesData.save();
- if (!OSUtils.hasMacOSStyleMenus()) {
+ if (!OSUtils.isMacOS()) {
// If this was fired from the menu or an AppleEvent (the Finder),
// then Mac OS X will send the terminate signal itself.
System.exit(0);
@@ -1062,9 +1098,8 @@ protected void rebuildSketchbookMenu(JMenu menu) {
}
}
- private List getSortedLibraries() {
- List installedLibraries = new LinkedList<>(BaseNoGui.librariesIndexer.getInstalledLibraries());
- Collections.sort(installedLibraries, new LibraryByTypeComparator());
+ private LibraryList getSortedLibraries() {
+ LibraryList installedLibraries = BaseNoGui.librariesIndexer.getInstalledLibraries();
Collections.sort(installedLibraries, new LibraryOfSameTypeComparator());
return installedLibraries;
}
@@ -1087,6 +1122,7 @@ public void rebuildImportMenu(JMenu importMenu) {
addLibraryMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Base.this.handleAddLibrary();
+ BaseNoGui.librariesIndexer.rescanLibraries();
Base.this.onBoardOrPortChange();
Base.this.rebuildImportMenu(Editor.importMenu);
Base.this.rebuildExamplesMenu(Editor.examplesMenu);
@@ -1099,16 +1135,16 @@ public void actionPerformed(ActionEvent e) {
TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform();
if (targetPlatform != null) {
- List libs = getSortedLibraries();
+ LibraryList libs = getSortedLibraries();
String lastLibType = null;
- for (ContributedLibrary lib : libs) {
+ for (UserLibrary lib : libs) {
String libType = lib.getTypes().get(0);
if (!libType.equals(lastLibType)) {
if (lastLibType != null) {
importMenu.addSeparator();
}
lastLibType = libType;
- JMenuItem platformItem = new JMenuItem(I18n.format(tr("{0} libraries"), tr(lastLibType)));
+ JMenuItem platformItem = new JMenuItem(format(tr("{0} libraries"), tr(lastLibType)));
platformItem.setEnabled(false);
importMenu.add(platformItem);
}
@@ -1119,7 +1155,7 @@ public void actionPerformed(ActionEvent event) {
try {
activeEditor.getSketchController().importLibrary(l);
} catch (IOException e) {
- showWarning(tr("Error"), I18n.format("Unable to list header files in {0}", l.getSrcFolder()), e);
+ showWarning(tr("Error"), format("Unable to list header files in {0}", l.getSrcFolder()), e);
}
}
};
@@ -1150,10 +1186,6 @@ public void rebuildExamplesMenu(JMenu menu) {
}
// Libraries can come from 4 locations: collect info about all four
- File ideLibraryPath = BaseNoGui.getContentFile("libraries");
- File sketchbookLibraryPath = BaseNoGui.getSketchbookLibrariesFolder();
- File platformLibraryPath = null;
- File referencedPlatformLibraryPath = null;
String boardId = null;
String referencedPlatformName = null;
String myArch = null;
@@ -1161,14 +1193,12 @@ public void rebuildExamplesMenu(JMenu menu) {
if (targetPlatform != null) {
myArch = targetPlatform.getId();
boardId = BaseNoGui.getTargetBoard().getName();
- platformLibraryPath = new File(targetPlatform.getFolder(), "libraries");
String core = BaseNoGui.getBoardPreferences().get("build.core", "arduino");
if (core.contains(":")) {
String refcore = core.split(":")[0];
TargetPlatform referencedPlatform = BaseNoGui.getTargetPlatform(refcore, myArch);
if (referencedPlatform != null) {
referencedPlatformName = referencedPlatform.getPreferences().get("name");
- referencedPlatformLibraryPath = new File(referencedPlatform.getFolder(), "libraries");
}
}
}
@@ -1188,7 +1218,7 @@ public void rebuildExamplesMenu(JMenu menu) {
LibraryList otherLibs = new LibraryList();
for (UserLibrary lib : allLibraries) {
// Get the library's location - used for sorting into categories
- File libraryLocation = lib.getInstalledFolder().getParentFile();
+ Location location = lib.getLocation();
// Is this library compatible?
List arch = lib.getArchitectures();
boolean compatible;
@@ -1198,31 +1228,28 @@ public void rebuildExamplesMenu(JMenu menu) {
compatible = arch.contains(myArch);
}
// IDE Libaries (including retired)
- if (libraryLocation.equals(ideLibraryPath)) {
+ if (location == Location.IDE_BUILTIN) {
if (compatible) {
// only compatible IDE libs are shown
- boolean retired = false;
- List types = lib.getTypes();
- if (types != null) retired = types.contains("Retired");
- if (retired) {
+ if (lib.getTypes().contains("Retired")) {
retiredIdeLibs.add(lib);
} else {
ideLibs.add(lib);
}
}
// Platform Libraries
- } else if (libraryLocation.equals(platformLibraryPath)) {
+ } else if (location == Location.CORE) {
// all platform libs are assumed to be compatible
platformLibs.add(lib);
// Referenced Platform Libraries
- } else if (libraryLocation.equals(referencedPlatformLibraryPath)) {
+ } else if (location == Location.REFERENCED_CORE) {
// all referenced platform libs are assumed to be compatible
referencedPlatformLibs.add(lib);
// Sketchbook Libraries (including incompatible)
- } else if (libraryLocation.equals(sketchbookLibraryPath)) {
+ } else if (location == Location.SKETCHBOOK) {
if (compatible) {
// libraries promoted from sketchbook (behave as builtin)
- if (lib.getTypes() != null && lib.getTypes().contains("Arduino")
+ if (!lib.getTypes().isEmpty() && lib.getTypes().contains("Arduino")
&& lib.getArchitectures().contains("*")) {
ideLibs.add(lib);
} else {
@@ -1260,7 +1287,7 @@ public void rebuildExamplesMenu(JMenu menu) {
if (!platformLibs.isEmpty()) {
menu.addSeparator();
platformLibs.sort();
- label = new JMenuItem(I18n.format(tr("Examples for {0}"), boardId));
+ label = new JMenuItem(format(tr("Examples for {0}"), boardId));
label.setEnabled(false);
menu.add(label);
for (UserLibrary lib : platformLibs) {
@@ -1271,7 +1298,7 @@ public void rebuildExamplesMenu(JMenu menu) {
if (!referencedPlatformLibs.isEmpty()) {
menu.addSeparator();
referencedPlatformLibs.sort();
- label = new JMenuItem(I18n.format(tr("Examples for {0}"), referencedPlatformName));
+ label = new JMenuItem(format(tr("Examples for {0}"), referencedPlatformName));
label.setEnabled(false);
menu.add(label);
for (UserLibrary lib : referencedPlatformLibs) {
@@ -1293,6 +1320,7 @@ public void rebuildExamplesMenu(JMenu menu) {
if (!sketchbookIncompatibleLibs.isEmpty()) {
sketchbookIncompatibleLibs.sort();
JMenu incompatible = new JMenu(tr("INCOMPATIBLE"));
+ MenuScroller.setScrollerFor(incompatible);
menu.add(incompatible);
for (UserLibrary lib : sketchbookIncompatibleLibs) {
addSketchesSubmenu(incompatible, lib);
@@ -1416,8 +1444,9 @@ public void actionPerformed(ActionEvent actionevent) {
String filterText = "";
String dropdownItem = "";
if (actionevent instanceof Event) {
- filterText = ((Event) actionevent).getPayload().get("filterText").toString();
- dropdownItem = ((Event) actionevent).getPayload().get("dropdownItem").toString();
+ Event e = ((Event) actionevent);
+ filterText = e.getPayload().get("filterText").toString();
+ dropdownItem = e.getPayload().get("dropdownItem").toString();
}
try {
openBoardsManager(filterText, dropdownItem);
@@ -1437,41 +1466,41 @@ public void actionPerformed(ActionEvent actionevent) {
boardMenu.add(new JSeparator());
// Generate custom menus for all platforms
- Set customMenusTitles = new HashSet<>();
for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
for (TargetPlatform targetPlatform : targetPackage.platforms()) {
- customMenusTitles.addAll(targetPlatform.getCustomMenus().values());
+ for (String customMenuTitle : targetPlatform.getCustomMenus().values()) {
+ JMenu customMenu = new JMenu(tr(customMenuTitle));
+ customMenu.putClientProperty("platform", getPlatformUniqueId(targetPlatform));
+ customMenu.putClientProperty("removeOnWindowDeactivation", true);
+ boardsCustomMenus.add(customMenu);
+ }
}
}
- for (String customMenuTitle : customMenusTitles) {
- JMenu customMenu = new JMenu(tr(customMenuTitle));
- customMenu.putClientProperty("removeOnWindowDeactivation", true);
- boardsCustomMenus.add(customMenu);
- }
List menuItemsToClickAfterStartup = new LinkedList<>();
ButtonGroup boardsButtonGroup = new ButtonGroup();
Map buttonGroupsMap = new HashMap<>();
+ List platformMenus = new ArrayList<>();
+
// Cycle through all packages
- boolean first = true;
for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
// For every package cycle through all platform
for (TargetPlatform targetPlatform : targetPackage.platforms()) {
- // Add a separator from the previous platform
- if (!first)
- boardMenu.add(new JSeparator());
- first = false;
-
// Add a title for each platform
String platformLabel = targetPlatform.getPreferences().get("name");
- if (platformLabel != null && !targetPlatform.getBoards().isEmpty()) {
- JMenuItem menuLabel = new JMenuItem(tr(platformLabel));
- menuLabel.setEnabled(false);
- boardMenu.add(menuLabel);
- }
+ if (platformLabel == null)
+ platformLabel = targetPackage.getId() + "-" + targetPlatform.getId();
+
+ // add an hint that this core lives in sketchbook
+ if (targetPlatform.isInSketchbook())
+ platformLabel += " (in sketchbook)";
+
+ JMenu platformBoardsMenu = new JMenu(platformLabel);
+ MenuScroller.setScrollerFor(platformBoardsMenu);
+ platformMenus.add(platformBoardsMenu);
// Cycle through all boards of this platform
for (TargetBoard board : targetPlatform.getBoards().values()) {
@@ -1480,14 +1509,40 @@ public void actionPerformed(ActionEvent actionevent) {
JMenuItem item = createBoardMenusAndCustomMenus(boardsCustomMenus, menuItemsToClickAfterStartup,
buttonGroupsMap,
board, targetPlatform, targetPackage);
- boardMenu.add(item);
+ platformBoardsMenu.add(item);
boardsButtonGroup.add(item);
}
}
}
+ platformMenus.sort((x,y) -> x.getText().compareToIgnoreCase(y.getText()));
+
+ JMenuItem firstBoardItem = null;
+ if (platformMenus.size() == 1) {
+ // When just one platform exists, add the board items directly,
+ // rather than using a submenu
+ for (Component boardItem : platformMenus.get(0).getMenuComponents()) {
+ boardMenu.add(boardItem);
+ if (firstBoardItem == null)
+ firstBoardItem = (JMenuItem)boardItem;
+ }
+ } else {
+ // For multiple platforms, use submenus
+ for (JMenu platformMenu : platformMenus) {
+ if (firstBoardItem == null && platformMenu.getItemCount() > 0)
+ firstBoardItem = platformMenu.getItem(0);
+ boardMenu.add(platformMenu);
+ }
+ }
+
+ if (firstBoardItem == null) {
+ throw new IllegalStateException("No available boards");
+ }
+
+ // If there is no current board yet (first startup, or selected
+ // board no longer defined), select first available board.
if (menuItemsToClickAfterStartup.isEmpty()) {
- menuItemsToClickAfterStartup.add(selectFirstEnabledMenuItem(boardMenu));
+ menuItemsToClickAfterStartup.add(firstBoardItem);
}
for (JMenuItem menuItemToClick : menuItemsToClickAfterStartup) {
@@ -1496,6 +1551,10 @@ public void actionPerformed(ActionEvent actionevent) {
}
}
+ private String getPlatformUniqueId(TargetPlatform platform) {
+ return platform.getId() + "_" + platform.getFolder();
+ }
+
private JRadioButtonMenuItem createBoardMenusAndCustomMenus(
final List boardsCustomMenus, List menuItemsToClickAfterStartup,
Map buttonGroupsMap,
@@ -1519,6 +1578,7 @@ public void actionPerformed(ActionEvent actionevent) {
onBoardOrPortChange();
rebuildImportMenu(Editor.importMenu);
rebuildExamplesMenu(Editor.examplesMenu);
+ rebuildProgrammerMenu();
}
};
action.putValue("b", board);
@@ -1533,7 +1593,7 @@ public void actionPerformed(ActionEvent actionevent) {
PreferencesMap customMenus = targetPlatform.getCustomMenus();
for (final String menuId : customMenus.keySet()) {
String title = customMenus.get(menuId);
- JMenu menu = getBoardCustomMenu(tr(title));
+ JMenu menu = getBoardCustomMenu(tr(title), getPlatformUniqueId(targetPlatform));
if (board.hasMenu(menuId)) {
PreferencesMap boardCustomMenu = board.getMenuLabels(menuId);
@@ -1541,11 +1601,16 @@ public void actionPerformed(ActionEvent actionevent) {
@SuppressWarnings("serial")
Action subAction = new AbstractAction(tr(boardCustomMenu.get(customMenuOption))) {
public void actionPerformed(ActionEvent e) {
- PreferencesData.set("custom_" + menuId, ((TargetBoard) getValue("board")).getId() + "_" + getValue("custom_menu_option"));
+ PreferencesData.set("custom_" + menuId, ((List) getValue("board")).get(0).getId() + "_" + getValue("custom_menu_option"));
onBoardOrPortChange();
}
};
- subAction.putValue("board", board);
+ List boards = (List) subAction.getValue("board");
+ if (boards == null) {
+ boards = new ArrayList<>();
+ }
+ boards.add(board);
+ subAction.putValue("board", boards);
subAction.putValue("custom_menu_option", customMenuOption);
if (!buttonGroupsMap.containsKey(menuId)) {
@@ -1573,7 +1638,9 @@ private void filterVisibilityOfSubsequentBoardMenus(List boardsCustomMenu
JMenu menu = boardsCustomMenus.get(i);
for (int m = 0; m < menu.getItemCount(); m++) {
JMenuItem menuItem = menu.getItem(m);
- menuItem.setVisible(menuItem.getAction().getValue("board").equals(board));
+ for (TargetBoard t_board : (List)menuItem.getAction().getValue("board")) {
+ menuItem.setVisible(t_board.equals(board));
+ }
}
menu.setVisible(ifThereAreVisibleItemsOn(menu));
@@ -1596,9 +1663,9 @@ private static boolean ifThereAreVisibleItemsOn(JMenu menu) {
return false;
}
- private JMenu getBoardCustomMenu(String label) throws Exception {
+ private JMenu getBoardCustomMenu(String label, String platformUniqueId) throws Exception {
for (JMenu menu : boardsCustomMenus) {
- if (label.equals(menu.getText())) {
+ if (label.equals(menu.getText()) && menu.getClientProperty("platform").equals(platformUniqueId)) {
return menu;
}
}
@@ -1630,40 +1697,48 @@ private static JMenuItem selectVisibleSelectedOrFirstMenuItem(JMenu menu) {
throw new IllegalStateException("Menu has no enabled items");
}
- private static JMenuItem selectFirstEnabledMenuItem(JMenu menu) {
- for (int i = 1; i < menu.getItemCount(); i++) {
- JMenuItem item = menu.getItem(i);
- if (item != null && item.isEnabled()) {
- return item;
- }
- }
- throw new IllegalStateException("Menu has no enabled items");
- }
-
public void rebuildProgrammerMenu() {
programmerMenus = new LinkedList<>();
-
ButtonGroup group = new ButtonGroup();
- for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
- for (TargetPlatform targetPlatform : targetPackage.platforms()) {
- for (String programmer : targetPlatform.getProgrammers().keySet()) {
- String id = targetPackage.getId() + ":" + programmer;
- @SuppressWarnings("serial")
- AbstractAction action = new AbstractAction(targetPlatform.getProgrammer(programmer).get("name")) {
- public void actionPerformed(ActionEvent actionevent) {
- PreferencesData.set("programmer", "" + getValue("id"));
- }
- };
- action.putValue("id", id);
- JMenuItem item = new JRadioButtonMenuItem(action);
- if (PreferencesData.get("programmer").equals(id)) {
- item.setSelected(true);
- }
- group.add(item);
- programmerMenus.add(item);
+ TargetBoard board = BaseNoGui.getTargetBoard();
+ TargetPlatform boardPlatform = board.getContainerPlatform();
+ TargetPlatform corePlatform = null;
+
+ String core = board.getPreferences().get("build.core");
+ if (core != null && core.contains(":")) {
+ String[] split = core.split(":", 2);
+ corePlatform = BaseNoGui.getCurrentTargetPlatformFromPackage(split[0]);
+ }
+
+ addProgrammersForPlatform(boardPlatform, programmerMenus, group);
+ if (corePlatform != null)
+ addProgrammersForPlatform(corePlatform, programmerMenus, group);
+
+ if (programmerMenus.isEmpty()) {
+ JMenuItem item = new JMenuItem(tr("No programmers available for this board"));
+ item.setEnabled(false);
+ programmerMenus.add(item);
+ }
+ }
+
+ public void addProgrammersForPlatform(TargetPlatform platform, List menus, ButtonGroup group) {
+ for (String programmer : platform.getProgrammers().keySet()) {
+ String id = platform.getContainerPackage().getId() + ":" + programmer;
+
+ @SuppressWarnings("serial")
+ AbstractAction action = new AbstractAction(platform.getProgrammer(programmer).get("name")) {
+ public void actionPerformed(ActionEvent actionevent) {
+ PreferencesData.set("programmer", "" + getValue("id"));
}
+ };
+ action.putValue("id", id);
+ JMenuItem item = new JRadioButtonMenuItem(action);
+ if (PreferencesData.get("programmer").equals(id)) {
+ item.setSelected(true);
}
+ group.add(item);
+ menus.add(item);
}
}
@@ -1692,19 +1767,12 @@ public int compare(File file, File file2) {
});
boolean ifound = false;
-
for (File subfolder : files) {
- if (FileUtils.isSCCSOrHiddenFile(subfolder)) {
- continue;
- }
-
- if (!subfolder.isDirectory()) continue;
-
- if (addSketchesSubmenu(menu, subfolder.getName(), subfolder)) {
+ if (!FileUtils.isSCCSOrHiddenFile(subfolder) && subfolder.isDirectory()
+ && addSketchesSubmenu(menu, subfolder.getName(), subfolder)) {
ifound = true;
}
}
-
return ifound;
}
@@ -1789,7 +1857,7 @@ public void actionPerformed(ActionEvent event) {
try {
activeEditor.getSketchController().importLibrary(l);
} catch (IOException e) {
- showWarning(tr("Error"), I18n.format("Unable to list header files in {0}", l.getSrcFolder()), e);
+ showWarning(tr("Error"), format("Unable to list header files in {0}", l.getSrcFolder()), e);
}
}
};
@@ -1878,6 +1946,75 @@ public void handleFontSizeChange(int change) {
getEditors().forEach(Editor::applyPreferences);
}
+ /**
+ * Adds a {@link MouseWheelListener} and {@link KeyListener} to the given
+ * component that will make "CTRL scroll" and "CTRL +/-"
+ * (with optional SHIFT for +) increase/decrease the editor text size.
+ * This method is equivalent to calling
+ * {@link #addEditorFontResizeMouseWheelListener(Component)} and
+ * {@link #addEditorFontResizeKeyListener(Component)} on the given component.
+ * Note that this also affects components that use the editor font settings.
+ * @param comp - The component to add the listener to.
+ */
+ public void addEditorFontResizeListeners(Component comp) {
+ addEditorFontResizeMouseWheelListener(comp);
+ addEditorFontResizeKeyListener(comp);
+ }
+
+ /**
+ * Adds a {@link MouseWheelListener} to the given component that will
+ * make "CTRL scroll" increase/decrease the editor text size.
+ * When CTRL is not pressed while scrolling, mouse wheel events are passed
+ * on to the parent of the given component.
+ * Note that this also affects components that use the editor font settings.
+ * @param comp - The component to add the listener to.
+ */
+ public void addEditorFontResizeMouseWheelListener(Component comp) {
+ comp.addMouseWheelListener(e -> {
+ if (e.isControlDown()) {
+ if (e.getWheelRotation() < 0) {
+ this.handleFontSizeChange(1);
+ } else {
+ this.handleFontSizeChange(-1);
+ }
+ } else {
+ if (e.getComponent() != null && e.getComponent().getParent() != null) {
+ e.getComponent().getParent().dispatchEvent(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Adds a {@link KeyListener} to the given component that will make "CTRL +/-"
+ * (with optional SHIFT for +) increase/decrease the editor text size.
+ * Note that this also affects components that use the editor font settings.
+ * @param comp - The component to add the listener to.
+ */
+ public void addEditorFontResizeKeyListener(Component comp) {
+ comp.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK
+ || e.getModifiersEx() == (KeyEvent.CTRL_DOWN_MASK
+ | KeyEvent.SHIFT_DOWN_MASK)) {
+ switch (e.getKeyCode()) {
+ case KeyEvent.VK_PLUS:
+ case KeyEvent.VK_EQUALS:
+ Base.this.handleFontSizeChange(1);
+ break;
+ case KeyEvent.VK_MINUS:
+ if (!e.isShiftDown()) {
+ Base.this.handleFontSizeChange(-1);
+ }
+ break;
+ default:
+ }
+ }
+ }
+ });
+ }
+
public List getBoardsCustomMenus() {
return boardsCustomMenus;
}
@@ -1939,10 +2076,9 @@ static protected File promptSketchbookLocation() {
static public void openURL(String url) {
try {
BaseNoGui.getPlatform().openURL(url);
-
} catch (Exception e) {
showWarning(tr("Problem Opening URL"),
- I18n.format(tr("Could not open the URL\n{0}"), url), e);
+ format(tr("Could not open the URL\n{0}"), url), e);
}
}
@@ -1964,10 +2100,9 @@ static protected boolean openFolderAvailable() {
static public void openFolder(File file) {
try {
BaseNoGui.getPlatform().openFolder(file);
-
} catch (Exception e) {
showWarning(tr("Problem Opening Folder"),
- I18n.format(tr("Could not open the folder\n{0}"), file.getAbsolutePath()), e);
+ format(tr("Could not open the folder\n{0}"), file.getAbsolutePath()), e);
}
}
@@ -2045,7 +2180,7 @@ static public void showReference(String prefix, String filename) {
if(referenceFile.exists()){
openURL(referenceFile.getAbsolutePath());
}else{
- showWarning(tr("Problem Opening URL"), I18n.format(tr("Could not open the URL\n{0}"), referenceFile), null);
+ showWarning(tr("Problem Opening URL"), format(tr("Could not open the URL\n{0}"), referenceFile), null);
}
}
@@ -2301,7 +2436,7 @@ public void handleAddLibrary() {
String libName = libFolder.getName();
if (!BaseNoGui.isSanitaryName(libName)) {
- String mess = I18n.format(tr("The library \"{0}\" cannot be used.\n"
+ String mess = format(tr("The library \"{0}\" cannot be used.\n"
+ "Library names must contain only basic letters and numbers.\n"
+ "(ASCII only and no spaces, and it cannot start with a number)"),
libName);
@@ -2310,8 +2445,10 @@ public void handleAddLibrary() {
}
String[] headers;
- if (new File(libFolder, "library.properties").exists()) {
- headers = BaseNoGui.headerListFromIncludePath(UserLibrary.create(libFolder).getSrcFolder());
+ File libProp = new File(libFolder, "library.properties");
+ File srcFolder = new File(libFolder, "src");
+ if (libProp.exists() && srcFolder.isDirectory()) {
+ headers = BaseNoGui.headerListFromIncludePath(srcFolder);
} else {
headers = BaseNoGui.headerListFromIncludePath(libFolder);
}
@@ -2321,9 +2458,9 @@ public void handleAddLibrary() {
}
// copy folder
- File destinationFolder = new File(BaseNoGui.getSketchbookLibrariesFolder(), sourceFile.getName());
+ File destinationFolder = new File(BaseNoGui.getSketchbookLibrariesFolder().folder, sourceFile.getName());
if (!destinationFolder.mkdir()) {
- activeEditor.statusError(I18n.format(tr("A library named {0} already exists"), sourceFile.getName()));
+ activeEditor.statusError(format(tr("A library named {0} already exists"), sourceFile.getName()));
return;
}
try {
diff --git a/app/src/processing/app/CommandHistory.java b/app/src/processing/app/CommandHistory.java
new file mode 100644
index 00000000000..cae3c2fc498
--- /dev/null
+++ b/app/src/processing/app/CommandHistory.java
@@ -0,0 +1,167 @@
+package processing.app;
+
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+/**
+ * Keeps track of command history in console-like applications.
+ * @author P.J.S. Kools
+ */
+public class CommandHistory {
+
+ private final LinkedList commandHistory = new LinkedList();
+ private final int maxHistorySize;
+ private ListIterator iterator = null;
+ private boolean iteratorAsc;
+
+ /**
+ * Create a new {@link CommandHistory}.
+ * @param maxHistorySize - The max command history size.
+ */
+ public CommandHistory(int maxHistorySize) {
+ this.maxHistorySize = (maxHistorySize < 0 ? 0 : maxHistorySize);
+ this.commandHistory.addLast(""); // Current command placeholder.
+ }
+
+ /**
+ * Adds the given command to the history and resets the history traversal
+ * position to the latest command. If the latest command in the history is
+ * equal to the given command, it will not be added to the history.
+ * If the max history size is exceeded, the oldest command will be removed
+ * from the history.
+ * @param command - The command to add.
+ */
+ public void addCommand(String command) {
+ if (this.maxHistorySize == 0) {
+ return;
+ }
+
+ // Remove 'current' command.
+ this.commandHistory.removeLast();
+
+ // Add new command if it differs from the latest command.
+ if (this.commandHistory.isEmpty()
+ || !this.commandHistory.getLast().equals(command)) {
+
+ // Remove oldest command if max history size is exceeded.
+ if (this.commandHistory.size() >= this.maxHistorySize) {
+ this.commandHistory.removeFirst();
+ }
+
+ // Add new command and reset 'current' command.
+ this.commandHistory.addLast(command);
+ }
+
+ // Re-add 'current' command and reset command iterator.
+ this.commandHistory.addLast(""); // Current command placeholder.
+ this.iterator = null;
+ }
+
+ /**
+ * Gets whether a next (more recent) command is available in the history.
+ * @return {@code true} if a next command is available,
+ * returns {@code false} otherwise.
+ */
+ public boolean hasNextCommand() {
+ if (this.iterator == null) {
+ return false;
+ }
+ if (!this.iteratorAsc) {
+ this.iterator.next(); // Current command, ascending.
+ this.iteratorAsc = true;
+ }
+ return this.iterator.hasNext();
+ }
+
+ /**
+ * Gets the next (more recent) command from the history.
+ * @return The next command or {@code null} if no next command is available.
+ */
+ public String getNextCommand() {
+
+ // Return null if there is no next command available.
+ if (!this.hasNextCommand()) {
+ return null;
+ }
+
+ // Get next command.
+ String next = this.iterator.next();
+
+ // Reset 'current' command when at the end of the list.
+ if (this.iterator.nextIndex() == this.commandHistory.size()) {
+ this.iterator.set(""); // Reset 'current' command.
+ }
+ return next;
+ }
+
+ /**
+ * Gets whether a previous (older) command is available in the history.
+ * @return {@code true} if a previous command is available,
+ * returns {@code false} otherwise.
+ */
+ public boolean hasPreviousCommand() {
+ if (this.iterator == null) {
+ return this.commandHistory.size() > 1;
+ }
+ if (this.iteratorAsc) {
+ this.iterator.previous(); // Current command, descending.
+ this.iteratorAsc = false;
+ }
+ return this.iterator.hasPrevious();
+ }
+
+ /**
+ * Gets the previous (older) command from the history.
+ * When this method is called while the most recent command in the history is
+ * selected, this will store the current command as temporary latest command
+ * so that {@link #getNextCommand()} will return it once. This temporary
+ * latest command gets reset when this case occurs again or when
+ * {@link #addCommand(String)} is invoked.
+ * @param currentCommand - The current unexecuted command.
+ * @return The previous command or {@code null} if no previous command is
+ * available.
+ */
+ public String getPreviousCommand(String currentCommand) {
+
+ // Return null if there is no previous command available.
+ if (!this.hasPreviousCommand()) {
+ return null;
+ }
+
+ // Store current unexecuted command and create iterator if not traversing.
+ if (this.iterator == null) {
+ this.iterator =
+ this.commandHistory.listIterator(this.commandHistory.size());
+ this.iterator.previous(); // Last element, descending.
+ this.iteratorAsc = false;
+ }
+
+ // Store current unexecuted command if on 'current' index.
+ if (this.iterator.nextIndex() == this.commandHistory.size() - 1) {
+ this.iterator.set(currentCommand == null ? "" : currentCommand);
+ }
+
+ // Return the previous command.
+ return this.iterator.previous();
+ }
+
+ /**
+ * Resets the history location to the most recent command.
+ * @returns The latest unexecuted command as stored by
+ * {@link #getPreviousCommand(String)} or an empty string if no such command
+ * was set.
+ */
+ public String resetHistoryLocation() {
+ this.iterator = null;
+ return this.commandHistory.set(this.commandHistory.size() - 1, "");
+ }
+
+ /**
+ * Clears the command history.
+ */
+ public void clear() {
+ this.iterator = null;
+ this.commandHistory.clear();
+ this.commandHistory.addLast(""); // Current command placeholder.
+ }
+}
diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java
index 99e53e488dc..2ec29c498cb 100644
--- a/app/src/processing/app/Editor.java
+++ b/app/src/processing/app/Editor.java
@@ -22,6 +22,72 @@
package processing.app;
+import static processing.app.I18n.tr;
+import static processing.app.Theme.scale;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.print.PageFormat;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.swing.AbstractAction;
+import javax.swing.Box;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JTextArea;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.TransferHandler;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+
+import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
+
+import com.jcraft.jsch.JSchException;
+
+import cc.arduino.CompilerProgressListener;
import cc.arduino.packages.BoardPort;
import cc.arduino.packages.MonitorFactory;
import cc.arduino.packages.Uploader;
@@ -29,10 +95,9 @@
import cc.arduino.view.GoToLineNumber;
import cc.arduino.view.StubMenuListener;
import cc.arduino.view.findreplace.FindReplace;
-import cc.arduino.CompilerProgressListener;
-import com.jcraft.jsch.JSchException;
import jssc.SerialPortException;
import processing.app.debug.RunnerException;
+import processing.app.debug.TargetBoard;
import processing.app.forms.PasswordAuthorizationDialog;
import processing.app.helpers.DocumentTextChangeListener;
import processing.app.helpers.Keys;
@@ -45,33 +110,6 @@
import processing.app.tools.MenuScroller;
import processing.app.tools.Tool;
-import javax.swing.*;
-import javax.swing.event.*;
-import javax.swing.text.BadLocationException;
-import java.awt.*;
-import java.awt.datatransfer.DataFlavor;
-import java.awt.datatransfer.Transferable;
-import java.awt.event.*;
-import java.awt.print.PageFormat;
-import java.awt.print.PrinterException;
-import java.awt.print.PrinterJob;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.net.ConnectException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.*;
-import java.util.List;
-import java.util.function.Predicate;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.ArrayList;
-
-import static processing.app.I18n.tr;
-import static processing.app.Theme.scale;
-
/**
* Main editor panel for the Processing Development Environment.
*/
@@ -94,24 +132,25 @@ private static class ShouldSaveIfModified
public boolean test(SketchController controller) {
return PreferencesData.getBoolean("editor.save_on_verify")
&& controller.getSketch().isModified()
- && !controller.isReadOnly(
- BaseNoGui.librariesIndexer
- .getInstalledLibraries(),
- BaseNoGui.getExamplesPath());
+ && !controller.isReadOnly();
}
}
- private static class ShouldSaveReadOnly implements Predicate {
+ private static class CanExportInSketchFolder
+ implements Predicate {
@Override
- public boolean test(SketchController sketch) {
- return sketch.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath());
+ public boolean test(SketchController controller) {
+ if (controller.isReadOnly()) {
+ return false;
+ }
+ if (controller.getSketch().isModified()) {
+ return PreferencesData.getBoolean("editor.save_on_verify");
+ }
+ return true;
}
}
- private final static List BOARD_PROTOCOLS_ORDER = Arrays.asList("serial", "network");
- private final static List BOARD_PROTOCOLS_ORDER_TRANSLATIONS = Arrays.asList(tr("Serial ports"), tr("Network ports"));
-
final Base base;
// otherwise, if the window is resized with the message label
@@ -144,7 +183,7 @@ public boolean test(SketchController sketch) {
private int numTools = 0;
- public boolean avoidMultipleOperations = false;
+ static public boolean avoidMultipleOperations = false;
private final EditorToolbar toolbar;
// these menus are shared so that they needn't be rebuilt for all windows
@@ -182,7 +221,7 @@ public boolean test(SketchController sketch) {
private JMenuItem saveAsMenuItem;
//boolean presenting;
- private boolean uploading;
+ static private boolean uploading;
// undo fellers
private JMenuItem undoItem;
@@ -194,10 +233,12 @@ public boolean test(SketchController sketch) {
Runnable presentHandler;
private Runnable runAndSaveHandler;
private Runnable presentAndSaveHandler;
- Runnable exportHandler;
- private Runnable exportAppHandler;
+ private UploadHandler uploadHandler;
+ private UploadHandler uploadUsingProgrammerHandler;
private Runnable timeoutUploadHandler;
+ private Map internalToolCache = new HashMap();
+
public Editor(Base ibase, File file, int[] storedLocation, int[] defaultLocation, Platform platform) throws Exception {
super("Arduino");
this.base = ibase;
@@ -228,8 +269,6 @@ public void windowActivated(WindowEvent e) {
// added for 1.0.5
// http://dev.processing.org/bugs/show_bug.cgi?id=1260
public void windowDeactivated(WindowEvent e) {
- fileMenu.remove(sketchbookMenu);
- fileMenu.remove(examplesMenu);
List toolsMenuItemsToRemove = new LinkedList<>();
for (Component menuItem : toolsMenu.getMenuComponents()) {
if (menuItem instanceof JComponent) {
@@ -278,7 +317,7 @@ public void windowDeactivated(WindowEvent e) {
status = new EditorStatus(this);
consolePanel.add(status, BorderLayout.NORTH);
- console = new EditorConsole();
+ console = new EditorConsole(base);
console.setName("console");
// windows puts an ugly border on this guy
console.setBorder(null);
@@ -339,6 +378,9 @@ public void windowDeactivated(WindowEvent e) {
// Open the document that was passed in
boolean loaded = handleOpenInternal(file);
if (!loaded) sketchController = null;
+
+ // default the console output to the last opened editor
+ EditorConsole.setCurrentEditorConsole(console);
}
@@ -451,8 +493,13 @@ public void applyPreferences() {
boolean external = PreferencesData.getBoolean("editor.external");
saveMenuItem.setEnabled(!external);
saveAsMenuItem.setEnabled(!external);
- for (EditorTab tab: tabs)
+ for (EditorTab tab: tabs) {
tab.applyPreferences();
+ }
+ console.applyPreferences();
+ if (serialMonitor != null) {
+ serialMonitor.applyPreferences();
+ }
}
@@ -465,11 +512,11 @@ private void buildMenuBar() {
fileMenu.addMenuListener(new StubMenuListener() {
@Override
public void menuSelected(MenuEvent e) {
- List components = Arrays.asList(fileMenu.getComponents());
+ List components = Arrays.asList(fileMenu.getMenuComponents());
if (!components.contains(sketchbookMenu)) {
fileMenu.insert(sketchbookMenu, 3);
}
- if (!components.contains(sketchbookMenu)) {
+ if (!components.contains(examplesMenu)) {
fileMenu.insert(examplesMenu, 4);
}
fileMenu.revalidate();
@@ -498,7 +545,7 @@ public void menuSelected(MenuEvent e) {
toolsMenu.addMenuListener(new StubMenuListener() {
@Override
public void menuSelected(MenuEvent e) {
- List components = Arrays.asList(toolsMenu.getComponents());
+ List components = Arrays.asList(toolsMenu.getMenuComponents());
int offset = 0;
for (JMenu menu : base.getBoardsCustomMenus()) {
if (!components.contains(menu)) {
@@ -528,37 +575,28 @@ private JMenu buildFileMenu() {
fileMenu.setMnemonic(KeyEvent.VK_F);
item = newJMenuItem(tr("New"), 'N');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- try {
- base.handleNew();
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- }
- });
+ item.addActionListener(event -> {
+ try {
+ base.handleNew();
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ });
fileMenu.add(item);
item = Editor.newJMenuItem(tr("Open..."), 'O');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- try {
- base.handleOpenPrompt();
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- }
- });
+ item.addActionListener(event -> {
+ try {
+ base.handleOpenPrompt();
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ });
fileMenu.add(item);
base.rebuildRecentSketchesMenuItems();
recentSketchesMenu = new JMenu(tr("Open Recent"));
- SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- rebuildRecentSketchesMenu();
- }
- });
+ SwingUtilities.invokeLater(() -> rebuildRecentSketchesMenu());
fileMenu.add(recentSketchesMenu);
if (sketchbookMenu == null) {
@@ -576,45 +614,25 @@ public void run() {
fileMenu.add(examplesMenu);
item = Editor.newJMenuItem(tr("Close"), 'W');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handleClose(Editor.this);
- }
- });
+ item.addActionListener(event -> base.handleClose(Editor.this));
fileMenu.add(item);
saveMenuItem = newJMenuItem(tr("Save"), 'S');
- saveMenuItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleSave(false);
- }
- });
+ saveMenuItem.addActionListener(event -> handleSave(false));
fileMenu.add(saveMenuItem);
saveAsMenuItem = newJMenuItemShift(tr("Save As..."), 'S');
- saveAsMenuItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleSaveAs();
- }
- });
+ saveAsMenuItem.addActionListener(event -> handleSaveAs());
fileMenu.add(saveAsMenuItem);
fileMenu.addSeparator();
item = newJMenuItemShift(tr("Page Setup"), 'P');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handlePageSetup();
- }
- });
+ item.addActionListener(event -> handlePageSetup());
fileMenu.add(item);
item = newJMenuItem(tr("Print"), 'P');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handlePrint();
- }
- });
+ item.addActionListener(event -> handlePrint());
fileMenu.add(item);
// macosx already has its own preferences and quit menu
@@ -622,21 +640,13 @@ public void actionPerformed(ActionEvent e) {
fileMenu.addSeparator();
item = newJMenuItem(tr("Preferences"), ',');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handlePrefs();
- }
- });
+ item.addActionListener(event -> base.handlePrefs());
fileMenu.add(item);
fileMenu.addSeparator();
item = newJMenuItem(tr("Quit"), 'Q');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handleQuit();
- }
- });
+ item.addActionListener(event -> base.handleQuit());
fileMenu.add(item);
}
return fileMenu;
@@ -653,58 +663,36 @@ private void buildSketchMenu(JMenu sketchMenu) {
sketchMenu.removeAll();
JMenuItem item = newJMenuItem(tr("Verify/Compile"), 'R');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleRun(false, Editor.this.presentHandler, Editor.this.runHandler);
- }
- });
+ item.addActionListener(event -> handleRun(false, presentHandler, runHandler));
sketchMenu.add(item);
item = newJMenuItem(tr("Upload"), 'U');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleExport(false);
- }
- });
+ item.addActionListener(event -> handleExport(false));
sketchMenu.add(item);
item = newJMenuItemShift(tr("Upload Using Programmer"), 'U');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleExport(true);
- }
- });
+ item.addActionListener(event -> handleExport(true));
sketchMenu.add(item);
-
item = newJMenuItemAlt(tr("Export compiled Binary"), 'S');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (new ShouldSaveReadOnly().test(sketchController) && !handleSave(true)) {
- System.out.println(tr("Export canceled, changes must first be saved."));
- return;
- }
- handleRun(false, new ShouldSaveReadOnly(), Editor.this.presentAndSaveHandler, Editor.this.runAndSaveHandler);
- }
- });
+ item.addActionListener(event -> {
+ if (!(new CanExportInSketchFolder().test(sketchController))) {
+ System.out.println(tr("Export canceled, changes must first be saved."));
+ return;
+ }
+ handleRun(false, new CanExportInSketchFolder(), presentAndSaveHandler, runAndSaveHandler);
+
+ });
sketchMenu.add(item);
// item = new JMenuItem("Stop");
-// item.addActionListener(new ActionListener() {
-// public void actionPerformed(ActionEvent e) {
-// handleStop();
-// }
-// });
+// item.addActionListener(event -> handleStop());
// sketchMenu.add(item);
sketchMenu.addSeparator();
item = newJMenuItem(tr("Show Sketch Folder"), 'K');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.openFolder(sketch.getFolder());
- }
- });
+ item.addActionListener(event -> Base.openFolder(sketch.getFolder()));
sketchMenu.add(item);
item.setEnabled(Base.openFolderAvailable());
@@ -716,11 +704,7 @@ public void actionPerformed(ActionEvent e) {
sketchMenu.add(importMenu);
item = new JMenuItem(tr("Add File..."));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- sketchController.handleAddFile();
- }
- });
+ item.addActionListener(event -> sketchController.handleAddFile());
sketchMenu.add(item);
}
@@ -777,6 +761,20 @@ private JMenu buildToolsMenu() {
toolsMenu.add(item);
toolsMenu.addMenuListener(new StubMenuListener() {
+ public JMenuItem getSelectedItemRecursive(JMenu menu) {
+ int count = menu.getItemCount();
+ for (int i=0; i < count; i++) {
+ JMenuItem item = menu.getItem(i);
+
+ if ((item instanceof JMenu))
+ item = getSelectedItemRecursive((JMenu)item);
+
+ if (item != null && item.isSelected())
+ return item;
+ }
+ return null;
+ }
+
public void menuSelected(MenuEvent e) {
//System.out.println("Tools menu selected.");
populatePortMenu();
@@ -788,15 +786,9 @@ public void menuSelected(MenuEvent e) {
String basename = name;
int index = name.indexOf(':');
if (index > 0) basename = name.substring(0, index);
- String sel = null;
- int count = menu.getItemCount();
- for (int i=0; i < count; i++) {
- JMenuItem item = menu.getItem(i);
- if (item != null && item.isSelected()) {
- sel = item.getText();
- if (sel != null) break;
- }
- }
+
+ JMenuItem item = getSelectedItemRecursive(menu);
+ String sel = item != null ? item.getText() : null;
if (sel == null) {
if (!name.equals(basename)) menu.setText(basename);
} else {
@@ -894,11 +886,9 @@ public boolean accept(File dir, String name) {
String title = tool.getMenuTitle();
JMenuItem item = new JMenuItem(title);
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- SwingUtilities.invokeLater(tool);
- //new Thread(tool).start();
- }
+ item.addActionListener(event -> {
+ SwingUtilities.invokeLater(tool);
+ //new Thread(tool).start();
});
//menu.add(item);
toolItems.put(title, item);
@@ -963,18 +953,13 @@ public void updateKeywords(PdeKeywords keywords) {
JMenuItem createToolMenuItem(String className) {
try {
- Class> toolClass = Class.forName(className);
- final Tool tool = (Tool) toolClass.newInstance();
+ final Tool tool = getOrCreateToolInstance(className);
JMenuItem item = new JMenuItem(tool.getMenuTitle());
tool.init(Editor.this);
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- SwingUtilities.invokeLater(tool);
- }
- });
+ item.addActionListener(event -> SwingUtilities.invokeLater(tool));
return item;
} catch (Exception e) {
@@ -983,6 +968,20 @@ public void actionPerformed(ActionEvent e) {
}
}
+ private Tool getOrCreateToolInstance(String className) {
+ Tool internalTool = internalToolCache.get(className);
+ if (internalTool == null) {
+ try {
+ Class> toolClass = Class.forName(className);
+ internalTool = (Tool) toolClass.newInstance();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ internalToolCache.put(className, internalTool);
+ }
+ return internalTool;
+ }
private void addInternalTools(JMenu menu) {
JMenuItem item;
@@ -1003,21 +1002,6 @@ private void addInternalTools(JMenu menu) {
}
- class SerialMenuListener implements ActionListener {
-
- private final String serialPort;
-
- public SerialMenuListener(String serialPort) {
- this.serialPort = serialPort;
- }
-
- public void actionPerformed(ActionEvent e) {
- selectSerialPort(serialPort);
- base.onBoardOrPortChange();
- }
-
- }
-
private void selectSerialPort(String name) {
if(portMenu == null) {
System.out.println(tr("serialMenu is null"));
@@ -1041,22 +1025,20 @@ private void selectSerialPort(String name) {
//System.out.println(item.getLabel());
BaseNoGui.selectSerialPort(name);
- if (serialMonitor != null) {
- try {
+ try {
+ boolean reopenMonitor = ((serialMonitor != null && serialMonitor.isVisible()) ||
+ serialPlotter != null && serialPlotter.isVisible());
+ if (serialMonitor != null) {
serialMonitor.close();
- serialMonitor.setVisible(false);
- } catch (Exception e) {
- // ignore
}
- }
-
- if (serialPlotter != null) {
- try {
+ if (serialPlotter != null) {
serialPlotter.close();
- serialPlotter.setVisible(false);
- } catch (Exception e) {
- // ignore
}
+ if (reopenMonitor) {
+ handleSerial();
+ }
+ } catch (Exception e) {
+ // ignore
}
onBoardOrPortChange();
@@ -1065,8 +1047,34 @@ private void selectSerialPort(String name) {
//System.out.println("set to " + get("serial.port"));
}
+ class BoardPortJCheckBoxMenuItem extends JCheckBoxMenuItem {
+ private BoardPort port;
+
+ public BoardPortJCheckBoxMenuItem(BoardPort port) {
+ super();
+ this.port = port;
+ setText(toString());
+ addActionListener(e -> {
+ selectSerialPort(port.getAddress());
+ base.onBoardOrPortChange();
+ });
+ }
+
+ @Override
+ public String toString() {
+ // This is required for serialPrompt()
+ String label = port.getLabel();
+ if (port.getBoardName() != null && !port.getBoardName().isEmpty()) {
+ label += " (" + port.getBoardName() + ")";
+ }
+ return label;
+ }
+ }
private void populatePortMenu() {
+ final List PROTOCOLS_ORDER = Arrays.asList("serial", "network");
+ final List PROTOCOLS_LABELS = Arrays.asList(tr("Serial ports"), tr("Network ports"));
+
portMenu.removeAll();
String selectedPort = PreferencesData.get("serial.port");
@@ -1075,36 +1083,48 @@ private void populatePortMenu() {
ports = platform.filterPorts(ports, PreferencesData.getBoolean("serial.ports.showall"));
- Collections.sort(ports, new Comparator() {
- @Override
- public int compare(BoardPort o1, BoardPort o2) {
- return BOARD_PROTOCOLS_ORDER.indexOf(o1.getProtocol()) - BOARD_PROTOCOLS_ORDER.indexOf(o2.getProtocol());
- }
+ ports.stream() //
+ .filter(port -> port.getProtocolLabel() == null || port.getProtocolLabel().isEmpty())
+ .forEach(port -> {
+ int labelIdx = PROTOCOLS_ORDER.indexOf(port.getProtocol());
+ if (labelIdx != -1) {
+ port.setProtocolLabel(PROTOCOLS_LABELS.get(labelIdx));
+ } else {
+ port.setProtocolLabel(port.getProtocol());
+ }
+ });
+
+ Collections.sort(ports, (port1, port2) -> {
+ String pr1 = port1.getProtocol();
+ String pr2 = port2.getProtocol();
+ int prIdx1 = PROTOCOLS_ORDER.contains(pr1) ? PROTOCOLS_ORDER.indexOf(pr1) : 999;
+ int prIdx2 = PROTOCOLS_ORDER.contains(pr2) ? PROTOCOLS_ORDER.indexOf(pr2) : 999;
+ int r = prIdx1 - prIdx2;
+ if (r != 0)
+ return r;
+ r = port1.getProtocolLabel().compareTo(port2.getProtocolLabel());
+ if (r != 0)
+ return r;
+ return port1.getAddress().compareTo(port2.getAddress());
});
- String lastProtocol = null;
- String lastProtocolTranslated;
+ String lastProtocol = "";
+ String lastProtocolLabel = "";
for (BoardPort port : ports) {
- if (lastProtocol == null || !port.getProtocol().equals(lastProtocol)) {
- if (lastProtocol != null) {
+ if (!port.getProtocol().equals(lastProtocol) || !port.getProtocolLabel().equals(lastProtocolLabel)) {
+ if (!lastProtocol.isEmpty()) {
portMenu.addSeparator();
}
lastProtocol = port.getProtocol();
-
- if (BOARD_PROTOCOLS_ORDER.indexOf(port.getProtocol()) != -1) {
- lastProtocolTranslated = BOARD_PROTOCOLS_ORDER_TRANSLATIONS.get(BOARD_PROTOCOLS_ORDER.indexOf(port.getProtocol()));
- } else {
- lastProtocolTranslated = port.getProtocol();
- }
- JMenuItem lastProtocolMenuItem = new JMenuItem(tr(lastProtocolTranslated));
- lastProtocolMenuItem.setEnabled(false);
- portMenu.add(lastProtocolMenuItem);
+ lastProtocolLabel = port.getProtocolLabel();
+ JMenuItem item = new JMenuItem(tr(lastProtocolLabel));
+ item.setEnabled(false);
+ portMenu.add(item);
}
String address = port.getAddress();
- String label = port.getLabel();
- JCheckBoxMenuItem item = new JCheckBoxMenuItem(label, address.equals(selectedPort));
- item.addActionListener(new SerialMenuListener(address));
+ BoardPortJCheckBoxMenuItem item = new BoardPortJCheckBoxMenuItem(port);
+ item.setSelected(address.equals(selectedPort));
portMenu.add(item);
}
@@ -1113,153 +1133,44 @@ public int compare(BoardPort o1, BoardPort o2) {
private JMenu buildHelpMenu() {
- // To deal with a Mac OS X 10.5 bug, add an extra space after the name
- // so that the OS doesn't try to insert its slow help menu.
JMenu menu = new JMenu(tr("Help"));
menu.setMnemonic(KeyEvent.VK_H);
- JMenuItem item;
-
- /*
- // testing internal web server to serve up docs from a zip file
- item = new JMenuItem("Web Server Test");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- //WebServer ws = new WebServer();
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- try {
- int port = WebServer.launch("/Users/fry/coconut/processing/build/shared/reference.zip");
- Base.openURL("http://127.0.0.1:" + port + "/reference/setup_.html");
-
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- }
- });
- }
- });
- menu.add(item);
- */
-
- /*
- item = new JMenuItem("Browser Test");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- //Base.openURL("http://processing.org/learning/gettingstarted/");
- //JFrame browserFrame = new JFrame("Browser");
- BrowserStartup bs = new BrowserStartup("jar:file:/Users/fry/coconut/processing/build/shared/reference.zip!/reference/setup_.html");
- bs.initUI();
- bs.launch();
- }
- });
- menu.add(item);
- */
- item = new JMenuItem(tr("Getting Started"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showArduinoGettingStarted();
- }
- });
+ JMenuItem item = new JMenuItem(tr("Getting Started"));
+ item.addActionListener(event -> Base.showArduinoGettingStarted());
menu.add(item);
item = new JMenuItem(tr("Environment"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showEnvironment();
- }
- });
+ item.addActionListener(event -> Base.showEnvironment());
menu.add(item);
item = new JMenuItem(tr("Troubleshooting"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showTroubleshooting();
- }
- });
+ item.addActionListener(event -> Base.showTroubleshooting());
menu.add(item);
item = new JMenuItem(tr("Reference"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showReference();
- }
- });
- menu.add(item);
-
- menu.addSeparator();
-
- item = new JMenuItem(tr("Galileo Help"));
- item.setEnabled(false);
- menu.add(item);
-
- item = new JMenuItem(tr("Getting Started"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showReference("reference/Galileo_help_files", "ArduinoIDE_guide_galileo");
- }
- });
- menu.add(item);
- item = new JMenuItem(tr("Troubleshooting"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showReference("reference/Galileo_help_files", "Guide_Troubleshooting_Galileo");
- }
- });
- menu.add(item);
-
- menu.addSeparator();
-
- item = new JMenuItem(tr("Edison Help"));
- item.setEnabled(false);
- menu.add(item);
-
- item = new JMenuItem(tr("Getting Started"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showReference("reference/Edison_help_files", "ArduinoIDE_guide_edison");
- }
- });
- menu.add(item);
- item = new JMenuItem(tr("Troubleshooting"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showReference("reference/Edison_help_files", "Guide_Troubleshooting_Edison");
- }
- });
+ item.addActionListener(event -> Base.showReference());
menu.add(item);
menu.addSeparator();
item = newJMenuItemShift(tr("Find in Reference"), 'F');
- item.addActionListener(this::handleFindReference);
+ item.addActionListener(event -> handleFindReference(event));
menu.add(item);
item = new JMenuItem(tr("Frequently Asked Questions"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showFAQ();
- }
- });
+ item.addActionListener(event -> Base.showFAQ());
menu.add(item);
item = new JMenuItem(tr("Visit Arduino.cc"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.openURL(tr("http://www.arduino.cc/"));
- }
- });
+ item.addActionListener(event -> Base.openURL(tr("http://www.arduino.cc/")));
menu.add(item);
// macosx already has its own about menu
if (!OSUtils.hasMacOSStyleMenus()) {
menu.addSeparator();
item = new JMenuItem(tr("About Arduino"));
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handleAbout();
- }
- });
+ item.addActionListener(event -> base.handleAbout());
menu.add(item);
}
@@ -1274,7 +1185,7 @@ private JMenu buildEditMenu() {
undoItem = newJMenuItem(tr("Undo"), 'Z');
undoItem.setName("menuEditUndo");
- undoItem.addActionListener(e -> getCurrentTab().handleUndo());
+ undoItem.addActionListener(event -> getCurrentTab().handleUndo());
menu.add(undoItem);
if (!OSUtils.isMacOS()) {
@@ -1283,61 +1194,37 @@ private JMenu buildEditMenu() {
redoItem = newJMenuItemShift(tr("Redo"), 'Z');
}
redoItem.setName("menuEditRedo");
- redoItem.addActionListener(e -> getCurrentTab().handleRedo());
+ redoItem.addActionListener(event -> getCurrentTab().handleRedo());
menu.add(redoItem);
menu.addSeparator();
JMenuItem cutItem = newJMenuItem(tr("Cut"), 'X');
- cutItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- getCurrentTab().handleCut();
- }
- });
+ cutItem.addActionListener(event -> getCurrentTab().handleCut());
menu.add(cutItem);
JMenuItem copyItem = newJMenuItem(tr("Copy"), 'C');
- copyItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- getCurrentTab().getTextArea().copy();
- }
- });
+ copyItem.addActionListener(event -> getCurrentTab().getTextArea().copy());
menu.add(copyItem);
JMenuItem copyForumItem = newJMenuItemShift(tr("Copy for Forum"), 'C');
- copyForumItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- getCurrentTab().handleDiscourseCopy();
- }
- });
+ copyForumItem.addActionListener(event -> getCurrentTab().handleDiscourseCopy());
menu.add(copyForumItem);
JMenuItem copyHTMLItem = newJMenuItemAlt(tr("Copy as HTML"), 'C');
- copyHTMLItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- getCurrentTab().handleHTMLCopy();
- }
- });
+ copyHTMLItem.addActionListener(event -> getCurrentTab().handleHTMLCopy());
menu.add(copyHTMLItem);
JMenuItem pasteItem = newJMenuItem(tr("Paste"), 'V');
- pasteItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- getCurrentTab().handlePaste();
- }
- });
+ pasteItem.addActionListener(event -> getCurrentTab().handlePaste());
menu.add(pasteItem);
JMenuItem selectAllItem = newJMenuItem(tr("Select All"), 'A');
- selectAllItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- getCurrentTab().handleSelectAll();
- }
- });
+ selectAllItem.addActionListener(event -> getCurrentTab().handleSelectAll());
menu.add(selectAllItem);
JMenuItem gotoLine = newJMenuItem(tr("Go to line..."), 'L');
- gotoLine.addActionListener(e -> {
+ gotoLine.addActionListener(event -> {
GoToLineNumber goToLineNumber = new GoToLineNumber(Editor.this);
goToLineNumber.setLocationRelativeTo(Editor.this);
goToLineNumber.setVisible(true);
@@ -1347,96 +1234,80 @@ public void actionPerformed(ActionEvent e) {
menu.addSeparator();
JMenuItem commentItem = newJMenuItem(tr("Comment/Uncomment"), PreferencesData.get("editor.keys.shortcut_comment", "/").charAt(0));
- commentItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- getCurrentTab().handleCommentUncomment();
- }
- });
+ commentItem.addActionListener(event -> getCurrentTab().handleCommentUncomment());
menu.add(commentItem);
JMenuItem increaseIndentItem = new JMenuItem(tr("Increase Indent"));
increaseIndentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0));
- increaseIndentItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- getCurrentTab().handleIndentOutdent(true);
- }
- });
+ increaseIndentItem.addActionListener(event -> getCurrentTab().handleIndentOutdent(true));
menu.add(increaseIndentItem);
JMenuItem decreseIndentItem = new JMenuItem(tr("Decrease Indent"));
decreseIndentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK));
decreseIndentItem.setName("menuDecreaseIndent");
- decreseIndentItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- getCurrentTab().handleIndentOutdent(false);
- }
- });
+ decreseIndentItem.addActionListener(event -> getCurrentTab().handleIndentOutdent(false));
menu.add(decreseIndentItem);
menu.addSeparator();
- JMenuItem increaseFontSizeItem = newJMenuItem(tr("Increase Font Size"), '+');
- increaseFontSizeItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handleFontSizeChange(1);
- }
- });
+ JMenuItem increaseFontSizeItem = newJMenuItem(tr("Increase Font Size"), KeyEvent.VK_PLUS);
+ increaseFontSizeItem.addActionListener(event -> base.handleFontSizeChange(1));
menu.add(increaseFontSizeItem);
-
- JMenuItem decreaseFontSizeItem = newJMenuItem(tr("Decrease Font Size"), '-');
- decreaseFontSizeItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handleFontSizeChange(-1);
- }
+ // Many keyboards have '+' and '=' on the same key. Allowing "CTRL +",
+ // "CTRL SHIFT +" and "CTRL =" covers the generally expected behavior.
+ KeyStroke ctrlShiftEq = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, SHORTCUT_KEY_MASK | ActionEvent.SHIFT_MASK);
+ menu.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ctrlShiftEq, "IncreaseFontSize");
+ KeyStroke ctrlEq = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, SHORTCUT_KEY_MASK);
+ menu.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ctrlEq, "IncreaseFontSize");
+ menu.getActionMap().put("IncreaseFontSize", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ base.handleFontSizeChange(1);
+ }
});
+
+ JMenuItem decreaseFontSizeItem = newJMenuItem(tr("Decrease Font Size"), KeyEvent.VK_MINUS);
+ decreaseFontSizeItem.addActionListener(event -> base.handleFontSizeChange(-1));
menu.add(decreaseFontSizeItem);
menu.addSeparator();
JMenuItem findItem = newJMenuItem(tr("Find..."), 'F');
- findItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (find == null) {
- find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE);
- }
- if (!OSUtils.isMacOS()) {
- find.setFindText(getCurrentTab().getSelectedText());
- }
- find.setLocationRelativeTo(Editor.this);
- find.setVisible(true);
+ findItem.addActionListener(event -> {
+ if (find == null) {
+ find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE);
+ }
+ if (!OSUtils.isMacOS()) {
+ find.setFindText(getCurrentTab().getSelectedText());
}
+ find.setLocationRelativeTo(Editor.this);
+ find.setVisible(true);
});
menu.add(findItem);
JMenuItem findNextItem = newJMenuItem(tr("Find Next"), 'G');
- findNextItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (find != null) {
- find.findNext();
- }
+ findNextItem.addActionListener(event -> {
+ if (find != null) {
+ find.findNext();
}
});
menu.add(findNextItem);
JMenuItem findPreviousItem = newJMenuItemShift(tr("Find Previous"), 'G');
- findPreviousItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (find != null) {
- find.findPrevious();
- }
+ findPreviousItem.addActionListener(event -> {
+ if (find != null) {
+ find.findPrevious();
}
});
menu.add(findPreviousItem);
if (OSUtils.isMacOS()) {
JMenuItem useSelectionForFindItem = newJMenuItem(tr("Use Selection For Find"), 'E');
- useSelectionForFindItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (find == null) {
- find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE);
- }
- find.setFindText(getCurrentTab().getSelectedText());
+ useSelectionForFindItem.addActionListener(event -> {
+ if (find == null) {
+ find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE);
}
+ find.setFindText(getCurrentTab().getSelectedText());
});
menu.add(useSelectionForFindItem);
}
@@ -1519,8 +1390,10 @@ private void resetHandlers() {
presentHandler = new BuildHandler(true);
runAndSaveHandler = new BuildHandler(false, true);
presentAndSaveHandler = new BuildHandler(true, true);
- exportHandler = new DefaultExportHandler();
- exportAppHandler = new DefaultExportAppHandler();
+ uploadHandler = new UploadHandler();
+ uploadHandler.setUsingProgrammer(false);
+ uploadUsingProgrammerHandler = new UploadHandler();
+ uploadUsingProgrammerHandler.setUsingProgrammer(true);
timeoutUploadHandler = new TimeoutUploadHandler();
}
@@ -1779,8 +1652,17 @@ public void removeAllLineHighlights() {
}
public void addLineHighlight(int line) throws BadLocationException {
- getCurrentTab().getTextArea().addLineHighlight(line, new Color(1, 0, 0, 0.2f));
- getCurrentTab().getTextArea().setCaretPosition(getCurrentTab().getTextArea().getLineStartOffset(line));
+ SketchTextArea textArea = getCurrentTab().getTextArea();
+ FoldManager foldManager = textArea.getFoldManager();
+ if (foldManager.isLineHidden(line)) {
+ for (int i = 0; i < foldManager.getFoldCount(); i++) {
+ if (foldManager.getFold(i).containsLine(line)) {
+ foldManager.getFold(i).setCollapsed(false);
+ }
+ }
+ }
+ textArea.addLineHighlight(line, new Color(1, 0, 0, 0.2f));
+ textArea.setCaretPosition(textArea.getLineStartOffset(line));
}
@@ -1951,7 +1833,7 @@ protected boolean handleOpenInternal(File sketchFile) {
return true;
}
- private void updateTitle() {
+ public void updateTitle() {
if (sketchController == null) {
return;
}
@@ -2016,7 +1898,12 @@ private boolean handleSave2() {
statusNotice(tr("Saving..."));
boolean saved = false;
try {
- boolean wasReadOnly = sketchController.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath());
+ if (PreferencesData.getBoolean("editor.autoformat_currentfile_before_saving")) {
+ Tool formatTool = getOrCreateToolInstance("cc.arduino.packages.formatter.AStyle");
+ formatTool.run();
+ }
+
+ boolean wasReadOnly = sketchController.isReadOnly();
String previousMainFilePath = sketch.getMainFilePath();
saved = sketchController.save();
if (saved) {
@@ -2091,32 +1978,30 @@ public boolean handleSaveAs() {
private boolean serialPrompt() {
- int count = portMenu.getItemCount();
- Object[] names = new Object[count];
- for (int i = 0; i < count; i++) {
- names[i] = portMenu.getItem(i).getText();
- }
-
- // FIXME: This is horribly unreadable
- String result = (String)
- JOptionPane.showInputDialog(this,
- I18n.format(
- tr("Serial port {0} not found.\n" +
- "Retry the upload with another serial port?"),
- PreferencesData.get("serial.port")
- ),
- "Serial port not found",
- JOptionPane.PLAIN_MESSAGE,
- null,
- names,
- 0);
- if (result == null) return false;
- selectSerialPort(result);
+ List items = new ArrayList<>();
+ for (int i = 0; i < portMenu.getItemCount(); i++) {
+ if (portMenu.getItem(i) instanceof BoardPortJCheckBoxMenuItem)
+ items.add((BoardPortJCheckBoxMenuItem) portMenu.getItem(i));
+ }
+
+ String port = PreferencesData.get("serial.port");
+ String title;
+ if (port == null || port.isEmpty()) {
+ title = tr("Serial port not selected.");
+ } else {
+ title = I18n.format(tr("Serial port {0} not found."), port);
+ }
+ String question = tr("Retry the upload with another serial port?");
+ BoardPortJCheckBoxMenuItem result = (BoardPortJCheckBoxMenuItem) JOptionPane
+ .showInputDialog(this, title + "\n" + question, title,
+ JOptionPane.PLAIN_MESSAGE, null, items.toArray(), 0);
+ if (result == null)
+ return false;
+ result.doClick();
base.onBoardOrPortChange();
return true;
}
-
/**
* Called by Sketch → Export.
* Handles calling the export() function on sketch, and
@@ -2134,11 +2019,7 @@ private boolean serialPrompt() {
*/
synchronized public void handleExport(final boolean usingProgrammer) {
if (PreferencesData.getBoolean("editor.save_on_verify")) {
- if (sketch.isModified()
- && !sketchController.isReadOnly(
- BaseNoGui.librariesIndexer
- .getInstalledLibraries(),
- BaseNoGui.getExamplesPath())) {
+ if (sketch.isModified() && !sketchController.isReadOnly()) {
handleSave(true);
}
}
@@ -2149,14 +2030,20 @@ synchronized public void handleExport(final boolean usingProgrammer) {
avoidMultipleOperations = true;
new Thread(timeoutUploadHandler).start();
- new Thread(usingProgrammer ? exportAppHandler : exportHandler).start();
+ new Thread(usingProgrammer ? uploadUsingProgrammerHandler : uploadHandler).start();
}
- // DAM: in Arduino, this is upload
- class DefaultExportHandler implements Runnable {
- public void run() {
+ class UploadHandler implements Runnable {
+ boolean usingProgrammer = false;
+
+ public void setUsingProgrammer(boolean usingProgrammer) {
+ this.usingProgrammer = usingProgrammer;
+ }
+ public void run() {
try {
+ uploading = true;
+
removeAllLineHighlights();
if (serialMonitor != null) {
serialMonitor.suspend();
@@ -2165,16 +2052,20 @@ public void run() {
serialPlotter.suspend();
}
- uploading = true;
-
- boolean success = sketchController.exportApplet(false);
+ boolean success = sketchController.exportApplet(usingProgrammer);
if (success) {
statusNotice(tr("Done uploading."));
}
} catch (SerialNotFoundException e) {
- if (portMenu.getItemCount() == 0) statusError(e);
- else if (serialPrompt()) run();
- else statusNotice(tr("Upload canceled."));
+ if (portMenu.getItemCount() == 0) {
+ statusError(tr("Serial port not selected."));
+ } else {
+ if (serialPrompt()) {
+ run();
+ } else {
+ statusNotice(tr("Upload canceled."));
+ }
+ }
} catch (PreferencesMapException e) {
statusError(I18n.format(
tr("Error while uploading: missing '{0}' configuration parameter"),
@@ -2201,9 +2092,18 @@ public void run() {
}
}
+ static public boolean isUploading() {
+ return uploading;
+ }
+
private void resumeOrCloseSerialMonitor() {
// Return the serial monitor window to its initial state
if (serialMonitor != null) {
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ // noop
+ }
BoardPort boardPort = BaseNoGui.getDiscoveryManager().find(PreferencesData.get("serial.port"));
long sleptFor = 0;
while (boardPort == null && sleptFor < MAX_TIME_AWAITING_FOR_RESUMING_SERIAL_MONITOR) {
@@ -2250,55 +2150,6 @@ private void resumeOrCloseSerialPlotter() {
}
}
- // DAM: in Arduino, this is upload (with verbose output)
- class DefaultExportAppHandler implements Runnable {
- public void run() {
-
- try {
- if (serialMonitor != null) {
- serialMonitor.suspend();
- }
- if (serialPlotter != null) {
- serialPlotter.suspend();
- }
-
- uploading = true;
-
- boolean success = sketchController.exportApplet(true);
- if (success) {
- statusNotice(tr("Done uploading."));
- }
- } catch (SerialNotFoundException e) {
- if (portMenu.getItemCount() == 0) statusError(e);
- else if (serialPrompt()) run();
- else statusNotice(tr("Upload canceled."));
- } catch (PreferencesMapException e) {
- statusError(I18n.format(
- tr("Error while uploading: missing '{0}' configuration parameter"),
- e.getMessage()));
- } catch (RunnerException e) {
- //statusError("Error during upload.");
- //e.printStackTrace();
- status.unprogress();
- statusError(e);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- avoidMultipleOperations = false;
- populatePortMenu();
- }
- status.unprogress();
- uploading = false;
- //toolbar.clear();
- toolbar.deactivateExport();
-
- resumeOrCloseSerialMonitor();
- resumeOrCloseSerialPlotter();
-
- base.onBoardOrPortChange();
- }
- }
-
class TimeoutUploadHandler implements Runnable {
public void run() {
@@ -2361,6 +2212,7 @@ public void handleSerial() {
return;
}
+ base.addEditorFontResizeListeners(serialMonitor);
Base.setIcon(serialMonitor);
// If currently uploading, disable the monitor (it will be later
@@ -2389,11 +2241,12 @@ public void handleSerial() {
}
try {
- serialMonitor.setVisible(true);
if (!avoidMultipleOperations) {
serialMonitor.open();
}
+ serialMonitor.setVisible(true);
success = true;
+ statusEmpty();
} catch (ConnectException e) {
statusError(tr("Unable to connect: is the sketch using the bridge?"));
} catch (JSchException e) {
@@ -2403,6 +2256,7 @@ public void handleSerial() {
if (e.getCause() != null && e.getCause() instanceof SerialPortException) {
errorMessage += " (" + ((SerialPortException) e.getCause()).getExceptionType() + ")";
}
+ serialMonitor = null;
statusError(errorMessage);
try {
serialMonitor.close();
@@ -2412,12 +2266,12 @@ public void handleSerial() {
} catch (Exception e) {
statusError(e);
} finally {
- if (serialMonitor.requiresAuthorization() && !success) {
+ if (serialMonitor != null && serialMonitor.requiresAuthorization() && !success) {
PreferencesData.remove(serialMonitor.getAuthorizationKey());
}
}
- } while (serialMonitor.requiresAuthorization() && !success);
+ } while (serialMonitor != null && serialMonitor.requiresAuthorization() && !success);
}
@@ -2437,6 +2291,7 @@ public void handlePlotter() {
if (serialPlotter.isClosed()) {
// If it's closed, clear the refrence to the existing
// plotter and create a new one
+ serialPlotter.dispose();
serialPlotter = null;
}
else {
@@ -2490,6 +2345,7 @@ public void handlePlotter() {
serialPlotter.open();
serialPlotter.setVisible(true);
success = true;
+ statusEmpty();
} catch (ConnectException e) {
statusError(tr("Unable to connect: is the sketch using the bridge?"));
} catch (JSchException e) {
@@ -2500,20 +2356,22 @@ public void handlePlotter() {
errorMessage += " (" + ((SerialPortException) e.getCause()).getExceptionType() + ")";
}
statusError(errorMessage);
+ serialPlotter = null;
} catch (Exception e) {
statusError(e);
} finally {
- if (serialPlotter.requiresAuthorization() && !success) {
+ if (serialPlotter != null && serialPlotter.requiresAuthorization() && !success) {
PreferencesData.remove(serialPlotter.getAuthorizationKey());
}
}
- } while (serialPlotter.requiresAuthorization() && !success);
+ } while (serialPlotter != null && serialPlotter.requiresAuthorization() && !success);
}
private void handleBurnBootloader() {
console.clear();
+ EditorConsole.setCurrentEditorConsole(this.console);
statusNotice(tr("Burning bootloader to I/O Board (this may take a minute)..."));
new Thread(() -> {
try {
@@ -2524,6 +2382,8 @@ private void handleBurnBootloader() {
SwingUtilities.invokeLater(() -> statusError(tr("Error while burning bootloader.")));
// error message will already be visible
}
+ } catch (SerialNotFoundException e) {
+ SwingUtilities.invokeLater(() -> statusError(tr("Error while burning bootloader: please select a serial port.")));
} catch (PreferencesMapException e) {
SwingUtilities.invokeLater(() -> {
statusError(I18n.format(
@@ -2555,9 +2415,9 @@ private void handleBoardInfo() {
for (BoardPort port : ports) {
if (port.getAddress().equals(selectedPort)) {
label = port.getBoardName();
- vid = port.getVID();
- pid = port.getPID();
- iserial = port.getISerial();
+ vid = port.getPrefs().get("vid");
+ pid = port.getPrefs().get("pid");
+ iserial = port.getPrefs().get("iserial");
protocol = port.getProtocol();
found = true;
break;
@@ -2727,12 +2587,12 @@ private void statusEmpty() {
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
protected void onBoardOrPortChange() {
- Map boardPreferences = BaseNoGui.getBoardPreferences();
- if (boardPreferences != null)
- lineStatus.setBoardName(boardPreferences.get("name"));
+ TargetBoard board = BaseNoGui.getTargetBoard();
+ if (board != null)
+ lineStatus.setBoardName(board.getName());
else
lineStatus.setBoardName("-");
- lineStatus.setSerialPort(PreferencesData.get("serial.port"));
+ lineStatus.setPort(PreferencesData.get("serial.port"));
lineStatus.repaint();
}
diff --git a/app/src/processing/app/EditorConsole.java b/app/src/processing/app/EditorConsole.java
index b32382354ec..3942908a1da 100644
--- a/app/src/processing/app/EditorConsole.java
+++ b/app/src/processing/app/EditorConsole.java
@@ -27,6 +27,8 @@
import javax.swing.text.*;
import java.awt.*;
import java.io.PrintStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import static processing.app.Theme.scale;
@@ -37,20 +39,21 @@ public class EditorConsole extends JScrollPane {
private static ConsoleOutputStream out;
private static ConsoleOutputStream err;
+ private int startOfLine = 0;
+ private int insertPosition = 0;
- private static synchronized void init(SimpleAttributeSet outStyle, PrintStream outStream, SimpleAttributeSet errStyle, PrintStream errStream) {
- if (out != null) {
- return;
- }
+ // Regex for linesplitting, see insertString for comments.
+ private static final Pattern newLinePattern = Pattern.compile("([^\r\n]*)([\r\n]*\n)?(\r+)?");
- out = new ConsoleOutputStream(outStyle, outStream);
- System.setOut(new PrintStream(out, true));
+ public static synchronized void setCurrentEditorConsole(EditorConsole console) {
+ if (out == null) {
+ out = new ConsoleOutputStream(console.stdOutStyle, System.out);
+ System.setOut(new PrintStream(out, true));
- err = new ConsoleOutputStream(errStyle, errStream);
- System.setErr(new PrintStream(err, true));
- }
+ err = new ConsoleOutputStream(console.stdErrStyle, System.err);
+ System.setErr(new PrintStream(err, true));
+ }
- public static void setCurrentEditorConsole(EditorConsole console) {
out.setCurrentEditorConsole(console);
err.setCurrentEditorConsole(console);
}
@@ -58,7 +61,10 @@ public static void setCurrentEditorConsole(EditorConsole console) {
private final DefaultStyledDocument document;
private final JTextPane consoleTextPane;
- public EditorConsole() {
+ private SimpleAttributeSet stdOutStyle;
+ private SimpleAttributeSet stdErrStyle;
+
+ public EditorConsole(Base base) {
document = new DefaultStyledDocument();
consoleTextPane = new JTextPane(document);
@@ -74,7 +80,7 @@ public EditorConsole() {
Font editorFont = PreferencesData.getFont("editor.font");
Font actualFont = new Font(consoleFont.getName(), consoleFont.getStyle(), scale(editorFont.getSize()));
- SimpleAttributeSet stdOutStyle = new SimpleAttributeSet();
+ stdOutStyle = new SimpleAttributeSet();
StyleConstants.setForeground(stdOutStyle, Theme.getColor("console.output.color"));
StyleConstants.setBackground(stdOutStyle, backgroundColour);
StyleConstants.setFontSize(stdOutStyle, actualFont.getSize());
@@ -84,7 +90,7 @@ public EditorConsole() {
consoleTextPane.setParagraphAttributes(stdOutStyle, true);
- SimpleAttributeSet stdErrStyle = new SimpleAttributeSet();
+ stdErrStyle = new SimpleAttributeSet();
StyleConstants.setForeground(stdErrStyle, Theme.getColor("console.error.color"));
StyleConstants.setBackground(stdErrStyle, backgroundColour);
StyleConstants.setFontSize(stdErrStyle, actualFont.getSize());
@@ -106,12 +112,64 @@ public EditorConsole() {
setPreferredSize(new Dimension(100, (height * lines)));
setMinimumSize(new Dimension(100, (height * lines)));
- EditorConsole.init(stdOutStyle, System.out, stdErrStyle, System.err);
+ // Add font size adjustment listeners.
+ if (base != null)
+ base.addEditorFontResizeListeners(consoleTextPane);
+ }
+
+ public void applyPreferences() {
+
+ // Update the console text pane font from the preferences.
+ Font consoleFont = Theme.getFont("console.font");
+ Font editorFont = PreferencesData.getFont("editor.font");
+ Font actualFont = new Font(consoleFont.getName(), consoleFont.getStyle(), scale(editorFont.getSize()));
+
+ AttributeSet stdOutStyleOld = stdOutStyle.copyAttributes();
+ AttributeSet stdErrStyleOld = stdErrStyle.copyAttributes();
+ StyleConstants.setFontSize(stdOutStyle, actualFont.getSize());
+ StyleConstants.setFontSize(stdErrStyle, actualFont.getSize());
+
+ // Re-insert console text with the new preferences if there were changes.
+ // This assumes that the document has single-child paragraphs (default).
+ if (!stdOutStyle.isEqual(stdOutStyleOld) || !stdErrStyle.isEqual(stdOutStyleOld)) {
+ if (out != null)
+ out.setAttibutes(stdOutStyle);
+ if (err != null)
+ err.setAttibutes(stdErrStyle);
+
+ int start;
+ for (int end = document.getLength() - 1; end >= 0; end = start - 1) {
+ Element elem = document.getParagraphElement(end);
+ start = elem.getStartOffset();
+ AttributeSet attrs = elem.getElement(0).getAttributes();
+ AttributeSet newAttrs;
+ if (attrs.isEqual(stdErrStyleOld)) {
+ newAttrs = stdErrStyle;
+ } else if (attrs.isEqual(stdOutStyleOld)) {
+ newAttrs = stdOutStyle;
+ } else {
+ continue;
+ }
+ try {
+ String text = document.getText(start, end - start);
+ document.remove(start, end - start);
+ document.insertString(start, text, newAttrs);
+ } catch (BadLocationException e) {
+ // Should only happen when text is async removed (through clear()).
+ // Accept this case, but throw an error when text could mess up.
+ if (document.getLength() != 0) {
+ throw new Error(e);
+ }
+ }
+ }
+ }
}
public void clear() {
try {
document.remove(0, document.getLength());
+ startOfLine = 0;
+ insertPosition = 0;
} catch (BadLocationException e) {
// ignore the error otherwise this will cause an infinite loop
// maybe not a good idea in the long run?
@@ -127,14 +185,53 @@ public boolean isEmpty() {
return document.getLength() == 0;
}
- public void insertString(String line, SimpleAttributeSet attributes) throws BadLocationException {
- line = line.replace("\r\n", "\n").replace("\r", "\n");
- int offset = document.getLength();
- document.insertString(offset, line, attributes);
+ public void insertString(String str, SimpleAttributeSet attributes) throws BadLocationException {
+ // Separate the string into content, newlines and lone carriage
+ // returns.
+ //
+ // Doing so allows lone CRs to move the insertPosition back to the
+ // start of the line to allow overwriting the most recent line (e.g.
+ // for a progress bar). Any CR or NL that are immediately followed
+ // by another NL are bunched together for efficiency, since these
+ // can just be inserted into the document directly and still be
+ // correct.
+ //
+ // The regex is written so it will necessarily match any string
+ // completely if applied repeatedly. This is important because any
+ // part not matched would be silently dropped.
+ Matcher m = newLinePattern.matcher(str);
+
+ while (m.find()) {
+ String content = m.group(1);
+ String newlines = m.group(2);
+ String crs = m.group(3);
+
+ // Replace (or append if at end of the document) the content first
+ int replaceLength = Math.min(content.length(), document.getLength() - insertPosition);
+ document.replace(insertPosition, replaceLength, content, attributes);
+ insertPosition += content.length();
+
+ // Then insert any newlines, but always at the end of the document
+ // e.g. if insertPosition is halfway a line, do not delete
+ // anything, just add the newline(s) at the end).
+ if (newlines != null) {
+ document.insertString(document.getLength(), newlines, attributes);
+ insertPosition = document.getLength();
+ startOfLine = insertPosition;
+ }
+
+ // Then, for any CRs not followed by newlines, move insertPosition
+ // to the start of the line. Note that if a newline follows before
+ // any content in the next call to insertString, it will be added
+ // at the end of the document anyway, as expected.
+ if (crs != null) {
+ insertPosition = startOfLine;
+ }
+ }
}
public String getText() {
- return consoleTextPane.getText().trim();
+ return consoleTextPane.getText();
}
}
diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java
index 25c09a8dfaa..c5695cf8abd 100644
--- a/app/src/processing/app/EditorHeader.java
+++ b/app/src/processing/app/EditorHeader.java
@@ -73,6 +73,7 @@ public class EditorHeader extends JComponent {
static final int PIECE_WIDTH = scale(4);
static final int PIECE_HEIGHT = scale(33);
+ static final int TAB_HEIGHT = scale(27);
// value for the size bars, buttons, etc
// TODO: Should be a Theme value?
@@ -270,8 +271,8 @@ public void paintComponent(Graphics screen) {
int textLeft = contentLeft + (pieceWidth - textWidth) / 2;
g.setColor(textColor[state]);
- int baseline = (sizeH + fontAscent) / 2;
- //g.drawString(sketch.code[i].name, textLeft, baseline);
+ int tabMarginTop = sizeH - TAB_HEIGHT;
+ int baseline = tabMarginTop + ((TAB_HEIGHT + fontAscent) / 2) ;
g.drawString(text, textLeft, baseline);
g.drawImage(pieces[state][RIGHT], x, 0, null);
diff --git a/app/src/processing/app/EditorLineStatus.java b/app/src/processing/app/EditorLineStatus.java
index 7d4e80b6577..7635437da4f 100644
--- a/app/src/processing/app/EditorLineStatus.java
+++ b/app/src/processing/app/EditorLineStatus.java
@@ -51,8 +51,7 @@ public class EditorLineStatus extends JComponent {
String text = "";
String name = "";
- String serialport = "";
- String serialnumber = "";
+ String port = "";
public EditorLineStatus() {
background = Theme.getColor("linestatus.bgcolor");
@@ -92,13 +91,13 @@ public void set(int newStart, int newStop) {
public void paintComponent(Graphics graphics) {
Graphics2D g = Theme.setupGraphics2D(graphics);
- if (name.isEmpty() && serialport.isEmpty()) {
+ if (name.isEmpty() && port.isEmpty()) {
PreferencesMap boardPreferences = BaseNoGui.getBoardPreferences();
if (boardPreferences != null)
setBoardName(boardPreferences.get("name"));
else
setBoardName("-");
- setSerialPort(PreferencesData.get("serial.port"));
+ setPort(PreferencesData.get("serial.port"));
}
g.setColor(background);
Dimension size = getSize();
@@ -110,11 +109,17 @@ public void paintComponent(Graphics graphics) {
g.drawString(text, scale(6), baseline);
g.setColor(messageForeground);
- String tmp = I18n.format(tr("{0} on {1}"), name, serialport);
-
- Rectangle2D bounds = g.getFontMetrics().getStringBounds(tmp, null);
-
- g.drawString(tmp, size.width - (int) bounds.getWidth() - RESIZE_IMAGE_SIZE,
+
+ String statusText;
+ if (port != null && !port.isEmpty()) {
+ statusText = I18n.format(tr("{0} on {1}"), name, port);
+ } else {
+ statusText = name;
+ }
+
+ Rectangle2D bounds = g.getFontMetrics().getStringBounds(statusText, null);
+
+ g.drawString(statusText, size.width - (int) bounds.getWidth() - RESIZE_IMAGE_SIZE,
baseline);
if (OSUtils.isMacOS()) {
@@ -126,12 +131,8 @@ public void setBoardName(String name) {
this.name = name;
}
- public void setSerialPort(String serialport) {
- this.serialport = serialport;
- }
-
- public void setSerialNumber(String serialnumber) {
- this.serialnumber = serialnumber;
+ public void setPort(String port) {
+ this.port = port;
}
public Dimension getPreferredSize() {
diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java
index 33dabdbbbc2..5e8f3e4bfcf 100644
--- a/app/src/processing/app/EditorTab.java
+++ b/app/src/processing/app/EditorTab.java
@@ -30,9 +30,8 @@
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import java.awt.event.MouseWheelListener;
-import java.awt.event.MouseWheelEvent;
-
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
import java.io.IOException;
import javax.swing.Action;
@@ -50,6 +49,7 @@
import javax.swing.text.DefaultCaret;
import javax.swing.text.Document;
+import org.apache.commons.lang3.StringUtils;
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit;
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
@@ -67,7 +67,7 @@
/**
* Single tab, editing a single file, in the main window.
*/
-public class EditorTab extends JPanel implements SketchFile.TextStorage, MouseWheelListener {
+public class EditorTab extends JPanel implements SketchFile.TextStorage {
protected Editor editor;
protected SketchTextArea textarea;
protected RTextScrollPane scrollPane;
@@ -109,7 +109,7 @@ public EditorTab(Editor editor, SketchFile file, String contents)
file.setStorage(this);
applyPreferences();
add(scrollPane, BorderLayout.CENTER);
- textarea.addMouseWheelListener(this);
+ editor.base.addEditorFontResizeMouseWheelListener(textarea);
}
private RSyntaxDocument createDocument(String contents) {
@@ -176,23 +176,20 @@ private SketchTextArea createTextArea(RSyntaxDocument document)
editor.lineStatus.set(lineStart, lineEnd);
});
+ textArea.addFocusListener(new FocusListener() {
+ public void focusGained(FocusEvent e) {
+ Element root = textArea.getDocument().getDefaultRootElement();
+ int lineStart = root.getElementIndex(textArea.getCaret().getMark());
+ int lineEnd = root.getElementIndex(textArea.getCaret().getDot());
+ editor.lineStatus.set(lineStart, lineEnd);
+ };
+ public void focusLost(FocusEvent e) {};
+ });
ToolTipManager.sharedInstance().registerComponent(textArea);
configurePopupMenu(textArea);
return textArea;
}
-
- public void mouseWheelMoved(MouseWheelEvent e) {
- if (e.isControlDown()) {
- if (e.getWheelRotation() < 0) {
- editor.base.handleFontSizeChange(1);
- } else {
- editor.base.handleFontSizeChange(-1);
- }
- } else {
- e.getComponent().getParent().dispatchEvent(e);
- }
- }
private void configurePopupMenu(final SketchTextArea textarea){
@@ -319,6 +316,20 @@ public void applyPreferences() {
}
// apply changes to the font size for the editor
Font editorFont = scale(PreferencesData.getFont("editor.font"));
+
+ // check whether a theme-defined editor font is available
+ Font themeFont = Theme.getFont("editor.font");
+ if (themeFont != null)
+ {
+ // Apply theme font if the editor font has *not* been changed by the user,
+ // This allows themes to specify an editor font which will only be applied
+ // if the user hasn't already changed their editor font via preferences.txt
+ String defaultFontName = StringUtils.defaultIfEmpty(PreferencesData.getDefault("editor.font"), "").split(",")[0];
+ if (defaultFontName.equals(editorFont.getName())) {
+ editorFont = new Font(themeFont.getName(), themeFont.getStyle(), editorFont.getSize());
+ }
+ }
+
textarea.setFont(editorFont);
scrollPane.getGutter().setLineNumberFont(editorFont);
}
@@ -431,6 +442,9 @@ public void setText(String what) {
} finally {
caret.setUpdatePolicy(policy);
}
+ // A trick to force textarea to recalculate the bracket matching rectangle.
+ // In the worst case scenario, this should be ineffective.
+ textarea.setLineWrap(textarea.getLineWrap());
}
/**
diff --git a/app/src/processing/app/EditorToolbar.java b/app/src/processing/app/EditorToolbar.java
index 00da0f37ac7..d37d0cc96a7 100644
--- a/app/src/processing/app/EditorToolbar.java
+++ b/app/src/processing/app/EditorToolbar.java
@@ -23,11 +23,25 @@
package processing.app;
+import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.MouseInputListener;
+
+import com.thizzer.jtouchbar.JTouchBar;
+import com.thizzer.jtouchbar.item.TouchBarItem;
+import com.thizzer.jtouchbar.item.view.TouchBarButton;
+
+import cc.arduino.contributions.VersionComparator;
+import processing.app.helpers.OSUtils;
+
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import static processing.app.I18n.tr;
import static processing.app.Theme.scale;
@@ -92,10 +106,13 @@ public class EditorToolbar extends JComponent implements MouseInputListener, Key
private final Color bgcolor;
private static Image[][] buttonImages;
+ private static com.thizzer.jtouchbar.common.Image[][] touchBarImages;
private int currentRollover;
private JPopupMenu popup;
private final JMenu menu;
+ private JTouchBar touchBar;
+ private TouchBarButton[] touchBarButtons;
private int buttonCount;
private int[] state = new int[BUTTON_COUNT];
@@ -133,10 +150,60 @@ public EditorToolbar(Editor editor, JMenu menu) {
statusFont = Theme.getFont("buttons.status.font");
statusColor = Theme.getColor("buttons.status.color");
+ if (OSUtils.isMacOS() && VersionComparator.greaterThanOrEqual(OSUtils.version(), "10.12")) {
+ editor.addWindowListener(new WindowAdapter() {
+ public void windowActivated(WindowEvent e) {
+ if (touchBar == null) {
+ buildTouchBar();
+
+ touchBar.show(editor);
+ }
+ }
+ });
+ }
+
addMouseListener(this);
addMouseMotionListener(this);
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this);
}
+
+ private void buildTouchBar() {
+ if (touchBarImages == null) {
+ loadTouchBarImages();
+ }
+
+ touchBar = new JTouchBar();
+ touchBarButtons = new TouchBarButton[BUTTON_COUNT];
+ touchBar.setCustomizationIdentifier("Arduino");
+
+ for (int i = 0; i < BUTTON_COUNT; i++) {
+ final int selection = i;
+
+ // add spacers before NEW and SERIAL buttons
+ if (i == NEW) {
+ touchBar.addItem(new TouchBarItem(TouchBarItem.NSTouchBarItemIdentifierFixedSpaceSmall));
+ } else if (i == SERIAL) {
+ touchBar.addItem(new TouchBarItem(TouchBarItem.NSTouchBarItemIdentifierFlexibleSpace));
+ }
+
+ touchBarButtons[i] = new TouchBarButton();
+ touchBarButtons[i].setImage(touchBarImages[i][ROLLOVER]);
+ touchBarButtons[i].setAction(event -> {
+ // Run event handler later to prevent hanging if a dialog needs to be open
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ handleSelectionPressed(selection);
+ }
+ });
+ });
+
+ TouchBarItem touchBarItem = new TouchBarItem(title[i], touchBarButtons[i], true);
+ touchBarItem.setCustomizationLabel(title[i]);
+
+ touchBar.addItem(touchBarItem);
+ }
+ }
private void loadButtons() {
Image allButtons = Theme.getThemeImage("buttons", this,
@@ -157,6 +224,36 @@ private void loadButtons() {
}
}
}
+
+ private void loadTouchBarImages() {
+ Image allButtonsRetina = Theme.getThemeImage("buttons", this,
+ BUTTON_IMAGE_SIZE * BUTTON_COUNT * 2,
+ BUTTON_IMAGE_SIZE * 3 * 2);
+ touchBarImages = new com.thizzer.jtouchbar.common.Image[BUTTON_COUNT][3];
+
+ for (int i = 0; i < BUTTON_COUNT; i++) {
+ for (int state = 0; state < 3; state++) {
+ BufferedImage image = new BufferedImage(BUTTON_WIDTH * 2, BUTTON_HEIGHT * 2,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics g = image.getGraphics();
+
+ int offset = (BUTTON_IMAGE_SIZE * 2 - BUTTON_WIDTH * 2) / 2;
+ g.drawImage(allButtonsRetina, -(i * BUTTON_IMAGE_SIZE * 2) - offset,
+ (-2 + state) * BUTTON_IMAGE_SIZE * 2, null);
+
+ // convert the image to a PNG to display on the touch bar
+ ByteArrayOutputStream pngStream = new ByteArrayOutputStream();
+
+ try {
+ ImageIO.write(image, "PNG", pngStream);
+
+ touchBarImages[i][state] = new com.thizzer.jtouchbar.common.Image(pngStream.toByteArray());
+ } catch (IOException e) {
+ // ignore errors
+ }
+ }
+ }
+ }
@Override
public void paintComponent(Graphics screen) {
@@ -305,6 +402,15 @@ private void setState(int slot, int newState, boolean updateAfter) {
if (updateAfter) {
repaint();
}
+
+ if (touchBarButtons != null) {
+ if (newState == INACTIVE) {
+ // use ROLLOVER state when INACTIVE
+ newState = ROLLOVER;
+ }
+
+ touchBarButtons[slot].setImage(touchBarImages[slot][newState]);
+ }
}
@@ -339,6 +445,20 @@ public void mousePressed(MouseEvent e) {
if (sel == -1) return;
currentRollover = -1;
+ handleSelectionPressed(sel, e.isShiftDown(), x, y);
+ }
+
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ private void handleSelectionPressed(int sel) {
+ handleSelectionPressed(sel, false, 0, 0);
+ }
+
+ private void handleSelectionPressed(int sel, boolean isShiftDown, int x, int y) {
switch (sel) {
case RUN:
if (!editor.avoidMultipleOperations) {
@@ -347,10 +467,10 @@ public void mousePressed(MouseEvent e) {
}
break;
-// case STOP:
-// editor.handleStop();
-// break;
-//
+// case STOP:
+// editor.handleStop();
+// break;
+//
case OPEN:
popup = menu.getPopupMenu();
popup.show(EditorToolbar.this, x, y);
@@ -365,7 +485,7 @@ public void mousePressed(MouseEvent e) {
break;
case SAVE:
- if (e.isShiftDown()) {
+ if (isShiftDown) {
editor.handleSaveAs();
} else {
editor.handleSave(false);
@@ -375,7 +495,7 @@ public void mousePressed(MouseEvent e) {
case EXPORT:
// launch a timeout timer which can reenable to upload button functionality an
if (!editor.avoidMultipleOperations) {
- editor.handleExport(e.isShiftDown());
+ editor.handleExport(isShiftDown);
}
break;
@@ -388,15 +508,6 @@ public void mousePressed(MouseEvent e) {
}
}
-
- public void mouseClicked(MouseEvent e) {
- }
-
-
- public void mouseReleased(MouseEvent e) {
- }
-
-
/**
* Set a particular button to be active.
*/
diff --git a/app/src/processing/app/SerialMonitor.java b/app/src/processing/app/SerialMonitor.java
index 45adbd7d54b..b2656ca653d 100644
--- a/app/src/processing/app/SerialMonitor.java
+++ b/app/src/processing/app/SerialMonitor.java
@@ -21,9 +21,10 @@
import cc.arduino.packages.BoardPort;
import processing.app.legacy.PApplet;
-import java.awt.*;
+import java.awt.Color;
import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
import static processing.app.I18n.tr;
@@ -33,17 +34,21 @@ public class SerialMonitor extends AbstractTextMonitor {
private Serial serial;
private int serialRate;
+ private static final int COMMAND_HISTORY_SIZE = 100;
+ private final CommandHistory commandHistory =
+ new CommandHistory(COMMAND_HISTORY_SIZE);
+
public SerialMonitor(BoardPort port) {
super(port);
serialRate = PreferencesData.getInteger("serial.debug_rate");
serialRates.setSelectedItem(serialRate + " " + tr("baud"));
- onSerialRateChange(new ActionListener() {
- public void actionPerformed(ActionEvent event) {
- String wholeString = (String) serialRates.getSelectedItem();
- String rateString = wholeString.substring(0, wholeString.indexOf(' '));
- serialRate = Integer.parseInt(rateString);
- PreferencesData.set("serial.debug_rate", rateString);
+ onSerialRateChange((ActionEvent event) -> {
+ String wholeString = (String) serialRates.getSelectedItem();
+ String rateString = wholeString.substring(0, wholeString.indexOf(' '));
+ serialRate = Integer.parseInt(rateString);
+ PreferencesData.set("serial.debug_rate", rateString);
+ if (serial != null) {
try {
close();
Thread.sleep(100); // Wait for serial port to properly close
@@ -56,16 +61,41 @@ public void actionPerformed(ActionEvent event) {
}
});
- onSendCommand(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- send(textField.getText());
- textField.setText("");
- }
+ onSendCommand((ActionEvent event) -> {
+ String command = textField.getText();
+ send(command);
+ commandHistory.addCommand(command);
+ textField.setText("");
});
-
- onClearCommand(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- textArea.setText("");
+
+ onClearCommand((ActionEvent event) -> textArea.setText(""));
+
+ // Add key listener to UP, DOWN, ESC keys for command history traversal.
+ textField.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ switch (e.getKeyCode()) {
+
+ // Select previous command.
+ case KeyEvent.VK_UP:
+ if (commandHistory.hasPreviousCommand()) {
+ textField.setText(
+ commandHistory.getPreviousCommand(textField.getText()));
+ }
+ break;
+
+ // Select next command.
+ case KeyEvent.VK_DOWN:
+ if (commandHistory.hasNextCommand()) {
+ textField.setText(commandHistory.getNextCommand());
+ }
+ break;
+
+ // Reset history location, restoring the last unexecuted command.
+ case KeyEvent.VK_ESCAPE:
+ textField.setText(commandHistory.resetHistoryLocation());
+ break;
+ }
}
});
}
@@ -93,6 +123,7 @@ private void send(String s) {
}
}
+ @Override
public void open() throws Exception {
super.open();
@@ -106,13 +137,13 @@ protected void message(char buff[], int n) {
};
}
+ @Override
public void close() throws Exception {
super.close();
if (serial != null) {
int[] location = getPlacement();
String locationStr = PApplet.join(PApplet.str(location), ",");
PreferencesData.set("last.serial.location", locationStr);
- textArea.setText("");
serial.dispose();
serial = null;
}
diff --git a/app/src/processing/app/SerialPlotter.java b/app/src/processing/app/SerialPlotter.java
index 363753749fe..035005ac362 100644
--- a/app/src/processing/app/SerialPlotter.java
+++ b/app/src/processing/app/SerialPlotter.java
@@ -26,8 +26,12 @@
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
+import javax.swing.text.DefaultEditorKit;
import java.awt.*;
+import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
@@ -40,12 +44,18 @@ public class SerialPlotter extends AbstractMonitor {
private Serial serial;
private int serialRate, xCount;
+ private JLabel noLineEndingAlert;
+ private JTextField textField;
+ private JButton sendButton;
+ private JComboBox lineEndings;
+
private ArrayList graphs;
private final static int BUFFER_CAPACITY = 500;
private static class Graph {
public CircularBuffer buffer;
private Color color;
+ public String label;
public Graph(int id) {
buffer = new CircularBuffer(BUFFER_CAPACITY);
@@ -185,12 +195,24 @@ public void paintComponent(Graphics g1) {
g.setTransform(AffineTransform.getTranslateInstance(xOffset, 0));
float xstep = (float) (bounds.width - xOffset - xPadding) / (float) BUFFER_CAPACITY;
- int legendLength = graphs.size() * 10 + (graphs.size() - 1) * 3;
+ // draw legend
+ int legendXOffset = 0;
for(int i = 0; i < graphs.size(); ++i) {
graphs.get(i).paint(g, xstep, minY, maxY, rangeY, bounds.height);
- if(graphs.size() > 1) {
- g.fillRect(bounds.width - (xOffset + legendLength + 10) + i * 13, 10, 10, 10);
+ if(graphs.size() > 1) {
+ //draw legend rectangle
+ g.fillRect(10 + legendXOffset, 10, 10, 10);
+ legendXOffset += 13;
+ //draw label
+ g.setColor(boundsColor);
+ String s = graphs.get(i).label;
+ if(s != null && s.length() > 0) {
+ Rectangle2D fBounds = fm.getStringBounds(s, g);
+ int sWidth = (int)fBounds.getWidth();
+ g.drawString(s, 10 + legendXOffset, 10 + (int)fBounds.getHeight() /2);
+ legendXOffset += sWidth + 3;
+ }
}
}
}
@@ -220,12 +242,14 @@ public SerialPlotter(BoardPort port) {
String rateString = wholeString.substring(0, wholeString.indexOf(' '));
serialRate = Integer.parseInt(rateString);
PreferencesData.set("serial.debug_rate", rateString);
- try {
- close();
- Thread.sleep(100); // Wait for serial port to properly close
- open();
- } catch (Exception e) {
- // ignore
+ if (serial != null) {
+ try {
+ close();
+ Thread.sleep(100); // Wait for serial port to properly close
+ open();
+ } catch (Exception e) {
+ // ignore
+ }
}
});
@@ -254,10 +278,111 @@ protected void onCreateWindow(Container mainPane) {
pane.add(serialRates);
mainPane.add(pane, BorderLayout.SOUTH);
+
+ textField = new JTextField(40);
+ // textField is selected every time the window is focused
+ addWindowFocusListener(new WindowAdapter() {
+ @Override
+ public void windowGainedFocus(WindowEvent e) {
+ textField.requestFocusInWindow();
+ }
+ });
+
+ // Add cut/copy/paste contextual menu to the text input field.
+ JPopupMenu menu = new JPopupMenu();
+
+ Action cut = new DefaultEditorKit.CutAction();
+ cut.putValue(Action.NAME, tr("Cut"));
+ menu.add(cut);
+
+ Action copy = new DefaultEditorKit.CopyAction();
+ copy.putValue(Action.NAME, tr("Copy"));
+ menu.add(copy);
+
+ Action paste = new DefaultEditorKit.PasteAction();
+ paste.putValue(Action.NAME, tr("Paste"));
+ menu.add(paste);
+
+ textField.setComponentPopupMenu(menu);
+
+ sendButton = new JButton(tr("Send"));
+
+ JPanel lowerPane = new JPanel();
+ lowerPane.setLayout(new BoxLayout(lowerPane, BoxLayout.X_AXIS));
+ lowerPane.setBorder(new EmptyBorder(4, 4, 4, 4));
+
+ noLineEndingAlert = new JLabel(I18n.format(tr("You've pressed {0} but nothing was sent. Should you select a line ending?"), tr("Send")));
+ noLineEndingAlert.setToolTipText(noLineEndingAlert.getText());
+ noLineEndingAlert.setForeground(pane.getBackground());
+ Dimension minimumSize = new Dimension(noLineEndingAlert.getMinimumSize());
+ minimumSize.setSize(minimumSize.getWidth() / 3, minimumSize.getHeight());
+ noLineEndingAlert.setMinimumSize(minimumSize);
+
+
+ lineEndings = new JComboBox(new String[]{tr("No line ending"), tr("Newline"), tr("Carriage return"), tr("Both NL & CR")});
+ lineEndings.addActionListener((ActionEvent event) -> {
+ PreferencesData.setInteger("serial.line_ending", lineEndings.getSelectedIndex());
+ noLineEndingAlert.setForeground(pane.getBackground());
+ });
+ lineEndings.setMaximumSize(lineEndings.getMinimumSize());
+
+ lowerPane.add(textField);
+ lowerPane.add(Box.createRigidArea(new Dimension(4, 0)));
+ lowerPane.add(sendButton);
+
+ pane.add(lowerPane);
+ pane.add(noLineEndingAlert);
+ pane.add(Box.createRigidArea(new Dimension(8, 0)));
+ pane.add(lineEndings);
+
+ applyPreferences();
+
+ onSendCommand((ActionEvent event) -> {
+ send(textField.getText());
+ textField.setText("");
+ });
+
+ }
+
+ private void send(String string) {
+ String s = string;
+ if (serial != null) {
+ switch (lineEndings.getSelectedIndex()) {
+ case 1:
+ s += "\n";
+ break;
+ case 2:
+ s += "\r";
+ break;
+ case 3:
+ s += "\r\n";
+ break;
+ default:
+ break;
+ }
+ if ("".equals(s) && lineEndings.getSelectedIndex() == 0 && !PreferencesData.has("runtime.line.ending.alert.notified")) {
+ noLineEndingAlert.setForeground(Color.RED);
+ PreferencesData.set("runtime.line.ending.alert.notified", "true");
+ }
+ serial.write(s);
+ }
+ }
+
+ public void onSendCommand(ActionListener listener) {
+ textField.addActionListener(listener);
+ sendButton.addActionListener(listener);
+ }
+
+ public void appyPreferences() {
+ // Apply line endings.
+ if (PreferencesData.get("serial.line_ending") != null) {
+ lineEndings.setSelectedIndex(PreferencesData.getInteger("serial.line_ending"));
+ }
}
protected void onEnableWindow(boolean enable) {
- serialRates.setEnabled(enable);
+ textField.setEnabled(enable);
+ sendButton.setEnabled(enable);
}
private void onSerialRateChange(ActionListener listener) {
@@ -276,23 +401,70 @@ public void message(final String s) {
messageBuffer.delete(0, linebreak + 1);
line = line.trim();
+ if (line.length() == 0) {
+ // the line only contained trimmable characters
+ continue;
+ }
String[] parts = line.split("[, \t]+");
if(parts.length == 0) {
continue;
}
int validParts = 0;
+ int validLabels = 0;
for(int i = 0; i < parts.length; ++i) {
- try {
- double value = Double.valueOf(parts[i]);
+ Double value = null;
+ String label = null;
+
+ // column formated name value pair
+ if(parts[i].contains(":")) {
+ // get label
+ String[] subString = parts[i].split("[:]+");
+
+ if(subString.length > 0) {
+ int labelLength = subString[0].length();
+
+ if(labelLength > 32) {
+ labelLength = 32;
+ }
+ label = subString[0].substring(0, labelLength);
+ } else {
+ label = "";
+ }
+
+ if(subString.length > 1) {
+ parts[i] = subString[1];
+ } else {
+ parts[i] = "";
+ }
+ }
+
+ try {
+ value = Double.valueOf(parts[i]);
+ } catch (NumberFormatException e) {
+ // ignored
+ }
+ //CSV header
+ if(label == null && value == null) {
+ label = parts[i];
+ }
+
+ if(value != null) {
if(validParts >= graphs.size()) {
graphs.add(new Graph(validParts));
}
graphs.get(validParts).buffer.add(value);
validParts++;
- } catch (NumberFormatException e) {
- // ignore
}
+ if(label != null) {
+ if(validLabels >= graphs.size()) {
+ graphs.add(new Graph(validLabels));
+ }
+ graphs.get(validLabels).label = label;
+ validLabels++;
+ }
+ if(validParts > validLabels) validLabels = validParts;
+ else if(validLabels > validParts) validParts = validLabels;
}
}
diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java
index ae775f57873..ce9e468cc68 100644
--- a/app/src/processing/app/SketchController.java
+++ b/app/src/processing/app/SketchController.java
@@ -75,7 +75,7 @@ public void handleNewCode() {
ensureExistence();
// if read-only, give an error
- if (isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) {
+ if (isReadOnly()) {
// if the files are read-only, need to first do a "save as".
Base.showMessage(tr("Sketch is Read-Only"),
tr("Some files are marked \"read-only\", so you'll\n" +
@@ -107,7 +107,7 @@ public void handleRenameCode() {
}
// if read-only, give an error
- if (isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) {
+ if (isReadOnly()) {
// if the files are read-only, need to first do a "save as".
Base.showMessage(tr("Sketch is Read-Only"),
tr("Some files are marked \"read-only\", so you'll\n" +
@@ -225,7 +225,7 @@ public void handleDeleteCode() throws IOException {
ensureExistence();
// if read-only, give an error
- if (isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) {
+ if (isReadOnly()) {
// if the files are read-only, need to first do a "save as".
Base.showMessage(tr("Sketch is Read-Only"),
tr("Some files are marked \"read-only\", so you'll\n" +
@@ -253,13 +253,21 @@ public void handleDeleteCode() throws IOException {
sketch.delete();
editor.base.handleClose(editor);
} else {
+
+ boolean neverSavedTab = !current.fileExists();
+
// delete the file
- if (!current.delete(sketch.getBuildPath().toPath())) {
+ if (!current.delete(sketch.getBuildPath().toPath()) && !neverSavedTab) {
Base.showMessage(tr("Couldn't do it"),
I18n.format(tr("Could not delete \"{0}\"."), current.getFileName()));
return;
}
+ if (neverSavedTab) {
+ // remove the file from the sketch list
+ sketch.removeFile(current);
+ }
+
editor.removeTab(current);
// just set current tab to the main tab
@@ -295,7 +303,7 @@ public boolean save() throws IOException {
// make sure the user didn't hide the sketch folder
ensureExistence();
- if (isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) {
+ if (isReadOnly()) {
Base.showMessage(tr("Sketch is read-only"),
tr("Some files are marked \"read-only\", so you'll\n" +
"need to re-save this sketch to another location."));
@@ -359,7 +367,7 @@ public boolean save() throws IOException {
protected boolean saveAs() throws IOException {
// get new name for folder
FileDialog fd = new FileDialog(editor, tr("Save sketch folder as..."), FileDialog.SAVE);
- if (isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath()) || isUntitled()) {
+ if (isReadOnly() || isUntitled()) {
// default to the sketchbook folder
fd.setDirectory(BaseNoGui.getSketchbookFolder().getAbsolutePath());
} else {
@@ -379,7 +387,14 @@ protected boolean saveAs() throws IOException {
if (newName == null) return false;
newName = SketchController.checkName(newName);
- File newFolder = new File(newParentDir, newName);
+ File newFolder;
+ // User may want to overwrite a .ino
+ // check if the parent folder name ends with the sketch name
+ if (newName.endsWith(".ino") && newParentDir.endsWith(newName.substring(0, newName.lastIndexOf('.'))+ File.separator)) {
+ newFolder = new File(newParentDir);
+ } else {
+ newFolder = new File(newParentDir, newName);
+ }
// check if the paths are identical
if (newFolder.equals(sketch.getFolder())) {
@@ -423,7 +438,7 @@ protected boolean saveAs() throws IOException {
//editor.sketchbook.rebuildMenusAsync();
editor.base.rebuildSketchbookMenus();
editor.header.rebuild();
-
+ editor.updateTitle();
// Make sure that it's not an untitled sketch
setUntitled(false);
@@ -441,7 +456,7 @@ public void handleAddFile() {
ensureExistence();
// if read-only, give an error
- if (isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) {
+ if (isReadOnly()) {
// if the files are read-only, need to first do a "save as".
Base.showMessage(tr("Sketch is Read-Only"),
tr("Some files are marked \"read-only\", so you'll\n" +
@@ -631,6 +646,8 @@ public String build(boolean verbose, boolean save) throws RunnerException, Prefe
progressListener.progress(20);
}
+ EditorConsole.setCurrentEditorConsole(editor.console);
+
ensureExistence();
@@ -657,7 +674,7 @@ private File saveSketchInTempFolder() throws IOException {
FileUtils.copy(sketch.getFolder(), tempFolder);
for (SketchFile file : Stream.of(sketch.getFiles()).filter(SketchFile::isModified).collect(Collectors.toList())) {
- Files.write(Paths.get(tempFolder.getAbsolutePath(), file.getFileName()), file.getProgram().getBytes());
+ Files.write(Paths.get(tempFolder.getAbsolutePath(), file.getFileName()), file.getProgram().getBytes("UTF-8"));
}
return Paths.get(tempFolder.getAbsolutePath(), sketch.getPrimaryFile().getFileName()).toFile();
@@ -693,6 +710,8 @@ private boolean upload(String suggestedClassName, boolean usingProgrammer) throw
UploaderUtils uploaderInstance = new UploaderUtils();
Uploader uploader = uploaderInstance.getUploaderByPreferences(false);
+ EditorConsole.setCurrentEditorConsole(editor.console);
+
boolean success = false;
do {
if (uploader.requiresAuthorization() && !PreferencesData.has(uploader.getAuthorizationKey())) {
@@ -772,7 +791,9 @@ private void ensureExistence() {
* examples directory, or when sketches are loaded from read-only
* volumes or folders without appropriate permissions.
*/
- public boolean isReadOnly(LibraryList libraries, String examplesPath) {
+ public boolean isReadOnly() {
+ LibraryList libraries = BaseNoGui.librariesIndexer.getInstalledLibraries();
+ String examplesPath = BaseNoGui.getExamplesPath();
String apath = sketch.getFolder().getAbsolutePath();
Optional libraryThatIncludesSketch = libraries.stream().filter(lib -> apath.startsWith(lib.getInstalledFolder().getAbsolutePath())).findFirst();
@@ -825,8 +846,9 @@ private static String checkName(String origName) {
if (!newName.equals(origName)) {
String msg =
- tr("The sketch name had to be modified. Sketch names can only consist\n" +
- "of ASCII characters and numbers and be less than 64 characters long.");
+ tr("The sketch name had to be modified.\n" +
+ "Sketch names must start with a letter or number, followed by letters,\n" +
+ "numbers, dashes, dots and underscores. Maximum length is 63 characters.");
System.out.println(msg);
}
return newName;
diff --git a/app/src/processing/app/Theme.java b/app/src/processing/app/Theme.java
index bfa39349888..d38875b3597 100644
--- a/app/src/processing/app/Theme.java
+++ b/app/src/processing/app/Theme.java
@@ -21,6 +21,7 @@
package processing.app;
+import static processing.app.I18n.format;
import static processing.app.I18n.tr;
import java.awt.Color;
@@ -38,10 +39,23 @@
import java.awt.font.TextAttribute;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
import javax.swing.text.StyleContext;
@@ -50,7 +64,8 @@
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
-
+import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.lang3.StringUtils;
import processing.app.helpers.OSUtils;
import processing.app.helpers.PreferencesHelper;
import processing.app.helpers.PreferencesMap;
@@ -61,6 +76,238 @@
* and to make way for future ability to customize.
*/
public class Theme {
+
+ static final String THEME_DIR = "theme/";
+ static final String THEME_FILE_NAME = "theme.txt";
+
+ static final String NAMESPACE_APP = "app:";
+ static final String NAMESPACE_USER = "user:";
+
+ /**
+ * A theme resource, this is returned instead of {@link File} so that we can
+ * support zip-packaged resources as well as files in the file system
+ */
+ public static class Resource {
+
+ // Priority levels used to determine whether one resource should override
+ // another
+ static public final int PRIORITY_DEFAULT = 0;
+ static public final int PRIORITY_USER_ZIP = 1;
+ static public final int PRIORITY_USER_FILE = 2;
+
+ /**
+ * Priority of this resource.
+ */
+ private final int priority;
+
+ /**
+ * Resource name (original name of requested resource, relative path only).
+ */
+ private final String name;
+
+ /**
+ * File if this resource represents a file, can be null.
+ */
+ private final File file;
+
+ /**
+ * Zip theme if the resource is contained within a zipped theme
+ */
+ private final ZippedTheme theme;
+
+ /**
+ * Zip entry if this resource represents a zip entry, can be null.
+ */
+ private final ZipEntry zipEntry;
+
+ /**
+ * URL of this resource regardless of type, theoretically shouldn't ever be
+ * null though it might be if a particular resource path can't be
+ * successfully transformed into a URL (eg. {@link Theme#getUrl} traps a
+ * MalformedURLException).
+ */
+ private final URL url;
+
+ /**
+ * If this resource supercedes a resource with a lower priority, this field
+ * stores a reference to the superceded resource. This allows consumers to
+ * traverse the resource hierarchy if required.
+ */
+ private Resource parent;
+
+ /**
+ * ctor for file resources
+ */
+ Resource(int priority, String name, URL url, File file) {
+ this(priority, name, url, file, null, null);
+ }
+
+ /**
+ * ctor for zip resources
+ */
+ Resource(int priority, String name, URL url, ZippedTheme theme, ZipEntry entry) {
+ this(priority, name, url, null, theme, entry);
+ }
+
+ private Resource(int priority, String name, URL url, File file, ZippedTheme theme, ZipEntry zipEntry) {
+ this.priority = priority;
+ this.name = name;
+ this.file = file;
+ this.theme = theme;
+ this.zipEntry = zipEntry;
+ this.url = url;
+ }
+
+ public Resource getParent() {
+ return this.parent;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public URL getUrl() {
+ return this.url;
+ }
+
+ public int getPriority() {
+ return this.priority;
+ }
+
+ public boolean isUserDefined() {
+ return this.priority > PRIORITY_DEFAULT;
+ }
+
+ public boolean exists() {
+ return this.zipEntry != null || this.file == null || this.file.exists();
+ }
+
+ public InputStream getInputStream() throws IOException {
+ if (this.file != null) {
+ return new FileInputStream(this.file);
+ }
+
+ if (this.zipEntry != null) {
+ return this.theme.getZip().getInputStream(this.zipEntry);
+ }
+
+ if (this.url != null) {
+ return this.url.openStream();
+ }
+
+ throw new FileNotFoundException(this.name);
+ }
+
+ public String toString() {
+ return this.name;
+ }
+
+ Resource withParent(Resource parent) {
+ this.parent = parent;
+ return this;
+ }
+ }
+
+ /**
+ * Struct which keeps information about a discovered .zip theme file
+ */
+ public static class ZippedTheme {
+
+ /**
+ * Configuration key, this key consists of a "namespace" which determines
+ * the root folder the theme was found in without actually storing the path
+ * itself, followed by the file name.
+ */
+ private final String key;
+
+ /**
+ * File containing the theme
+ */
+ private final File file;
+
+ /**
+ * Zip file handle for retrieving entries
+ */
+ private final ZipFile zip;
+
+ /**
+ * Display name, defaulted to filename but can be read from metadata
+ */
+ private final String name;
+
+ /**
+ * Version number, plain text string read from metadata
+ */
+ private final String version;
+
+ private ZippedTheme(String namespace, File file, ZipFile zip, String name, String version) {
+ this.key = namespace + file.getName();
+ this.file = file;
+ this.zip = zip;
+ this.name = name;
+ this.version = version;
+ }
+
+ public String getKey() {
+ return this.key;
+ }
+
+ public File getFile() {
+ return this.file;
+ }
+
+ public ZipFile getZip() {
+ return this.zip;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getVersion() {
+ return this.version;
+ }
+
+ public String toString() {
+ String description = String.format("%s %s (%s)", this.getName(), this.getVersion(), this.file.getName());
+ return StringUtils.abbreviate(description, 40);
+ }
+
+ /**
+ * Attempts to parse the supplied zip file as a theme file. This is largely
+ * determined by the file being readable and containing a theme.txt entry.
+ * Returns null if the file is unreadable or doesn't contain theme.txt
+ */
+ static ZippedTheme load(String namespace, File file) {
+ ZipFile zip = null;
+ try {
+ zip = new ZipFile(file);
+ ZipEntry themeTxtEntry = zip.getEntry(THEME_FILE_NAME);
+ if (themeTxtEntry != null) {
+ String name = file.getName().substring(0, file.getName().length() - 4);
+ String version = "";
+
+ ZipEntry themePropsEntry = zip.getEntry("theme.properties");
+ if (themePropsEntry != null) {
+ Properties themeProperties = new Properties();
+ themeProperties.load(zip.getInputStream(themePropsEntry));
+
+ name = themeProperties.getProperty("name", name);
+ version = themeProperties.getProperty("version", version);
+ }
+
+ return new ZippedTheme(namespace, file, zip, name, version);
+ }
+ } catch (Exception ex) {
+ System.err.println(format(tr("Error loading theme {0}: {1}"),
+ file.getAbsolutePath(), ex.getMessage()));
+ IOUtils.closeQuietly(zip);
+ }
+
+ return null;
+ }
+
+ }
/**
* Copy of the defaults in case the user mangles a preference.
@@ -70,10 +317,22 @@ public class Theme {
* Table of attributes/values for the theme.
*/
static PreferencesMap table = new PreferencesMap();
-
+
+ /**
+ * Available zipped themes
+ */
+ static private final Map availableThemes = new TreeMap<>();
+
+ /**
+ * Zip file containing user-defined theme elements
+ */
+ static private ZippedTheme zipTheme;
+
static protected void init() {
+ zipTheme = openZipTheme();
+
try {
- table.load(new File(BaseNoGui.getContentFile("lib"), "theme/theme.txt"));
+ loadFromResource(table, THEME_DIR + THEME_FILE_NAME);
} catch (Exception te) {
Base.showError(null, tr("Could not read color theme settings.\n"
+ "You'll need to reinstall Arduino."),
@@ -86,6 +345,44 @@ static protected void init() {
// clone the hash table
defaults = new PreferencesMap(table);
}
+
+ static private ZippedTheme openZipTheme() {
+ refreshAvailableThemes();
+ String selectedTheme = PreferencesData.get("theme.file", "");
+ synchronized(availableThemes) {
+ return availableThemes.get(selectedTheme);
+ }
+ }
+
+ static private void refreshAvailableThemes() {
+ Map discoveredThemes = new TreeMap<>();
+
+ refreshAvailableThemes(discoveredThemes, NAMESPACE_APP, new File(BaseNoGui.getContentFile("lib"), THEME_DIR));
+ refreshAvailableThemes(discoveredThemes, NAMESPACE_USER, new File(BaseNoGui.getSketchbookFolder(), THEME_DIR));
+
+ synchronized (availableThemes) {
+ availableThemes.clear();
+ availableThemes.putAll(discoveredThemes);
+ }
+ }
+
+ static private void refreshAvailableThemes(Map discoveredThemes, String namespace, File folder) {
+ if (!folder.isDirectory()) {
+ return;
+ }
+
+ for (File zipFile : folder.listFiles((dir, name) -> name.endsWith(".zip"))) {
+ ZippedTheme theme = ZippedTheme.load(namespace, zipFile);
+ if (theme != null) {
+ discoveredThemes.put(theme.getKey(), theme);
+ }
+ }
+ }
+
+ public static Collection getAvailablethemes() {
+ refreshAvailableThemes();
+ return Collections.unmodifiableCollection(availableThemes.values());
+ }
static public String get(String attribute) {
return table.get(attribute);
@@ -177,6 +474,9 @@ static public Font getFont(String attr) {
String value = getDefault(attr);
set(attr, value);
font = PreferencesHelper.getFont(table, attr);
+ if (font == null) {
+ return null;
+ }
}
return font.deriveFont((float) scale(font.getSize()));
}
@@ -245,33 +545,34 @@ public static Map getStyledFont(String what, Font font) {
*/
static public Image getLibImage(String filename, Component who, int width,
int height) {
- File libFolder = BaseNoGui.getContentFile("lib");
Image image = null;
// Use vector image when available
- File vectorFile = new File(libFolder, filename + ".svg");
+ Resource vectorFile = getThemeResource(filename + ".svg");
if (vectorFile.exists()) {
try {
- image = imageFromSVG(vectorFile.toURI().toURL(), width, height);
+ image = imageFromSVG(vectorFile.getUrl(), width, height);
} catch (Exception e) {
- System.err.println("Failed to load " + vectorFile.getAbsolutePath()
- + ": " + e.getMessage());
+ System.err.println("Failed to load " + vectorFile + ": " + e.getMessage());
}
}
- // Otherwise fall-back to PNG bitmaps
- if (image == null) {
- File bitmapFile = new File(libFolder, filename + ".png");
- File bitmap2xFile = new File(libFolder, filename + "@2x.png");
+ Resource bitmapFile = getThemeResource(filename + ".png");
+
+ // Otherwise fall-back to PNG bitmaps, allowing user-defined bitmaps to
+ // override built-in svgs
+ if (image == null || bitmapFile.getPriority() > vectorFile.getPriority()) {
+ Resource bitmap2xFile = getThemeResource(filename + "@2x.png");
- File imageFile;
- if ((getScale() > 125 && bitmap2xFile.exists()) || !bitmapFile.exists()) {
+ Resource imageFile;
+ if (((getScale() > 125 && bitmap2xFile.exists()) || !bitmapFile.exists())
+ && (bitmapFile.isUserDefined() && bitmap2xFile.isUserDefined())) {
imageFile = bitmap2xFile;
} else {
imageFile = bitmapFile;
}
Toolkit tk = Toolkit.getDefaultToolkit();
- image = tk.getImage(imageFile.getAbsolutePath());
+ image = tk.getImage(imageFile.getUrl());
}
MediaTracker tracker = new MediaTracker(who);
@@ -298,7 +599,7 @@ static public Image getLibImage(String filename, Component who, int width,
*/
static public Image getThemeImage(String name, Component who, int width,
int height) {
- return getLibImage("theme/" + name, who, width, height);
+ return getLibImage(THEME_DIR + name, who, width, height);
}
private static Image imageFromSVG(URL url, int width, int height)
@@ -324,5 +625,103 @@ static public Graphics2D setupGraphics2D(Graphics graphics) {
}
return g;
}
+
+ /**
+ * Loads the supplied {@link PreferencesMap} from the specified resource,
+ * recursively loading parent resources such that entries are loaded in order
+ * of priority (lowest first).
+ *
+ * @param map preference map to populate
+ * @param name name of resource to load
+ */
+ static public PreferencesMap loadFromResource(PreferencesMap map, String name) throws IOException {
+ return loadFromResource(map, getThemeResource(name));
+ }
+ static private PreferencesMap loadFromResource(PreferencesMap map, Resource resource) throws IOException {
+ if (resource != null) {
+ loadFromResource(map, resource.getParent());
+ map.load(resource.getInputStream());
+ }
+ return map;
+ }
+
+ /**
+ * @param name
+ * @return
+ */
+ static public Resource getThemeResource(String name) {
+ File defaultfile = getDefaultFile(name);
+ Resource resource = new Resource(Resource.PRIORITY_DEFAULT, name, getUrl(defaultfile), defaultfile);
+
+ ZipEntry themeZipEntry = getThemeZipEntry(name);
+ if (themeZipEntry != null) {
+ resource = new Resource(Resource.PRIORITY_USER_ZIP, name, getUrl(themeZipEntry), zipTheme, themeZipEntry).withParent(resource);
+ }
+
+ File themeFile = getThemeFile(name);
+ if (themeFile != null) {
+ resource = new Resource(Resource.PRIORITY_USER_FILE, name, getUrl(themeFile), themeFile).withParent(resource);
+ }
+
+ return resource;
+ }
+
+ static private File getThemeFile(String name) {
+ File sketchBookThemeFolder = new File(BaseNoGui.getSketchbookFolder(), THEME_DIR);
+ File themeFile = new File(sketchBookThemeFolder, name);
+ if (themeFile.exists()) {
+ return themeFile;
+ }
+
+ if (name.startsWith(THEME_DIR)) {
+ themeFile = new File(sketchBookThemeFolder, name.substring(THEME_DIR.length()));
+ if (themeFile.exists()) {
+ return themeFile;
+ }
+ }
+
+ return null;
+ }
+
+ static private ZipEntry getThemeZipEntry(String name) {
+ if (zipTheme == null) {
+ return null;
+ }
+
+ if (name.startsWith(THEME_DIR)) {
+ name = name.substring(THEME_DIR.length());
+ }
+
+ return zipTheme.getZip().getEntry(name);
+ }
+
+ static private File getDefaultFile(String name) {
+ return new File(BaseNoGui.getContentFile("lib"), name);
+ }
+
+ static URL getUrl(File file) {
+ try {
+ return file.toURI().toURL();
+ } catch (MalformedURLException ex) {
+ return null;
+ }
+ }
+
+ static URL getUrl(ZipEntry entry) {
+ try {
+ // Adjust file name for URL format on Windows
+ String zipFile = zipTheme.getZip().getName().replace('\\', '/');
+ if (!zipFile.startsWith("/")) {
+ zipFile = "/" + zipFile;
+ }
+
+ // Construct a URL which points to the internal resource
+ URI uri = new URI("jar", "file:" + zipFile + "!/" + entry.getName(), null);
+ return uri.toURL();
+
+ } catch (MalformedURLException | URISyntaxException ex) {
+ return null;
+ }
+ }
}
diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java
index 39c555069a1..cdca1b71783 100644
--- a/app/src/processing/app/UpdateCheck.java
+++ b/app/src/processing/app/UpdateCheck.java
@@ -51,7 +51,7 @@
*/
public class UpdateCheck implements Runnable {
Base base;
- String downloadURL = tr("http://www.arduino.cc/latest.txt");
+ String downloadURL = tr("https://www.arduino.cc/latest.txt");
static final long ONE_DAY = 24 * 60 * 60 * 1000;
@@ -66,14 +66,14 @@ public UpdateCheck(Base base) {
public void run() {
//System.out.println("checking for updates...");
- // generate a random id in case none exists yet
- Random r = new Random();
- long id = r.nextLong();
-
+ long id;
String idString = PreferencesData.get("update.id");
if (idString != null) {
id = Long.parseLong(idString);
} else {
+ // generate a random id in case none exists yet
+ Random r = new Random();
+ id = r.nextLong();
PreferencesData.set("update.id", String.valueOf(id));
}
@@ -116,7 +116,7 @@ public void run() {
options,
options[0]);
if (result == JOptionPane.YES_OPTION) {
- Base.openURL(tr("http://www.arduino.cc/en/Main/Software"));
+ Base.openURL(tr("https://www.arduino.cc/en/Main/Software"));
}
}
}
diff --git a/app/src/processing/app/forms/PasswordAuthorizationDialog.java b/app/src/processing/app/forms/PasswordAuthorizationDialog.java
index d050a8ce2ff..983c36e73ff 100644
--- a/app/src/processing/app/forms/PasswordAuthorizationDialog.java
+++ b/app/src/processing/app/forms/PasswordAuthorizationDialog.java
@@ -1,12 +1,12 @@
package processing.app.forms;
import processing.app.Base;
+import processing.app.Theme;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
-import java.io.File;
import static processing.app.I18n.tr;
@@ -34,7 +34,7 @@ public PasswordAuthorizationDialog(Frame parent, String dialogText) {
typePasswordLabel.setText(dialogText);
- icon.setIcon(new ImageIcon(new File(Base.getContentFile("lib"), "theme/lock.png").getAbsolutePath()));
+ icon.setIcon(new ImageIcon(Theme.getThemeResource("theme/lock.png").getUrl()));
passwordLabel.setText(tr("Password:"));
diff --git a/app/src/processing/app/macosx/ThinkDifferent.java b/app/src/processing/app/macosx/ThinkDifferent.java
index e946bdc0fd7..590196ace95 100644
--- a/app/src/processing/app/macosx/ThinkDifferent.java
+++ b/app/src/processing/app/macosx/ThinkDifferent.java
@@ -23,6 +23,8 @@
package processing.app.macosx;
import com.apple.eawt.*;
+import com.apple.eawt.AppEvent.AppReOpenedEvent;
+
import processing.app.Base;
import processing.app.Editor;
@@ -45,6 +47,20 @@ public class ThinkDifferent {
static public void init() {
Application application = Application.getApplication();
+
+ application.addAppEventListener(new AppReOpenedListener() {
+ @Override
+ public void appReOpened(AppReOpenedEvent aroe) {
+ try {
+ if (Base.INSTANCE.getEditors().size() == 0) {
+ Base.INSTANCE.handleNew();
+ }
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ });
application.setAboutHandler(new AboutHandler() {
@Override
public void handleAbout(AppEvent.AboutEvent aboutEvent) {
diff --git a/app/src/processing/app/syntax/PdeKeywords.java b/app/src/processing/app/syntax/PdeKeywords.java
index 62d719f9746..838800b3d5d 100644
--- a/app/src/processing/app/syntax/PdeKeywords.java
+++ b/app/src/processing/app/syntax/PdeKeywords.java
@@ -24,12 +24,12 @@
package processing.app.syntax;
-import cc.arduino.contributions.libraries.ContributedLibrary;
import org.apache.commons.compress.utils.IOUtils;
import org.fife.ui.rsyntaxtextarea.TokenMap;
import org.fife.ui.rsyntaxtextarea.TokenTypes;
import processing.app.Base;
import processing.app.BaseNoGui;
+import processing.app.packages.UserLibrary;
import processing.app.debug.TargetPlatform;
import java.io.BufferedReader;
@@ -89,7 +89,7 @@ public void reload() {
File platformKeywords = new File(tp.getFolder(), "keywords.txt");
if (platformKeywords.exists()) parseKeywordsTxt(platformKeywords);
}
- for (ContributedLibrary lib : BaseNoGui.librariesIndexer.getInstalledLibraries()) {
+ for (UserLibrary lib : BaseNoGui.librariesIndexer.getInstalledLibraries()) {
File keywords = new File(lib.getInstalledFolder(), "keywords.txt");
if (keywords.exists()) {
parseKeywordsTxt(keywords);
diff --git a/app/src/processing/app/syntax/SketchTextArea.java b/app/src/processing/app/syntax/SketchTextArea.java
index a2d78703ac1..ce74a3f1f8f 100644
--- a/app/src/processing/app/syntax/SketchTextArea.java
+++ b/app/src/processing/app/syntax/SketchTextArea.java
@@ -30,32 +30,42 @@
package processing.app.syntax;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Font;
+import java.awt.Insets;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+import java.util.logging.Logger;
+
import javax.swing.KeyStroke;
+import javax.swing.event.EventListenerList;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Segment;
+
import org.apache.commons.compress.utils.IOUtils;
-import org.fife.ui.rsyntaxtextarea.*;
+import org.fife.ui.rsyntaxtextarea.LinkGenerator;
+import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult;
+import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+import org.fife.ui.rsyntaxtextarea.Style;
+import org.fife.ui.rsyntaxtextarea.Theme;
import org.fife.ui.rsyntaxtextarea.Token;
+import org.fife.ui.rsyntaxtextarea.TokenImpl;
+import org.fife.ui.rsyntaxtextarea.TokenTypes;
import org.fife.ui.rtextarea.RTextArea;
import org.fife.ui.rtextarea.RTextAreaUI;
+
import processing.app.Base;
-import processing.app.BaseNoGui;
import processing.app.PreferencesData;
-
-import javax.swing.event.EventListenerList;
-import javax.swing.event.HyperlinkEvent;
-import javax.swing.event.HyperlinkListener;
-import javax.swing.text.BadLocationException;
-import javax.swing.text.Segment;
-import java.awt.*;
-import java.awt.event.MouseEvent;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Map;
-import java.util.logging.Logger;
import processing.app.helpers.OSUtils;
/**
@@ -91,9 +101,9 @@ private void installFeatures() throws IOException {
}
private void setTheme(String name) throws IOException {
- FileInputStream defaultXmlInputStream = null;
+ InputStream defaultXmlInputStream = null;
try {
- defaultXmlInputStream = new FileInputStream(new File(BaseNoGui.getContentFile("lib"), "theme/syntax/" + name + ".xml"));
+ defaultXmlInputStream = processing.app.Theme.getThemeResource("theme/syntax/" + name + ".xml").getInputStream();
Theme theme = Theme.load(defaultXmlInputStream);
theme.apply(this);
} finally {
diff --git a/app/src/processing/app/tools/MenuScroller.java b/app/src/processing/app/tools/MenuScroller.java
index 3523ec7ceca..9e9aacbcafd 100644
--- a/app/src/processing/app/tools/MenuScroller.java
+++ b/app/src/processing/app/tools/MenuScroller.java
@@ -3,6 +3,8 @@
*/
package processing.app.tools;
+import processing.app.PreferencesData;
+
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
@@ -567,6 +569,43 @@ public MenuScrollTimer(final int increment, int interval) {
public void actionPerformed(ActionEvent e) {
firstIndex += increment * accelerator;
refreshMenu();
+ if (PreferencesData.getBoolean("ide.accessible")) {
+ // If the user has chosen to use accessibility features, it means that they are using a screen reader
+ // to assist them in development of their project. This scroller is very unfriendly toward screen readers
+ // because it does not tell the user that it is scrolling through the board options, and it does not read
+ // the name of the boards as they scroll by. It is possible that the desired board will never become
+ // accessible.
+ // Because this scroller is quite nice for the sighted user, the idea here is to continue to use the
+ // scroller, but to fool it into scrolling one item at a time for accessible features users so that the
+ // screen readers work well, too.
+ // It's not the prettiest of code, but it works.
+ String itemClassName;
+ int keyEvent;
+
+ // The blind user likely used an arrow key to get to the scroller. Determine which arrow key
+ // so we can send an event for the opposite arrow key. This fools the scroller into scrolling
+ // a single item. Get the class name of the new item while we're here
+ if (increment > 0) {
+ itemClassName = menuItems[firstIndex + scrollCount - 1].getClass().getName();
+ keyEvent = KeyEvent.VK_UP;
+ }
+ else {
+ itemClassName = menuItems[firstIndex].getClass().getName();
+ keyEvent = KeyEvent.VK_DOWN;
+ }
+
+ // Use the class name to check if the next item is a separator. If it is, just let it scroll on like
+ // normal, otherwise move the cursor back with the opposite key event to the new item so that item is read
+ // by a screen reader and the user can use their arrow keys to navigate the list one item at a time
+ if (!itemClassName.equals(JSeparator.class.getName()) ) {
+ KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
+ Component comp = manager.getFocusOwner();
+ KeyEvent event = new KeyEvent(comp,
+ KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0,
+ keyEvent, KeyEvent.CHAR_UNDEFINED);
+ comp.dispatchEvent(event);
+ }
+ }
}
});
}
diff --git a/app/test/cc/arduino/contributions/GzippedJsonDownloaderTest.java b/app/test/cc/arduino/contributions/GzippedJsonDownloaderTest.java
index e1e231acd3a..0892c361516 100644
--- a/app/test/cc/arduino/contributions/GzippedJsonDownloaderTest.java
+++ b/app/test/cc/arduino/contributions/GzippedJsonDownloaderTest.java
@@ -4,10 +4,11 @@
import cc.arduino.utils.MultiStepProgress;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.mrbean.MrBeanModule;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+
+import processing.app.BaseNoGui;
import processing.app.helpers.FileUtils;
import java.io.File;
@@ -38,11 +39,13 @@ public void tearDown() throws Exception {
@Test
public void testJsonDownload() throws Exception {
- new GZippedJsonDownloader(downloader, new URL("http://downloads.arduino.cc/libraries/library_index.json"), new URL("http://downloads.arduino.cc/libraries/library_index.json.gz")).download(tempFile, new MultiStepProgress(1), "", new NoopProgressListener());
+ BaseNoGui.initPlatform();
+ new GZippedJsonDownloader(downloader, new URL("http://downloads.arduino.cc/libraries/library_index.json"),
+ new URL("http://downloads.arduino.cc/libraries/library_index.json.gz"))
+ .download(tempFile, new MultiStepProgress(1), "", new NoopProgressListener(), true);
InputStream indexIn = new FileInputStream(tempFile);
ObjectMapper mapper = new ObjectMapper();
- mapper.registerModule(new MrBeanModule());
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
mapper.configure(DeserializationFeature.EAGER_DESERIALIZER_FETCH, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
diff --git a/app/test/cc/arduino/contributions/JsonDownloaderTest.java b/app/test/cc/arduino/contributions/JsonDownloaderTest.java
index ebf3c0913ce..1315fe223a0 100644
--- a/app/test/cc/arduino/contributions/JsonDownloaderTest.java
+++ b/app/test/cc/arduino/contributions/JsonDownloaderTest.java
@@ -4,10 +4,11 @@
import cc.arduino.utils.MultiStepProgress;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.mrbean.MrBeanModule;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+
+import processing.app.BaseNoGui;
import processing.app.helpers.FileUtils;
import java.io.File;
@@ -38,11 +39,12 @@ public void tearDown() throws Exception {
@Test
public void testJsonDownload() throws Exception {
- new JsonDownloader(downloader, new URL("http://downloads.arduino.cc/libraries/library_index.json")).download(tempFile, new MultiStepProgress(1), "", new NoopProgressListener());
+ BaseNoGui.initPlatform();
+ new JsonDownloader(downloader, new URL("http://downloads.arduino.cc/libraries/library_index.json"))
+ .download(tempFile, new MultiStepProgress(1), "", new NoopProgressListener(), true);
InputStream indexIn = new FileInputStream(tempFile);
ObjectMapper mapper = new ObjectMapper();
- mapper.registerModule(new MrBeanModule());
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
mapper.configure(DeserializationFeature.EAGER_DESERIALIZER_FETCH, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
diff --git a/app/test/cc/arduino/contributions/UpdatableLibraryTest.java b/app/test/cc/arduino/contributions/UpdatableLibraryTest.java
new file mode 100644
index 00000000000..0dab3531cd1
--- /dev/null
+++ b/app/test/cc/arduino/contributions/UpdatableLibraryTest.java
@@ -0,0 +1,82 @@
+package cc.arduino.contributions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.LibrariesIndexer;
+import processing.app.BaseNoGui;
+import processing.app.packages.UserLibraryFolder;
+import processing.app.packages.UserLibraryFolder.Location;
+
+public class UpdatableLibraryTest {
+
+ File testdata = new File(
+ UpdatableLibraryTest.class.getResource("/").getFile(),
+ "../testdata/libraries");
+ File index_SD_only = new File(testdata, "index_SD_only");
+ File SD111 = new File(testdata, "SD_1.1.1");
+ File SD121 = new File(testdata, "SD_1.2.1");
+ File index_Bridge_only = new File(testdata, "index_Bridge_only");
+ File Bridge163 = new File(testdata, "Bridge_1.6.3");
+ File Bridge170 = new File(testdata, "Bridge_1.7.0");
+
+ @Test
+ public void testUpdatableLibrary() throws Exception {
+ List folders = new ArrayList<>();
+ folders.add(new UserLibraryFolder(SD111, Location.IDE_BUILTIN));
+
+ LibrariesIndexer indexer = new LibrariesIndexer(index_SD_only);
+ BaseNoGui.librariesIndexer = indexer;
+ indexer.parseIndex();
+ indexer.setLibrariesFoldersAndRescan(folders);
+
+ ContributedLibrary sdLib = indexer.getIndex().getInstalled("SD").get();
+ assertTrue("SD lib is installed", sdLib.isLibraryInstalled());
+ assertEquals("SD installed version", "1.1.1", sdLib.getParsedVersion());
+
+ assertTrue(ContributionsSelfCheck.checkForUpdatableLibraries());
+
+ folders.add(new UserLibraryFolder(SD121, Location.SKETCHBOOK));
+ indexer.setLibrariesFoldersAndRescan(folders);
+
+ sdLib = indexer.getIndex().getInstalled("SD").get();
+ assertTrue("SD lib is installed", sdLib.isLibraryInstalled());
+ assertEquals("SD installed version", "1.2.1", sdLib.getParsedVersion());
+
+ assertFalse(ContributionsSelfCheck.checkForUpdatableLibraries());
+ }
+
+ @Test
+ public void testUpdatableLibraryWithBundled() throws Exception {
+ List folders = new ArrayList<>();
+ folders.add(new UserLibraryFolder(Bridge163, Location.IDE_BUILTIN));
+
+ LibrariesIndexer indexer = new LibrariesIndexer(index_Bridge_only);
+ BaseNoGui.librariesIndexer = indexer;
+ indexer.parseIndex();
+ indexer.setLibrariesFoldersAndRescan(folders);
+
+ ContributedLibrary l = indexer.getIndex().getInstalled("Bridge").get();
+ assertTrue("Bridge lib is installed", l.isLibraryInstalled());
+ assertEquals("Bridge installed version", "1.6.3", l.getParsedVersion());
+
+ assertTrue(ContributionsSelfCheck.checkForUpdatableLibraries());
+
+ folders.add(new UserLibraryFolder(Bridge170, Location.SKETCHBOOK));
+ indexer.setLibrariesFoldersAndRescan(folders);
+
+ l = indexer.getIndex().getInstalled("Bridge").get();
+ assertTrue("Bridge lib is installed", l.isLibraryInstalled());
+ assertEquals("Bridge installed version", "1.7.0", l.getParsedVersion());
+
+ assertFalse(ContributionsSelfCheck.checkForUpdatableLibraries());
+ }
+}
diff --git a/app/test/cc/arduino/contributions/VersionHelperTest.java b/app/test/cc/arduino/contributions/VersionHelperTest.java
index fecbcb8a8cb..de7463da16d 100644
--- a/app/test/cc/arduino/contributions/VersionHelperTest.java
+++ b/app/test/cc/arduino/contributions/VersionHelperTest.java
@@ -30,28 +30,38 @@
package cc.arduino.contributions;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Optional;
import org.junit.Test;
+import com.github.zafarkhaja.semver.Version;
+
public class VersionHelperTest {
+ public void assertOptionalEquals(String expected, Optional value) {
+ assertTrue(value.isPresent());
+ assertEquals(expected, value.get().toString());
+ }
+
@Test
public void testVersions() throws Exception {
- assertEquals("1.0.0", VersionHelper.valueOf("1.0.0").toString());
- assertEquals("1.0.0", VersionHelper.valueOf("1.0").toString());
- assertEquals("1.0.0", VersionHelper.valueOf("1").toString());
- assertEquals("1.0.0-abc", VersionHelper.valueOf("1.0.0-abc").toString());
- assertEquals("1.0.0-abc", VersionHelper.valueOf("1.0-abc").toString());
- assertEquals("1.0.0-abc", VersionHelper.valueOf("1-abc").toString());
- assertEquals("1.0.0+abc", VersionHelper.valueOf("1.0.0+abc").toString());
- assertEquals("1.0.0+abc", VersionHelper.valueOf("1.0+abc").toString());
- assertEquals("1.0.0+abc", VersionHelper.valueOf("1+abc").toString());
- assertEquals("1.0.0-def+abc", VersionHelper.valueOf("1.0.0-def+abc").toString());
- assertEquals("1.0.0-def+abc", VersionHelper.valueOf("1.0-def+abc").toString());
- assertEquals("1.0.0-def+abc", VersionHelper.valueOf("1-def+abc").toString());
- assertEquals("1.0.0+def-abc", VersionHelper.valueOf("1.0.0+def-abc").toString());
- assertEquals("1.0.0+def-abc", VersionHelper.valueOf("1.0+def-abc").toString());
- assertEquals("1.0.0+def-abc", VersionHelper.valueOf("1+def-abc").toString());
+ assertOptionalEquals("1.0.0", VersionHelper.valueOf("1.0.0"));
+ assertOptionalEquals("1.0.0", VersionHelper.valueOf("1.0"));
+ assertOptionalEquals("1.0.0", VersionHelper.valueOf("1"));
+ assertOptionalEquals("1.0.0-abc", VersionHelper.valueOf("1.0.0-abc"));
+ assertOptionalEquals("1.0.0-abc", VersionHelper.valueOf("1.0-abc"));
+ assertOptionalEquals("1.0.0-abc", VersionHelper.valueOf("1-abc"));
+ assertOptionalEquals("1.0.0+abc", VersionHelper.valueOf("1.0.0+abc"));
+ assertOptionalEquals("1.0.0+abc", VersionHelper.valueOf("1.0+abc"));
+ assertOptionalEquals("1.0.0+abc", VersionHelper.valueOf("1+abc"));
+ assertOptionalEquals("1.0.0-def+abc", VersionHelper.valueOf("1.0.0-def+abc"));
+ assertOptionalEquals("1.0.0-def+abc", VersionHelper.valueOf("1.0-def+abc"));
+ assertOptionalEquals("1.0.0-def+abc", VersionHelper.valueOf("1-def+abc"));
+ assertOptionalEquals("1.0.0+def-abc", VersionHelper.valueOf("1.0.0+def-abc"));
+ assertOptionalEquals("1.0.0+def-abc", VersionHelper.valueOf("1.0+def-abc"));
+ assertOptionalEquals("1.0.0+def-abc", VersionHelper.valueOf("1+def-abc"));
}
}
diff --git a/app/test/cc/arduino/net/CustomProxySelectorTest.java b/app/test/cc/arduino/net/CustomProxySelectorTest.java
index 005f5cce822..411f1aa2366 100644
--- a/app/test/cc/arduino/net/CustomProxySelectorTest.java
+++ b/app/test/cc/arduino/net/CustomProxySelectorTest.java
@@ -83,8 +83,8 @@ public void testProxyPACHTTP() throws Exception {
public void testProxyPACHTTPWithLogin() throws Exception {
preferences.put(Constants.PREF_PROXY_TYPE, Constants.PROXY_TYPE_AUTO);
preferences.put(Constants.PREF_PROXY_PAC_URL, CustomProxySelectorTest.class.getResource("proxy_http.pac").toExternalForm());
- preferences.put(Constants.PREF_PROXY_AUTO_USERNAME, "auto");
- preferences.put(Constants.PREF_PROXY_AUTO_PASSWORD, "autopassword");
+ preferences.put(Constants.PREF_PROXY_USERNAME, "auto");
+ preferences.put(Constants.PREF_PROXY_PASSWORD, "autopassword");
CustomProxySelector proxySelector = new CustomProxySelector(preferences);
Proxy proxy = proxySelector.getProxyFor(uri);
@@ -154,8 +154,8 @@ public void testManualProxyWithLogin() throws Exception {
preferences.put(Constants.PREF_PROXY_MANUAL_TYPE, Constants.PROXY_MANUAL_TYPE_HTTP);
preferences.put(Constants.PREF_PROXY_MANUAL_HOSTNAME, "localhost");
preferences.put(Constants.PREF_PROXY_MANUAL_PORT, "8080");
- preferences.put(Constants.PREF_PROXY_MANUAL_USERNAME, "username");
- preferences.put(Constants.PREF_PROXY_MANUAL_PASSWORD, "pwd");
+ preferences.put(Constants.PREF_PROXY_USERNAME, "username");
+ preferences.put(Constants.PREF_PROXY_PASSWORD, "pwd");
CustomProxySelector proxySelector = new CustomProxySelector(preferences);
Proxy proxy = proxySelector.getProxyFor(uri);
diff --git a/app/test/cc/arduino/packages/uploaders/MergeSketchWithUploaderTest.java b/app/test/cc/arduino/packages/uploaders/MergeSketchWithUploaderTest.java
index 384aa169deb..2f048b2a48b 100644
--- a/app/test/cc/arduino/packages/uploaders/MergeSketchWithUploaderTest.java
+++ b/app/test/cc/arduino/packages/uploaders/MergeSketchWithUploaderTest.java
@@ -35,18 +35,29 @@
import processing.app.helpers.FileUtils;
import java.io.File;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.List;
import static org.junit.Assert.assertEquals;
public class MergeSketchWithUploaderTest {
private File sketch;
+ private File bootloader;
@Before
public void setup() throws Exception {
- File originalSketch = new File(MergeSketchWithUploaderTest.class.getResource("/sketch.hex").getFile());
+ File originalSketch = getResourceFile("/sketch.hex");
sketch = new File(System.getProperty("java.io.tmpdir"), "sketch.hex");
FileUtils.copyFile(originalSketch, sketch);
+ removeCariageReturns(sketch);
+
+ File originalBootloader = getResourceFile("/optiboot_atmega328.hex");
+ bootloader = new File(System.getProperty("java.io.tmpdir"), "optiboot_atmega328.hex");
+ FileUtils.copyFile(originalBootloader, bootloader);
+ removeCariageReturns(bootloader);
}
@After
@@ -57,11 +68,24 @@ public void removeTmpFile() {
@Test
public void shouldMergeWithOptiboot() throws Exception {
assertEquals(11720, sketch.length());
+ assertEquals(1432, bootloader.length());
- File bootloader = new File(MergeSketchWithUploaderTest.class.getResource("/optiboot_atmega328.hex").getFile());
+ File bootloader = getResourceFile("/optiboot_atmega328.hex");
new MergeSketchWithBooloader().merge(sketch, bootloader);
assertEquals(13140, sketch.length());
}
+ private static File getResourceFile(String resourcePath) throws Exception {
+ return new File(URLDecoder.decode(
+ MergeSketchWithUploaderTest.class.getResource(resourcePath).getFile(), "UTF-8"));
+ }
+ private static void removeCariageReturns(File file) throws Exception {
+ List lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
+ StringBuilder contentBuilder = new StringBuilder();
+ for(String line : lines) {
+ contentBuilder.append(line).append('\n');
+ }
+ Files.write(file.toPath(), contentBuilder.toString().getBytes(StandardCharsets.UTF_8));
+ }
}
diff --git a/app/test/processing/app/AbstractGUITest.java b/app/test/processing/app/AbstractGUITest.java
index d1db60d98aa..37f0ebefe58 100644
--- a/app/test/processing/app/AbstractGUITest.java
+++ b/app/test/processing/app/AbstractGUITest.java
@@ -41,30 +41,26 @@
import javax.swing.*;
import java.util.Random;
-public abstract class AbstractGUITest {
+public abstract class AbstractGUITest extends AbstractWithPreferencesTest {
protected ArduinoFrameFixture window;
@Before
public void startUpTheIDE() throws Exception {
+ // This relies on AbstractWithPreferencesTest to set up the
+ // non-gui-specific stuff.
+
System.setProperty("mrj.version", "whynot"); //makes sense only on osx. See https://github.com/alexruiz/fest-swing-1.x/issues/2#issuecomment-86532042
- Runtime.getRuntime().addShutdownHook(new Thread(DeleteFilesOnShutdown.INSTANCE));
FailOnThreadViolationRepaintManager.install();
- BaseNoGui.initPlatform();
- BaseNoGui.getPlatform().init();
- PreferencesData.init(null);
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
- Theme.init();
BaseNoGui.getPlatform().setLookAndFeel();
- Base.untitledFolder = FileUtils.createTempFolder("untitled" + new Random().nextInt(Integer.MAX_VALUE), ".tmp");
- DeleteFilesOnShutdown.add(Base.untitledFolder);
window = GuiActionRunner.execute(new GuiQuery() {
@Override
protected ArduinoFrameFixture executeInEDT() throws Throwable {
- return new ArduinoFrameFixture(new Base(new String[0]).editors.get(0));
+ return new ArduinoFrameFixture(createBase().editors.get(0));
}
});
}
diff --git a/app/test/processing/app/AbstractWithPreferencesTest.java b/app/test/processing/app/AbstractWithPreferencesTest.java
index f0d2f3a2b07..1075aebda4f 100644
--- a/app/test/processing/app/AbstractWithPreferencesTest.java
+++ b/app/test/processing/app/AbstractWithPreferencesTest.java
@@ -29,26 +29,88 @@
package processing.app;
-import cc.arduino.files.DeleteFilesOnShutdown;
+import static org.junit.Assert.assertEquals;
import org.junit.Before;
+import org.junit.After;
+
import processing.app.helpers.FileUtils;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
import java.util.Random;
+import java.util.List;
+import java.util.LinkedList;
public abstract class AbstractWithPreferencesTest {
+ /**
+ * Files or directories that will be deleted after each test.
+ * Subclasses can add files here in @Test or @Before functions.
+ */
+ protected List deleteAfter = new LinkedList();
+ protected File preferencesFile;
@Before
public void init() throws Exception {
- Runtime.getRuntime().addShutdownHook(new Thread(DeleteFilesOnShutdown.INSTANCE));
+ File settingsDir = Files.createTempDirectory("arduino_test_settings").toFile();
+ deleteAfter.add(settingsDir);
+
+ preferencesFile = new File(settingsDir, "preferences.txt");
+ File sketchbookDir = new File(settingsDir, "sketchbook");
+ sketchbookDir.mkdir();
+
BaseNoGui.initPlatform();
BaseNoGui.getPlatform().init();
- PreferencesData.init(null);
+
+ PreferencesData.init(preferencesFile);
+ // Do not read anything from e.g. ~/.arduino15
+ PreferencesData.set("settings.path", settingsDir.toString());
+ // Do not read or write the default ~/Arduino sketchbook
+ PreferencesData.set("sketchbook.path", sketchbookDir.toString());
+ // Do not perform any update checks
+ PreferencesData.set("update.check", sketchbookDir.toString());
+ // Write the defaults, with these changes to file. This allows them
+ // to be reloaded when creating a Base instance (see getBaseArgs()
+ // below).
+ PreferencesData.save();
+
Theme.init();
BaseNoGui.initPackages();
Base.untitledFolder = FileUtils.createTempFolder("untitled" + new Random().nextInt(Integer.MAX_VALUE), ".tmp");
- DeleteFilesOnShutdown.add(Base.untitledFolder);
+ deleteAfter.add(Base.untitledFolder);
}
+ /**
+ * Returns arguments to be passed to the Base constructor or on the
+ * commandline to set up the created dummy environment.
+ */
+ protected String[] getBaseArgs() {
+ return new String[] {
+ // Preferences are loaded (using --preferences-file) before
+ // processing any other commandline options (e.g. --pref), so only
+ // use --preferences-file here. Also, this does not affect the
+ // "action" mode, for tests that require the GUI to be loaded.
+ "--preferences-file", preferencesFile.toString(),
+ };
+ }
+
+ /**
+ * Creates a new instance of Base. Always use this rather than calling
+ * it directly, to ensure the right settings are used.
+ */
+ protected Base createBase() throws Exception {
+ Base base = new Base(getBaseArgs());
+ // Doublecheck that the right preferencesFile was loaded
+ assertEquals(preferencesFile, PreferencesData.preferencesFile);
+ return base;
+ }
+
+ @After
+ public void cleanup() throws IOException {
+ for (File f : deleteAfter)
+ FileUtils.recursiveDelete(f);
+ deleteAfter = new LinkedList();
+ }
}
diff --git a/app/test/processing/app/CommandLineTest.java b/app/test/processing/app/CommandLineTest.java
index a2532ff8116..1db6afe12f1 100644
--- a/app/test/processing/app/CommandLineTest.java
+++ b/app/test/processing/app/CommandLineTest.java
@@ -32,21 +32,32 @@
import static org.junit.Assert.*;
import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
import org.apache.commons.compress.utils.IOUtils;
-import org.junit.Before;
+import org.fest.assertions.Assertions;
+import org.junit.BeforeClass;
import org.junit.Test;
import processing.app.helpers.OSUtils;
import processing.app.helpers.PreferencesMap;
-public class CommandLineTest {
+/**
+ * This extends AbstractWithPreferencesTest which initializes part of
+ * the internal Arduino structures. Most of that is not required, but it
+ * also conveniently sets up a settings directory and preferences file,
+ * which we can use here (through getBaseArgs()).
+ */
+public class CommandLineTest extends AbstractWithPreferencesTest {
- File buildPath;
- File arduinoPath;
+ private static File buildPath;
+ private static File arduinoPath;
- @Before
- public void findBuildPaths() throws Exception {
+ @BeforeClass
+ public static void findBuildPaths() throws Exception {
buildPath = new File(System.getProperty("user.dir"));
while (!new File(buildPath, "build").isDirectory()) {
buildPath = buildPath.getParentFile();
@@ -71,60 +82,84 @@ public void findBuildPaths() throws Exception {
System.out.println("found arduino: " + arduinoPath);
}
+ public Process runArduino(boolean output, boolean success, File wd, String[] extraArgs) throws IOException, InterruptedException {
+ Runtime rt = Runtime.getRuntime();
+
+ List args = new ArrayList();
+ args.add(arduinoPath.getAbsolutePath());
+ args.addAll(Arrays.asList(getBaseArgs()));
+ args.addAll(Arrays.asList(extraArgs));
+
+ System.out.println("Running: " + String.join(" ", args));
+
+ Process pr = rt.exec(args.toArray(new String[0]), null, wd);
+ if (output) {
+ IOUtils.copy(pr.getInputStream(), System.out);
+ IOUtils.copy(pr.getErrorStream(), System.out);
+ }
+ pr.waitFor();
+ if (success)
+ assertEquals(0, pr.exitValue());
+ return pr;
+ }
+
@Test
public void testCommandLineBuildWithRelativePath() throws Exception {
- Runtime rt = Runtime.getRuntime();
File wd = new File(buildPath, "build/shared/examples/01.Basics/Blink/");
- Process pr = rt
- .exec(arduinoPath + " --board arduino:avr:uno --verify Blink.ino", null,
- wd);
- IOUtils.copy(pr.getInputStream(), System.out);
- pr.waitFor();
- assertEquals(0, pr.exitValue());
+ runArduino(true, true, wd, new String[] {
+ "--board", "arduino:avr:uno",
+ "--verify", "Blink.ino",
+ });
}
@Test
public void testCommandLinePreferencesSave() throws Exception {
- Runtime rt = Runtime.getRuntime();
File prefFile = File.createTempFile("test_pref", ".txt");
prefFile.deleteOnExit();
- Process pr = rt.exec(new String[] {
- arduinoPath.getAbsolutePath(),
+ runArduino(true, true, null, new String[] {
"--save-prefs",
"--preferences-file", prefFile.getAbsolutePath(),
- "--get-pref", // avoids starting the GUI
+ "--version", // avoids starting the GUI
});
- IOUtils.copy(pr.getInputStream(), System.out);
- IOUtils.copy(pr.getErrorStream(), System.out);
- pr.waitFor();
- assertEquals(0, pr.exitValue());
- pr = rt.exec(new String[] {
- arduinoPath.getAbsolutePath(),
+ runArduino(true, true, null, new String[] {
"--pref", "test_pref=xxx",
"--preferences-file", prefFile.getAbsolutePath(),
});
- IOUtils.copy(pr.getInputStream(), System.out);
- IOUtils.copy(pr.getErrorStream(), System.out);
- pr.waitFor();
- assertEquals(0, pr.exitValue());
PreferencesMap prefs = new PreferencesMap(prefFile);
assertNull("preference should not be saved", prefs.get("test_pref"));
- pr = rt.exec(new String[] {
- arduinoPath.getAbsolutePath(),
+ runArduino(true, true, null, new String[] {
"--pref", "test_pref=xxx",
"--preferences-file", prefFile.getAbsolutePath(),
"--save-prefs",
});
- IOUtils.copy(pr.getInputStream(), System.out);
- IOUtils.copy(pr.getErrorStream(), System.out);
- pr.waitFor();
- assertEquals(0, pr.exitValue());
prefs = new PreferencesMap(prefFile);
assertEquals("preference should be saved", "xxx", prefs.get("test_pref"));
-}
+ }
+
+ @Test
+ public void testCommandLineVersion() throws Exception {
+ Process pr = runArduino(false, true, null, new String[] {
+ "--version",
+ });
+
+ Assertions.assertThat(new String(IOUtils.toByteArray(pr.getInputStream())))
+ .matches("Arduino: \\d+\\.\\d+\\.\\d+.*\r?\n");
+ }
+
+ @Test
+ public void testCommandLineMultipleAction() throws Exception {
+ Process pr = runArduino(true, false, null, new String[] {
+ "--version",
+ "--verify",
+ });
+
+ Assertions.assertThat(pr.exitValue())
+ .as("Multiple Action will be rejected")
+ .isEqualTo(3);
+ }
}
diff --git a/app/test/processing/app/DefaultTargetTest.java b/app/test/processing/app/DefaultTargetTest.java
index 37819c84cff..24767bee30d 100644
--- a/app/test/processing/app/DefaultTargetTest.java
+++ b/app/test/processing/app/DefaultTargetTest.java
@@ -57,7 +57,7 @@ public void testDefaultTarget() throws Exception {
PreferencesData.set("board", "unreal_board");
// should not raise an exception
- new Base(new String[0]);
+ createBase();
// skip test if no target platforms are available
Assume.assumeNotNull(BaseNoGui.getTargetPlatform());
diff --git a/app/test/processing/app/EditorConsoleTest.java b/app/test/processing/app/EditorConsoleTest.java
new file mode 100644
index 00000000000..308523ce6ef
--- /dev/null
+++ b/app/test/processing/app/EditorConsoleTest.java
@@ -0,0 +1,155 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2020 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package processing.app;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class EditorConsoleTest extends AbstractWithPreferencesTest {
+ private EditorConsole console;
+
+ @Before
+ public void createConsole() {
+ console = new EditorConsole(null);
+ }
+
+ public String escapeString(String input) {
+ // This escapes backslashes, newlines and carriage returns, to get
+ // more readable assertion failures.
+ return input.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r");
+ }
+
+ public void assertOutput(String output) {
+ assertEquals(escapeString(output), escapeString(console.getText()));
+ }
+
+ @Test
+ public void testHelloWorld() throws Exception {
+ console.insertString("Hello, world!", null);
+
+ assertOutput("Hello, world!");
+ }
+
+ @Test
+ public void testCrNlHandling() throws Exception {
+ // Do some basic tests with \r\n
+ console.insertString("abc\r\ndef", null);
+ assertOutput("abc\r\ndef");
+
+ console.insertString("xyz", null);
+ assertOutput("abc\r\ndefxyz");
+
+ console.insertString("000\r\n123", null);
+ assertOutput("abc\r\ndefxyz000\r\n123");
+
+ console.insertString("\r\n", null);
+ assertOutput("abc\r\ndefxyz000\r\n123\r\n");
+ }
+
+ @Test
+ public void testNlHandling() throws Exception {
+ // Basic tests, but with just \n
+ console.insertString("abc\ndef", null);
+ assertOutput("abc\ndef");
+
+ console.insertString("xyz", null);
+ assertOutput("abc\ndefxyz");
+
+ console.insertString("000\n123", null);
+ assertOutput("abc\ndefxyz000\n123");
+
+ console.insertString("\n", null);
+ assertOutput("abc\ndefxyz000\n123\n");
+ }
+
+ @Test
+ public void testCrHandling() throws Exception {
+ // Then test that single \r clears the current line
+ console.clear();
+ console.insertString("abc\rdef", null);
+ assertOutput("def");
+
+ // A single \r at the end is not added to the document
+ console.insertString("\r", null);
+ assertOutput("def");
+
+ // Nor are multiple \r at the end
+ console.insertString("\r\r\r", null);
+ assertOutput("def");
+
+ // But it does clear the line on the next write
+ console.insertString("123", null);
+ assertOutput("123");
+
+ // Same when combined with some data
+ console.insertString("\r456\r\r", null);
+ assertOutput("456");
+
+ console.insertString("000", null);
+ assertOutput("000");
+
+ // Then add a newline so preceding data is kept
+ console.insertString("\r\nxxx\r", null);
+ assertOutput("000\r\nxxx");
+
+ // But data after the newline is removed
+ console.insertString("yyy", null);
+ assertOutput("000\r\nyyy");
+
+ // When a \r\n is split across inserts, it becomes a lone \n
+ console.insertString("\r", null);
+ assertOutput("000\r\nyyy");
+ console.insertString("\n", null);
+ assertOutput("000\r\nyyy\n");
+ }
+
+ @Test
+ public void testCrPartialOverwrite() throws Exception {
+ console.insertString("abcdef\r", null);
+ assertOutput("abcdef");
+
+ console.insertString("123", null);
+ assertOutput("123def");
+
+ console.insertString("4", null);
+ assertOutput("1234ef");
+
+ console.insertString("\r\n56", null);
+ assertOutput("1234ef\r\n56");
+ }
+
+ @Test
+ public void testTogether() throws Exception {
+ console.insertString("abc\n123456\rdef\rx\r\nyyy\nzzz\r999", null);
+ assertOutput("abc\nxef456\r\nyyy\n999");
+ }
+}
diff --git a/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java b/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java
index 83897b1963c..59dff4c3595 100644
--- a/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java
+++ b/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java
@@ -30,6 +30,7 @@
package processing.app;
import org.fest.swing.core.KeyPressInfo;
+import org.fest.swing.core.matcher.DialogMatcher;
import org.fest.swing.finder.WindowFinder;
import org.fest.swing.fixture.DialogFixture;
import org.junit.Test;
@@ -39,6 +40,7 @@
import java.awt.event.KeyEvent;
import static org.junit.Assert.assertEquals;
+import static processing.app.I18n.tr;
public class HittingEscapeOnCloseConfirmationDialogTest extends AbstractGUITest {
@@ -49,7 +51,8 @@ public void shouldJustCloseTheDialog() throws Exception {
window.close();
- DialogFixture dialog = WindowFinder.findDialog(JDialog.class).using(window.robot);
+ DialogMatcher matcher = DialogMatcher.withTitle(tr("Close")).andShowing();
+ DialogFixture dialog = WindowFinder.findDialog(matcher).using(window.robot);
dialog.pressAndReleaseKey(KeyPressInfo.keyCode(KeyEvent.VK_ESCAPE));
EditorConsole console = (EditorConsole) window.scrollPane("console").component();
diff --git a/app/test/processing/app/SerialTest.java b/app/test/processing/app/SerialTest.java
new file mode 100644
index 00000000000..63280811e24
--- /dev/null
+++ b/app/test/processing/app/SerialTest.java
@@ -0,0 +1,58 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2020 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package processing.app;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class SerialTest {
+ class NullSerial extends Serial {
+ public NullSerial() throws SerialException {
+ super("none", 0, 'n', 0, 0, false, false);
+ }
+
+ @Override
+ protected void message(char[] chars, int length) {
+ output += new String(chars, 0, length);
+ }
+
+ String output = "";
+ }
+
+ @Test
+ public void testSerialUTF8Decoder() throws Exception {
+ NullSerial s = new NullSerial();
+ // https://github.com/arduino/Arduino/issues/9808
+ String testdata = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789°0123456789";
+ s.processSerialEvent(testdata.getBytes());
+ assertEquals(s.output, testdata);
+ }
+}
diff --git a/app/test/processing/app/UpdateTextAreaActionTest.java b/app/test/processing/app/UpdateTextAreaActionTest.java
new file mode 100644
index 00000000000..b32ea1850be
--- /dev/null
+++ b/app/test/processing/app/UpdateTextAreaActionTest.java
@@ -0,0 +1,91 @@
+package processing.app;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import cc.arduino.packages.BoardPort;
+import processing.app.helpers.PreferencesMap;
+
+public class UpdateTextAreaActionTest {
+
+ private static final String TIMESTAMP_REGEX = "\\d\\d:\\d\\d:\\d\\d.\\d\\d\\d";
+
+ class DummyTextMonitor extends AbstractTextMonitor {
+ public DummyTextMonitor(BoardPort boardPort) {
+ super(boardPort);
+ }
+ }
+
+ @Before
+ public void setup() {
+ PreferencesData.defaults = new PreferencesMap();
+ PreferencesData.set("editor.font", "Monospaced,plain,12");
+ PreferencesData.set("gui.scale", "100");
+ Theme.defaults = new PreferencesMap();
+ Theme.table.put("console.font", "Monospaced,plain,12");
+ }
+
+ @Test
+ public void noTimestampAdded() {
+ DummyTextMonitor textMon = new DummyTextMonitor(new BoardPort());
+ textMon.addTimeStampBox.setSelected(false);
+
+ textMon.updateTextArea("line1\nline2\r\nline3");
+ assertThat(textMon.textArea.getText()).matches("line1\nline2\r\nline3");
+ }
+
+ @Test
+ public void all3LinesHaveTimestampAdded() {
+ DummyTextMonitor textMon = new DummyTextMonitor(new BoardPort());
+ textMon.addTimeStampBox.setSelected(true);
+
+ textMon.updateTextArea("line1\nline2\r\nline3");
+ assertThat(textMon.textArea.getText())
+ .matches(TIMESTAMP_REGEX + " -> line1\\n" + //
+ TIMESTAMP_REGEX + " -> line2\\r\\n" + //
+ TIMESTAMP_REGEX + " -> line3");
+ }
+
+ @Test
+ public void emptyLinesHaveTimestampToo() {
+ DummyTextMonitor textMon = new DummyTextMonitor(new BoardPort());
+ textMon.addTimeStampBox.setSelected(true);
+
+ textMon.updateTextArea("line_1\n\nline_2");
+ assertThat(textMon.textArea.getText())
+ .matches(TIMESTAMP_REGEX + " -> line_1\\n" + //
+ TIMESTAMP_REGEX + " -> \\n" + //
+ TIMESTAMP_REGEX + " -> line_2");
+ }
+
+ @Test
+ public void newLinesAreRememberedWhenNewBufferIsUsed() {
+ DummyTextMonitor textMon = new DummyTextMonitor(new BoardPort());
+ textMon.addTimeStampBox.setSelected(true);
+
+ textMon.updateTextArea("no newline");
+ assertThat(textMon.textArea.getText())
+ .matches(TIMESTAMP_REGEX + " -> no newline");
+
+ textMon.updateTextArea(" more text");
+ assertThat(textMon.textArea.getText())
+ .matches(TIMESTAMP_REGEX + " -> no newline more text");
+
+ textMon.updateTextArea("\n");
+ assertThat(textMon.textArea.getText())
+ .matches(TIMESTAMP_REGEX + " -> no newline more text\n");
+
+ textMon.updateTextArea("\n");
+ assertThat(textMon.textArea.getText())
+ .matches(TIMESTAMP_REGEX + " -> no newline more text\n" + //
+ TIMESTAMP_REGEX + " -> \n");
+
+ textMon.updateTextArea("third line");
+ assertThat(textMon.textArea.getText())
+ .matches(TIMESTAMP_REGEX + " -> no newline more text\n" + //
+ TIMESTAMP_REGEX + " -> \n" + //
+ TIMESTAMP_REGEX + " -> third line");
+ }
+}
\ No newline at end of file
diff --git a/app/test/processing/app/debug/TargetPlatformStub.java b/app/test/processing/app/debug/TargetPlatformStub.java
index a8048c514a4..59b655f96f5 100644
--- a/app/test/processing/app/debug/TargetPlatformStub.java
+++ b/app/test/processing/app/debug/TargetPlatformStub.java
@@ -99,4 +99,10 @@ public TargetBoard getBoard(String boardId) {
public TargetPackage getContainerPackage() {
return targetPackage;
}
+
+ @Override
+ public boolean isInSketchbook() {
+ // TODO Auto-generated method stub
+ return false;
+ }
}
diff --git a/app/test/processing/app/helpers/StringUtilsTest.java b/app/test/processing/app/helpers/StringUtilsTest.java
deleted file mode 100644
index 1ddbf51b35e..00000000000
--- a/app/test/processing/app/helpers/StringUtilsTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * This file is part of Arduino.
- *
- * Arduino 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 St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * As a special exception, you may use this file as part of a free software
- * library without restriction. Specifically, if other files instantiate
- * templates or use macros or inline functions from this file, or you compile
- * this file and link it with other files to produce an executable, this
- * file does not by itself cause the resulting executable to be covered by
- * the GNU General Public License. This exception does not however
- * invalidate any other reasons why the executable file might be covered by
- * the GNU General Public License.
- *
- * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
- */
-
-package processing.app.helpers;
-
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-public class StringUtilsTest {
-
- @Test
- public void shouldJoinAnArray() {
- assertEquals("1 - 2 - 3", StringUtils.join(new String[]{"1", "2", "3"}, " - "));
- }
-}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/README.adoc b/app/testdata/libraries/Bridge_1.6.3/Bridge/README.adoc
new file mode 100644
index 00000000000..c660f86eec2
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/README.adoc
@@ -0,0 +1,24 @@
+= Bridge Library for Arduino =
+
+The Bridge library simplifies communication between the ATmega32U4 and the AR9331.
+
+For more information about this library please visit us at
+http://www.arduino.cc/en/Reference/YunBridgeLibrary
+
+== License ==
+
+Copyright (c) 2014 Arduino LLC. All right reserved.
+
+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; either
+version 2.1 of the License, or (at your option) any later version.
+
+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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/Bridge/Bridge.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/Bridge/Bridge.ino
new file mode 100644
index 00000000000..35c3aac5a4a
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/Bridge/Bridge.ino
@@ -0,0 +1,183 @@
+/*
+ Arduino Yún Bridge example
+
+ This example for the YunShield/Yún shows how
+ to use the Bridge library to access the digital and
+ analog pins on the board through REST calls.
+ It demonstrates how you can create your own API when
+ using REST style calls through the browser.
+
+ Possible commands created in this shetch:
+
+ "/arduino/digital/13" -> digitalRead(13)
+ "/arduino/digital/13/1" -> digitalWrite(13, HIGH)
+ "/arduino/analog/2/123" -> analogWrite(2, 123)
+ "/arduino/analog/2" -> analogRead(2)
+ "/arduino/mode/13/input" -> pinMode(13, INPUT)
+ "/arduino/mode/13/output" -> pinMode(13, OUTPUT)
+
+ This example code is part of the public domain
+
+ http://www.arduino.cc/en/Tutorial/Bridge
+
+*/
+
+#include
+#include
+#include
+
+// Listen to the default port 5555, the Yún webserver
+// will forward there all the HTTP requests you send
+BridgeServer server;
+
+void setup() {
+ // Bridge startup
+ pinMode(13, OUTPUT);
+ digitalWrite(13, LOW);
+ Bridge.begin();
+ digitalWrite(13, HIGH);
+
+ // Listen for incoming connection only from localhost
+ // (no one from the external network could connect)
+ server.listenOnLocalhost();
+ server.begin();
+}
+
+void loop() {
+ // Get clients coming from server
+ BridgeClient client = server.accept();
+
+ // There is a new client?
+ if (client) {
+ // Process request
+ process(client);
+
+ // Close connection and free resources.
+ client.stop();
+ }
+
+ delay(50); // Poll every 50ms
+}
+
+void process(BridgeClient client) {
+ // read the command
+ String command = client.readStringUntil('/');
+
+ // is "digital" command?
+ if (command == "digital") {
+ digitalCommand(client);
+ }
+
+ // is "analog" command?
+ if (command == "analog") {
+ analogCommand(client);
+ }
+
+ // is "mode" command?
+ if (command == "mode") {
+ modeCommand(client);
+ }
+}
+
+void digitalCommand(BridgeClient client) {
+ int pin, value;
+
+ // Read pin number
+ pin = client.parseInt();
+
+ // If the next character is a '/' it means we have an URL
+ // with a value like: "/digital/13/1"
+ if (client.read() == '/') {
+ value = client.parseInt();
+ digitalWrite(pin, value);
+ } else {
+ value = digitalRead(pin);
+ }
+
+ // Send feedback to client
+ client.print(F("Pin D"));
+ client.print(pin);
+ client.print(F(" set to "));
+ client.println(value);
+
+ // Update datastore key with the current pin value
+ String key = "D";
+ key += pin;
+ Bridge.put(key, String(value));
+}
+
+void analogCommand(BridgeClient client) {
+ int pin, value;
+
+ // Read pin number
+ pin = client.parseInt();
+
+ // If the next character is a '/' it means we have an URL
+ // with a value like: "/analog/5/120"
+ if (client.read() == '/') {
+ // Read value and execute command
+ value = client.parseInt();
+ analogWrite(pin, value);
+
+ // Send feedback to client
+ client.print(F("Pin D"));
+ client.print(pin);
+ client.print(F(" set to analog "));
+ client.println(value);
+
+ // Update datastore key with the current pin value
+ String key = "D";
+ key += pin;
+ Bridge.put(key, String(value));
+ } else {
+ // Read analog pin
+ value = analogRead(pin);
+
+ // Send feedback to client
+ client.print(F("Pin A"));
+ client.print(pin);
+ client.print(F(" reads analog "));
+ client.println(value);
+
+ // Update datastore key with the current pin value
+ String key = "A";
+ key += pin;
+ Bridge.put(key, String(value));
+ }
+}
+
+void modeCommand(BridgeClient client) {
+ int pin;
+
+ // Read pin number
+ pin = client.parseInt();
+
+ // If the next character is not a '/' we have a malformed URL
+ if (client.read() != '/') {
+ client.println(F("error"));
+ return;
+ }
+
+ String mode = client.readStringUntil('\r');
+
+ if (mode == "input") {
+ pinMode(pin, INPUT);
+ // Send feedback to client
+ client.print(F("Pin D"));
+ client.print(pin);
+ client.print(F(" configured as INPUT!"));
+ return;
+ }
+
+ if (mode == "output") {
+ pinMode(pin, OUTPUT);
+ // Send feedback to client
+ client.print(F("Pin D"));
+ client.print(pin);
+ client.print(F(" configured as OUTPUT!"));
+ return;
+ }
+
+ client.print(F("error: invalid mode "));
+ client.print(mode);
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ConsoleAsciiTable/ConsoleAsciiTable.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ConsoleAsciiTable/ConsoleAsciiTable.ino
new file mode 100644
index 00000000000..e8b07d7aa71
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ConsoleAsciiTable/ConsoleAsciiTable.ino
@@ -0,0 +1,95 @@
+/*
+ Console ASCII table for YunShield/Yún
+ Prints out byte values in all possible formats:
+ * as raw binary values
+ * as ASCII-encoded decimal, hex, octal, and binary values
+
+ For more on ASCII, see http://www.asciitable.com and http://en.wikipedia.org/wiki/ASCII
+
+ The circuit:
+ - YunShield/Yún
+
+ created 2006
+ by Nicholas Zambetti
+ http://www.zambetti.com
+ modified 9 Apr 2012
+ by Tom Igoe
+ modified 22 May 2013
+ by Cristian Maglie
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/ConsoleAsciiTable
+
+ */
+
+#include
+
+void setup() {
+ //Initialize Console and wait for port to open:
+ Bridge.begin();
+ Console.begin();
+
+ // Uncomment the following line to enable buffering:
+ // - better transmission speed and efficiency
+ // - needs to call Console.flush() to ensure that all
+ // transmitted data is sent
+
+ //Console.buffer(64);
+
+ while (!Console) {
+ ; // wait for Console port to connect.
+ }
+
+ // prints title with ending line break
+ Console.println("ASCII Table ~ Character Map");
+}
+
+// first visible ASCIIcharacter '!' is number 33:
+int thisByte = 33;
+// you can also write ASCII characters in single quotes.
+// for example. '!' is the same as 33, so you could also use this:
+//int thisByte = '!';
+
+void loop() {
+ // prints value unaltered, i.e. the raw binary version of the
+ // byte. The Console monitor interprets all bytes as
+ // ASCII, so 33, the first number, will show up as '!'
+ Console.write(thisByte);
+
+ Console.print(", dec: ");
+ // prints value as string as an ASCII-encoded decimal (base 10).
+ // Decimal is the default format for Console.print() and Console.println(),
+ // so no modifier is needed:
+ Console.print(thisByte);
+ // But you can declare the modifier for decimal if you want to.
+ //this also works if you uncomment it:
+
+ // Console.print(thisByte, DEC);
+
+ Console.print(", hex: ");
+ // prints value as string in hexadecimal (base 16):
+ Console.print(thisByte, HEX);
+
+ Console.print(", oct: ");
+ // prints value as string in octal (base 8);
+ Console.print(thisByte, OCT);
+
+ Console.print(", bin: ");
+ // prints value as string in binary (base 2)
+ // also prints ending line break:
+ Console.println(thisByte, BIN);
+
+ // if printed last visible character '~' or 126, stop:
+ if (thisByte == 126) { // you could also use if (thisByte == '~') {
+ // ensure the latest bit of data is sent
+ Console.flush();
+
+ // This loop loops forever and does nothing
+ while (true) {
+ continue;
+ }
+ }
+ // go on to the next character
+ thisByte++;
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ConsolePixel/ConsolePixel.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ConsolePixel/ConsolePixel.ino
new file mode 100644
index 00000000000..6479b135b4e
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ConsolePixel/ConsolePixel.ino
@@ -0,0 +1,63 @@
+/*
+ Console Pixel
+
+ An example of using YunShield/Yún board to receive data from the
+ Console on the Yún. In this case, the board turns on an LED when
+ it receives the character 'H', and turns off the LED when it
+ receives the character 'L'.
+
+ To see the Console, pick your Yún's name and IP address in the Port menu
+ then open the Port Monitor. You can also see it by opening a terminal window
+ and typing
+ ssh root@ yourYunsName.local 'telnet localhost 6571'
+ then pressing enter. When prompted for the password, enter it.
+
+
+ The circuit:
+ * LED connected from digital pin 13 to ground
+
+ created 2006
+ by David A. Mellis
+ modified 25 Jun 2013
+ by Tom Igoe
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/ConsolePixel
+
+ */
+
+#include
+
+const int ledPin = 13; // the pin that the LED is attached to
+char incomingByte; // a variable to read incoming Console data into
+
+void setup() {
+ Bridge.begin(); // Initialize Bridge
+ Console.begin(); // Initialize Console
+
+ // Wait for the Console port to connect
+ while (!Console);
+
+ Console.println("type H or L to turn pin 13 on or off");
+
+ // initialize the LED pin as an output:
+ pinMode(ledPin, OUTPUT);
+}
+
+void loop() {
+ // see if there's incoming Console data:
+ if (Console.available() > 0) {
+ // read the oldest byte in the Console buffer:
+ incomingByte = Console.read();
+ Console.println(incomingByte);
+ // if it's a capital H (ASCII 72), turn on the LED:
+ if (incomingByte == 'H') {
+ digitalWrite(ledPin, HIGH);
+ }
+ // if it's an L (ASCII 76) turn off the LED:
+ if (incomingByte == 'L') {
+ digitalWrite(ledPin, LOW);
+ }
+ }
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ConsoleRead/ConsoleRead.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ConsoleRead/ConsoleRead.ino
new file mode 100644
index 00000000000..fc63fcc6dd3
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ConsoleRead/ConsoleRead.ino
@@ -0,0 +1,58 @@
+/*
+Console Read example for YunShield/Yún
+
+ Read data coming from bridge using the Console.read() function
+ and store it in a string.
+
+ To see the Console, pick your Yún's name and IP address in the Port menu
+ then open the Port Monitor. You can also see it by opening a terminal window
+ and typing:
+ ssh root@ yourYunsName.local 'telnet localhost 6571'
+ then pressing enter. When prompted for the password, enter it.
+
+ created 13 Jun 2013
+ by Angelo Scialabba
+ modified 16 June 2013
+ by Tom Igoe
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/ConsoleRead
+
+ */
+
+#include
+
+String name;
+
+void setup() {
+ // Initialize Console and wait for port to open:
+ Bridge.begin();
+ Console.begin();
+
+ // Wait for Console port to connect
+ while (!Console);
+
+ Console.println("Hi, what's your name?");
+}
+
+void loop() {
+ if (Console.available() > 0) {
+ char c = Console.read(); // read the next char received
+ // look for the newline character, this is the last character in the string
+ if (c == '\n') {
+ //print text with the name received
+ Console.print("Hi ");
+ Console.print(name);
+ Console.println("! Nice to meet you!");
+ Console.println();
+ // Ask again for name and clear the old name
+ Console.println("Hi, what's your name?");
+ name = ""; // clear the name string
+ } else { // if the buffer is empty Cosole.read() returns -1
+ name += c; // append the read char from Console to the name string
+ }
+ } else {
+ delay(100);
+ }
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/Datalogger/Datalogger.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/Datalogger/Datalogger.ino
new file mode 100644
index 00000000000..cc84828a080
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/Datalogger/Datalogger.ino
@@ -0,0 +1,102 @@
+/*
+ SD card datalogger
+
+ This example shows how to log data from three analog sensors
+ to an SD card mounted on the YunShield/Yún using the Bridge library.
+
+ The circuit:
+ * analog sensors on analog pins 0, 1 and 2
+ * SD card attached to SD card slot of the YunShield/Yún
+
+ Prepare your SD card creating an empty folder in the SD root
+ named "arduino". This will ensure that the Yún will create a link
+ to the SD to the "/mnt/sd" path.
+
+ You can remove the SD card while the Linux and the
+ sketch are running but be careful not to remove it while
+ the system is writing to it.
+
+ created 24 Nov 2010
+ modified 9 Apr 2012
+ by Tom Igoe
+ adapted to the Yún Bridge library 20 Jun 2013
+ by Federico Vanzati
+ modified 21 Jun 2013
+ by Tom Igoe
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/YunDatalogger
+
+ */
+
+#include
+
+void setup() {
+ // Initialize the Bridge and the Serial
+ Bridge.begin();
+ Serial.begin(9600);
+ FileSystem.begin();
+
+ while (!SerialUSB); // wait for Serial port to connect.
+ SerialUSB.println("Filesystem datalogger\n");
+}
+
+
+void loop() {
+ // make a string that start with a timestamp for assembling the data to log:
+ String dataString;
+ dataString += getTimeStamp();
+ dataString += " = ";
+
+ // read three sensors and append to the string:
+ for (int analogPin = 0; analogPin < 3; analogPin++) {
+ int sensor = analogRead(analogPin);
+ dataString += String(sensor);
+ if (analogPin < 2) {
+ dataString += ","; // separate the values with a comma
+ }
+ }
+
+ // open the file. note that only one file can be open at a time,
+ // so you have to close this one before opening another.
+ // The FileSystem card is mounted at the following "/mnt/FileSystema1"
+ File dataFile = FileSystem.open("/mnt/sd/datalog.txt", FILE_APPEND);
+
+ // if the file is available, write to it:
+ if (dataFile) {
+ dataFile.println(dataString);
+ dataFile.close();
+ // print to the serial port too:
+ SerialUSB.println(dataString);
+ }
+ // if the file isn't open, pop up an error:
+ else {
+ SerialUSB.println("error opening datalog.txt");
+ }
+
+ delay(15000);
+
+}
+
+// This function return a string with the time stamp
+String getTimeStamp() {
+ String result;
+ Process time;
+ // date is a command line utility to get the date and the time
+ // in different formats depending on the additional parameter
+ time.begin("date");
+ time.addParameter("+%D-%T"); // parameters: D for the complete date mm/dd/yy
+ // T for the time hh:mm:ss
+ time.run(); // run the command
+
+ // read the output of the command
+ while (time.available() > 0) {
+ char c = time.read();
+ if (c != '\n') {
+ result += c;
+ }
+ }
+
+ return result;
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/FileWriteScript/FileWriteScript.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/FileWriteScript/FileWriteScript.ino
new file mode 100644
index 00000000000..e080bce36f4
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/FileWriteScript/FileWriteScript.ino
@@ -0,0 +1,83 @@
+/*
+ Write to file using FileIO classes.
+
+ This sketch demonstrate how to write file into the YunShield/Yún filesystem.
+ A shell script file is created in /tmp, and it is executed afterwards.
+
+ created 7 June 2010
+ by Cristian Maglie
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/FileWriteScript
+
+ */
+
+#include
+
+void setup() {
+ // Setup Bridge (needed every time we communicate with the Arduino Yún)
+ Bridge.begin();
+ // Initialize the Serial
+ SerialUSB.begin(9600);
+
+ while (!SerialUSB); // wait for Serial port to connect.
+ SerialUSB.println("File Write Script example\n\n");
+
+ // Setup File IO
+ FileSystem.begin();
+
+ // Upload script used to gain network statistics
+ uploadScript();
+}
+
+void loop() {
+ // Run stats script every 5 secs.
+ runScript();
+ delay(5000);
+}
+
+// this function creates a file into the linux processor that contains a shell script
+// to check the network traffic of the WiFi interface
+void uploadScript() {
+ // Write our shell script in /tmp
+ // Using /tmp stores the script in RAM this way we can preserve
+ // the limited amount of FLASH erase/write cycles
+ File script = FileSystem.open("/tmp/wlan-stats.sh", FILE_WRITE);
+ // Shell script header
+ script.print("#!/bin/sh\n");
+ // shell commands:
+ // ifconfig: is a command line utility for controlling the network interfaces.
+ // wlan0 is the interface we want to query
+ // grep: search inside the output of the ifconfig command the "RX bytes" keyword
+ // and extract the line that contains it
+ script.print("ifconfig wlan0 | grep 'RX bytes'\n");
+ script.close(); // close the file
+
+ // Make the script executable
+ Process chmod;
+ chmod.begin("chmod"); // chmod: change mode
+ chmod.addParameter("+x"); // x stays for executable
+ chmod.addParameter("/tmp/wlan-stats.sh"); // path to the file to make it executable
+ chmod.run();
+}
+
+
+// this function run the script and read the output data
+void runScript() {
+ // Run the script and show results on the Serial
+ Process myscript;
+ myscript.begin("/tmp/wlan-stats.sh");
+ myscript.run();
+
+ String output = "";
+
+ // read the output of the script
+ while (myscript.available()) {
+ output += (char)myscript.read();
+ }
+ // remove the blank spaces at the beginning and the ending of the string
+ output.trim();
+ SerialUSB.println(output);
+ SerialUSB.flush();
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/HttpClient/HttpClient.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/HttpClient/HttpClient.ino
new file mode 100644
index 00000000000..47a37c3f2eb
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/HttpClient/HttpClient.ino
@@ -0,0 +1,51 @@
+/*
+ Yún HTTP Client
+
+ This example for the YunShield/Yún shows how create a basic
+ HTTP client that connects to the internet and downloads
+ content. In this case, you'll connect to the Arduino
+ website and download a version of the logo as ASCII text.
+
+ created by Tom igoe
+ May 2013
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/HttpClient
+
+ */
+
+#include
+#include
+
+void setup() {
+ // Bridge takes about two seconds to start up
+ // it can be helpful to use the on-board LED
+ // as an indicator for when it has initialized
+ pinMode(13, OUTPUT);
+ digitalWrite(13, LOW);
+ Bridge.begin();
+ digitalWrite(13, HIGH);
+
+ SerialUSB.begin(9600);
+
+ while (!SerialUSB); // wait for a serial connection
+}
+
+void loop() {
+ // Initialize the client library
+ HttpClient client;
+
+ // Make a HTTP request:
+ client.get("http://www.arduino.cc/asciilogo.txt");
+
+ // if there are incoming bytes available
+ // from the server, read them and print them:
+ while (client.available()) {
+ char c = client.read();
+ SerialUSB.print(c);
+ }
+ SerialUSB.flush();
+
+ delay(5000);
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/HttpClientConsole/HttpClientConsole.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/HttpClientConsole/HttpClientConsole.ino
new file mode 100644
index 00000000000..3b07f601c8f
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/HttpClientConsole/HttpClientConsole.ino
@@ -0,0 +1,53 @@
+/*
+ Yún HTTP Client Console version for Arduino Uno and Mega using Yún Shield
+
+ This example for the YunShield/Yún shows how create a basic
+ HTTP client that connects to the internet and downloads
+ content. In this case, you'll connect to the Arduino
+ website and download a version of the logo as ASCII text.
+
+ created by Tom igoe
+ May 2013
+ modified by Marco Brianza to use Console
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/HttpClient
+
+ */
+
+#include
+#include
+#include
+
+void setup() {
+ // Bridge takes about two seconds to start up
+ // it can be helpful to use the on-board LED
+ // as an indicator for when it has initialized
+ pinMode(13, OUTPUT);
+ digitalWrite(13, LOW);
+ Bridge.begin();
+ digitalWrite(13, HIGH);
+
+ Console.begin();
+
+ while (!Console); // wait for a serial connection
+}
+
+void loop() {
+ // Initialize the client library
+ HttpClient client;
+
+ // Make a HTTP request:
+ client.get("http://www.arduino.cc/asciilogo.txt");
+
+ // if there are incoming bytes available
+ // from the server, read them and print them:
+ while (client.available()) {
+ char c = client.read();
+ Console.print(c);
+ }
+ Console.flush();
+
+ delay(5000);
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/MailboxReadMessage/MailboxReadMessage.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/MailboxReadMessage/MailboxReadMessage.ino
new file mode 100644
index 00000000000..11aaecd3d24
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/MailboxReadMessage/MailboxReadMessage.ino
@@ -0,0 +1,58 @@
+/*
+ Read Messages from the Mailbox
+
+ This example for the YunShield/Yún shows how to
+ read the messages queue, called Mailbox, using the
+ Bridge library.
+ The messages can be sent to the queue through REST calls.
+ Appen the message in the URL after the keyword "/mailbox".
+ Example
+
+ "/mailbox/hello"
+
+ created 3 Feb 2014
+ by Federico Vanzati & Federico Fissore
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/MailboxReadMessage
+
+ */
+
+#include
+
+void setup() {
+ pinMode(13, OUTPUT);
+ digitalWrite(13, LOW);
+ // Initialize Bridge and Mailbox
+ Bridge.begin();
+ Mailbox.begin();
+ digitalWrite(13, HIGH);
+
+ // Initialize Serial
+ SerialUSB.begin(9600);
+
+ // Wait until a Serial Monitor is connected.
+ while (!SerialUSB);
+
+ SerialUSB.println("Mailbox Read Message\n");
+ SerialUSB.println("The Mailbox is checked every 10 seconds. The incoming messages will be shown below.\n");
+}
+
+void loop() {
+ String message;
+
+ // if there is a message in the Mailbox
+ if (Mailbox.messageAvailable()) {
+ // read all the messages present in the queue
+ while (Mailbox.messageAvailable()) {
+ Mailbox.readMessage(message);
+ SerialUSB.println(message);
+ }
+
+ SerialUSB.println("Waiting 10 seconds before checking the Mailbox again");
+ }
+
+ // wait 10 seconds
+ delay(10000);
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/Process/Process.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/Process/Process.ino
new file mode 100644
index 00000000000..7f5752c7780
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/Process/Process.ino
@@ -0,0 +1,71 @@
+/*
+ Running process using Process class.
+
+ This sketch demonstrate how to run linux processes
+ using a YunShield/Yún
+
+ created 5 Jun 2013
+ by Cristian Maglie
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/Process
+
+ */
+
+#include
+
+void setup() {
+ // Initialize Bridge
+ Bridge.begin();
+
+ // Initialize Serial
+ SerialUSB.begin(9600);
+
+ // Wait until a Serial Monitor is connected.
+ while (!SerialUSB);
+
+ // run various example processes
+ runCurl();
+ runCpuInfo();
+}
+
+void loop() {
+ // Do nothing here.
+}
+
+void runCurl() {
+ // Launch "curl" command and get Arduino ascii art logo from the network
+ // curl is command line program for transferring data using different internet protocols
+ Process p; // Create a process and call it "p"
+ p.begin("curl"); // Process that launch the "curl" command
+ p.addParameter("http://www.arduino.cc/asciilogo.txt"); // Add the URL parameter to "curl"
+ p.run(); // Run the process and wait for its termination
+
+ // Print arduino logo over the Serial
+ // A process output can be read with the stream methods
+ while (p.available() > 0) {
+ char c = p.read();
+ SerialUSB.print(c);
+ }
+ // Ensure the last bit of data is sent.
+ SerialUSB.flush();
+}
+
+void runCpuInfo() {
+ // Launch "cat /proc/cpuinfo" command (shows info on Atheros CPU)
+ // cat is a command line utility that shows the content of a file
+ Process p; // Create a process and call it "p"
+ p.begin("cat"); // Process that launch the "cat" command
+ p.addParameter("/proc/cpuinfo"); // Add the cpuifo file path as parameter to cut
+ p.run(); // Run the process and wait for its termination
+
+ // Print command output on the SerialUSB.
+ // A process output can be read with the stream methods
+ while (p.available() > 0) {
+ char c = p.read();
+ SerialUSB.print(c);
+ }
+ // Ensure the last bit of data is sent.
+ SerialUSB.flush();
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/RemoteDueBlink/RemoteDueBlink.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/RemoteDueBlink/RemoteDueBlink.ino
new file mode 100644
index 00000000000..4b5d2713cf1
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/RemoteDueBlink/RemoteDueBlink.ino
@@ -0,0 +1,33 @@
+/*
+ Blink
+ Turns on an LED on for one second, then off for one second, repeatedly.
+
+ Most Arduinos have an on-board LED you can control. On the Uno and
+ Leonardo, it is attached to digital pin 13. If you're unsure what
+ pin the on-board LED is connected to on your Arduino model, check
+ the documentation at http://www.arduino.cc
+
+ This example code is in the public domain.
+
+ modified 8 May 2014
+ by Scott Fitzgerald
+
+ modified by Marco Brianza to show the remote sketch update feature on Arduino Due using Yún Shield
+ */
+
+#include
+
+// the setup function runs once when you press reset or power the board
+void setup() {
+ checkForRemoteSketchUpdate();
+ // initialize digital pin 13 as an output.
+ pinMode(13, OUTPUT);
+}
+
+// the loop function runs over and over again forever
+void loop() {
+ digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level)
+ delay(100); // wait for a second
+ digitalWrite(13, LOW); // turn the LED off by making the voltage LOW
+ delay(100); // wait for a second
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ShellCommands/ShellCommands.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ShellCommands/ShellCommands.ino
new file mode 100644
index 00000000000..acd82896e32
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/ShellCommands/ShellCommands.ino
@@ -0,0 +1,52 @@
+/*
+ Running shell commands using Process class.
+
+ This sketch demonstrate how to run linux shell commands
+ using a YunShield/Yún. It runs the wifiCheck script on the Linux side
+ of the Yún, then uses grep to get just the signal strength line.
+ Then it uses parseInt() to read the wifi signal strength as an integer,
+ and finally uses that number to fade an LED using analogWrite().
+
+ The circuit:
+ * YunShield/Yún with LED connected to pin 9
+
+ created 12 Jun 2013
+ by Cristian Maglie
+ modified 25 June 2013
+ by Tom Igoe
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/ShellCommands
+
+ */
+
+#include
+
+void setup() {
+ Bridge.begin(); // Initialize the Bridge
+ SerialUSB.begin(9600); // Initialize the Serial
+
+ // Wait until a Serial Monitor is connected.
+ while (!SerialUSB);
+}
+
+void loop() {
+ Process p;
+ // This command line runs the WifiStatus script, (/usr/bin/pretty-wifi-info.lua), then
+ // sends the result to the grep command to look for a line containing the word
+ // "Signal:" the result is passed to this sketch:
+ p.runShellCommand("/usr/bin/pretty-wifi-info.lua | grep Signal");
+
+ // do nothing until the process finishes, so you get the whole output:
+ while (p.running());
+
+ // Read command output. runShellCommand() should have passed "Signal: xx&":
+ while (p.available()) {
+ int result = p.parseInt(); // look for an integer
+ int signal = map(result, 0, 100, 0, 255); // map result from 0-100 range to 0-255
+ analogWrite(9, signal); // set the brightness of LED on pin 9
+ SerialUSB.println(result); // print the number as well
+ }
+ delay(5000); // wait 5 seconds before you do it again
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/TemperatureWebPanel/TemperatureWebPanel.ino b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/TemperatureWebPanel/TemperatureWebPanel.ino
new file mode 100644
index 00000000000..7bf25a8a0e2
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/TemperatureWebPanel/TemperatureWebPanel.ino
@@ -0,0 +1,122 @@
+/*
+ Temperature web interface
+
+ This example shows how to serve data from an analog input
+ via the YunShield/Yún built-in webserver using the Bridge library.
+
+ The circuit:
+ * TMP36 temperature sensor on analog pin A1
+ * SD card attached to SD card slot of the YunShield/Yún
+
+ This sketch must be uploaded via wifi. REST API must be set to "open".
+
+ Prepare your SD card with an empty folder in the SD root
+ named "arduino" and a subfolder of that named "www".
+ This will ensure that the Yún will create a link
+ to the SD to the "/mnt/sd" path.
+
+ In this sketch folder is a basic webpage and a copy of zepto.js, a
+ minimized version of jQuery. When you upload your sketch, these files
+ will be placed in the /arduino/www/TemperatureWebPanel folder on your SD card.
+
+ You can then go to http://arduino.local/sd/TemperatureWebPanel
+ to see the output of this sketch.
+
+ You can remove the SD card while the Linux and the
+ sketch are running but be careful not to remove it while
+ the system is writing to it.
+
+ created 6 July 2013
+ by Tom Igoe
+
+ This example code is in the public domain.
+
+ http://www.arduino.cc/en/Tutorial/TemperatureWebPanel
+
+ */
+
+#include
+#include
+#include
+
+// Listen on default port 5555, the webserver on the Yún
+// will forward there all the HTTP requests for us.
+BridgeServer server;
+String startString;
+long hits = 0;
+
+void setup() {
+ SerialUSB.begin(9600);
+
+ // Bridge startup
+ pinMode(13, OUTPUT);
+ digitalWrite(13, LOW);
+ Bridge.begin();
+ digitalWrite(13, HIGH);
+
+ // using A0 and A2 as vcc and gnd for the TMP36 sensor:
+ pinMode(A0, OUTPUT);
+ pinMode(A2, OUTPUT);
+ digitalWrite(A0, HIGH);
+ digitalWrite(A2, LOW);
+
+ // Listen for incoming connection only from localhost
+ // (no one from the external network could connect)
+ server.listenOnLocalhost();
+ server.begin();
+
+ // get the time that this sketch started:
+ Process startTime;
+ startTime.runShellCommand("date");
+ while (startTime.available()) {
+ char c = startTime.read();
+ startString += c;
+ }
+}
+
+void loop() {
+ // Get clients coming from server
+ BridgeClient client = server.accept();
+
+ // There is a new client?
+ if (client) {
+ // read the command
+ String command = client.readString();
+ command.trim(); //kill whitespace
+ SerialUSB.println(command);
+ // is "temperature" command?
+ if (command == "temperature") {
+
+ // get the time from the server:
+ Process time;
+ time.runShellCommand("date");
+ String timeString = "";
+ while (time.available()) {
+ char c = time.read();
+ timeString += c;
+ }
+ SerialUSB.println(timeString);
+ int sensorValue = analogRead(A1);
+ // convert the reading to millivolts:
+ float voltage = sensorValue * (5000.0f / 1024.0f);
+ // convert the millivolts to temperature celsius:
+ float temperature = (voltage - 500.0f) / 10.0f;
+ // print the temperature:
+ client.print("Current time on the Yún: ");
+ client.println(timeString);
+ client.print(" Current temperature: ");
+ client.print(temperature);
+ client.print(" °C");
+ client.print(" This sketch has been running since ");
+ client.print(startString);
+ client.print(" Hits so far: ");
+ client.print(hits);
+ }
+
+ // Close connection and free resources.
+ client.stop();
+ hits++;
+ }
+
+ delay(50); // Poll every 50ms
+}
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/TemperatureWebPanel/www/index.html b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/TemperatureWebPanel/www/index.html
new file mode 100644
index 00000000000..c6b674771a5
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/TemperatureWebPanel/www/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+ 0
+
+
+
diff --git a/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/TemperatureWebPanel/www/zepto.min.js b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/TemperatureWebPanel/www/zepto.min.js
new file mode 100644
index 00000000000..dbe4e3c3f89
--- /dev/null
+++ b/app/testdata/libraries/Bridge_1.6.3/Bridge/examples/TemperatureWebPanel/www/zepto.min.js
@@ -0,0 +1,2 @@
+/* Zepto v1.0-1-ga3cab6c - polyfill zepto detect event ajax form fx - zeptojs.com/license */
+(function(a){String.prototype.trim===a&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),Array.prototype.reduce===a&&(Array.prototype.reduce=function(b){if(this===void 0||this===null)throw new TypeError;var c=Object(this),d=c.length>>>0,e=0,f;if(typeof b!="function")throw new TypeError;if(d==0&&arguments.length==1)throw new TypeError;if(arguments.length>=2)f=arguments[1];else do{if(e in c){f=c[e++];break}if(++e>=d)throw new TypeError}while(!0);while(e0?c.fn.concat.apply([],a):a}function O(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function P(a){return a in j?j[a]:j[a]=new RegExp("(^|\\s)"+a+"(\\s|$)")}function Q(a,b){return typeof b=="number"&&!l[O(a)]?b+"px":b}function R(a){var b,c;return i[a]||(b=h.createElement(a),h.body.appendChild(b),c=k(b,"").getPropertyValue("display"),b.parentNode.removeChild(b),c=="none"&&(c="block"),i[a]=c),i[a]}function S(a){return"children"in a?f.call(a.children):c.map(a.childNodes,function(a){if(a.nodeType==1)return a})}function T(c,d,e){for(b in d)e&&(J(d[b])||K(d[b]))?(J(d[b])&&!J(c[b])&&(c[b]={}),K(d[b])&&!K(c[b])&&(c[b]=[]),T(c[b],d[b],e)):d[b]!==a&&(c[b]=d[b])}function U(b,d){return d===a?c(b):c(b).filter(d)}function V(a,b,c,d){return F(b)?b.call(a,c,d):b}function W(a,b,c){c==null?a.removeAttribute(b):a.setAttribute(b,c)}function X(b,c){var d=b.className,e=d&&d.baseVal!==a;if(c===a)return e?d.baseVal:d;e?d.baseVal=c:b.className=c}function Y(a){var b;try{return a?a=="true"||(a=="false"?!1:a=="null"?null:isNaN(b=Number(a))?/^[\[\{]/.test(a)?c.parseJSON(a):a:b):a}catch(d){return a}}function Z(a,b){b(a);for(var c in a.childNodes)Z(a.childNodes[c],b)}var a,b,c,d,e=[],f=e.slice,g=e.filter,h=window.document,i={},j={},k=h.defaultView.getComputedStyle,l={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},m=/^\s*<(\w+|!)[^>]*>/,n=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,o=/^(?:body|html)$/i,p=["val","css","html","text","data","width","height","offset"],q=["after","prepend","before","append"],r=h.createElement("table"),s=h.createElement("tr"),t={tr:h.createElement("tbody"),tbody:r,thead:r,tfoot:r,td:s,th:s,"*":h.createElement("div")},u=/complete|loaded|interactive/,v=/^\.([\w-]+)$/,w=/^#([\w-]*)$/,x=/^[\w-]+$/,y={},z=y.toString,A={},B,C,D=h.createElement("div");return A.matches=function(a,b){if(!a||a.nodeType!==1)return!1;var c=a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.matchesSelector;if(c)return c.call(a,b);var d,e=a.parentNode,f=!e;return f&&(e=D).appendChild(a),d=~A.qsa(e,b).indexOf(a),f&&D.removeChild(a),d},B=function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},C=function(a){return g.call(a,function(b,c){return a.indexOf(b)==c})},A.fragment=function(b,d,e){b.replace&&(b=b.replace(n,"<$1>$2>")),d===a&&(d=m.test(b)&&RegExp.$1),d in t||(d="*");var g,h,i=t[d];return i.innerHTML=""+b,h=c.each(f.call(i.childNodes),function(){i.removeChild(this)}),J(e)&&(g=c(h),c.each(e,function(a,b){p.indexOf(a)>-1?g[a](b):g.attr(a,b)})),h},A.Z=function(a,b){return a=a||[],a.__proto__=c.fn,a.selector=b||"",a},A.isZ=function(a){return a instanceof A.Z},A.init=function(b,d){if(!b)return A.Z();if(F(b))return c(h).ready(b);if(A.isZ(b))return b;var e;if(K(b))e=M(b);else if(I(b))e=[J(b)?c.extend({},b):b],b=null;else if(m.test(b))e=A.fragment(b.trim(),RegExp.$1,d),b=null;else{if(d!==a)return c(d).find(b);e=A.qsa(h,b)}return A.Z(e,b)},c=function(a,b){return A.init(a,b)},c.extend=function(a){var b,c=f.call(arguments,1);return typeof a=="boolean"&&(b=a,a=c.shift()),c.forEach(function(c){T(a,c,b)}),a},A.qsa=function(a,b){var c;return H(a)&&w.test(b)?(c=a.getElementById(RegExp.$1))?[c]:[]:a.nodeType!==1&&a.nodeType!==9?[]:f.call(v.test(b)?a.getElementsByClassName(RegExp.$1):x.test(b)?a.getElementsByTagName(b):a.querySelectorAll(b))},c.contains=function(a,b){return a!==b&&a.contains(b)},c.type=E,c.isFunction=F,c.isWindow=G,c.isArray=K,c.isPlainObject=J,c.isEmptyObject=function(a){var b;for(b in a)return!1;return!0},c.inArray=function(a,b,c){return e.indexOf.call(b,a,c)},c.camelCase=B,c.trim=function(a){return a.trim()},c.uuid=0,c.support={},c.expr={},c.map=function(a,b){var c,d=[],e,f;if(L(a))for(e=0;e=0?b:b+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){this.parentNode!=null&&this.parentNode.removeChild(this)})},each:function(a){return e.every.call(this,function(b,c){return a.call(b,c,b)!==!1}),this},filter:function(a){return F(a)?this.not(this.not(a)):c(g.call(this,function(b){return A.matches(b,a)}))},add:function(a,b){return c(C(this.concat(c(a,b))))},is:function(a){return this.length>0&&A.matches(this[0],a)},not:function(b){var d=[];if(F(b)&&b.call!==a)this.each(function(a){b.call(this,a)||d.push(this)});else{var e=typeof b=="string"?this.filter(b):L(b)&&F(b.item)?f.call(b):c(b);this.forEach(function(a){e.indexOf(a)<0&&d.push(a)})}return c(d)},has:function(a){return this.filter(function(){return I(a)?c.contains(this,a):c(this).find(a).size()})},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){var a=this[0];return a&&!I(a)?a:c(a)},last:function(){var a=this[this.length-1];return a&&!I(a)?a:c(a)},find:function(a){var b,d=this;return typeof a=="object"?b=c(a).filter(function(){var a=this;return e.some.call(d,function(b){return c.contains(b,a)})}):this.length==1?b=c(A.qsa(this[0],a)):b=this.map(function(){return A.qsa(this,a)}),b},closest:function(a,b){var d=this[0],e=!1;typeof a=="object"&&(e=c(a));while(d&&!(e?e.indexOf(d)>=0:A.matches(d,a)))d=d!==b&&!H(d)&&d.parentNode;return c(d)},parents:function(a){var b=[],d=this;while(d.length>0)d=c.map(d,function(a){if((a=a.parentNode)&&!H(a)&&b.indexOf(a)<0)return b.push(a),a});return U(b,a)},parent:function(a){return U(C(this.pluck("parentNode")),a)},children:function(a){return U(this.map(function(){return S(this)}),a)},contents:function(){return this.map(function(){return f.call(this.childNodes)})},siblings:function(a){return U(this.map(function(a,b){return g.call(S(b.parentNode),function(a){return a!==b})}),a)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(a){return c.map(this,function(b){return b[a]})},show:function(){return this.each(function(){this.style.display=="none"&&(this.style.display=null),k(this,"").getPropertyValue("display")=="none"&&(this.style.display=R(this.nodeName))})},replaceWith:function(a){return this.before(a).remove()},wrap:function(a){var b=F(a);if(this[0]&&!b)var d=c(a).get(0),e=d.parentNode||this.length>1;return this.each(function(f){c(this).wrapAll(b?a.call(this,f):e?d.cloneNode(!0):d)})},wrapAll:function(a){if(this[0]){c(this[0]).before(a=c(a));var b;while((b=a.children()).length)a=b.first();c(a).append(this)}return this},wrapInner:function(a){var b=F(a);return this.each(function(d){var e=c(this),f=e.contents(),g=b?a.call(this,d):a;f.length?f.wrapAll(g):e.append(g)})},unwrap:function(){return this.parent().each(function(){c(this).replaceWith(c(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(b){return this.each(function(){var d=c(this);(b===a?d.css("display")=="none":b)?d.show():d.hide()})},prev:function(a){return c(this.pluck("previousElementSibling")).filter(a||"*")},next:function(a){return c(this.pluck("nextElementSibling")).filter(a||"*")},html:function(b){return b===a?this.length>0?this[0].innerHTML:null:this.each(function(a){var d=this.innerHTML;c(this).empty().append(V(this,b,a,d))})},text:function(b){return b===a?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=b})},attr:function(c,d){var e;return typeof c=="string"&&d===a?this.length==0||this[0].nodeType!==1?a:c=="value"&&this[0].nodeName=="INPUT"?this.val():!(e=this[0].getAttribute(c))&&c in this[0]?this[0][c]:e:this.each(function(a){if(this.nodeType!==1)return;if(I(c))for(b in c)W(this,b,c[b]);else W(this,c,V(this,d,a,this.getAttribute(c)))})},removeAttr:function(a){return this.each(function(){this.nodeType===1&&W(this,a)})},prop:function(b,c){return c===a?this[0]&&this[0][b]:this.each(function(a){this[b]=V(this,c,a,this[b])})},data:function(b,c){var d=this.attr("data-"+O(b),c);return d!==null?Y(d):a},val:function(b){return b===a?this[0]&&(this[0].multiple?c(this[0]).find("option").filter(function(a){return this.selected}).pluck("value"):this[0].value):this.each(function(a){this.value=V(this,b,a,this.value)})},offset:function(a){if(a)return this.each(function(b){var d=c(this),e=V(this,a,b,d.offset()),f=d.offsetParent().offset(),g={top:e.top-f.top,left:e.left-f.left};d.css("position")=="static"&&(g.position="relative"),d.css(g)});if(this.length==0)return null;var b=this[0].getBoundingClientRect();return{left:b.left+window.pageXOffset,top:b.top+window.pageYOffset,width:Math.round(b.width),height:Math.round(b.height)}},css:function(a,c){if(arguments.length<2&&typeof a=="string")return this[0]&&(this[0].style[B(a)]||k(this[0],"").getPropertyValue(a));var d="";if(E(a)=="string")!c&&c!==0?this.each(function(){this.style.removeProperty(O(a))}):d=O(a)+":"+Q(a,c);else for(b in a)!a[b]&&a[b]!==0?this.each(function(){this.style.removeProperty(O(b))}):d+=O(b)+":"+Q(b,a[b])+";";return this.each(function(){this.style.cssText+=";"+d})},index:function(a){return a?this.indexOf(c(a)[0]):this.parent().children().indexOf(this[0])},hasClass:function(a){return e.some.call(this,function(a){return this.test(X(a))},P(a))},addClass:function(a){return this.each(function(b){d=[];var e=X(this),f=V(this,a,b,e);f.split(/\s+/g).forEach(function(a){c(this).hasClass(a)||d.push(a)},this),d.length&&X(this,e+(e?" ":"")+d.join(" "))})},removeClass:function(b){return this.each(function(c){if(b===a)return X(this,"");d=X(this),V(this,b,c,d).split(/\s+/g).forEach(function(a){d=d.replace(P(a)," ")}),X(this,d.trim())})},toggleClass:function(b,d){return this.each(function(e){var f=c(this),g=V(this,b,e,X(this));g.split(/\s+/g).forEach(function(b){(d===a?!f.hasClass(b):d)?f.addClass(b):f.removeClass(b)})})},scrollTop:function(){if(!this.length)return;return"scrollTop"in this[0]?this[0].scrollTop:this[0].scrollY},position:function(){if(!this.length)return;var a=this[0],b=this.offsetParent(),d=this.offset(),e=o.test(b[0].nodeName)?{top:0,left:0}:b.offset();return d.top-=parseFloat(c(a).css("margin-top"))||0,d.left-=parseFloat(c(a).css("margin-left"))||0,e.top+=parseFloat(c(b[0]).css("border-top-width"))||0,e.left+=parseFloat(c(b[0]).css("border-left-width"))||0,{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||h.body;while(a&&!o.test(a.nodeName)&&c(a).css("position")=="static")a=a.offsetParent;return a})}},c.fn.detach=c.fn.remove,["width","height"].forEach(function(b){c.fn[b]=function(d){var e,f=this[0],g=b.replace(/./,function(a){return a[0].toUpperCase()});return d===a?G(f)?f["inner"+g]:H(f)?f.documentElement["offset"+g]:(e=this.offset())&&e[b]:this.each(function(a){f=c(this),f.css(b,V(this,d,a,f[b]()))})}}),q.forEach(function(a,b){var d=b%2;c.fn[a]=function(){var a,e=c.map(arguments,function(b){return a=E(b),a=="object"||a=="array"||b==null?b:A.fragment(b)}),f,g=this.length>1;return e.length<1?this:this.each(function(a,h){f=d?h:h.parentNode,h=b==0?h.nextSibling:b==1?h.firstChild:b==2?h:null,e.forEach(function(a){if(g)a=a.cloneNode(!0);else if(!f)return c(a).remove();Z(f.insertBefore(a,h),function(a){a.nodeName!=null&&a.nodeName.toUpperCase()==="SCRIPT"&&(!a.type||a.type==="text/javascript")&&!a.src&&window.eval.call(window,a.innerHTML)})})})},c.fn[d?a+"To":"insert"+(b?"Before":"After")]=function(b){return c(b)[a](this),this}}),A.Z.prototype=c.fn,A.uniq=C,A.deserializeValue=Y,c.zepto=A,c}();window.Zepto=Zepto,"$"in window||(window.$=Zepto),function(a){function b(a){var b=this.os={},c=this.browser={},d=a.match(/WebKit\/([\d.]+)/),e=a.match(/(Android)\s+([\d.]+)/),f=a.match(/(iPad).*OS\s([\d_]+)/),g=!f&&a.match(/(iPhone\sOS)\s([\d_]+)/),h=a.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),i=h&&a.match(/TouchPad/),j=a.match(/Kindle\/([\d.]+)/),k=a.match(/Silk\/([\d._]+)/),l=a.match(/(BlackBerry).*Version\/([\d.]+)/),m=a.match(/(BB10).*Version\/([\d.]+)/),n=a.match(/(RIM\sTablet\sOS)\s([\d.]+)/),o=a.match(/PlayBook/),p=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),q=a.match(/Firefox\/([\d.]+)/);if(c.webkit=!!d)c.version=d[1];e&&(b.android=!0,b.version=e[2]),g&&(b.ios=b.iphone=!0,b.version=g[2].replace(/_/g,".")),f&&(b.ios=b.ipad=!0,b.version=f[2].replace(/_/g,".")),h&&(b.webos=!0,b.version=h[2]),i&&(b.touchpad=!0),l&&(b.blackberry=!0,b.version=l[2]),m&&(b.bb10=!0,b.version=m[2]),n&&(b.rimtabletos=!0,b.version=n[2]),o&&(c.playbook=!0),j&&(b.kindle=!0,b.version=j[1]),k&&(c.silk=!0,c.version=k[1]),!k&&b.android&&a.match(/Kindle Fire/)&&(c.silk=!0),p&&(c.chrome=!0,c.version=p[1]),q&&(c.firefox=!0,c.version=q[1]),b.tablet=!!(f||o||e&&!a.match(/Mobile/)||q&&a.match(/Tablet/)),b.phone=!b.tablet&&!!(e||g||h||l||m||p&&a.match(/Android/)||p&&a.match(/CriOS\/([\d.]+)/)||q&&a.match(/Mobile/))}b.call(a,navigator.userAgent),a.__detect=b}(Zepto),function(a){function g(a){return a._zid||(a._zid=d++)}function h(a,b,d,e){b=i(b);if(b.ns)var f=j(b.ns);return(c[g(a)]||[]).filter(function(a){return a&&(!b.e||a.e==b.e)&&(!b.ns||f.test(a.ns))&&(!d||g(a.fn)===g(d))&&(!e||a.sel==e)})}function i(a){var b=(""+a).split(".");return{e:b[0],ns:b.slice(1).sort().join(" ")}}function j(a){return new RegExp("(?:^| )"+a.replace(" "," .* ?")+"(?: |$)")}function k(b,c,d){a.type(b)!="string"?a.each(b,d):b.split(/\s/).forEach(function(a){d(a,c)})}function l(a,b){return a.del&&(a.e=="focus"||a.e=="blur")||!!b}function m(a){return f[a]||a}function n(b,d,e,h,j,n){var o=g(b),p=c[o]||(c[o]=[]);k(d,e,function(c,d){var e=i(c);e.fn=d,e.sel=h,e.e in f&&(d=function(b){var c=b.relatedTarget;if(!c||c!==this&&!a.contains(this,c))return e.fn.apply(this,arguments)}),e.del=j&&j(d,c);var g=e.del||d;e.proxy=function(a){var c=g.apply(b,[a].concat(a.data));return c===!1&&(a.preventDefault(),a.stopPropagation()),c},e.i=p.length,p.push(e),b.addEventListener(m(e.e),e.proxy,l(e,n))})}function o(a,b,d,e,f){var i=g(a);k(b||"",d,function(b,d){h(a,b,d,e).forEach(function(b){delete c[i][b.i],a.removeEventListener(m(b.e),b.proxy,l(b,f))})})}function t(b){var c,d={originalEvent:b};for(c in b)!r.test(c)&&b[c]!==undefined&&(d[c]=b[c]);return a.each(s,function(a,c){d[a]=function(){return this[c]=p,b[a].apply(b,arguments)},d[c]=q}),d}function u(a){if(!("defaultPrevented"in a)){a.defaultPrevented=!1;var b=a.preventDefault;a.preventDefault=function(){this.defaultPrevented=!0,b.call(this)}}}var b=a.zepto.qsa,c={},d=1,e={},f={mouseenter:"mouseover",mouseleave:"mouseout"};e.click=e.mousedown=e.mouseup=e.mousemove="MouseEvents",a.event={add:n,remove:o},a.proxy=function(b,c){if(a.isFunction(b)){var d=function(){return b.apply(c,arguments)};return d._zid=g(b),d}if(typeof c=="string")return a.proxy(b[c],b);throw new TypeError("expected function")},a.fn.bind=function(a,b){return this.each(function(){n(this,a,b)})},a.fn.unbind=function(a,b){return this.each(function(){o(this,a,b)})},a.fn.one=function(a,b){return this.each(function(c,d){n(this,a,b,null,function(a,b){return function(){var c=a.apply(d,arguments);return o(d,b,a),c}})})};var p=function(){return!0},q=function(){return!1},r=/^([A-Z]|layer[XY]$)/,s={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};a.fn.delegate=function(b,c,d){return this.each(function(e,f){n(f,c,d,b,function(c){return function(d){var e,g=a(d.target).closest(b,f).get(0);if(g)return e=a.extend(t(d),{currentTarget:g,liveFired:f}),c.apply(g,[e].concat([].slice.call(arguments,1)))}})})},a.fn.undelegate=function(a,b,c){return this.each(function(){o(this,b,c,a)})},a.fn.live=function(b,c){return a(document.body).delegate(this.selector,b,c),this},a.fn.die=function(b,c){return a(document.body).undelegate(this.selector,b,c),this},a.fn.on=function(b,c,d){return!c||a.isFunction(c)?this.bind(b,c||d):this.delegate(c,b,d)},a.fn.off=function(b,c,d){return!c||a.isFunction(c)?this.unbind(b,c||d):this.undelegate(c,b,d)},a.fn.trigger=function(b,c){if(typeof b=="string"||a.isPlainObject(b))b=a.Event(b);return u(b),b.data=c,this.each(function(){"dispatchEvent"in this&&this.dispatchEvent(b)})},a.fn.triggerHandler=function(b,c){var d,e;return this.each(function(f,g){d=t(typeof b=="string"?a.Event(b):b),d.data=c,d.target=g,a.each(h(g,b.type||b),function(a,b){e=b.proxy(d);if(d.isImmediatePropagationStopped())return!1})}),e},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.trigger(b)}}),["focus","blur"].forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.each(function(){try{this[b]()}catch(a){}}),this}}),a.Event=function(a,b){typeof a!="string"&&(b=a,a=b.type);var c=document.createEvent(e[a]||"Events"),d=!0;if(b)for(var f in b)f=="bubbles"?d=!!b[f]:c[f]=b[f];return c.initEvent(a,d,!0,null,null,null,null,null,null,null,null,null,null,null,null),c.isDefaultPrevented=function(){return this.defaultPrevented},c}}(Zepto),function($){function triggerAndReturn(a,b,c){var d=$.Event(b);return $(a).trigger(d,c),!d.defaultPrevented}function triggerGlobal(a,b,c,d){if(a.global)return triggerAndReturn(b||document,c,d)}function ajaxStart(a){a.global&&$.active++===0&&triggerGlobal(a,null,"ajaxStart")}function ajaxStop(a){a.global&&!--$.active&&triggerGlobal(a,null,"ajaxStop")}function ajaxBeforeSend(a,b){var c=b.context;if(b.beforeSend.call(c,a,b)===!1||triggerGlobal(b,c,"ajaxBeforeSend",[a,b])===!1)return!1;triggerGlobal(b,c,"ajaxSend",[a,b])}function ajaxSuccess(a,b,c){var d=c.context,e="success";c.success.call(d,a,e,b),triggerGlobal(c,d,"ajaxSuccess",[b,c,a]),ajaxComplete(e,b,c)}function ajaxError(a,b,c,d){var e=d.context;d.error.call(e,c,b,a),triggerGlobal(d,e,"ajaxError",[c,d,a]),ajaxComplete(b,c,d)}function ajaxComplete(a,b,c){var d=c.context;c.complete.call(d,b,a),triggerGlobal(c,d,"ajaxComplete",[b,c]),ajaxStop(c)}function empty(){}function mimeToDataType(a){return a&&(a=a.split(";",2)[0]),a&&(a==htmlType?"html":a==jsonType?"json":scriptTypeRE.test(a)?"script":xmlTypeRE.test(a)&&"xml")||"text"}function appendQuery(a,b){return(a+"&"+b).replace(/[&?]{1,2}/,"?")}function serializeData(a){a.processData&&a.data&&$.type(a.data)!="string"&&(a.data=$.param(a.data,a.traditional)),a.data&&(!a.type||a.type.toUpperCase()=="GET")&&(a.url=appendQuery(a.url,a.data))}function parseArguments(a,b,c,d){var e=!$.isFunction(b);return{url:a,data:e?b:undefined,success:e?$.isFunction(c)?c:undefined:b,dataType:e?d||c:c}}function serialize(a,b,c,d){var e,f=$.isArray(b);$.each(b,function(b,g){e=$.type(g),d&&(b=c?d:d+"["+(f?"":b)+"]"),!d&&f?a.add(g.name,g.value):e=="array"||!c&&e=="object"?serialize(a,g,c,b):a.add(b,g)})}var jsonpID=0,document=window.document,key,name,rscript=/
+
+
+
+
+ 0
+
+