diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000000..41534ed7ba
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: processing
+custom: https://processingfoundation.org
diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml
new file mode 100644
index 0000000000..5ed1e8ee84
--- /dev/null
+++ b/.github/workflows/lock.yml
@@ -0,0 +1,29 @@
+name: 'Lock Threads'
+
+on:
+ schedule:
+ - cron: '0 6 * * *'
+
+permissions:
+ contents: read
+
+jobs:
+ lock:
+ permissions:
+ issues: write
+ pull-requests: write
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dessant/lock-threads@v2.0.1
+ with:
+ github-token: ${{ github.token }}
+ issue-lock-inactive-days: '30'
+ issue-lock-comment: >
+ This issue has been automatically locked. To avoid confusion
+ with reports that have already been resolved, closed issues
+ are automatically locked 30 days after the last comment.
+ Please open a new issue for related bugs.
+ pr-lock-comment: >
+ This pull request has been automatically locked.
+ Pull requests that have been closed are automatically
+ locked 30 days after the last comment.
diff --git a/.gitignore b/.gitignore
index 528a5fbbbc..cd99249298 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,75 @@
._*
*~
/build/shared/reference.zip
+
+# temporary, until we complete the move to IntelliJ
+*.iml
+/.idea
+
+# via https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..afb13c6f19
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,24 @@
+## Welcome to Processing!
+
+### Bug Report?
+
+We have a page on [troubleshooting](https://github.com/processing/processing/wiki/Troubleshooting) common problems. Check there first!
+
+We also host an [online forum](https://forum.processing.org) for coding questions, which is also helpful for general "getting started" queries.
+
+If you don't find an answer, please let us know by [filing an issue](https://github.com/processing/processing/issues). We can only fix the things we've heard about.
+
+Please keep the tone polite. This project is volunteer work done in our free time. We give it away at no cost. We do this because we think it's important for the community and enjoy it. Complaints that things *suck* are *annoying* or lectures about things that *must* be fixed are... weird things to hear from strangers at best, demotivating at worst.
+
+### Want to Help?
+
+Great! The number of contributors on this project is *tiny*, especially relative to the number of users. There are [only 2 or 3 people](https://github.com/processing/processing/graphs/contributors) who actively work on this repository, for instance. We need help!
+
+How to start:
+
+* Issues marked [help](https://github.com/processing/processing/issues?q=is%3Aissue+is%3Aopen+label%3Ahelp) are a good place to start, because they're something that's isolated enough that someone can jump into it without significant reworking of other code.
+* Mind the [style guidelines](https://github.com/processing/processing/wiki/Style-Guidelines) when submitting pull requests. Otherwise someone else will have to reformat your code so that it fits everything else (or we'll have to reject it if it'll take us too long to clean it up).
+
+### Other Details
+
+This document was hastily thrown together in an attempt to improve the bug reporting process. It needs more detail about our intent with the project, the community behind it, our values, and an explanation of how the code itself is designed.
\ No newline at end of file
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000000..bc9287c602
--- /dev/null
+++ b/ISSUE_TEMPLATE.md
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Description
+
+
+
+## Expected Behavior
+
+
+
+## Current Behavior
+
+
+## Steps to Reproduce
+
+
+1.
+2.
+3.
+
+## Your Environment
+
+
+
+* Processing version:
+* Operating System and OS version:
+* Other information:
+
+## Possible Causes / Solutions
+
diff --git a/README.md b/README.md
index 1c3e154ec0..ece7d3ab72 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,48 @@
-Processing
+
+
+>[!WARNING]
+> # Development has moved to [a new repository](https://github.com/processing/processing4/).
+
+Since the release of Processing 3.5.4 in January 2020, development has continued in a new repository: [processing/processing4](https://github.com/processing/processing4/).
+
+We chose to move to a new repository so that we could clean out old files accumulated over the last 20 years.
+
+To report bugs or request features, please open a [new issue](https://github.com/processing/processing4/issues/new/choose) on the Processing 4 repository.
+
+
+
+
+~~Processing~~
==========
-This is the official source code for the [Processing](http://processing.org) Development Environment (PDE),
-the “core” and the libraries that are included with the [download](http://processing.org/download).
+~~This is the official source code for the [Processing](http://processing.org) Development Environment (PDE),
+the “core” and the libraries that are included with the [download](http://processing.org/download).~~
+
+~~__I've found a bug!__~~
+~~Let us know [here](https://github.com/processing/processing/issues) (after first checking if someone has already posted a similar problem).
+If it's a reference, web site, or examples issue, take that up with folks [here](https://github.com/processing/processing-docs/issues).
+There are also separate locations for [Android Mode](https://github.com/processing/processing-android/issues), or the [Video](https://github.com/processing/processing-video/issues) and [Sound](https://github.com/processing/processing-sound/issues) libraries.
+The [processing.js](http://processingjs.org) project is not affiliated with us, but you can find their issue tracker [here](https://github.com/processing-js/processing-js/issues).~~
-__I've found a bug!__
-Let us know [here](https://github.com/processing/processing/issues) (after first checking if someone has already posted a similar problem).
-If it's a documentation, web site, or examples problem, take that up with folks [here](https://github.com/processing/processing-docs/issues).
-There are also separate locations for [Android Mode](https://github.com/processing/processing-android/issues), or the [Video](https://github.com/processing/processing-video/issues) and [Sound](https://github.com/processing/processing-sound/issues) libraries.
-The [processing.js](http://processingjs.org) project is not affiliated with us, but you can find their issue tracker [here](https://github.com/processing-js/processing-js/issues).
+~~__Locked Issues__
+Where possible, I've started locking issues once resolved. This helps reduce the amount of noise from folks adding to an issue that's been closed for years. Because this project has existed for a long time and we have thousands of closed issues, lots of them may sound similar to an issue you're having. But if there's a new problem, it'll be missed if it's lost in a comment added to an already closed issue. I don't like to lock issues because it cuts off conversation, but it's better than legitimate problems being missed. Once an issue has been resolved for 30 days, it will automatically lock.~~
-__That [processing-bugs](https://github.com/processing-bugs) fella is a damn liar.__
-The issues list has been imported from Google Code, so there are many spurious references
-amongst them since the numbering changed. Basically, any time you see references to
+~~__That [processing-bugs](https://github.com/processing-bugs) fella is suspicious.__
+The issues list has been imported from Google Code, so there are many spurious references
+amongst them since the numbering changed. Basically, any time you see references to
changes made by [processing-bugs](https://github.com/processing-bugs), it may be somewhat suspect.
-Over time this will clean itself up as bugs are fixed and new issues are added from within Github.
-Help speed this process along by helping us!
+Over time this will clean itself up as bugs are fixed and new issues are added from within GitHub.
+Help speed this process along by helping us!~~
-__Please help.__
-The instructions for building the source [are here](https://github.com/processing/processing/wiki/Build-Instructions).
-Please help us fix problems, and if you're submitting code, following the [style guidelines](https://github.com/processing/processing/wiki/Style-Guidelines) helps save us a lot of time.
+~~__Please help.__
+The instructions for building the source [are here](https://github.com/processing/processing/wiki/Build-Instructions).
+Please help us fix problems, and if you're submitting code, following the [style guidelines](https://github.com/processing/processing/wiki/Style-Guidelines) helps save me a lot of time.~~
-__And finally...__
-Someday we'll also fix all these bugs, throw together hundreds of unit tests, and get rich off all this stuff that we're giving away for free. But not today.
+~~__And finally...__
+Someday we'll also fix all these bugs, throw together hundreds of unit tests, and get rich off all this stuff that we're giving away for free. But not today.~~
-So in the meantime, I ask for your patience,
-[participation](https://github.com/processing/processing/wiki/Project-List),
-and [patches](https://github.com/processing/processing/pulls).
+~~So in the meantime, I ask for your patience,
+[participation](https://github.com/processing/processing/wiki/Project-List),
+and [patches](https://github.com/processing/processing/pulls).~~
-Ben Fry, 6 August 2015
+~~Ben Fry, 20 January 2019~~
diff --git a/app/.classpath b/app/.classpath
index d63b38f43f..72c3737104 100644
--- a/app/.classpath
+++ b/app/.classpath
@@ -4,6 +4,7 @@
+
diff --git a/app/.settings/org.eclipse.jdt.core.prefs b/app/.settings/org.eclipse.jdt.core.prefs
index aee82626e8..af36b24305 100644
--- a/app/.settings/org.eclipse.jdt.core.prefs
+++ b/app/.settings/org.eclipse.jdt.core.prefs
@@ -6,9 +6,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nul
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -93,8 +93,13 @@ org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disa
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
+org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false
+org.eclipse.jdt.core.formatter.align_with_spaces=false
+org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=18
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
@@ -103,24 +108,39 @@ org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=18
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=36
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
+org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=18
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=18
+org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0
+org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
+org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=1
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
@@ -129,6 +149,7 @@ org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
@@ -138,32 +159,38 @@ org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=false
+org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=false
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.indent_tag_description=false
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=1
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
@@ -176,6 +203,7 @@ org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
org.eclipse.jdt.core.formatter.indentation.size=2
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
@@ -185,6 +213,7 @@ org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
@@ -198,11 +227,15 @@ org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
@@ -229,9 +262,14 @@ org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declar
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
@@ -256,13 +294,20 @@ org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert
+org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
@@ -306,9 +351,13 @@ org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_decla
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
@@ -345,9 +394,12 @@ org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not inser
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
@@ -359,20 +411,59 @@ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_decla
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.lineSplit=80
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=true
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=2
+org.eclipse.jdt.core.formatter.text_block_indentation=0
org.eclipse.jdt.core.formatter.use_on_off_tags=false
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true
org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/app/.settings/org.eclipse.jdt.ui.prefs b/app/.settings/org.eclipse.jdt.ui.prefs
index 7f5ba1ed84..66aaa0890e 100644
--- a/app/.settings/org.eclipse.jdt.ui.prefs
+++ b/app/.settings/org.eclipse.jdt.ui.prefs
@@ -1,3 +1,3 @@
eclipse.preferences.version=1
formatter_profile=_processing
-formatter_settings_version=12
+formatter_settings_version=18
diff --git a/app/build.xml b/app/build.xml
index de7fd993db..b6d97218ab 100644
--- a/app/build.xml
+++ b/app/build.xml
@@ -28,8 +28,8 @@
-
diff --git a/app/lib/jna-platform.jar b/app/lib/jna-platform.jar
new file mode 100644
index 0000000000..d0fd2e4f52
Binary files /dev/null and b/app/lib/jna-platform.jar differ
diff --git a/app/lib/jna.jar b/app/lib/jna.jar
index 748adb001b..25243176ea 100644
Binary files a/app/lib/jna.jar and b/app/lib/jna.jar differ
diff --git a/app/lib/jna.txt b/app/lib/jna.txt
index acc3f19fb3..4dbac83c25 100644
--- a/app/lib/jna.txt
+++ b/app/lib/jna.txt
@@ -1,2 +1,4 @@
-The JAR file is JNA 3.5.2 (presumably) downloaded from
-https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna/3.5.2/
+The JAR file is JNA 4.2.0
+
+You can find the corresponding file for Maven here:
+https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna/4.2.0/
diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index 2b823078a7..5d9b0a114c 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2012-15 The Processing Foundation
+ Copyright (c) 2012-19 The Processing Foundation
Copyright (c) 2004-12 Ben Fry and Casey Reas
Copyright (c) 2001-04 Massachusetts Institute of Technology
@@ -31,6 +31,7 @@
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.*;
+import java.util.Map.Entry;
import javax.swing.JFileChooser;
import javax.swing.JMenu;
@@ -55,14 +56,17 @@
public class Base {
// Added accessors for 0218 because the UpdateCheck class was not properly
// updating the values, due to javac inlining the static final values.
- static private final int REVISION = 248;
+ static private final int REVISION = 271;
/** This might be replaced by main() if there's a lib/version.txt file. */
- static private String VERSION_NAME = "0248"; //$NON-NLS-1$
+ static private String VERSION_NAME = "0271"; //$NON-NLS-1$
/** Set true if this a proper release rather than a numbered revision. */
- /** True if heavy debugging error/log messages are enabled */
- static public boolean DEBUG = false;
-// static public boolean DEBUG = true;
+ /**
+ * True if heavy debugging error/log messages are enabled. Set to true
+ * if an empty file named 'debug' is found in the settings folder.
+ * See implementation in createAndShowGUI().
+ */
+ static public boolean DEBUG;
static private boolean commandLine;
@@ -112,9 +116,31 @@ static public void main(final String[] args) {
public void run() {
try {
createAndShowGUI(args);
+
} catch (Throwable t) {
- Messages.showTrace("It was not meant to be",
- "A serious problem happened during startup. Please report:\n" +
+ // Windows Defender has been insisting on destroying each new
+ // release by removing core.jar and other files. Yay!
+ // https://github.com/processing/processing/issues/5537
+ if (Platform.isWindows()) {
+ String mess = t.getMessage();
+ String missing = null;
+ if (mess.contains("Could not initialize class com.sun.jna.Native")) {
+ missing = "jnidispatch.dll";
+ } else if (mess.contains("NoClassDefFoundError: processing/core/PApplet")) {
+ missing = "core.jar";
+ }
+ if (missing != null) {
+ Messages.showError("Necessary files are missing",
+ "A file required by Processing (" + missing + ") is missing.\n\n" +
+ "Make sure that you're not trying to run Processing from inside\n" +
+ "the .zip file you downloaded, and check that Windows Defender\n" +
+ "hasn't removed files from the Processing folder.\n\n" +
+ "(It sometimes flags parts of Processing as a trojan or virus.\n" +
+ "It is neither, but Microsoft has ignored our pleas for help.)", t);
+ }
+ }
+ Messages.showTrace("Unknown Problem",
+ "A serious error happened during startup. Please report:\n" +
"http://github.com/processing/processing/issues/new", t, true);
}
}
@@ -129,7 +155,6 @@ static private void createAndShowGUI(String[] args) {
String version = PApplet.loadStrings(versionFile)[0];
if (!version.equals(VERSION_NAME)) {
VERSION_NAME = version;
-// RELEASE = true;
}
}
} catch (Exception e) {
@@ -137,6 +162,19 @@ static private void createAndShowGUI(String[] args) {
}
Platform.init();
+ // call after Platform.init() because we need the settings folder
+ Console.startup();
+
+ // Set the debug flag based on a file being present in the settings folder
+ File debugFile = getSettingsFile("debug.txt");
+ /*
+ if (debugFile.isDirectory()) {
+ // if it's a directory, it's a leftover from older releases, clear it
+ Util.removeDir(debugFile);
+ } else*/
+ if (debugFile.exists()) {
+ DEBUG = true;
+ }
// Use native popups so they don't look so crappy on OS X
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
@@ -163,6 +201,7 @@ static private void createAndShowGUI(String[] args) {
boolean sketchbookPrompt = false;
if (Preferences.getBoolean("welcome.show")) {
+ // only ask once about split sketchbooks
if (!Preferences.getBoolean("welcome.seen")) {
// Check if there's a 2.0 sketchbook present
String oldPath = Preferences.getOldSketchbookPath();
@@ -173,9 +212,9 @@ static private void createAndShowGUI(String[] args) {
sketchbookPrompt = true;
} else if (oldPath.equals(newPath)) {
- // If both exist and are identical, then the user has been using
- // alpha releases of 3.x and needs to be warned about the larger
- // changes in this release.
+ // If both exist and are identical, then the user has used
+ // pre-releases of 3.x and needs to be warned about the
+ // larger changes in this release.
sketchbookPrompt = true;
}
}
@@ -185,7 +224,6 @@ static private void createAndShowGUI(String[] args) {
// Get the sketchbook path, and make sure it's set properly
locateSketchbookFolder();
-
// Create a location for untitled sketches
try {
untitledFolder = Util.createTempFolder("untitled", "sketches", null);
@@ -196,29 +234,28 @@ static private void createAndShowGUI(String[] args) {
"That's gonna prevent us from continuing.", e);
}
- Messages.log("about to create base..."); //$NON-NLS-1$
+ Messages.log("About to create Base..."); //$NON-NLS-1$
try {
final Base base = new Base(args);
+ Messages.log("Base() constructor succeeded");
+
// Prevent more than one copy of the PDE from running.
SingleInstance.startServer(base);
// Needs to be shown after the first editor window opens, so that it
// shows up on top, and doesn't prevent an editor window from opening.
if (Preferences.getBoolean("welcome.show")) {
- final boolean prompt = sketchbookPrompt;
- EventQueue.invokeLater(new Runnable() {
- public void run() {
- try {
- new Welcome(base, prompt);
- } catch (IOException e) {
- Messages.showTrace("Unwelcoming",
- "Please report this error to\n" +
- "https://github.com/processing/processing/issues", e, false);
- }
- }
- });
+ try {
+ new Welcome(base, sketchbookPrompt);
+ } catch (IOException e) {
+ Messages.showTrace("Unwelcoming",
+ "Please report this error to\n" +
+ "https://github.com/processing/processing/issues", e, false);
+ }
}
+ checkDriverBug();
+
} catch (Throwable t) {
// Catch-all to pick up badness during startup.
if (t.getCause() != null) {
@@ -229,7 +266,53 @@ public void run() {
Messages.showTrace("We're off on the wrong foot",
"An error occurred during startup.", t, true);
}
- Messages.log("done creating base..."); //$NON-NLS-1$
+ Messages.log("Done creating Base..."); //$NON-NLS-1$
+ }
+ }
+
+
+ // Remove this code in a couple months [fry 170211]
+ // https://github.com/processing/processing/issues/4853
+ // Or maybe not, if NVIDIA keeps doing this [fry 170423]
+ // https://github.com/processing/processing/issues/4997
+ static private void checkDriverBug() {
+ if (System.getProperty("os.name").contains("Windows 10")) {
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ Process p = Runtime.getRuntime().exec("powershell Get-WmiObject Win32_PnPSignedDriver| select devicename, driverversion | where {$_.devicename -like \\\"*nvidia*\\\"}");
+ BufferedReader reader = PApplet.createReader(p.getInputStream());
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ if (line.contains("3.7849")) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ Messages.showWarning("NVIDIA screwed up",
+ "Due to an NVIDIA bug, you need to update your graphics drivers,\n" +
+ "otherwise you won't be able to run any sketches. Update here:\n" +
+ "http://nvidia.custhelp.com/app/answers/detail/a_id/4378\n" +
+ "or read background about the issue at this link:\n" +
+ "https://github.com/processing/processing/issues/4853");
+ }
+ });
+ } else if (line.contains("3.8165")) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ Messages.showWarning("NVIDIA screwed up again",
+ "Due to an NVIDIA bug, you need to update your graphics drivers,\n" +
+ "otherwise you won't be able to run any sketches. Update here:\n" +
+ "http://nvidia.custhelp.com/app/answers/detail/a_id/4453/\n" +
+ "or read background about the issue at this link:\n" +
+ "https://github.com/processing/processing/issues/4997");
+ }
+ });
+ }
+ }
+ } catch (Exception e) {
+ Messages.loge("Problem checking NVIDIA driver", e);
+ }
+ }
+ }).start();
}
}
@@ -314,6 +397,8 @@ public Base(String[] args) throws Exception {
// Check if any files were passed in on the command line
for (int i = 0; i < args.length; i++) {
+ Messages.logf("Parsing command line... args[%d] = '%s'", i, args[i]);
+
String path = args[i];
// Fix a problem with systems that use a non-ASCII languages. Paths are
// being passed in with 8.3 syntax, which makes the sketch loader code
@@ -323,6 +408,7 @@ public Base(String[] args) throws Exception {
try {
File file = new File(args[i]);
path = file.getCanonicalPath();
+ Messages.logf("Changing %s to canonical %s", i, args[i], path);
} catch (IOException e) {
e.printStackTrace();
}
@@ -334,10 +420,10 @@ public Base(String[] args) throws Exception {
// Create a new empty window (will be replaced with any files to be opened)
if (!opened) {
-// System.out.println("opening a new window");
+ Messages.log("Calling handleNew() to open a new window");
handleNew();
-// } else {
-// System.out.println("something else was opened");
+ } else {
+ Messages.log("No handleNew(), something passed on the command line");
}
// check for updates
@@ -378,12 +464,12 @@ void buildCoreModes() {
*/
void rebuildContribModes() {
if (modeContribs == null) {
- modeContribs = new ArrayList();
+ modeContribs = new ArrayList<>();
}
File modesFolder = getSketchbookModesFolder();
List contribModes = getModeContribs();
- Map known = new HashMap();
+ Map known = new HashMap<>();
for (ModeContribution contrib : contribModes) {
known.put(contrib.getFolder(), contrib);
}
@@ -397,13 +483,18 @@ void rebuildContribModes() {
contribModes.add(new ModeContribution(this, folder, null));
} catch (NoSuchMethodError nsme) {
System.err.println(folder.getName() + " is not compatible with this version of Processing");
+ if (DEBUG) nsme.printStackTrace();
} catch (NoClassDefFoundError ncdfe) {
System.err.println(folder.getName() + " is not compatible with this version of Processing");
+ if (DEBUG) ncdfe.printStackTrace();
} catch (InvocationTargetException ite) {
System.err.println(folder.getName() + " could not be loaded and may not compatible with this version of Processing");
+ if (DEBUG) ite.printStackTrace();
} catch (IgnorableException ig) {
Messages.log(ig.getMessage());
+ if (DEBUG) ig.printStackTrace();
} catch (Throwable e) {
+ System.err.println("Could not load Mode from " + folder);
e.printStackTrace();
}
} else {
@@ -422,7 +513,10 @@ void rebuildContribModes() {
System.out.println("Attempting to load " + modeClass + " with resources at " + modeResourcePath);
ModeContribution mc = ModeContribution.load(this, new File(modeResourcePath), modeClass);
contribModes.add(mc);
- known.remove(mc);
+ File key = getFileForContrib(mc, known);
+ if (key != null) {
+ known.remove(key);
+ }
}
if (known.size() != 0) {
for (ModeContribution mc : known.values()) {
@@ -432,6 +526,17 @@ void rebuildContribModes() {
}
+ static private File getFileForContrib(ModeContribution contrib,
+ Map known) {
+ for (Entry entry : known.entrySet()) {
+ if (entry.getValue() == contrib) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
+
+
/**
* Instantiates and adds new contributed modes to the contribModes list.
* Checks for duplicates so the same mode isn't instantiates twice. Does not
@@ -439,7 +544,7 @@ void rebuildContribModes() {
*/
void rebuildContribExamples() {
if (exampleContribs == null) {
- exampleContribs = new ArrayList();
+ exampleContribs = new ArrayList<>();
}
ExamplesContribution.loadMissing(this);
}
@@ -565,7 +670,7 @@ public void removeToolContrib(ToolContribution tc) {
public void rebuildToolList() {
// Only do this once because the list of internal tools will never change
if (internalTools == null) {
- internalTools = new ArrayList();
+ internalTools = new ArrayList<>();
initInternalTool("processing.app.tools.CreateFont");
initInternalTool("processing.app.tools.ColorSelector");
@@ -630,7 +735,8 @@ public void rebuildToolList() {
protected void initInternalTool(String className) {
try {
Class> toolClass = Class.forName(className);
- final Tool tool = (Tool) toolClass.newInstance();
+ final Tool tool = (Tool)
+ toolClass.getDeclaredConstructor().newInstance();
tool.init(this);
internalTools.add(tool);
@@ -773,7 +879,7 @@ public List getModeContribs() {
public List getModeList() {
- List allModes = new ArrayList();
+ List allModes = new ArrayList<>();
allModes.addAll(Arrays.asList(coreModes));
if (modeContribs != null) {
for (ModeContribution contrib : modeContribs) {
@@ -790,14 +896,14 @@ public List getExampleContribs() {
private List getInstalledContribs() {
- List contributions = new ArrayList();
+ List contributions = new ArrayList<>();
List modeContribs = getModeContribs();
contributions.addAll(modeContribs);
for (ModeContribution modeContrib : modeContribs) {
Mode mode = modeContrib.getMode();
- contributions.addAll(new ArrayList(mode.contribLibraries));
+ contributions.addAll(new ArrayList<>(mode.contribLibraries));
}
// TODO this duplicates code in Editor, but it's not editor-specific
@@ -818,11 +924,11 @@ public byte[] getInstalledContribsInfo() {
String entry = c.getTypeName() + "=" +
PApplet.urlEncode(String.format("name=%s\nurl=%s\nrevision=%d\nversion=%s",
c.getName(), c.getUrl(),
- c.getVersion(), c.getPrettyVersion()));
+ c.getVersion(), c.getBenignVersion()));
entries.append(entry);
}
String joined =
- "id=" + Preferences.get("update.id") + "&" + entries.join("&");
+ "id=" + UpdateCheck.getUpdateID() + "&" + entries.join("&");
// StringBuilder sb = new StringBuilder();
// try {
// // Truly ridiculous attempt to shove everything into a GET request.
@@ -880,14 +986,16 @@ public Mode getNextMode() {
/**
* The call has already checked to make sure this sketch is not modified,
* now change the mode.
+ * @return true if mode is changed.
*/
- public void changeMode(Mode mode) {
- if (activeEditor.getMode() != mode) {
+ public boolean changeMode(Mode mode) {
+ Mode oldMode = activeEditor.getMode();
+ if (oldMode != mode) {
Sketch sketch = activeEditor.getSketch();
nextMode = mode;
if (sketch.isUntitled()) {
- // If no changes have been made, just close and start fresh.
+ // The current sketch is empty, just close and start fresh.
// (Otherwise the editor would lose its 'untitled' status.)
handleClose(activeEditor, true);
handleNew();
@@ -896,20 +1004,30 @@ public void changeMode(Mode mode) {
// If the current editor contains file extensions that the new mode can handle, then
// write a sketch.properties file with the new mode specified, and reopen.
boolean newModeCanHandleCurrentSource = true;
- for (final SketchCode code: sketch.getCode()) {
+ for (final SketchCode code : sketch.getCode()) {
if (!mode.validExtension(code.getExtension())) {
newModeCanHandleCurrentSource = false;
break;
}
}
- if (newModeCanHandleCurrentSource) {
+ if (!newModeCanHandleCurrentSource) {
+ return false;
+ } else {
final File props = new File(sketch.getCodeFolder(), "sketch.properties");
saveModeSettings(props, nextMode);
handleClose(activeEditor, true);
- handleOpen(sketch.getMainFilePath());
+ Editor editor = handleOpen(sketch.getMainFilePath());
+ if (editor == null) {
+ // the Mode change failed (probably code that's out of date)
+ // re-open the sketch using the mode we were in before
+ saveModeSettings(props, oldMode);
+ handleOpen(sketch.getMainFilePath());
+ return false;
+ }
}
}
}
+ return true;
}
@@ -949,7 +1067,7 @@ private static ModeInfo modeInfoFor(final File sketch) {
private Mode promptForMode(final File sketch, final ModeInfo preferredMode) {
final String extension =
sketch.getName().substring(sketch.getName().lastIndexOf('.') + 1);
- final List possibleModes = new ArrayList();
+ final List possibleModes = new ArrayList<>();
for (final Mode mode : getModeList()) {
if (mode.canEdit(sketch)) {
possibleModes.add(mode);
@@ -1077,12 +1195,17 @@ public void handleNew() {
// Make the directory for the new sketch
newbieDir.mkdirs();
+ // Add any template files from the Mode itself
+ File newbieFile = nextMode.addTemplateFiles(newbieDir, newbieName);
+
+ /*
// Make an empty pde file
File newbieFile =
new File(newbieDir, newbieName + "." + nextMode.getDefaultExtension()); //$NON-NLS-1$
if (!newbieFile.createNewFile()) {
throw new IOException(newbieFile + " already exists.");
}
+ */
// Create sketch properties file if it's not the default mode.
if (!nextMode.equals(getDefaultMode())) {
@@ -1269,6 +1392,31 @@ protected Editor handleOpen(String path, boolean untitled,
"Try updating the Mode or contact its author for a new version.", t, false);
}
}
+ if (editors.isEmpty()) {
+ Mode defaultMode = getDefaultMode();
+ if (nextMode == defaultMode) {
+ // unreachable? hopefully?
+ Messages.showError("Editor Problems",
+ "An error occurred while trying to change modes.\n" +
+ "We'll have to quit for now because it's an\n" +
+ "unfortunate bit of indigestion with the default Mode.",
+ null);
+ } else {
+ // Don't leave the user hanging or the PDE locked up
+ // https://github.com/processing/processing/issues/4467
+ if (untitled) {
+ nextMode = defaultMode;
+ handleNew();
+ return null; // ignored by any caller
+
+ } else {
+ // This null response will be kicked back to changeMode(),
+ // signaling it to re-open the sketch in the default Mode.
+ return null;
+ }
+ }
+ }
+
/*
if (editors.isEmpty()) {
// if the bad mode is the default mode, don't go into an infinite loop
@@ -1410,6 +1558,9 @@ public boolean handleQuit() {
// Save out the current prefs state
Preferences.save();
+ // Finished with this guy
+ Console.shutdown();
+
if (!Platform.isMacOS()) {
// If this was fired from the menu or an AppleEvent (the Finder),
// then Mac OS X will send the terminate signal itself.
@@ -1548,6 +1699,15 @@ protected boolean addSketches(JMenu menu, File folder,
return false; // let's not go there
}
+ if (folder.getName().equals("sdk")) {
+ // This could be Android's SDK folder. Let's double check:
+ File suspectSDKPath = new File(folder.getParent(), folder.getName());
+ File expectedSDKPath = new File(sketchbookFolder, "android" + File.separator + "sdk");
+ if (expectedSDKPath.getAbsolutePath().equals(suspectSDKPath.getAbsolutePath())) {
+ return false; // Most likely the SDK folder, skip it
+ }
+ }
+
String[] list = folder.list();
// If a bad folder or unreadable or whatever, this will come back null
if (list == null) {
@@ -1759,8 +1919,10 @@ static public File getSettingsFolder() {
}
}
} catch (Exception e) {
- Messages.showError("Problem getting the settings folder",
- "Error getting the Processing the settings folder.", e);
+ Messages.showTrace("An rare and unknowable thing happened",
+ "Could not get the settings folder. Please report:\n" +
+ "http://github.com/processing/processing/issues/new",
+ e, true);
}
return settingsFolder;
}
@@ -1827,6 +1989,7 @@ static protected void makeSketchbookSubfolders() {
getSketchbookToolsFolder().mkdirs();
getSketchbookModesFolder().mkdirs();
getSketchbookExamplesFolder().mkdirs();
+ getSketchbookTemplatesFolder().mkdirs();
}
@@ -1855,6 +2018,11 @@ static public File getSketchbookExamplesFolder() {
}
+ static public File getSketchbookTemplatesFolder() {
+ return new File(sketchbookFolder, "templates");
+ }
+
+
static protected File getDefaultSketchbookFolder() {
File sketchbookFolder = null;
try {
diff --git a/app/src/processing/app/BaseSplash.java b/app/src/processing/app/BaseSplash.java
index 39848de5ea..5dc125f6a1 100644
--- a/app/src/processing/app/BaseSplash.java
+++ b/app/src/processing/app/BaseSplash.java
@@ -9,7 +9,7 @@
public class BaseSplash {
static public void main(String[] args) {
try {
- final boolean hidpi = Toolkit.highResDisplay();
+ final boolean hidpi = Toolkit.highResImages();
final String filename = "lib/about-" + (hidpi ? 2 : 1) + "x.png";
File splashFile = Platform.getContentFile(filename);
SplashWindow.splash(splashFile.toURI().toURL(), hidpi);
diff --git a/app/src/processing/app/Console.java b/app/src/processing/app/Console.java
new file mode 100644
index 0000000000..73b652f337
--- /dev/null
+++ b/app/src/processing/app/Console.java
@@ -0,0 +1,261 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2012-16 The Processing Foundation
+ Copyright (c) 2004-12 Ben Fry and Casey Reas
+ Copyright (c) 2001-04 Massachusetts Institute of Technology
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.app;
+
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+
+/**
+ * Non-GUI handling of System.out and System.err redirection.
+ *
+ * Be careful when debugging this class, because if it's throwing exceptions,
+ * don't take over System.err, and debug while watching just System.out
+ * or just call println() or whatever directly to systemOut or systemErr.
+ *
+ * Also note that encodings will not work properly when run from Eclipse. This
+ * means that if you use non-ASCII characters in a println() or some such,
+ * the characters won't print properly in the Processing and/or Eclipse console.
+ * It seems that Eclipse's console-grabbing and that of Processing don't
+ * get along with one another. Use 'ant run' to work on encoding-related issues.
+ */
+public class Console {
+ // Single static instance shared because there's only one real System.out.
+ // Within the input handlers, the currentConsole variable will be used to
+ // echo things to the correct location.
+
+ /** The original System.out */
+ static PrintStream systemOut;
+ /** The original System.err */
+ static PrintStream systemErr;
+
+ /** Our replacement System.out */
+ static PrintStream consoleOut;
+ /** Our replacement System.err */
+ static PrintStream consoleErr;
+
+ /** All stdout also written to a file */
+ static OutputStream stdoutFile;
+ /** All stderr also written to a file */
+ static OutputStream stderrFile;
+
+ /** stdout listener for the currently active Editor */
+ static OutputStream editorOut;
+ /** stderr listener for the currently active Editor */
+ static OutputStream editorErr;
+
+
+ static public void startup() {
+ if (systemOut != null) {
+ // TODO fix this dreadful style choice in how the Console is initialized
+ // (This is not good code.. startup() should gracefully deal with this.
+ // It's just a low priority relative to the likelihood of trouble.)
+ new Exception("startup() called more than once").printStackTrace(systemErr);
+ return;
+ }
+ systemOut = System.out;
+ systemErr = System.err;
+
+ // placing everything inside a try block because this can be a dangerous
+ // time for the lights to blink out and crash for and obscure reason.
+ try {
+ SimpleDateFormat formatter = new SimpleDateFormat("yyMMdd_HHmmss");
+ // Moving away from a random string in 0256 (and adding hms) because
+ // the random digits looked like times anyway, causing confusion.
+ //String randy = String.format("%04d", (int) (1000 * Math.random()));
+ //final String stamp = formatter.format(new Date()) + "_" + randy;
+ final String stamp = formatter.format(new Date());
+
+ File consoleDir = Base.getSettingsFile("console");
+ if (consoleDir.exists()) {
+ // clear old debug files
+ File[] stdFiles = consoleDir.listFiles(new FileFilter() {
+ final String todayPrefix = stamp.substring(0, 4);
+
+ public boolean accept(File file) {
+ if (!file.isDirectory()) {
+ String name = file.getName();
+ if (name.endsWith(".err") || name.endsWith(".out")) {
+ // don't delete any of today's debug messages
+ return !name.startsWith(todayPrefix);
+ }
+ }
+ return false;
+ }
+ });
+ // Remove any files that aren't from today
+ for (File file : stdFiles) {
+ file.delete();
+ }
+ } else {
+ consoleDir.mkdirs();
+ consoleDir.setWritable(true, false);
+ }
+
+ File outFile = new File(consoleDir, stamp + ".out");
+ outFile.setWritable(true, false);
+ stdoutFile = new FileOutputStream(outFile);
+ File errFile = new File(consoleDir, stamp + ".err");
+ errFile.setWritable(true, false);
+ stderrFile = new FileOutputStream(errFile);
+
+ consoleOut = new PrintStream(new ConsoleStream(false));
+ consoleErr = new PrintStream(new ConsoleStream(true));
+
+ System.setOut(consoleOut);
+ System.setErr(consoleErr);
+
+ } catch (Exception e) {
+ stdoutFile = null;
+ stderrFile = null;
+
+ consoleOut = null;
+ consoleErr = null;
+
+ System.setOut(systemOut);
+ System.setErr(systemErr);
+
+ e.printStackTrace();
+ }
+ }
+
+
+ static public void setEditor(OutputStream out, OutputStream err) {
+ editorOut = out;
+ editorErr = err;
+ }
+
+
+ static public void systemOut(String what) {
+ systemOut.println(what);
+ }
+
+
+ static public void systemErr(String what) {
+ systemErr.println(what);
+ }
+
+
+ /**
+ * Close the streams so that the temporary files can be deleted.
+ *
+ * File.deleteOnExit() cannot be used because the stdout and stderr
+ * files are inside a folder, and have to be deleted before the
+ * folder itself is deleted, which can't be guaranteed when using
+ * the deleteOnExit() method.
+ */
+ static public void shutdown() {
+ // replace original streams to remove references to console's streams
+ System.setOut(systemOut);
+ System.setErr(systemErr);
+
+ cleanup(consoleOut);
+ cleanup(consoleErr);
+
+ // also have to close the original FileOutputStream
+ // otherwise it won't be shut down completely
+ cleanup(stdoutFile);
+ cleanup(stderrFile);
+ }
+
+
+ static private void cleanup(OutputStream output) {
+ try {
+ if (output != null) {
+ output.flush();
+ output.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ static class ConsoleStream extends OutputStream {
+ boolean err; // whether stderr or stdout
+ byte single[] = new byte[1];
+
+ public ConsoleStream(boolean err) {
+ this.err = err;
+ }
+
+ public void close() { }
+
+ public void flush() { }
+
+ public void write(byte b[]) { // appears never to be used
+ write(b, 0, b.length);
+ }
+
+ public void write(byte b[], int offset, int length) {
+ // First write to the original stdout/stderr
+ if (err) {
+ systemErr.write(b, offset, length);
+ } else {
+ systemOut.write(b, offset, length);
+ }
+
+ // Write to the files that are storing this information
+ writeFile(b, offset, length);
+
+ // Write to the console of the current Editor, if any
+ try {
+ if (err) {
+ if (editorErr != null) {
+ editorErr.write(b, offset, length);
+ }
+ } else {
+ if (editorOut != null) {
+ editorOut.write(b, offset, length);
+ }
+ }
+ } catch (IOException e) {
+ // Avoid this function being called in a recursive, infinite loop
+ e.printStackTrace(systemErr);
+ }
+ }
+
+ public void writeFile(byte b[], int offset, int length) {
+ final OutputStream echo = err ? stderrFile : stdoutFile;
+ if (echo != null) {
+ try {
+ echo.write(b, offset, length);
+ echo.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void write(int b) {
+ single[0] = (byte) b;
+ write(single, 0, 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/Language.java b/app/src/processing/app/Language.java
index 23a35349a1..06be82fc5a 100644
--- a/app/src/processing/app/Language.java
+++ b/app/src/processing/app/Language.java
@@ -87,18 +87,22 @@ private Language() {
static private String[] listSupported() {
// List of languages in alphabetical order. (Add yours here.)
- // Also remember to add it to the corresponding build/build.xml rule.
+ // Also remember to add it to build/shared/lib/languages/languages.txt.
final String[] SUPPORTED = {
+ "ar", // Arabic
"de", // German, Deutsch
"en", // English
"el", // Greek
"es", // Spanish
"fr", // French, Français
+ "it", // Italiano, Italian
"ja", // Japanese
"ko", // Korean
"nl", // Dutch, Nederlands
"pt", // Portuguese
+ "ru", // Russian
"tr", // Turkish
+ "uk", // Ukrainian
"zh" // Chinese
};
return SUPPORTED;
@@ -145,6 +149,7 @@ static private String loadLanguage() {
static public void saveLanguage(String language) {
try {
Util.saveFile(language, prefFile);
+ prefFile.setWritable(true, false);
} catch (Exception e) {
e.printStackTrace();
}
@@ -235,6 +240,18 @@ static public String getLanguage() {
}
+ /**
+ * Is this a CJK language where Input Method support is suggested/required?
+ * @return true if the user is running in Japanese, Korean, or Chinese
+ */
+ static public boolean useInputMethod() {
+ final String language = getLanguage();
+ return (language.equals("ja") ||
+ language.equals("ko") ||
+ language.equals("zh"));
+ }
+
+
// /** Set new language (called by Preferences) */
// static public void setLanguage(String language) {
// this.language = language;
@@ -315,6 +332,9 @@ static class LanguageBundle {
void read(File additions) {
String[] lines = PApplet.loadStrings(additions);
+ if (lines == null) {
+ throw new NullPointerException("File not found:\n" + additions.getAbsolutePath());
+ }
//for (String line : lines) {
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
diff --git a/app/src/processing/app/Library.java b/app/src/processing/app/Library.java
index d8692b0602..34bc6360e1 100644
--- a/app/src/processing/app/Library.java
+++ b/app/src/processing/app/Library.java
@@ -71,6 +71,7 @@ public boolean accept(File dir, String name) {
if (name.equals("linux32")) return false;
if (name.equals("linux64")) return false;
if (name.equals("linux-armv6hf")) return false;
+ if (name.equals("linux-arm64")) return false;
if (name.equals("android")) return false;
}
return true;
@@ -115,11 +116,19 @@ private Library(File folder, String groupName) {
examplesFolder = new File(folder, "examples");
referenceFile = new File(folder, "reference/index.html");
+ handle();
+ }
+
+
+ /**
+ * Handles all the Java-specific parsing for library handling.
+ */
+ protected void handle() {
File exportSettings = new File(libraryFolder, "export.txt");
StringDict exportTable = exportSettings.exists() ?
Util.readSettings(exportSettings) : new StringDict();
- exportList = new HashMap();
+ exportList = new HashMap<>();
// get the list of files just in the library root
String[] baseList = libraryFolder.list(standardFilter);
@@ -165,6 +174,12 @@ private Library(File folder, String groupName) {
nativeLibraryFolder = hostLibrary;
}
}
+ if (hostPlatform.equals("linux") && System.getProperty("os.arch").equals("aarch64")) {
+ hostLibrary = new File(libraryFolder, "linux-arm64");
+ if (hostLibrary.exists()) {
+ nativeLibraryFolder = hostLibrary;
+ }
+ }
// save that folder for later use
nativeLibraryPath = nativeLibraryFolder.getAbsolutePath();
@@ -174,7 +189,8 @@ private Library(File folder, String groupName) {
String platformName = platformNames[i];
String platformName32 = platformName + "32";
String platformName64 = platformName + "64";
- String platformNameArmv6hh = platformName + "-armv6hf";
+ String platformNameArmv6hf = platformName + "-armv6hf";
+ String platformNameArm64 = platformName + "-arm64";
// First check for things like 'application.macosx=' or 'application.windows32' in the export.txt file.
// These will override anything in the platform-specific subfolders.
@@ -186,6 +202,8 @@ private Library(File folder, String groupName) {
String[] platformList64 = platform64 == null ? null : PApplet.splitTokens(platform64, ", ");
String platformArmv6hf = exportTable.get("application." + platformName + "-armv6hf");
String[] platformListArmv6hf = platformArmv6hf == null ? null : PApplet.splitTokens(platformArmv6hf, ", ");
+ String platformArm64 = exportTable.get("application." + platformName + "-arm64");
+ String[] platformListArm64 = platformArm64 == null ? null : PApplet.splitTokens(platformArm64, ", ");
// If nothing specified in the export.txt entries, look for the platform-specific folders.
if (platformAll == null) {
@@ -198,16 +216,19 @@ private Library(File folder, String groupName) {
platformList64 = listPlatformEntries(libraryFolder, platformName64, baseList);
}
if (platformListArmv6hf == null) {
- platformListArmv6hf = listPlatformEntries(libraryFolder, platformNameArmv6hh, baseList);
+ platformListArmv6hf = listPlatformEntries(libraryFolder, platformNameArmv6hf, baseList);
+ }
+ if (platformListArm64 == null) {
+ platformListArm64 = listPlatformEntries(libraryFolder, platformNameArm64, baseList);
}
- if (platformList32 != null || platformList64 != null || platformListArmv6hf != null) {
+ if (platformList32 != null || platformList64 != null || platformListArmv6hf != null || platformListArm64 != null) {
multipleArch[i] = true;
}
// if there aren't any relevant imports specified or in their own folders,
// then use the baseList (root of the library folder) as the default.
- if (platformList == null && platformList32 == null && platformList64 == null && platformListArmv6hf == null) {
+ if (platformList == null && platformList32 == null && platformList64 == null && platformListArmv6hf == null && platformListArm64 == null) {
exportList.put(platformName, baseList);
} else {
@@ -223,7 +244,10 @@ private Library(File folder, String groupName) {
exportList.put(platformName64, platformList64);
}
if (platformListArmv6hf != null) {
- exportList.put(platformNameArmv6hh, platformListArmv6hf);
+ exportList.put(platformNameArmv6hf, platformListArmv6hf);
+ }
+ if (platformListArm64 != null) {
+ exportList.put(platformNameArm64, platformListArm64);
}
}
}
@@ -258,7 +282,7 @@ static String[] listPlatformEntries(File libraryFolder, String folderName, Strin
}
- static protected HashMap packageWarningMap = new HashMap();
+ static protected HashMap packageWarningMap = new HashMap<>();
/**
* Add the packages provided by this library to the master list that maps
@@ -274,7 +298,7 @@ public void addPackageList(Map> importToLibraryTable) {
// Library library = importToLibraryTable.get(pkg);
List libraries = importToLibraryTable.get(pkg);
if (libraries == null) {
- libraries = new ArrayList();
+ libraries = new ArrayList<>();
importToLibraryTable.put(pkg, libraries);
} else {
if (Base.DEBUG) {
@@ -338,10 +362,13 @@ public String getClassPath() {
cp.append(File.pathSeparatorChar);
cp.append(new File(libraryFolder, jar).getAbsolutePath());
}
- jarHeads = new File(nativeLibraryPath).list(jarFilter);
- for (String jar : jarHeads) {
- cp.append(File.pathSeparatorChar);
- cp.append(new File(nativeLibraryPath, jar).getAbsolutePath());
+ File nativeLibraryFolder = new File(nativeLibraryPath);
+ if (!libraryFolder.equals(nativeLibraryFolder)) {
+ jarHeads = new File(nativeLibraryPath).list(jarFilter);
+ for (String jar : jarHeads) {
+ cp.append(File.pathSeparatorChar);
+ cp.append(new File(nativeLibraryPath, jar).getAbsolutePath());
+ }
}
//cp.setLength(cp.length() - 1); // remove the last separator
return cp.toString();
@@ -401,6 +428,9 @@ public String[] getApplicationExportList(int platform, String variant) {
} else if (variant.equals("armv6hf")) {
String[] pieces = exportList.get(platformName + "-armv6hf");
if (pieces != null) return pieces;
+ } else if (variant.equals("arm64")) {
+ String[] pieces = exportList.get(platformName + "-arm64");
+ if (pieces != null) return pieces;
}
return exportList.get(platformName);
}
@@ -431,12 +461,7 @@ public boolean supportsArch(int platform, String variant) {
static public boolean hasMultipleArch(int platform, List libraries) {
- for (Library library : libraries) {
- if (library.hasMultipleArch(platform)) {
- return true;
- }
- }
- return false;
+ return libraries.stream().anyMatch(library -> library.hasMultipleArch(platform));
}
@@ -445,24 +470,26 @@ static public boolean hasMultipleArch(int platform, List libraries) {
static protected FilenameFilter junkFolderFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
- // skip .DS_Store files, .svn folders, etc
+ // skip .DS_Store files, .svn and .git folders, etc
if (name.charAt(0) == '.') return false;
- if (name.equals("CVS")) return false;
- return (new File(dir, name).isDirectory());
+ if (name.equals("CVS")) return false; // old skool
+ return new File(dir, name).isDirectory();
}
};
static public List discover(File folder) {
- List libraries = new ArrayList();
+ List libraries = new ArrayList<>();
String[] folderNames = folder.list(junkFolderFilter);
- // if a bad folder or something like that, this might come back null
+ // if a bad folder or unreadable, folderNames might be null
if (folderNames != null) {
// alphabetize list, since it's not always alpha order
// replaced hella slow bubble sort with this feller for 0093
Arrays.sort(folderNames, String.CASE_INSENSITIVE_ORDER);
+ // TODO some weirdness because ContributionType.LIBRARY.isCandidate()
+ // handles some, but not all, of this [fry 200116]
for (String potentialName : folderNames) {
File baseFolder = new File(folder, potentialName);
File libraryFolder = new File(baseFolder, "library");
@@ -475,11 +502,10 @@ static public List discover(File folder) {
libraries.add(baseFolder);
} else {
- String mess = "The library \""
- + potentialName
- + "\" 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)";
+ final String mess =
+ "The library \"" + potentialName + "\" 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)";
Messages.showMessage("Ignoring bad library name", mess);
continue;
}
@@ -491,14 +517,15 @@ static public List discover(File folder) {
static public List list(File folder) {
- List libraries = new ArrayList();
- List librariesFolders = new ArrayList();
+ List libraries = new ArrayList<>();
+ List librariesFolders = new ArrayList<>();
librariesFolders.addAll(discover(folder));
for (File baseFolder : librariesFolders) {
libraries.add(new Library(baseFolder));
}
+ /*
// Support libraries inside of one level of subfolders? I believe this was
// the compromise for supporting library groups, but probably a bad idea
// because it's not compatible with the Manager.
@@ -515,6 +542,7 @@ static public List list(File folder) {
}
}
}
+ */
return libraries;
}
diff --git a/app/src/processing/app/Messages.java b/app/src/processing/app/Messages.java
index 07e1a62472..d8c78d43f1 100644
--- a/app/src/processing/app/Messages.java
+++ b/app/src/processing/app/Messages.java
@@ -156,7 +156,7 @@ static public void showError(String title, String message, Throwable e) {
* Testing a new warning window that includes the stack trace.
*/
static public void showTrace(String title, String message,
- Throwable t, boolean fatal) {
+ Throwable t, boolean fatal) {
if (title == null) title = fatal ? "Error" : "Warning";
if (Base.isCommandLine()) {
@@ -265,6 +265,34 @@ static public int showYesNoQuestion(Frame editor, String title,
"
" + secondary, title,
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
+ } else {
+ int result = showCustomQuestion(editor, title, primary, secondary,
+ 0, "Yes", "No");
+ if (result == 0) {
+ return JOptionPane.YES_OPTION;
+ } else if (result == 1) {
+ return JOptionPane.NO_OPTION;
+ } else {
+ return JOptionPane.CLOSED_OPTION;
+ }
+ }
+ }
+
+
+ /**
+ * @param highlight A valid array index for options[] that specifies the
+ * default (i.e. safe) choice.
+ * @return The (zero-based) index of the selected value, -1 otherwise.
+ */
+ static public int showCustomQuestion(Frame editor, String title,
+ String primary, String secondary,
+ int highlight, String... options) {
+ Object result;
+ if (!Platform.isMacOS()) {
+ return JOptionPane.showOptionDialog(editor,
+ "" + primary + "
" + secondary, title,
+ JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null,
+ options, options[highlight]);
} else {
// Pane formatting adapted from the Quaqua guide
// http://www.randelshofer.ch/quaqua/guide/joptionpane.html
@@ -275,29 +303,23 @@ static public int showYesNoQuestion(Frame editor, String title,
"p { font: 11pt \"Lucida Grande\"; margin-top: 8px; width: 300px }"+
" " +
"" + primary + "" +
- "" + secondary + "
",
+ "" + secondary, // + "
",
JOptionPane.QUESTION_MESSAGE);
- String[] options = new String[] {
- "Yes", "No"
- };
pane.setOptions(options);
// highlight the safest option ala apple hig
- pane.setInitialValue(options[0]);
+ pane.setInitialValue(options[highlight]);
JDialog dialog = pane.createDialog(editor, null);
dialog.setVisible(true);
- Object result = pane.getValue();
- if (result == options[0]) {
- return JOptionPane.YES_OPTION;
- } else if (result == options[1]) {
- return JOptionPane.NO_OPTION;
- } else {
- return JOptionPane.CLOSED_OPTION;
- }
+ result = pane.getValue();
}
+ for (int i = 0; i < options.length; i++) {
+ if (result != null && result.equals(options[i])) return i;
+ }
+ return -1;
}
@@ -327,7 +349,9 @@ static public void logf(String message, Object... args) {
static public void loge(String message, Throwable e) {
if (Base.DEBUG) {
- System.err.println(message);
+ if (message != null) {
+ System.err.println(message);
+ }
e.printStackTrace();
}
}
@@ -335,7 +359,7 @@ static public void loge(String message, Throwable e) {
static public void loge(String message) {
if (Base.DEBUG) {
- System.out.println(message);
+ System.err.println(message);
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/processing/app/Mode.java b/app/src/processing/app/Mode.java
index 06b7bb057a..9c87f7232b 100644
--- a/app/src/processing/app/Mode.java
+++ b/app/src/processing/app/Mode.java
@@ -53,8 +53,7 @@ public abstract class Mode {
protected File folder;
protected TokenMarker tokenMarker;
- protected Map keywordToReference =
- new HashMap();
+ protected Map keywordToReference = new HashMap<>();
protected Settings theme;
// protected Formatter formatter;
@@ -166,7 +165,13 @@ protected void loadKeywords(File keywordFile,
if (htmlFilename.endsWith("_")) {
keyword += "_";
}
- keywordToReference.put(keyword, htmlFilename);
+ // Allow the bare size() command to override the lookup
+ // for StringList.size() and others, but not vice-versa.
+ // https://github.com/processing/processing/issues/4224
+ boolean seen = keywordToReference.containsKey(keyword);
+ if (!seen || (seen && keyword.equals(htmlFilename))) {
+ keywordToReference.put(keyword, htmlFilename);
+ }
}
}
}
@@ -206,11 +211,17 @@ public void setupGUI() {
theme.load(modeTheme);
}
+ // Against my better judgment, adding the ability to override themes
+ // https://github.com/processing/processing/issues/5445
+ File sketchbookTheme =
+ new File(Base.getSketchbookFolder(), "theme.txt");
+ if (sketchbookTheme.exists()) {
+ theme.load(sketchbookTheme);
+ }
+
// other things that have to be set explicitly for the defaults
theme.setColor("run.window.bgcolor", SystemColor.control);
-// loadBackground();
-
} catch (IOException e) {
Messages.showError("Problem loading theme.txt",
"Could not load theme.txt, please re-install Processing", e);
@@ -228,6 +239,71 @@ public InputStream getContentStream(String path) throws FileNotFoundException {
}
+ /**
+ * Add files to a folder to create an empty sketch. This can be overridden
+ * to add template files to a sketch for Modes that need them.
+ *
+ * @param sketchFolder the directory where the new sketch should live
+ * @param sketchName the name of the new sketch
+ * @return the main file for the sketch to be opened via handleOpen()
+ * @throws IOException if the file somehow already exists
+ */
+ public File addTemplateFiles(File sketchFolder,
+ String sketchName) throws IOException {
+ // Make an empty .pde file
+ File newbieFile =
+ new File(sketchFolder, sketchName + "." + getDefaultExtension());
+
+ try {
+ // First see if the user has overridden the default template
+ File templateFolder = checkSketchbookTemplate();
+
+ // Next see if the Mode has its own template
+ if (templateFolder == null) {
+ templateFolder = getTemplateFolder();
+ }
+ if (templateFolder.exists()) {
+ Util.copyDir(templateFolder, sketchFolder);
+ File templateFile =
+ new File(sketchFolder, "sketch." + getDefaultExtension());
+ if (!templateFile.renameTo(newbieFile)) {
+ System.err.println("Error while assigning the sketch template.");
+ }
+ } else {
+ if (!newbieFile.createNewFile()) {
+ System.err.println(newbieFile + " already exists.");
+ }
+ }
+ } catch (Exception e) {
+ // just spew out this error and try to recover below
+ e.printStackTrace();
+ }
+ return newbieFile;
+ }
+
+
+ /**
+ * See if the user has their own template for this Mode. If the default
+ * extension is "pde", this will look for a file called sketch.pde to use
+ * as the template for all sketches.
+ */
+ protected File checkSketchbookTemplate() {
+ File user = new File(Base.getSketchbookTemplatesFolder(), getTitle());
+ if (user.exists()) {
+ File template = new File(user, "sketch." + getDefaultExtension());
+ if (template.exists() && template.canRead()) {
+ return user;
+ }
+ }
+ return null;
+ }
+
+
+ public File getTemplateFolder() {
+ return getContentFile("template");
+ }
+
+
/**
* Return the pretty/printable/menu name for this mode. This is separate from
* the single word name of the folder that contains this mode. It could even
@@ -282,11 +358,11 @@ public File getReferenceFolder() {
public void rebuildLibraryList() {
//new Exception("Rebuilding library list").printStackTrace(System.out);
// reset the table mapping imports to libraries
- importToLibraryTable = new HashMap>();
+ Map> newTable = new HashMap<>();
Library core = getCoreLibrary();
if (core != null) {
- core.addPackageList(importToLibraryTable);
+ core.addPackageList(newTable);
}
coreLibraries = Library.list(librariesFolder);
@@ -304,24 +380,19 @@ public void rebuildLibraryList() {
coreLibraries.addAll(foundationLibraries);
contribLibraries.removeAll(foundationLibraries);
- /*
- File sketchbookLibs = Base.getSketchbookLibrariesFolder();
- File videoFolder = new File(sketchbookLibs, "video");
- if (videoFolder.exists()) {
- coreLibraries.add(new Library(videoFolder));
- }
- File soundFolder = new File(sketchbookLibs, "sound");
- if (soundFolder.exists()) {
- coreLibraries.add(new Library(soundFolder));
- }
- */
-
for (Library lib : coreLibraries) {
- lib.addPackageList(importToLibraryTable);
+ lib.addPackageList(newTable);
}
for (Library lib : contribLibraries) {
- lib.addPackageList(importToLibraryTable);
+ lib.addPackageList(newTable);
+ }
+
+ // Make this Map thread-safe
+ importToLibraryTable = Collections.unmodifiableMap(newTable);
+
+ if (base != null) {
+ base.getEditors().forEach(Editor::librariesChanged);
}
}
@@ -534,7 +605,7 @@ public void actionPerformed(ActionEvent e) {
contrib.setEnabled(false);
importMenu.add(contrib);
- HashMap subfolders = new HashMap();
+ HashMap subfolders = new HashMap<>();
for (Library library : contribLibraries) {
JMenuItem item = new JMenuItem(library.getName());
@@ -560,6 +631,18 @@ public void actionPerformed(ActionEvent e) {
}
+ /**
+ * Require examples to explicitly state that they're compatible with this
+ * Mode before they're included. Helpful for Modes like p5js or Python
+ * where the .java examples cannot be used.
+ * @since 3.2
+ * @return true if an examples package must list this Mode's identifier
+ */
+ public boolean requireExampleCompatibility() {
+ return false;
+ }
+
+
/**
* Override this to control the order of the first set of example folders
* and how they appear in the examples window.
@@ -580,6 +663,7 @@ public void rebuildExamplesFrame() {
if (visible) {
bounds = examplesFrame.getBounds();
examplesFrame.setVisible(false);
+ examplesFrame.dispose();
}
examplesFrame = null;
if (visible) {
@@ -615,11 +699,19 @@ public DefaultMutableTreeNode buildSketchbookTree() {
/** Sketchbook has changed, update it on next viewing. */
public void rebuildSketchbookFrame() {
- boolean wasVisible =
- (sketchbookFrame == null) ? false : sketchbookFrame.isVisible();
- sketchbookFrame = null; // Force a rebuild
- if (wasVisible) {
- showSketchbookFrame();
+ if (sketchbookFrame != null) {
+ boolean visible = sketchbookFrame.isVisible();
+ Rectangle bounds = null;
+ if (visible) {
+ bounds = sketchbookFrame.getBounds();
+ sketchbookFrame.setVisible(false);
+ sketchbookFrame.dispose();
+ }
+ sketchbookFrame = null;
+ if (visible) {
+ showSketchbookFrame();
+ sketchbookFrame.setBounds(bounds);
+ }
}
}
@@ -668,7 +760,7 @@ public Image loadImage(String filename) {
public Image loadImageX(String filename) {
- final int res = Toolkit.highResDisplay() ? 2 : 1;
+ final int res = Toolkit.highResImages() ? 2 : 1;
return loadImage(filename + "-" + res + "x.png");
}
@@ -692,16 +784,24 @@ public String lookupReference(String keyword) {
}
- //public TokenMarker getTokenMarker() throws IOException {
- // File keywordsFile = new File(folder, "keywords.txt");
- // return new PdeKeywords(keywordsFile);
- //}
+ /**
+ * Specialized version of getTokenMarker() that can be overridden to
+ * provide different TokenMarker objects for different file types.
+ * @since 3.2
+ * @param code the code for which we need a TokenMarker
+ */
+ public TokenMarker getTokenMarker(SketchCode code) {
+ return getTokenMarker();
+ }
+
+
public TokenMarker getTokenMarker() {
return tokenMarker;
}
+
protected TokenMarker createTokenMarker() {
- return new PdeKeywords();
+ return new PdeTokenMarker();
}
@@ -924,9 +1024,14 @@ public void prepareExportFolder(File targetFolder) {
if (targetFolder != null) {
// Nuke the old applet/application folder because it can cause trouble
if (Preferences.getBoolean("export.delete_target_folder")) {
-// System.out.println("temporarily skipping deletion of " + targetFolder);
- Util.removeDir(targetFolder);
- // targetFolder.renameTo(dest);
+ if (targetFolder.exists()) {
+ try {
+ Platform.deleteFile(targetFolder);
+ } catch (IOException e) {
+ // ignore errors/continue; likely to be ok
+ e.printStackTrace();
+ }
+ }
}
// Create a fresh output folder (needed before preproc is run next)
targetFolder.mkdirs();
diff --git a/app/src/processing/app/Platform.java b/app/src/processing/app/Platform.java
index f492daad76..16a31bf80e 100644
--- a/app/src/processing/app/Platform.java
+++ b/app/src/processing/app/Platform.java
@@ -26,6 +26,8 @@
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
import java.util.HashMap;
import java.util.Map;
@@ -39,14 +41,14 @@
public class Platform {
static DefaultPlatform inst;
- static Map platformNames = new HashMap();
+ static Map platformNames = new HashMap<>();
static {
platformNames.put(PConstants.WINDOWS, "windows"); //$NON-NLS-1$
platformNames.put(PConstants.MACOSX, "macosx"); //$NON-NLS-1$
platformNames.put(PConstants.LINUX, "linux"); //$NON-NLS-1$
}
- static Map platformIndices = new HashMap();
+ static Map platformIndices = new HashMap<>();
static {
platformIndices.put("windows", PConstants.WINDOWS); //$NON-NLS-1$
platformIndices.put("macosx", PConstants.MACOSX); //$NON-NLS-1$
@@ -84,7 +86,7 @@ static public void init() {
} else if (Platform.isLinux()) {
platformClass = Class.forName("processing.app.platform.LinuxPlatform"); //$NON-NLS-1$
}
- inst = (DefaultPlatform) platformClass.newInstance();
+ inst = (DefaultPlatform) platformClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
Messages.showError("Problem Setting the Platform",
"An unknown error occurred while trying to load\n" +
@@ -148,7 +150,7 @@ static public void openURL(String url) {
} catch (Exception e) {
Messages.showWarning("Problem Opening URL",
- "Could not open the URL\n" + url, e);
+ "Could not open the URL\n" + url, e);
}
}
@@ -193,6 +195,7 @@ static public int getNativeBits() {
* Return the value of the os.arch property
*/
static public String getNativeArch() {
+ // This will return "arm" for 32-bit ARM, "aarch64" for 64-bit ARM (both on Linux)
return System.getProperty("os.arch");
}
@@ -209,8 +212,12 @@ static public String getVariant() {
static public String getVariant(int platform, String arch, int bits) {
if (platform == PConstants.LINUX &&
bits == 32 && "arm".equals(Platform.getNativeArch())) {
- return "armv6hf"; // assume armv6hf for now
+ return "armv6hf"; // assume armv6hf
+ } else if (platform == PConstants.LINUX &&
+ bits == 64 && "aarch64".equals(Platform.getNativeArch())) {
+ return "arm64";
}
+
return Integer.toString(bits); // 32 or 64
}
@@ -281,18 +288,25 @@ static public boolean isLinux() {
static public File getContentFile(String name) {
if (processingRoot == null) {
// Get the path to the .jar file that contains Base.class
- String path = Base.class.getProtectionDomain().getCodeSource().getLocation().getPath();
- // Path may have URL encoding, so remove it
- String decodedPath = PApplet.urlDecode(path);
+ URL pathURL =
+ Base.class.getProtectionDomain().getCodeSource().getLocation();
+ // Decode URL
+ String decodedPath;
+ try {
+ decodedPath = pathURL.toURI().getSchemeSpecificPart();
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ return null;
+ }
if (decodedPath.contains("/app/bin")) { // This means we're in Eclipse
+ final File build = new File(decodedPath, "../../build").getAbsoluteFile();
if (Platform.isMacOS()) {
- processingRoot =
- new File(path, "../../build/macosx/work/Processing.app/Contents/Java");
+ processingRoot = new File(build, "macosx/work/Processing.app/Contents/Java");
} else if (Platform.isWindows()) {
- processingRoot = new File(path, "../../build/windows/work");
+ processingRoot = new File(build, "windows/work");
} else if (Platform.isLinux()) {
- processingRoot = new File(path, "../../build/linux/work");
+ processingRoot = new File(build, "linux/work");
}
} else {
// The .jar file will be in the lib folder
@@ -311,7 +325,7 @@ static public File getContentFile(String name) {
System.err.println("Could not find lib folder via " +
jarFolder.getAbsolutePath() +
", switching to user.dir");
- processingRoot = new File(System.getProperty("user.dir"));
+ processingRoot = new File(""); // resolves to "user.dir"
}
}
}
@@ -390,4 +404,12 @@ static public String getenv(String variable) {
static public int unsetenv(String variable) {
return inst.unsetenv(variable);
}
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ static public int getSystemDPI() {
+ return inst.getSystemDPI();
+ }
}
\ No newline at end of file
diff --git a/app/src/processing/app/Preferences.java b/app/src/processing/app/Preferences.java
index a963d22b60..1b05083e19 100644
--- a/app/src/processing/app/Preferences.java
+++ b/app/src/processing/app/Preferences.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2014 The Processing Foundation
+ Copyright (c) 2014-19 The Processing Foundation
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
@@ -53,7 +53,7 @@ public class Preferences {
static final String PREFS_FILE = "preferences.txt"; //$NON-NLS-1$
static Map defaults;
- static Map table = new HashMap();
+ static Map table = new HashMap<>();
static File preferencesFile;
@@ -68,39 +68,21 @@ static public void init() {
load(Base.getLibStream(DEFAULTS_FILE));
} catch (Exception e) {
Messages.showError(null, "Could not read default settings.\n" +
- "You'll need to reinstall Processing.", e);
+ "You'll need to reinstall Processing.", e);
}
- /* provisionally removed in 3.0a6, see changes in load()
-
- // check for platform-specific properties in the defaults
- String platformExt = "." + PConstants.platformNames[PApplet.platform]; //$NON-NLS-1$
- int platformExtLength = platformExt.length();
-
- // Get a list of keys that are specific to this platform
- ArrayList platformKeys = new ArrayList();
- for (String key : table.keySet()) {
- if (key.endsWith(platformExt)) {
- platformKeys.add(key);
- }
- }
-
- // Use those platform-specific keys to override
- for (String key : platformKeys) {
- // this is a key specific to a particular platform
- String actualKey = key.substring(0, key.length() - platformExtLength);
- String value = get(key);
- set(actualKey, value);
- }
- */
-
// Clone the defaults, then override any them with the user's preferences.
// This ensures that any new/added preference will be present.
- defaults = new HashMap(table);
+ defaults = new HashMap<>(table);
// other things that have to be set explicitly for the defaults
setColor("run.window.bgcolor", SystemColor.control); //$NON-NLS-1$
+ // For CJK users, enable IM support by default
+ if (Language.useInputMethod()) {
+ setBoolean("editor.input_method_support", true);
+ }
+
// next load user preferences file
preferencesFile = Base.getSettingsFile(PREFS_FILE);
boolean firstRun = !preferencesFile.exists();
@@ -127,9 +109,12 @@ static public void init() {
PApplet.useNativeSelect =
Preferences.getBoolean("chooser.files.native"); //$NON-NLS-1$
- // Use the system proxy settings by default
- // https://github.com/processing/processing/issues/2643
- System.setProperty("java.net.useSystemProxies", "true");
+ // Adding option to disable this in case it's getting in the way
+ if (get("proxy.system").equals("true")) {
+ // Use the system proxy settings by default
+ // https://github.com/processing/processing/issues/2643
+ System.setProperty("java.net.useSystemProxies", "true");
+ }
// Set HTTP, HTTPS, and SOCKS proxies for individuals
// who want/need to override the system setting
@@ -225,23 +210,47 @@ static protected boolean isPlatformSpecific(String key, String value,
static public void save() {
- // on startup, don't worry about it
- // this is trying to update the prefs for who is open
- // before Preferences.init() has been called.
- if (preferencesFile == null) return;
-
- // Fix for 0163 to properly use Unicode when writing preferences.txt
- PrintWriter writer = PApplet.createWriter(preferencesFile);
-
- String[] keyList = table.keySet().toArray(new String[table.size()]);
- // Sorting is really helpful for debugging, diffing, and finding keys
- keyList = PApplet.sort(keyList);
- for (String key : keyList) {
- writer.println(key + "=" + table.get(key)); //$NON-NLS-1$
- }
+ // On startup it'll be null, don't worry about it. It's trying to update
+ // the prefs for the open sketch before Preferences.init() has been called.
+ if (preferencesFile != null) {
+ try {
+ File dir = preferencesFile.getParentFile();
+ File preferencesTemp = File.createTempFile("preferences", ".txt", dir);
+ preferencesTemp.setWritable(true, false);
+
+ // Fix for 0163 to properly use Unicode when writing preferences.txt
+ PrintWriter writer = PApplet.createWriter(preferencesTemp);
+
+ String[] keyList = table.keySet().toArray(new String[table.size()]);
+ // Sorting is really helpful for debugging, diffing, and finding keys
+ keyList = PApplet.sort(keyList);
+ for (String key : keyList) {
+ writer.println(key + "=" + table.get(key)); //$NON-NLS-1$
+ }
+ writer.flush();
+ writer.close();
+
+ // Rename preferences.txt to preferences.old
+ File oldPreferences = new File(dir, "preferences.old");
+ if (oldPreferences.exists()) {
+ if (!oldPreferences.delete()) {
+ throw new IOException("Could not delete preferences.old");
+ }
+ }
+ if (preferencesFile.exists() &&
+ !preferencesFile.renameTo(oldPreferences)) {
+ throw new IOException("Could not replace preferences.old");
+ }
+ // Make the temporary file into the real preferences
+ if (!preferencesTemp.renameTo(preferencesFile)) {
+ throw new IOException("Could not move preferences file into place");
+ }
- writer.flush();
- writer.close();
+ } catch (IOException e) {
+ Messages.showWarning("Preferences",
+ "Could not save the Preferences file.", e);
+ }
+ }
}
diff --git a/app/src/processing/app/Problem.java b/app/src/processing/app/Problem.java
new file mode 100644
index 0000000000..cb12ad5e3e
--- /dev/null
+++ b/app/src/processing/app/Problem.java
@@ -0,0 +1,35 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+ Copyright (c) 2012-16 The Processing Foundation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+package processing.app;
+
+
+public interface Problem {
+ public boolean isError();
+ public boolean isWarning();
+
+ public int getTabIndex();
+ public int getLineNumber(); // 0-indexed
+ public String getMessage();
+
+ public int getStartOffset();
+ public int getStopOffset();
+}
+
diff --git a/app/src/processing/app/RunnerListenerEdtAdapter.java b/app/src/processing/app/RunnerListenerEdtAdapter.java
new file mode 100644
index 0000000000..a436eefbca
--- /dev/null
+++ b/app/src/processing/app/RunnerListenerEdtAdapter.java
@@ -0,0 +1,48 @@
+package processing.app;
+
+import java.awt.EventQueue;
+
+public class RunnerListenerEdtAdapter implements RunnerListener {
+
+ private RunnerListener wrapped;
+
+ public RunnerListenerEdtAdapter(RunnerListener wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public void statusError(String message) {
+ EventQueue.invokeLater(() -> wrapped.statusError(message));
+ }
+
+ @Override
+ public void statusError(Exception exception) {
+ EventQueue.invokeLater(() -> wrapped.statusError(exception));
+ }
+
+ @Override
+ public void statusNotice(String message) {
+ EventQueue.invokeLater(() -> wrapped.statusNotice(message));
+ }
+
+ @Override
+ public void startIndeterminate() {
+ EventQueue.invokeLater(() -> wrapped.startIndeterminate());
+ }
+
+ @Override
+ public void stopIndeterminate() {
+ EventQueue.invokeLater(() -> wrapped.stopIndeterminate());
+ }
+
+ @Override
+ public void statusHalt() {
+ EventQueue.invokeLater(() -> wrapped.statusHalt());
+ }
+
+ @Override
+ public boolean isHalted() {
+ return wrapped.isHalted();
+ }
+}
+
diff --git a/app/src/processing/app/Settings.java b/app/src/processing/app/Settings.java
index 4b155a5edb..efd016f231 100644
--- a/app/src/processing/app/Settings.java
+++ b/app/src/processing/app/Settings.java
@@ -205,6 +205,7 @@ public Font getFont(String attr) {
style |= Font.ITALIC;
}
int size = PApplet.parseInt(pieces[2], 12);
+ size = Toolkit.zoom(size);
// replace bad font with the default from lib/preferences.txt
if (replace) {
diff --git a/app/src/processing/app/SingleInstance.java b/app/src/processing/app/SingleInstance.java
index e04678ce67..d9409d1526 100644
--- a/app/src/processing/app/SingleInstance.java
+++ b/app/src/processing/app/SingleInstance.java
@@ -57,12 +57,15 @@ static boolean alreadyRunning(String[] args) {
static void startServer(final Base base) {
try {
- final ServerSocket ss = new ServerSocket(0, 0, InetAddress.getByName(null));
+ Messages.log("Opening SingleInstance socket");
+ final ServerSocket ss =
+ new ServerSocket(0, 0, InetAddress.getLoopbackAddress());
Preferences.set(SERVER_PORT, "" + ss.getLocalPort());
final String key = "" + Math.random();
Preferences.set(SERVER_KEY, key);
Preferences.save();
+ Messages.log("Starting SingleInstance thread");
new Thread(new Runnable() {
public void run() {
while (true) {
@@ -119,15 +122,17 @@ public void run() {
static boolean sendArguments(String[] args) { //, long timeout) {
try {
+ Messages.log("Checking to see if Processing is already running");
int port = Preferences.getInteger(SERVER_PORT);
String key = Preferences.get(SERVER_KEY);
Socket socket = null;
try {
- socket = new Socket(InetAddress.getByName(null), port);
+ socket = new Socket(InetAddress.getLoopbackAddress(), port);
} catch (Exception ignored) { }
if (socket != null) {
+ Messages.log("Processing is already running, sending command line");
PrintWriter writer = PApplet.createWriter(socket.getOutputStream());
writer.println(key);
for (String arg : args) {
@@ -138,9 +143,9 @@ static boolean sendArguments(String[] args) { //, long timeout) {
return true;
}
} catch (IOException e) {
- System.err.println("Error sending commands to other instance.");
- e.printStackTrace();
+ Messages.loge("Error sending commands to other instance", e);
}
+ Messages.log("Processing is not already running (or could not connect)");
return false;
}
}
diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java
index c3d6382f59..e686dc1823 100644
--- a/app/src/processing/app/Sketch.java
+++ b/app/src/processing/app/Sketch.java
@@ -41,6 +41,7 @@
import java.io.*;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
@@ -90,6 +91,8 @@ public class Sketch {
/** Moved out of Editor and into here for cleaner access. */
private boolean untitled;
+ /** true if we've posted a "sketch disappeared" warning */
+ private boolean disappearedWarning;
/**
* Used by the command-line version to create a sketch object.
@@ -122,6 +125,7 @@ protected void load(String path) {
int suffixLength = mode.getDefaultExtension().length() + 1;
name = mainFilename.substring(0, mainFilename.length() - suffixLength);
folder = new File(new File(path).getParent());
+ disappearedWarning = false;
load();
}
@@ -226,6 +230,20 @@ public void reload() {
}
+ /**
+ * Load a tab that the user added to the sketch or modified with an external
+ * editor.
+ */
+ public void loadNewTab(String filename, String ext, boolean newAddition) {
+ if (newAddition) {
+ insertCode(new SketchCode(new File(folder, filename), ext));
+ } else {
+ replaceCode(new SketchCode(new File(folder, filename), ext));
+ }
+ sortCode();
+ }
+
+
protected void replaceCode(SketchCode newCode) {
for (int i = 0; i < codeCount; i++) {
if (code[i].getFileName().equals(newCode.getFileName())) {
@@ -555,7 +573,7 @@ protected void nameCode(String newName) {
code[i].setFolder(newFolder);
}
// Update internal state to reflect the new location
- updateInternal(sanitaryName, newFolder);
+ updateInternal(sanitaryName, newFolder, renamingCode);
// File newMainFile = new File(newFolder, newName + ".pde");
// String newMainFilePath = newMainFile.getAbsolutePath();
@@ -656,9 +674,10 @@ public void handleDeleteCode() {
// get the changes into the sketchbook menu
//sketchbook.rebuildMenus();
- // make a new sketch, and i think this will rebuild the sketch menu
+ // make a new sketch and rebuild the sketch menu
//editor.handleNewUnchecked();
//editor.handleClose2();
+ editor.getBase().rebuildSketchbookMenus();
editor.getBase().handleClose(editor, false);
} else {
@@ -673,17 +692,22 @@ public void handleDeleteCode() {
// remove code from the list
removeCode(current);
+ // update the tabs
+ editor.rebuildHeader();
+
// just set current tab to the main tab
setCurrentCode(0);
- // update the tabs
- editor.rebuildHeader();
}
}
}
- protected void removeCode(SketchCode which) {
+ /**
+ * Remove a SketchCode from the list of files without deleting its file.
+ * @see #handleDeleteCode()
+ */
+ public void removeCode(SketchCode which) {
// remove it from the internal list of files
// resort internal list of files
for (int i = 0; i < codeCount; i++) {
@@ -755,6 +779,16 @@ public boolean isModified() {
}
+ /**
+ * Ensure that all SketchCodes are up-to-date, so that sc.save() works.
+ */
+ public void updateSketchCodes() {
+// if (current.isModified()) {
+ current.setProgram(editor.getText());
+// }
+ }
+
+
/**
* Save all code in the current sketch. This just forces the files to save
* in place, so if it's an untitled (un-saved) sketch, saveAs() should be
@@ -765,9 +799,7 @@ public boolean save() throws IOException {
ensureExistence();
// first get the contents of the editor text area
-// if (current.isModified()) {
- current.setProgram(editor.getText());
-// }
+ updateSketchCodes();
// don't do anything if not actually modified
//if (!modified) return false;
@@ -909,9 +941,7 @@ public boolean saveAs() throws IOException {
// grab the contents of the current tab before saving
// first get the contents of the editor text area
- if (current.isModified()) {
- current.setProgram(editor.getText());
- }
+ updateSketchCodes();
File[] copyItems = folder.listFiles(new FileFilter() {
public boolean accept(File file) {
@@ -921,9 +951,12 @@ public boolean accept(File file) {
return false;
}
// list of files/folders to be ignored during "save as"
- for (String ignorable : mode.getIgnorable()) {
- if (name.equals(ignorable)) {
- return false;
+ String[] ignorable = mode.getIgnorable();
+ if (ignorable != null) {
+ for (String ignore : ignorable) {
+ if (name.equals(ignore)) {
+ return false;
+ }
}
}
// ignore the extensions for code, since that'll be copied below
@@ -952,15 +985,17 @@ public boolean accept(File file) {
// While the old path to the main .pde is still set, remove the entry from
// the Recent menu so that it's not sticking around after the rename.
// If untitled, it won't be in the menu, so there's no point.
- if (!isUntitled()) {
- Recent.remove(editor);
- }
+// if (!isUntitled()) {
+// Recent.remove(editor);
+// }
+ // Folks didn't like this behavior, so shutting it off
+ // https://github.com/processing/processing/issues/5902
// save the main tab with its new name
File newFile = new File(newFolder, newName + "." + mode.getDefaultExtension());
code[0].saveAs(newFile);
- updateInternal(newName, newFolder);
+ updateInternal(newName, newFolder, false);
// Make sure that it's not an untitled sketch
setUntitled(false);
@@ -973,6 +1008,13 @@ public boolean accept(File file) {
}
+ AtomicBoolean saving = new AtomicBoolean();
+
+ public boolean isSaving() {
+ return saving.get();
+ }
+
+
/**
* Kick off a background thread to copy everything *but* the .pde files.
* Due to the poor way (dating back to the late 90s with DBN) that our
@@ -982,15 +1024,16 @@ public boolean accept(File file) {
* As a result, this method will return 'true' before the full "Save As"
* has completed, which will cause problems in weird cases.
*
- * For instance, saving an untitled sketch that has an enormous data
- * folder while quitting. The save thread to move those data folder files
- * won't have finished before this returns true, and the PDE may quit
- * before the SwingWorker completes its job.
+ * For instance, the threading will cause problems while saving an untitled
+ * sketch that has an enormous data folder while quitting. The save thread to
+ * move those data folder files won't have finished before this returns true,
+ * and the PDE may quit before the SwingWorker completes its job.
*
* 3843
*/
void startSaveAsThread(final String oldName, final String newName,
final File newFolder, final File[] copyItems) {
+ saving.set(true);
EventQueue.invokeLater(new Runnable() {
public void run() {
final JFrame frame =
@@ -1060,6 +1103,7 @@ public void propertyChange(PropertyChangeEvent evt) {
}
}
}
+ saving.set(false);
return null;
}
@@ -1149,7 +1193,8 @@ public void done() {
/**
* Update internal state for new sketch name or folder location.
*/
- protected void updateInternal(String sketchName, File sketchFolder) {
+ protected void updateInternal(String sketchName, File sketchFolder,
+ boolean renaming) {
// reset all the state information for the sketch object
String oldPath = getMainFilePath();
primaryFile = code[0].getFile();
@@ -1158,6 +1203,7 @@ protected void updateInternal(String sketchName, File sketchFolder) {
name = sketchName;
folder = sketchFolder;
+ disappearedWarning = false;
codeFolder = new File(folder, "code");
dataFolder = new File(folder, "data");
@@ -1170,7 +1216,11 @@ protected void updateInternal(String sketchName, File sketchFolder) {
// System.out.println("modified is now " + modified);
editor.updateTitle();
editor.getBase().rebuildSketchbookMenus();
- Recent.rename(editor, oldPath);
+ if (renaming) {
+ // only update the Recent menu if it's a rename, not a Save As
+ // https://github.com/processing/processing/issues/5902
+ Recent.rename(editor, oldPath);
+ }
// editor.header.rebuild();
}
@@ -1234,6 +1284,8 @@ public boolean addFile(File sourceFile) {
String codeExtension = null;
boolean replacement = false;
+ boolean isCode = false;
+
// if the file appears to be code related, drop it
// into the code folder, instead of the data folder
if (filename.toLowerCase().endsWith(".class") ||
@@ -1246,7 +1298,7 @@ public boolean addFile(File sourceFile) {
//if (!codeFolder.exists()) codeFolder.mkdirs();
prepareCodeFolder();
destFile = new File(codeFolder, filename);
-
+ isCode = true;
} else {
for (String extension : mode.getExtensions()) {
String lower = filename.toLowerCase();
@@ -1316,6 +1368,10 @@ public boolean addFile(File sourceFile) {
}
}
+ if (isCode) {
+ editor.codeFolderChanged();
+ }
+
if (codeExtension != null) {
SketchCode newCode = new SketchCode(destFile, codeExtension);
@@ -1359,8 +1415,8 @@ public void setCurrentCode(int which) {
// System.out.println(current.visited);
// }
// if current is null, then this is the first setCurrent(0)
- if (((currentIndex == which) && (current != null))
- || which >= codeCount || which < 0) {
+ if (which < 0 || which >= codeCount ||
+ ((currentIndex == which) && (current == code[currentIndex]))) {
return;
}
@@ -1453,28 +1509,34 @@ public void prepareBuild(File targetFolder) throws SketchException {
/**
- * Make sure the sketch hasn't been moved or deleted by some
- * nefarious user. If they did, try to re-create it and save.
- * Only checks to see if the main folder is still around,
- * but not its contents.
+ * Make sure the sketch hasn't been moved or deleted by a nefarious user.
+ * If they did, try to re-create it and save. Only checks whether the
+ * main folder is still around, but not its contents.
*/
public void ensureExistence() {
if (!folder.exists()) {
- // Disaster recovery, try to salvage what's there already.
- Messages.showWarning(Language.text("ensure_exist.messages.missing_sketch"),
- Language.text("ensure_exist.messages.missing_sketch.description"));
- try {
- folder.mkdirs();
- modified = true;
+ // Avoid an infinite loop if we've already warned about this
+ // https://github.com/processing/processing/issues/4805
+ if (!disappearedWarning) {
+ disappearedWarning = true;
+
+ // Disaster recovery, try to salvage what's there already.
+ Messages.showWarning(Language.text("ensure_exist.messages.missing_sketch"),
+ Language.text("ensure_exist.messages.missing_sketch.description"));
+ try {
+ folder.mkdirs();
+ modified = true;
+
+ for (int i = 0; i < codeCount; i++) {
+ code[i].save(); // this will force a save
+ }
+ calcModified();
- for (int i = 0; i < codeCount; i++) {
- code[i].save(); // this will force a save
+ } catch (Exception e) {
+ // disappearedWarning prevents infinite loop in this scenario
+ Messages.showWarning(Language.text("ensure_exist.messages.unrecoverable"),
+ Language.text("ensure_exist.messages.unrecoverable.description"), e);
}
- calcModified();
-
- } catch (Exception e) {
- Messages.showWarning(Language.text("ensure_exist.messages.unrecoverable"),
- Language.text("ensure_exist.messages.unrecoverable.description"), e);
}
}
}
diff --git a/app/src/processing/app/SketchCode.java b/app/src/processing/app/SketchCode.java
index 1a52bfb9e7..59ed06652e 100644
--- a/app/src/processing/app/SketchCode.java
+++ b/app/src/processing/app/SketchCode.java
@@ -283,6 +283,12 @@ public long lastVisited() {
public void load() throws IOException {
program = Util.loadFile(file);
+ if (program == null) {
+ System.err.println("There was a problem loading " + file);
+ System.err.println("This may happen because you don't have permissions to read the file, or the file has gone missing.");
+ throw new IOException("Cannot read or access " + file);
+ }
+
// Remove NUL characters because they'll cause problems,
// and their presence is very difficult to debug.
// https://github.com/processing/processing/issues/1973
@@ -292,7 +298,7 @@ public void load() throws IOException {
savedProgram = program;
// This used to be the "Fix Encoding and Reload" warning, but since that
- // tool has been removed, it just rambles about text editors and encodings.
+ // tool has been removed, let's ramble about text editors and encodings.
if (program.indexOf('\uFFFD') != -1) {
System.err.println(file.getName() + " contains unrecognized characters.");
System.err.println("You should re-open " + file.getName() +
diff --git a/app/src/processing/app/SketchException.java b/app/src/processing/app/SketchException.java
index ccf8b924e6..4a32d2e79d 100644
--- a/app/src/processing/app/SketchException.java
+++ b/app/src/processing/app/SketchException.java
@@ -130,6 +130,11 @@ public void hideStackTrace() {
}
+ public boolean isStackTraceEnabled() {
+ return showStackTrace;
+ }
+
+
/**
* Nix the java.lang crap out of an exception message
* because it scares the children.
diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java
index fd43088ab9..5103f5d856 100644
--- a/app/src/processing/app/UpdateCheck.java
+++ b/app/src/processing/app/UpdateCheck.java
@@ -83,7 +83,12 @@ public void run() {
}
- public void updateCheck() throws IOException, InterruptedException {
+ /**
+ * Turned into a separate method so that anyone needed update.id will get
+ * a legit answer. Had a problem with the contribs script where the id
+ * wouldn't be set so a null id would be sent to the contribs server.
+ */
+ static public long getUpdateID() {
// generate a random id in case none exists yet
Random r = new Random();
long id = r.nextLong();
@@ -94,8 +99,12 @@ public void updateCheck() throws IOException, InterruptedException {
} else {
Preferences.set("update.id", String.valueOf(id));
}
+ return id;
+ }
- String info = PApplet.urlEncode(id + "\t" +
+
+ public void updateCheck() throws IOException, InterruptedException {
+ String info = PApplet.urlEncode(getUpdateID() + "\t" +
PApplet.nf(Base.getRevision(), 4) + "\t" +
System.getProperty("java.version") + "\t" +
System.getProperty("java.vendor") + "\t" +
diff --git a/app/src/processing/app/Util.java b/app/src/processing/app/Util.java
index 4dd5797814..fefc3b8b71 100644
--- a/app/src/processing/app/Util.java
+++ b/app/src/processing/app/Util.java
@@ -25,7 +25,6 @@
import java.io.*;
import java.nio.file.Files;
import java.util.Enumeration;
-import java.util.Vector;
import java.util.zip.*;
import processing.core.PApplet;
@@ -75,7 +74,7 @@ static public byte[] loadBytesRaw(File file) throws IOException {
*/
static public StringDict readSettings(File inputFile) {
if (!inputFile.exists()) {
- if (Base.DEBUG) System.err.println(inputFile + " does not exist.");
+ Messages.loge(inputFile + " does not exist inside readSettings()");
return null;
}
String lines[] = PApplet.loadStrings(inputFile);
@@ -163,12 +162,11 @@ static public String loadFile(File file) throws IOException {
* Spew the contents of a String object out to a file. As of 3.0 beta 2,
* this will replace and write \r\n for newlines on Windows.
* https://github.com/processing/processing/issues/3455
+ * As of 3.3.7, this puts a newline at the end of the file,
+ * per good practice/POSIX: https://stackoverflow.com/a/729795
*/
- static public void saveFile(String str, File file) throws IOException {
- if (Platform.isWindows()) {
- String[] lines = str.split("\\r?\\n");
- str = PApplet.join(lines, "\r\n");
- }
+ static public void saveFile(String text, File file) throws IOException {
+ String[] lines = text.split("\\r?\\n");
File temp = File.createTempFile(file.getName(), null, file.getParentFile());
try {
// fix from cjwant to prevent symlinks from being destroyed.
@@ -179,9 +177,11 @@ static public void saveFile(String str, File file) throws IOException {
throw new IOException("Could not resolve canonical representation of " +
file.getAbsolutePath());
}
- // Can't use saveStrings() here b/c Windows will add a ^M to the file
+ // Could use saveStrings(), but the we wouldn't be able to checkError()
PrintWriter writer = PApplet.createWriter(temp);
- writer.print(str);
+ for (String line : lines) {
+ writer.println(line);
+ }
boolean error = writer.checkError(); // calls flush()
writer.close(); // attempt to close regardless
if (error) {
@@ -379,61 +379,55 @@ static public long calcFolderSize(File folder) {
}
-// /**
-// * Recursively creates a list of all files within the specified folder,
-// * and returns a list of their relative paths.
-// * Ignores any files/folders prefixed with a dot.
-// */
-// static public String[] listFiles(String path, boolean relative) {
-// return listFiles(new File(path), relative);
-// }
-
-
+ /**
+ * Recursively creates a list of all files within the specified folder,
+ * and returns a list of their relative paths.
+ * Ignores any files/folders prefixed with a dot.
+ * @param relative true return relative paths instead of absolute paths
+ */
static public String[] listFiles(File folder, boolean relative) {
- String path = folder.getAbsolutePath();
- Vector vector = new Vector();
- listFiles(relative ? (path + File.separator) : "", path, null, vector);
- String outgoing[] = new String[vector.size()];
- vector.copyInto(outgoing);
- return outgoing;
+ return listFiles(folder, relative, null);
}
- static public String[] listFiles(File folder, boolean relative, String extension) {
- String path = folder.getAbsolutePath();
- Vector vector = new Vector();
+ static public String[] listFiles(File folder, boolean relative,
+ String extension) {
if (extension != null) {
if (!extension.startsWith(".")) {
extension = "." + extension;
}
}
- listFiles(relative ? (path + File.separator) : "", path, extension, vector);
- String outgoing[] = new String[vector.size()];
- vector.copyInto(outgoing);
- return outgoing;
+
+ StringList list = new StringList();
+ listFilesImpl(folder, relative, extension, list);
+
+ if (relative) {
+ String[] outgoing = new String[list.size()];
+ // remove the slash (or backslash) as well
+ int prefixLength = folder.getAbsolutePath().length() + 1;
+ for (int i = 0; i < outgoing.length; i++) {
+ outgoing[i] = list.get(i).substring(prefixLength);
+ }
+ return outgoing;
+ }
+ return list.array();
}
- static protected void listFiles(String basePath,
- String path, String extension,
- Vector vector) {
- File folder = new File(path);
- String[] list = folder.list();
- if (list != null) {
- for (String item : list) {
- if (item.charAt(0) == '.') continue;
- if (extension == null || item.toLowerCase().endsWith(extension)) {
- File file = new File(path, item);
- String newPath = file.getAbsolutePath();
- if (newPath.startsWith(basePath)) {
- newPath = newPath.substring(basePath.length());
- }
- // only add if no ext or match
- if (extension == null || item.toLowerCase().endsWith(extension)) {
- vector.add(newPath);
- }
- if (file.isDirectory()) { // use absolute path
- listFiles(basePath, file.getAbsolutePath(), extension, vector);
+ static void listFilesImpl(File folder, boolean relative,
+ String extension, StringList list) {
+ File[] items = folder.listFiles();
+ if (items != null) {
+ for (File item : items) {
+ String name = item.getName();
+ if (name.charAt(0) != '.') {
+ if (item.isDirectory()) {
+ listFilesImpl(item, relative, extension, list);
+
+ } else { // a file
+ if (extension == null || name.endsWith(extension)) {
+ list.append(item.getAbsolutePath());
+ }
}
}
}
@@ -565,15 +559,14 @@ static private void packageListFromZip(String filename, StringList list) {
if (!entry.isDirectory()) {
String name = entry.getName();
- if (name.endsWith(".class")) {
+ // Avoid META-INF because some jokers but .class files in there
+ // https://github.com/processing/processing/issues/5778
+ if (name.endsWith(".class") && !name.startsWith("META-INF/")) {
int slash = name.lastIndexOf('/');
- if (slash == -1) continue;
-
- String pname = name.substring(0, slash);
-// if (map.get(pname) == null) {
-// map.put(pname, new Object());
-// }
- list.appendUnique(pname);
+ if (slash != -1) {
+ String packageName = name.substring(0, slash);
+ list.appendUnique(packageName);
+ }
}
}
}
@@ -621,24 +614,31 @@ static private void packageListFromFolder(File dir, String sofar,
}
+ /**
+ * Extract the contents of a .zip archive into a folder.
+ * Ignores (does not extract) any __MACOSX files from macOS archives.
+ */
static public void unzip(File zipFile, File dest) {
try {
FileInputStream fis = new FileInputStream(zipFile);
CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32());
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum));
- ZipEntry next = null;
- while ((next = zis.getNextEntry()) != null) {
- File currentFile = new File(dest, next.getName());
- if (next.isDirectory()) {
- currentFile.mkdirs();
- } else {
- File parentDir = currentFile.getParentFile();
- // Sometimes the directory entries aren't already created
- if (!parentDir.exists()) {
- parentDir.mkdirs();
+ ZipEntry entry = null;
+ while ((entry = zis.getNextEntry()) != null) {
+ final String name = entry.getName();
+ if (!name.startsWith(("__MACOSX"))) {
+ File currentFile = new File(dest, name);
+ if (entry.isDirectory()) {
+ currentFile.mkdirs();
+ } else {
+ File parentDir = currentFile.getParentFile();
+ // Sometimes the directory entries aren't already created
+ if (!parentDir.exists()) {
+ parentDir.mkdirs();
+ }
+ currentFile.createNewFile();
+ unzipEntry(zis, currentFile);
}
- currentFile.createNewFile();
- unzipEntry(zis, currentFile);
}
}
} catch (Exception e) {
@@ -666,4 +666,12 @@ static public byte[] gzipEncode(byte[] what) throws IOException {
output.close();
return baos.toByteArray();
}
+
+
+ static public final boolean containsNonASCII(String what) {
+ for (char c : what.toCharArray()) {
+ if (c < 32 || c > 127) return true;
+ }
+ return false;
+ }
}
diff --git a/app/src/processing/app/contrib/AvailableContribution.java b/app/src/processing/app/contrib/AvailableContribution.java
index 7d15eafb3d..4d5240c451 100644
--- a/app/src/processing/app/contrib/AvailableContribution.java
+++ b/app/src/processing/app/contrib/AvailableContribution.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013-15 The Processing Foundation
+ Copyright (c) 2013-20 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -49,9 +49,9 @@ public AvailableContribution(ContributionType type, StringDict params) {
imports = parseImports(params);
name = params.get("name");
authors = params.get("authors");
- if (authors == null) {
- authors = params.get("authorList");
- }
+// if (authors == null) {
+// authors = params.get("authorList");
+// }
url = params.get("url");
sentence = params.get("sentence");
paragraph = params.get("paragraph");
@@ -61,7 +61,7 @@ public AvailableContribution(ContributionType type, StringDict params) {
version = PApplet.parseInt(versionStr, 0);
}
- prettyVersion = params.get("prettyVersion");
+ setPrettyVersion(params.get("prettyVersion"));
String lastUpdatedStr = params.get("lastUpdated");
if (lastUpdatedStr != null) {
@@ -99,7 +99,6 @@ public LocalContribution install(Base base, File contribArchive,
// Unzip the file into the modes, tools, or libraries folder inside the
// sketchbook. Unzipping to /tmp is problematic because it may be on
// another file system, so move/rename operations will break.
-// File sketchbookContribFolder = type.getSketchbookFolder();
File tempFolder = null;
try {
@@ -110,84 +109,61 @@ public LocalContribution install(Base base, File contribArchive,
return null;
}
Util.unzip(contribArchive, tempFolder);
-// System.out.println("temp folder is " + tempFolder);
-// Base.openFolder(tempFolder);
// Now go looking for a legit contrib inside what's been unpacked.
File contribFolder = null;
- // Sometimes contrib authors place all their folders in the base directory
- // of the .zip file instead of in single folder as the guidelines suggest.
- if (type.isCandidate(tempFolder)) {
- /*
- // Can't just rename the temp folder, because a contrib with this name
- // may already exist. Instead, create a new temp folder, and rename the
- // old one to be the correct folder.
- File enclosingFolder = null;
- try {
- enclosingFolder = Base.createTempFolder(type.toString(), "tmp", sketchbookContribFolder);
- } catch (IOException e) {
- status.setErrorMessage("Could not create a secondary folder to install.");
- return null;
- }
- contribFolder = new File(enclosingFolder, getName());
- tempFolder.renameTo(contribFolder);
- tempFolder = enclosingFolder;
- */
+ /*
+ if (!type.isCandidate(tempFolder)) {
if (status != null) {
status.setErrorMessage(Language.interpolate("contrib.errors.needs_repackage", getName(), type.getTitle()));
}
return null;
}
+ */
-// if (contribFolder == null) {
- // Find the first legitimate looking folder in what we just unzipped
- contribFolder = type.findCandidate(tempFolder);
-// }
LocalContribution installedContrib = null;
-
+ // Find the first legitimate folder in what we just unzipped
+ contribFolder = type.findCandidate(tempFolder);
if (contribFolder == null) {
if (status != null) {
status.setErrorMessage(Language.interpolate("contrib.errors.no_contribution_found", type));
}
-
} else {
File propFile = new File(contribFolder, type + ".properties");
- if (writePropertiesFile(propFile)) {
- // 1. contribFolder now has a legit contribution, load it to get info.
+ if (!propFile.exists()) {
+ status.setErrorMessage("This contribution is missing " +
+ propFile.getName() +
+ ", please contact the author for a fix.");
+
+ } else if (writePropertiesFile(propFile)) {
+ // contribFolder now has a legit contribution, load it to get info.
LocalContribution newContrib = type.load(base, contribFolder);
- // 1.1. get info we need to delete the newContrib folder later
+ // get info we need to delete the newContrib folder later
File newContribFolder = newContrib.getFolder();
- // 2. Check to make sure nothing has the same name already,
+ // Check to make sure nothing has the same name already,
// backup old if needed, then move things into place and reload.
installedContrib =
newContrib.copyAndLoad(base, confirmReplace, status);
- // Restart no longer needed. Yay!
-// if (newContrib != null && type.requiresRestart()) {
-// installedContrib.setRestartFlag();
-// //status.setMessage("Restart Processing to finish the installation.");
-// }
-
- // 3.1 Unlock all the jars if it is a mode or tool
+ // Unlock all the jars if it is a mode or tool
if (newContrib.getType() == ContributionType.MODE) {
((ModeContribution) newContrib).clearClassLoader(base);
- }
- else if (newContrib.getType() == ContributionType.TOOL) {
+
+ } else if (newContrib.getType() == ContributionType.TOOL) {
((ToolContribution) newContrib).clearClassLoader();
}
- // 3.2 Delete the newContrib, do a garbage collection, hope and pray
+ // Delete the newContrib, do a garbage collection, hope and pray
// that Java will unlock the temp folder on Windows now
newContrib = null;
System.gc();
-
if (Platform.isWindows()) {
- // we'll even give it a second to finish up ... because file ops are
- // just that flaky on Windows.
+ // we'll even give it a second to finish up,
+ // because file ops are just that flaky on Windows.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
@@ -195,7 +171,7 @@ else if (newContrib.getType() == ContributionType.TOOL) {
}
}
- // 4. Okay, now actually delete that temp folder
+ // delete the contrib folder inside the libraryXXXXXXtmp folder
Util.removeDir(newContribFolder, false);
} else {
@@ -231,9 +207,6 @@ public ContributionType getType() {
* manager. However, it also ensures that valid fields in the properties file
* aren't overwritten, since the properties file may be more recent than the
* contributions.txt file.
- *
- * @param propFile
- * @return
*/
public boolean writePropertiesFile(File propFile) {
try {
@@ -256,9 +229,9 @@ public boolean writePropertiesFile(File propFile) {
StringList importsList = parseImports(properties);
String authors = properties.get(AUTHORS_PROPERTY);
- if (authors == null) {
- authors = properties.get("authorList"); // before 3.0a11
- }
+// if (authors == null) {
+// authors = properties.get("authorList"); // before 3.0a11
+// }
if (authors == null || authors.isEmpty()) {
authors = getAuthorList();
}
@@ -283,13 +256,14 @@ public boolean writePropertiesFile(File propFile) {
version = Integer.parseInt(properties.get("version"));
} catch (NumberFormatException e) {
version = getVersion();
- System.err.println("The version number for “" + name + "” is not set properly.");
+ System.err.println("The version number for “" + name + "” is not a number.");
System.err.println("Please contact the author to fix it according to the guidelines.");
}
String prettyVersion = properties.get("prettyVersion");
- if (prettyVersion == null || prettyVersion.isEmpty())
- prettyVersion = getPrettyVersion();
+ if (prettyVersion != null && prettyVersion.isEmpty()) {
+ prettyVersion = null;
+ }
String compatibleContribsList = null;
if (getType() == ContributionType.EXAMPLES) {
@@ -336,7 +310,9 @@ public boolean writePropertiesFile(File propFile) {
writer.println("sentence=" + sentence);
writer.println("paragraph=" + paragraph);
writer.println("version=" + version);
- writer.println("prettyVersion=" + prettyVersion);
+ if (prettyVersion != null) {
+ writer.println("prettyVersion=" + prettyVersion);
+ }
writer.println("lastUpdated=" + lastUpdated);
writer.println("minRevision=" + minRev);
writer.println("maxRevision=" + maxRev);
diff --git a/app/src/processing/app/contrib/ContribProgressBar.java b/app/src/processing/app/contrib/ContribProgressBar.java
index c86cb16ae4..8a0d41c3b6 100644
--- a/app/src/processing/app/contrib/ContribProgressBar.java
+++ b/app/src/processing/app/contrib/ContribProgressBar.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013-15 The Processing Foundation
+ Copyright (c) 2013-20 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
diff --git a/app/src/processing/app/contrib/Contribution.java b/app/src/processing/app/contrib/Contribution.java
index 65b6ec09c9..aa401375e6 100644
--- a/app/src/processing/app/contrib/Contribution.java
+++ b/app/src/processing/app/contrib/Contribution.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013-15 The Processing Foundation
+ Copyright (c) 2013-16 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -158,21 +158,41 @@ public int getVersion() {
}
- // "1.0.2"
+ public void setPrettyVersion(String pretty) {
+ if (pretty != null) {
+ // some entries were written as "null", causing that to show in the ui
+ if (pretty.equals("null") || pretty.length() == 0) {
+ pretty = null;
+ }
+ }
+ prettyVersion = pretty;
+ }
+
+
+ // "1.0.2" or null if not present
public String getPrettyVersion() {
return prettyVersion;
}
+
+ // returns prettyVersion, or "" if null
+ public String getBenignVersion() {
+ return (prettyVersion != null) ? prettyVersion : "";
+ }
+
+
// 1402805757
public long getLastUpdated() {
return lastUpdated;
}
+
// 0
public int getMinRevision() {
return minRevision;
}
+
// 227
public int getMaxRevision() {
return maxRevision;
@@ -316,6 +336,27 @@ static StringList parseImports(StringDict properties) {
}
+ /**
+ * Helper function that creates a StringList of the compatible Modes
+ * for this Contribution.
+ */
+ static StringList parseModeList(StringDict properties) {
+ String unparsedModes = properties.get(MODES_PROPERTY);
+
+ // Workaround for 3.0 alpha/beta bug for 3.0b2
+ if ("null".equals(unparsedModes)) {
+ properties.remove(MODES_PROPERTY);
+ unparsedModes = null;
+ }
+
+ StringList outgoing = new StringList();
+ if (unparsedModes != null) {
+ outgoing.append(PApplet.trim(PApplet.split(unparsedModes, ',')));
+ }
+ return outgoing;
+ }
+
+
static private String translateCategory(String cat) {
// Converts Other to other, I/O to i_o, Video & Vision to video_vision
String cleaned = cat.replaceAll("[\\W]+", "_").toLowerCase();
@@ -326,6 +367,27 @@ static private String translateCategory(String cat) {
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o instanceof Contribution) {
+ Contribution that = (Contribution) o;
+ return name.toLowerCase().equals(that.name.toLowerCase());
+ }
+ return false;
+ }
+
+
+ @Override
+ public int hashCode() {
+ return name.toLowerCase().hashCode();
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
public interface Filter {
boolean matches(Contribution contrib);
}
diff --git a/app/src/processing/app/contrib/ContributionListing.java b/app/src/processing/app/contrib/ContributionListing.java
index cf35ff628c..6f35ca3fd7 100644
--- a/app/src/processing/app/contrib/ContributionListing.java
+++ b/app/src/processing/app/contrib/ContributionListing.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013-15 The Processing Foundation
+ Copyright (c) 2013-16 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -51,23 +51,25 @@ public class ContributionListing {
List advertisedContributions;
Map> librariesByCategory;
Map librariesByImportHeader;
- List allContributions;
+ // TODO: Every contribution is getting added twice
+ // and nothing is replaced ever.
+ Set allContributions;
boolean listDownloaded;
boolean listDownloadFailed;
ReentrantLock downloadingListingLock;
private ContributionListing() {
- listeners = new ArrayList();
- advertisedContributions = new ArrayList();
- librariesByCategory = new HashMap>();
- librariesByImportHeader = new HashMap();
- allContributions = new ArrayList();
+ listeners = new ArrayList<>();
+ advertisedContributions = new ArrayList<>();
+ librariesByCategory = new HashMap<>();
+ librariesByImportHeader = new HashMap<>();
+ allContributions = new LinkedHashSet<>();
downloadingListingLock = new ReentrantLock();
//listingFile = Base.getSettingsFile("contributions.txt");
listingFile = Base.getSettingsFile(LOCAL_FILENAME);
- listingFile.setWritable(true);
+ listingFile.setWritable(true, false);
if (listingFile.exists()) {
setAdvertisedList(listingFile);
}
@@ -94,7 +96,6 @@ private void setAdvertisedList(File file) {
for (Contribution contribution : advertisedContributions) {
addContribution(contribution);
}
- Collections.sort(allContributions, COMPARATOR);
}
@@ -137,11 +138,8 @@ protected void replaceContribution(Contribution oldLib, Contribution newLib) {
}
}
- for (int i = 0; i < allContributions.size(); i++) {
- if (allContributions.get(i) == oldLib) {
- allContributions.set(i, newLib);
- }
- }
+ allContributions.remove(oldLib);
+ allContributions.add(newLib);
notifyChange(oldLib, newLib);
}
@@ -161,13 +159,12 @@ private void addContribution(Contribution contribution) {
Collections.sort(list, COMPARATOR);
} else {
- ArrayList list = new ArrayList();
+ ArrayList list = new ArrayList<>();
list.add(contribution);
librariesByCategory.put(category, list);
}
allContributions.add(contribution);
notifyAdd(contribution);
- Collections.sort(allContributions, COMPARATOR);
}
}
@@ -213,7 +210,7 @@ protected AvailableContribution getAvailableContribution(Contribution info) {
protected Set getCategories(Contribution.Filter filter) {
- Set outgoing = new HashSet();
+ Set outgoing = new HashSet<>();
Set categorySet = librariesByCategory.keySet();
for (String categoryName : categorySet) {
@@ -232,43 +229,7 @@ protected Set getCategories(Contribution.Filter filter) {
}
-// public List getAllContributions() {
-// return new ArrayList(allContributions);
-// }
-
-
-// public List getLibararies(String category) {
-// ArrayList libinfos =
-// new ArrayList(librariesByCategory.get(category));
-// Collections.sort(libinfos, nameComparator);
-// return libinfos;
-// }
-
-
- protected List getFilteredLibraryList(String category, List filters) {
- ArrayList filteredList =
- new ArrayList(allContributions);
-
- Iterator it = filteredList.iterator();
- while (it.hasNext()) {
- Contribution libInfo = it.next();
- //if (category != null && !category.equals(libInfo.getCategory())) {
- if (category != null && !libInfo.hasCategory(category)) {
- it.remove();
- } else {
- for (String filter : filters) {
- if (!matches(libInfo, filter)) {
- it.remove();
- break;
- }
- }
- }
- }
- return filteredList;
- }
-
-
- private boolean matches(Contribution contrib, String typed) {
+ public boolean matches(Contribution contrib, String typed) {
int colon = typed.indexOf(":");
if (colon != -1) {
String isText = typed.substring(0, colon);
@@ -420,7 +381,7 @@ public void run() {
// System.out.println(contribInfo.length() + " " + contribInfo);
File tempContribFile = Base.getSettingsFile("contribs.tmp");
- tempContribFile.setWritable(true);
+ tempContribFile.setWritable(true, false);
ContributionManager.download(url, base.getInstalledContribsInfo(),
tempContribFile, progress);
if (!progress.isCanceled() && !progress.isError()) {
@@ -465,28 +426,6 @@ public void run() {
}
- /*
- boolean hasUpdates(Base base) {
- for (ModeContribution mc : base.getModeContribs()) {
- if (hasUpdates(mc)) {
- return true;
- }
- }
- for (Library lib : base.getActiveEditor().getMode().contribLibraries) {
- if (hasUpdates(lib)) {
- return true;
- }
- }
- for (ToolContribution tc : base.getToolContribs()) {
- if (hasUpdates(tc)) {
- return true;
- }
- }
- return false;
- }
- */
-
-
protected boolean hasUpdates(Contribution contribution) {
if (contribution.isInstalled()) {
Contribution advertised = getAvailableContribution(contribution);
@@ -500,7 +439,7 @@ protected boolean hasUpdates(Contribution contribution) {
}
- protected String getLatestVersion(Contribution contribution) {
+ protected String getLatestPrettyVersion(Contribution contribution) {
Contribution newestContrib = getAvailableContribution(contribution);
if (newestContrib == null) {
return null;
@@ -509,7 +448,6 @@ protected String getLatestVersion(Contribution contribution) {
}
-
protected boolean hasDownloadedLatestList() {
return listDownloaded;
}
@@ -522,7 +460,7 @@ protected boolean hasListDownloadFailed() {
private List parseContribList(File file) {
List outgoing =
- new ArrayList();
+ new ArrayList<>();
if (file != null && file.exists()) {
String[] lines = PApplet.loadStrings(file);
@@ -580,6 +518,11 @@ public int countUpdates(Base base) {
count++;
}
}
+ for (Library lib : base.getActiveEditor().getMode().coreLibraries) {
+ if (hasUpdates(lib)) {
+ count++;
+ }
+ }
for (ToolContribution tc : base.getToolContribs()) {
if (hasUpdates(tc)) {
count++;
@@ -600,11 +543,7 @@ public Map getLibrariesByImportHeader() {
}
- static public Comparator COMPARATOR = new Comparator() {
- public int compare(Contribution o1, Contribution o2) {
- return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase());
- }
- };
+ static public Comparator COMPARATOR = Comparator.comparing(o -> o.getName().toLowerCase());
public interface ChangeListener {
diff --git a/app/src/processing/app/contrib/ContributionManager.java b/app/src/processing/app/contrib/ContributionManager.java
index 4a6bbf4d96..af9c4d8995 100644
--- a/app/src/processing/app/contrib/ContributionManager.java
+++ b/app/src/processing/app/contrib/ContributionManager.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013 The Processing Foundation
+ Copyright (c) 2013-20 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -39,7 +39,7 @@
public class ContributionManager {
- static final ContributionListing listing = ContributionListing.getInstance();
+ static ContributionListing listing;
/**
@@ -61,6 +61,7 @@ static boolean download(URL source, byte[] post,
boolean success = false;
try {
HttpURLConnection conn = (HttpURLConnection) source.openConnection();
+ // Will not handle a protocol change (see below)
HttpURLConnection.setFollowRedirects(true);
conn.setConnectTimeout(15 * 1000);
conn.setReadTimeout(60 * 1000);
@@ -89,27 +90,37 @@ static boolean download(URL source, byte[] post,
progress.startTask(Language.text("contrib.progress.downloading"), fileSize);
}
- InputStream in = conn.getInputStream();
- FileOutputStream out = new FileOutputStream(dest);
+ int response = conn.getResponseCode();
+ // Default won't follow HTTP -> HTTPS redirects for security reasons
+ // http://stackoverflow.com/a/1884427
+ if (response >= 300 && response < 400) {
+ // Handle SSL redirects from HTTP sources
+ // https://github.com/processing/processing/issues/5554
+ String newLocation = conn.getHeaderField("Location");
+ return download(new URL(newLocation), post, dest, progress);
- byte[] b = new byte[8192];
- int amount;
- if (progress != null) {
- int total = 0;
- while (!progress.isCanceled() && (amount = in.read(b)) != -1) {
- out.write(b, 0, amount);
- total += amount;
- progress.setProgress(total);
- }
} else {
- while ((amount = in.read(b)) != -1) {
- out.write(b, 0, amount);
+ InputStream in = conn.getInputStream();
+ FileOutputStream out = new FileOutputStream(dest);
+
+ byte[] b = new byte[8192];
+ int amount;
+ if (progress != null) {
+ int total = 0;
+ while (!progress.isCanceled() && (amount = in.read(b)) != -1) {
+ out.write(b, 0, amount);
+ total += amount;
+ progress.setProgress(total);
+ }
+ } else {
+ while ((amount = in.read(b)) != -1) {
+ out.write(b, 0, amount);
+ }
}
+ out.flush();
+ out.close();
+ success = true;
}
- out.flush();
- out.close();
- success = true;
-
} catch (SocketTimeoutException ste) {
if (progress != null) {
progress.error(ste);
@@ -120,8 +131,6 @@ static boolean download(URL source, byte[] post,
progress.error(ioe);
progress.cancel();
}
- // Hiding stack trace. An error has been shown where needed.
-// ioe.printStackTrace();
}
if (progress != null) {
progress.finished();
@@ -190,8 +199,8 @@ public void run() {
}
}
installProgress.finished();
- }
- else {
+
+ } else {
if (downloadProgress.exception instanceof SocketTimeoutException) {
status.setErrorMessage(Language
.interpolate("contrib.errors.contrib_download.timeout",
@@ -204,7 +213,6 @@ public void run() {
}
contribZip.delete();
- //} catch (NoClassDefFoundError ncdfe) {
} catch (Exception e) {
String msg = null;
if (e instanceof RuntimeException) {
@@ -220,6 +228,8 @@ public void run() {
if (msg == null) {
msg = Language.interpolate("contrib.errors.download_and_install", ad.getName());
+ // Something unexpected, so print the trace
+ e.printStackTrace();
}
status.setErrorMessage(msg);
downloadProgress.cancel();
@@ -361,7 +371,7 @@ static public void downloadAndInstallOnImport(final Base base,
editor.getTextArea().setEditable(false);
// base.getActiveEditor().getConsole().clear();
- List installedLibList = new ArrayList();
+ List installedLibList = new ArrayList<>();
// boolean variable to check if previous lib was installed successfully,
// to give the user an idea about progress being made.
@@ -543,6 +553,8 @@ static private void cleanup(final Base base) throws Exception {
@Override
protected Void doInBackground() throws Exception {
try {
+ // TODO: pls explain the sleep and why this runs on a worker thread,
+ // but a couple of lines above on EDT [jv]
Thread.sleep(1000);
installPreviouslyFailed(base, Base.getSketchbookToolsFolder());
} catch (InterruptedException e) {
@@ -588,8 +600,10 @@ public boolean accept(File folder) {
LocalContribution.isDeletionFlagged(folder));
}
});
- for (File folder : markedForDeletion) {
- Util.removeDir(folder);
+ if (markedForDeletion != null) {
+ for (File folder : markedForDeletion) {
+ Util.removeDir(folder);
+ }
}
}
@@ -605,14 +619,21 @@ public boolean accept(File folder) {
}
});
- for (File file : installList) {
- for (AvailableContribution contrib : listing.advertisedContributions) {
- if (file.getName().equals(contrib.getName())) {
- file.delete();
- installOnStartUp(base, contrib);
- listing.replaceContribution(contrib, contrib);
+ // https://github.com/processing/processing/issues/5823
+ if (installList != null) {
+ for (File file : installList) {
+ for (AvailableContribution contrib : listing.advertisedContributions) {
+ if (file.getName().equals(contrib.getName())) {
+ file.delete();
+ installOnStartUp(base, contrib);
+ EventQueue.invokeAndWait(() -> {
+ listing.replaceContribution(contrib, contrib);
+ });
+ }
}
}
+ } else {
+ System.err.println("Could not read " + root);
}
}
@@ -628,8 +649,14 @@ public boolean accept(File folder) {
}
});
- ArrayList updateContribsNames = new ArrayList();
- LinkedList updateContribsList = new LinkedList();
+ List updateContribsNames = new ArrayList<>();
+ List updateContribsList = new LinkedList<>();
+
+ // TODO This is bad code... This root.getName() stuff to get the folder
+ // type, plus "libraries.properties" (not the correct file name),
+ // and I have no idea what "putting this here, in just in case" means.
+ // Not sure the function here so I'm not fixing it at the moment,
+ // but this whole function could use some cleaning. [fry 180105]
String type = root.getName().substring(root.getName().lastIndexOf('/') + 1);
String propFileName = null;
@@ -700,6 +727,7 @@ public boolean accept(File folder) {
static public void init(Base base) throws Exception {
+ listing = ContributionListing.getInstance(); // Moved here to make sure it runs on EDT [jv 170121]
managerDialog = new ManagerFrame(base);
cleanup(base);
}
diff --git a/app/src/processing/app/contrib/ContributionTab.java b/app/src/processing/app/contrib/ContributionTab.java
index b10801ca41..9028e0644d 100644
--- a/app/src/processing/app/contrib/ContributionTab.java
+++ b/app/src/processing/app/contrib/ContributionTab.java
@@ -23,14 +23,13 @@
package processing.app.contrib;
import java.awt.Color;
+import java.awt.Cursor;
import java.awt.Dimension;
-import java.awt.Font;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
-import javax.swing.text.*;
import processing.app.*;
import processing.app.ui.Editor;
@@ -39,7 +38,7 @@
public class ContributionTab extends JPanel {
static final String ANY_CATEGORY = Language.text("contrib.all");
- static final int FILTER_WIDTH = 180;
+ static final int FILTER_WIDTH = Toolkit.zoom(180);
ContributionType contribType;
ManagerFrame contribDialog;
@@ -74,36 +73,14 @@ public ContributionTab(ManagerFrame dialog, ContributionType type) {
this.contribDialog = dialog;
this.contribType = type;
- /*
- if (type == ContributionType.MODE) {
- title = Language.text("contrib.manager_title.mode");
- } else if (type == ContributionType.TOOL) {
- title = Language.text("contrib.manager_title.tool");
- } else if (type == ContributionType.LIBRARY) {
- title = Language.text("contrib.manager_title.library");
- } else if (type == ContributionType.EXAMPLES) {
- title = Language.text("contrib.manager_title.examples");
- }
- */
-
- filter = new Contribution.Filter() {
- public boolean matches(Contribution contrib) {
- return contrib.getType() == contribType;
- }
- };
+ filter = contrib -> contrib.getType() == contribType;
contribListing = ContributionListing.getInstance();
statusPanel = new StatusPanel(this, 650);
- contributionListPanel = new ListPanel(this, filter);
+ contributionListPanel = new ListPanel(this, filter, false);
contribListing.addListener(contributionListPanel);
}
-
-// public boolean hasUpdates(Base base) {
-// return contribListing.hasUpdates(base);
-// }
-
-
public void showFrame(final Editor editor, boolean error, boolean loading) {
this.editor = editor;
@@ -184,7 +161,7 @@ private void createComponents() {
categoryChooser = new JComboBox();
categoryChooser.setMaximumRowCount(20);
- categoryChooser.setFont(Toolkit.getSansFont(14, Font.PLAIN));
+ categoryChooser.setFont(ManagerFrame.NORMAL_PLAIN);
updateCategoryChooser();
@@ -212,24 +189,26 @@ protected void buildErrorPanel() {
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
errorPanel.setLayout(layout);
-// errorPanel.setBorder(BorderFactory.createMatteBorder(2, 0, 0, 0, Color.BLACK));
errorMessage = new JTextPane();
errorMessage.setEditable(false);
errorMessage.setContentType("text/html");
- errorMessage.setText("Could not connect to the Processing server.
"
+ errorMessage.setText("Could not connect to the Processing server.
"
+ "Contributions cannot be installed or updated without an Internet connection.
"
- + "Please verify your network connection again, then try connecting again.");
- errorMessage.setFont(Toolkit.getSansFont(14, Font.PLAIN));
- errorMessage.setMaximumSize(new Dimension(550, 50));
+ + "Please verify your network connection again, then try connecting again.");
+ DetailPanel.setTextStyle(errorMessage, "1em");
+ Dimension dim = new Dimension(550, 60);
+ errorMessage.setMaximumSize(dim);
+ errorMessage.setMinimumSize(dim);
errorMessage.setOpaque(false);
+ /*
StyledDocument doc = errorMessage.getStyledDocument();
SimpleAttributeSet center = new SimpleAttributeSet();
StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
doc.setParagraphAttributes(0, doc.getLength(), center, false);
+ */
- // TODO https://github.com/processing/processing/issues/3706
- closeButton = new JButton("X");
+ closeButton = Toolkit.createIconButton("manager/close");
closeButton.setContentAreaFilled(false);
closeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
@@ -237,7 +216,7 @@ public void actionPerformed(ActionEvent e) {
}
});
tryAgainButton = new JButton("Try Again");
- tryAgainButton.setFont(Toolkit.getSansFont(14, Font.PLAIN));
+ tryAgainButton.setFont(ManagerFrame.NORMAL_PLAIN);
tryAgainButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
contribDialog.makeAndShowTab(false, true);
@@ -288,9 +267,7 @@ protected void updateCategoryChooser() {
protected void filterLibraries(String category, List filters) {
- List filteredLibraries =
- contribListing.getFilteredLibraryList(category, filters);
- contributionListPanel.filterLibraries(filteredLibraries);
+ contributionListPanel.filterLibraries(category, filters);
}
@@ -353,62 +330,79 @@ protected void setFilterText(String filter) {
}
- //TODO: this is causing a lot of bugs as the hint is wrongly firing applyFilter()
class FilterField extends JTextField {
- Icon searchIcon;
List filters;
- JLabel filterLabel;
public FilterField () {
super("");
- filterLabel = new JLabel("Filter");
- filterLabel.setFont(Toolkit.getSansFont(14, Font.PLAIN));
+ JLabel filterLabel = new JLabel("Filter");
+ filterLabel.setFont(ManagerFrame.NORMAL_PLAIN);
filterLabel.setOpaque(false);
- setFont(Toolkit.getSansFont(14, Font.PLAIN));
- searchIcon = Toolkit.getLibIconX("manager/search");
- filterLabel.setIcon(searchIcon);
+ setFont(ManagerFrame.NORMAL_PLAIN);
+ filterLabel.setIcon(Toolkit.getLibIconX("manager/search"));
+ JButton removeFilter = Toolkit.createIconButton("manager/remove");
+ removeFilter.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2));
+ removeFilter.setBorderPainted(false);
+ removeFilter.setContentAreaFilled(false);
+ removeFilter.setCursor(Cursor.getDefaultCursor());
+ removeFilter.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setText("");
+ filterField.requestFocusInWindow();
+ }
+ });
//searchIcon = new ImageIcon(java.awt.Toolkit.getDefaultToolkit().getImage("NSImage://NSComputerTemplate"));
setOpaque(false);
- //setBorder(BorderFactory.createMatteBorder(0, 33, 0, 0, searchIcon));
GroupLayout fl = new GroupLayout(this);
setLayout(fl);
- fl.setHorizontalGroup(fl.createSequentialGroup().addComponent(filterLabel));
+ fl.setHorizontalGroup(fl
+ .createSequentialGroup()
+ .addComponent(filterLabel)
+ .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED,
+ GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
+ .addComponent(removeFilter));
+
fl.setVerticalGroup(fl.createSequentialGroup()
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED,
GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
+ .addGroup(fl.createParallelGroup()
.addComponent(filterLabel)
+ .addComponent(removeFilter))
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED,
GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE));
+ removeFilter.setVisible(false);
filters = new ArrayList();
addFocusListener(new FocusListener() {
public void focusLost(FocusEvent focusEvent) {
if (getText().isEmpty()) {
-// setBorder(BorderFactory.createMatteBorder(0, 33, 0, 0, searchIcon));
filterLabel.setVisible(true);
}
}
public void focusGained(FocusEvent focusEvent) {
-// setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 0));
filterLabel.setVisible(false);
}
});
getDocument().addDocumentListener(new DocumentListener() {
public void removeUpdate(DocumentEvent e) {
+ removeFilter.setVisible(!getText().isEmpty());
applyFilter();
}
public void insertUpdate(DocumentEvent e) {
+ removeFilter.setVisible(!getText().isEmpty());
applyFilter();
}
public void changedUpdate(DocumentEvent e) {
+ removeFilter.setVisible(!getText().isEmpty());
applyFilter();
}
});
@@ -444,10 +438,15 @@ protected void updateAll() {
for (DetailPanel detailPanel : collection) {
detailPanel.update();
}
+ contributionListPanel.model.fireTableDataChanged();
}
protected boolean hasUpdates() {
return contributionListPanel.getRowCount() > 0;
}
+
+ public boolean filterHasFocus() {
+ return filterField != null && filterField.hasFocus();
+ }
}
diff --git a/app/src/processing/app/contrib/ContributionType.java b/app/src/processing/app/contrib/ContributionType.java
index 197aaa4f5c..2ea1dd2d78 100644
--- a/app/src/processing/app/contrib/ContributionType.java
+++ b/app/src/processing/app/contrib/ContributionType.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013 The Processing Foundation
+ Copyright (c) 2013-20 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -172,7 +172,7 @@ public File getSketchbookFolder() {
}
- boolean isCandidate(File potential) {
+ public boolean isCandidate(File potential) {
return (potential.isDirectory() &&
new File(potential, toString()).exists() &&
!isTempFolderName(potential.getName()));
@@ -237,7 +237,7 @@ LocalContribution load(Base base, File folder) {
ArrayList listContributions(Editor editor) {
- ArrayList contribs = new ArrayList();
+ ArrayList contribs = new ArrayList<>();
switch (this) {
case LIBRARY:
contribs.addAll(editor.getMode().contribLibraries);
diff --git a/app/src/processing/app/contrib/DetailPanel.java b/app/src/processing/app/contrib/DetailPanel.java
index 4cf327165d..e36978e114 100644
--- a/app/src/processing/app/contrib/DetailPanel.java
+++ b/app/src/processing/app/contrib/DetailPanel.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013-15 The Processing Foundation
+ Copyright (c) 2013-16 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -48,16 +48,6 @@
import processing.app.ui.Toolkit;
-// TODO clean up accessors (too many cases of several de-references for basic tasks
-// TODO hyperlink listener seems far too complicated for what it does,
-// and why have a 'null' version rather than detecting whether selected or not
-// TODO don't add/remove listeners for install/remove/undo based on function,
-// just keep track of current behavior and call that. too many things can go wrong.
-// TODO get rid of huge actionPerformed() blocks with anonymous classes,
-// just make handleInstall(), etc methods and a single actionPerformed
-// for the button that calls the necessary behavior (see prev note)
-// TODO switch to the built-in fonts (available from Toolkit) rather than verdana et al
-
/**
* Panel that expands and gives a brief overview of a library when clicked.
*/
@@ -81,7 +71,7 @@ class DetailPanel extends JPanel {
private final ListPanel listPanel;
private final ContributionListing contribListing = ContributionListing.getInstance();
- static final int BUTTON_WIDTH = 100;
+ static final int BUTTON_WIDTH = Toolkit.zoom(100);
static Icon foundationIcon;
/**
@@ -94,9 +84,13 @@ public Contribution getContrib() {
return contrib;
}
+ private LocalContribution getLocalContrib() {
+ return (LocalContribution) contrib;
+ }
+
private boolean alreadySelected;
private boolean enableHyperlinks;
- private HyperlinkListener conditionalHyperlinkOpener;
+ //private HyperlinkListener conditionalHyperlinkOpener;
private JTextPane descriptionPane;
private JLabel notificationLabel;
private JButton updateButton;
@@ -104,11 +98,13 @@ public Contribution getContrib() {
private JButton installRemoveButton;
private JPopupMenu contextMenu;
private JMenuItem openFolder;
+
private JPanel barButtonCardPane;
+ private CardLayout barButtonCardLayout;
- private ActionListener removeActionListener;
- private ActionListener installActionListener;
- private ActionListener undoActionListener;
+ static private final String installText = Language.text("contrib.install");
+ static private final String removeText = Language.text("contrib.remove");
+ static private final String undoText = Language.text("contrib.undo");
boolean updateInProgress;
boolean installInProgress;
@@ -125,57 +121,6 @@ public Contribution getContrib() {
listPanel = contributionListPanel;
barButtonCardPane = new JPanel();
- enableHyperlinks = false;
- alreadySelected = false;
- conditionalHyperlinkOpener = new HyperlinkListener() {
- public void hyperlinkUpdate(HyperlinkEvent e) {
- if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
- if (enableHyperlinks) {
- if (e.getURL() != null) {
- Platform.openURL(e.getURL().toString());
- }
- }
- }
- }
- };
-
- installActionListener = new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- install();
- }
- };
-
- undoActionListener = new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- listPanel.contributionTab.statusPanel.clearMessage();
- if (contrib instanceof LocalContribution) {
- LocalContribution installed = (LocalContribution) contrib;
- installed.setDeletionFlag(false);
- contribListing.replaceContribution(contrib, contrib); // ??
- Iterator contribsListIter = contribListing.allContributions.iterator();
- boolean toBeRestarted = false;
- while (contribsListIter.hasNext()) {
- Contribution contribElement = contribsListIter.next();
- if (contrib.getType().equals(contribElement.getType())) {
- if (contribElement.isDeletionFlagged() ||
- contribElement.isUpdateFlagged()) {
- toBeRestarted = !toBeRestarted;
- break;
- }
- }
- }
- // TODO: remove or uncomment if the button was added
- //listPanel.contributionTab.restartButton.setVisible(toBeRestarted);
- }
- }
- };
-
- removeActionListener = new ActionListener() {
- public void actionPerformed(ActionEvent arg) {
- remove();
- }
- };
-
contextMenu = new JPopupMenu();
openFolder = new JMenuItem("Open Folder");
openFolder.addActionListener(new ActionListener() {
@@ -200,9 +145,8 @@ public void mousePressed(MouseEvent e) {
if (contrib.isCompatible(Base.getRevision())) {
listPanel.setSelectedPanel(DetailPanel.this);
} else {
- final String msg = contrib.getName()
- + " is not compatible with this version of Processing";
- listPanel.contributionTab.statusPanel.setErrorMessage(msg);
+ setErrorMessage(contrib.getName() +
+ " cannot be used with this version of Processing");
}
}
});
@@ -223,31 +167,38 @@ private void addPaneComponents() {
margin.bottom = 0;
descriptionPane.setMargin(margin);
descriptionPane.setContentType("text/html");
- setTextStyle(descriptionPane);
+ setTextStyle(descriptionPane, "0.95em");
descriptionPane.setOpaque(false);
if (UIManager.getLookAndFeel().getID().equals("Nimbus")) {
descriptionPane.setBackground(new Color(0, 0, 0, 0));
}
-// stripTextSelectionListeners(descriptionBlock);
descriptionPane.setBorder(new EmptyBorder(4, 7, 7, 7));
descriptionPane.setHighlighter(null);
+ descriptionPane.addHyperlinkListener(new HyperlinkListener() {
+ public void hyperlinkUpdate(HyperlinkEvent e) {
+ if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ // for 3.2.3, added the isSelected() prompt here, rather than
+ // adding/removing the listener repeatedly
+ if (isSelected()) {
+ if (enableHyperlinks && e.getURL() != null) {
+ Platform.openURL(e.getURL().toString());
+ }
+ }
+ }
+ }
+ });
+
add(descriptionPane, BorderLayout.CENTER);
- JPanel updateBox = new JPanel(); //new BoxLayout(filterPanel, BoxLayout.X_AXIS)
+ JPanel updateBox = new JPanel();
updateBox.setLayout(new BorderLayout());
notificationLabel = new JLabel();
notificationLabel.setInheritsPopupMenu(true);
notificationLabel.setVisible(false);
notificationLabel.setOpaque(false);
- // not needed after changing to JLabel
-// notificationBlock.setContentType("text/html");
-// notificationBlock.setHighlighter(null);
-// setTextStyle(notificationBlock);
-// notificationLabel.setFont(new Font("Verdana", Font.ITALIC, 10));
- notificationLabel.setFont(Toolkit.getSansFont(12, Font.PLAIN));
-// stripTextSelectionListeners(notificationBlock);
+ notificationLabel.setFont(ManagerFrame.SMALL_PLAIN);
{
updateButton = new JButton("Update");
@@ -276,13 +227,14 @@ public void actionPerformed(ActionEvent e) {
rightPane.setInheritsPopupMenu(true);
rightPane.setOpaque(false);
rightPane.setLayout(new BoxLayout(rightPane, BoxLayout.Y_AXIS));
- rightPane.setMinimumSize(new Dimension(DetailPanel.BUTTON_WIDTH, 1));
+ rightPane.setMinimumSize(new Dimension(BUTTON_WIDTH, 1));
add(rightPane, BorderLayout.EAST);
- barButtonCardPane.setLayout(new CardLayout());
+ barButtonCardLayout = new CardLayout();
+ barButtonCardPane.setLayout(barButtonCardLayout);
barButtonCardPane.setInheritsPopupMenu(true);
barButtonCardPane.setOpaque(false);
- barButtonCardPane.setMinimumSize(new Dimension(DetailPanel.BUTTON_WIDTH, 1));
+ barButtonCardPane.setMinimumSize(new Dimension(BUTTON_WIDTH, 1));
{
installProgressBar = new JProgressBar();
@@ -290,7 +242,7 @@ public void actionPerformed(ActionEvent e) {
installProgressBar.setStringPainted(true);
resetInstallProgressBarState();
Dimension dim =
- new Dimension(DetailPanel.BUTTON_WIDTH,
+ new Dimension(BUTTON_WIDTH,
installProgressBar.getPreferredSize().height);
installProgressBar.setPreferredSize(dim);
installProgressBar.setMaximumSize(dim);
@@ -301,9 +253,21 @@ public void actionPerformed(ActionEvent e) {
installRemoveButton = new JButton(" ");
installRemoveButton.setInheritsPopupMenu(true);
+ installRemoveButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ String mode = installRemoveButton.getText();
+ if (mode.equals(installText)) {
+ install();
+ } else if (mode.equals(removeText)) {
+ remove();
+ } else if (mode.equals(undoText)) {
+ undo();
+ }
+ }
+ });
Dimension installButtonDimensions = installRemoveButton.getPreferredSize();
- installButtonDimensions.width = DetailPanel.BUTTON_WIDTH;
+ installButtonDimensions.width = BUTTON_WIDTH;
installRemoveButton.setPreferredSize(installButtonDimensions);
installRemoveButton.setMaximumSize(installButtonDimensions);
installRemoveButton.setMinimumSize(installButtonDimensions);
@@ -312,7 +276,6 @@ public void actionPerformed(ActionEvent e) {
JPanel barPane = new JPanel();
barPane.setOpaque(false);
-// barPane.add(installProgressBar);
JPanel buttonPane = new JPanel();
buttonPane.setOpaque(false);
@@ -320,15 +283,14 @@ public void actionPerformed(ActionEvent e) {
barButtonCardPane.add(buttonPane, BUTTON_CONSTRAINT);
barButtonCardPane.add(barPane, PROGRESS_BAR_CONSTRAINT);
-
- ((CardLayout) barButtonCardPane.getLayout()).show(barButtonCardPane, BUTTON_CONSTRAINT);
+ barButtonCardLayout.show(barButtonCardPane, BUTTON_CONSTRAINT);
rightPane.add(barButtonCardPane);
// Set the minimum size of this pane to be the sum of the height of the
// progress bar and install button
Dimension dim =
- new Dimension(DetailPanel.BUTTON_WIDTH,
+ new Dimension(BUTTON_WIDTH,
installRemoveButton.getPreferredSize().height);
rightPane.setMinimumSize(dim);
rightPane.setPreferredSize(dim);
@@ -352,10 +314,9 @@ private void reorganizePaneComponents() {
rightPane.setInheritsPopupMenu(true);
rightPane.setOpaque(false);
rightPane.setLayout(new BoxLayout(rightPane, BoxLayout.Y_AXIS));
- rightPane.setMinimumSize(new Dimension(DetailPanel.BUTTON_WIDTH, 1));
+ rightPane.setMinimumSize(new Dimension(BUTTON_WIDTH, 1));
add(rightPane, BorderLayout.EAST);
-
if (updateButton.isVisible() && !removeInProgress && !contrib.isDeletionFlagged()) {
JPanel updateRemovePanel = new JPanel();
updateRemovePanel.setLayout(new FlowLayout());
@@ -368,21 +329,18 @@ private void reorganizePaneComponents() {
JPanel barPane = new JPanel();
barPane.setOpaque(false);
barPane.setInheritsPopupMenu(true);
-// barPane.add(installProgressBar);
rightPane.add(barPane);
- if (updateInProgress)
- ((CardLayout) barButtonCardPane.getLayout()).show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT);
-
- }
- else {
+ if (updateInProgress) {
+ barButtonCardLayout.show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT);
+ }
+ } else {
updateBox.add(updateButton, BorderLayout.EAST);
barButtonCardPane.removeAll();
JPanel barPane = new JPanel();
barPane.setOpaque(false);
barPane.setInheritsPopupMenu(true);
-// barPane.add(installProgressBar);
JPanel buttonPane = new JPanel();
buttonPane.setOpaque(false);
@@ -391,34 +349,33 @@ private void reorganizePaneComponents() {
barButtonCardPane.add(buttonPane, BUTTON_CONSTRAINT);
barButtonCardPane.add(barPane, PROGRESS_BAR_CONSTRAINT);
- if (installInProgress || removeInProgress || updateInProgress)
- ((CardLayout) barButtonCardPane.getLayout()).show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT);
- else
- ((CardLayout) barButtonCardPane.getLayout()).show(barButtonCardPane, BUTTON_CONSTRAINT);
-
+ if (installInProgress || removeInProgress || updateInProgress) {
+ barButtonCardLayout.show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT);
+ } else {
+ barButtonCardLayout.show(barButtonCardPane, BUTTON_CONSTRAINT);
+ }
rightPane.add(barButtonCardPane);
}
- Dimension d = installProgressBar.getPreferredSize();
- Dimension d2 = installRemoveButton.getPreferredSize();
- d.width = DetailPanel.BUTTON_WIDTH;
- d.height = Math.max(d.height,d2.height);
- rightPane.setMinimumSize(d);
- rightPane.setPreferredSize(d);
+ Dimension progressDim = installProgressBar.getPreferredSize();
+ Dimension installDim = installRemoveButton.getPreferredSize();
+ progressDim.width = BUTTON_WIDTH;
+ progressDim.height = Math.max(progressDim.height, installDim.height);
+ rightPane.setMinimumSize(progressDim);
+ rightPane.setPreferredSize(progressDim);
}
private void setExpandListener(Component component,
MouseListener expandListener) {
- if (component instanceof JButton) {
- // This will confuse the button, causing it to stick on OS X
- // https://github.com/processing/processing/issues/3172
- return;
- }
- component.addMouseListener(expandListener);
- if (component instanceof Container) {
- for (Component child : ((Container) component).getComponents()) {
- setExpandListener(child, expandListener);
+ // If it's a JButton, adding the listener will make this stick on OS X
+ // https://github.com/processing/processing/issues/3172
+ if (!(component instanceof JButton)) {
+ component.addMouseListener(expandListener);
+ if (component instanceof Container) {
+ for (Component child : ((Container) component).getComponents()) {
+ setExpandListener(child, expandListener);
+ }
}
}
}
@@ -427,8 +384,9 @@ private void setExpandListener(Component component,
private void blurContributionPanel(Component component) {
component.setFocusable(false);
component.setEnabled(false);
- if (component instanceof JComponent)
+ if (component instanceof JComponent) {
((JComponent) component).setToolTipText(INCOMPATIBILITY_BLUR);
+ }
if (component instanceof Container) {
for (Component child : ((Container) component).getComponents()) {
blurContributionPanel(child);
@@ -448,7 +406,7 @@ public void setContribution(Contribution contrib) {
}
// Avoid ugly synthesized bold
- Font boldFont = Toolkit.getSansFont(12, Font.BOLD);
+ Font boldFont = ManagerFrame.SMALL_BOLD;
String fontFace = "";
StringBuilder desc = new StringBuilder();
@@ -460,9 +418,9 @@ public void setContribution(Contribution contrib) {
}
desc.append(" ");
- String version = contrib.getPrettyVersion();
- if (version != null) {
- desc.append(version);
+ String prettyVersion = contrib.getPrettyVersion();
+ if (prettyVersion != null) {
+ desc.append(prettyVersion);
}
desc.append("
");
@@ -493,8 +451,9 @@ public void setContribution(Contribution contrib) {
if (lastUpdatedUTC != 0) {
DateFormat dateFormatter = DateFormat.getDateInstance(DateFormat.MEDIUM);
Date lastUpdatedDate = new Date(lastUpdatedUTC);
- if (version != null && !version.isEmpty())
+ if (prettyVersion != null) {
desc.append(", ");
+ }
desc.append("Last Updated on " + dateFormatter.format(lastUpdatedDate));
}
@@ -510,7 +469,7 @@ public void setContribution(Contribution contrib) {
// versionText.append("To finish an update, reinstall this contribution after restarting.");
;
} else {
- String latestVersion = contribListing.getLatestVersion(contrib);
+ String latestVersion = contribListing.getLatestPrettyVersion(contrib);
if (latestVersion != null) {
versionText.append("New version (" + latestVersion + ") available.");
} else {
@@ -530,26 +489,19 @@ public void setContribution(Contribution contrib) {
updateButton.setVisible((contribListing.hasUpdates(contrib) && !contrib.isUpdateFlagged() && !contrib.isDeletionFlagged()) || updateInProgress);
}
- installRemoveButton.removeActionListener(installActionListener);
- installRemoveButton.removeActionListener(removeActionListener);
- installRemoveButton.removeActionListener(undoActionListener);
-
if (contrib.isDeletionFlagged()) {
- installRemoveButton.addActionListener(undoActionListener);
- installRemoveButton.setText(Language.text("contrib.undo"));
+ installRemoveButton.setText(undoText);
+
} else if (contrib.isInstalled()) {
- installRemoveButton.addActionListener(removeActionListener);
- installRemoveButton.setText(Language.text("contrib.remove"));
+ installRemoveButton.setText(removeText);
installRemoveButton.setVisible(true);
installRemoveButton.setEnabled(!contrib.isUpdateFlagged());
reorganizePaneComponents();
} else {
- installRemoveButton.addActionListener(installActionListener);
- installRemoveButton.setText(Language.text("contrib.install"));
+ installRemoveButton.setText(installText);
}
contextMenu.removeAll();
-
if (contrib.isInstalled()) {
contextMenu.add(openFolder);
setComponentPopupMenu(contextMenu);
@@ -562,15 +514,33 @@ public void setContribution(Contribution contrib) {
}
}
+
private void installContribution(AvailableContribution info) {
if (info.link == null) {
- listPanel.contributionTab.statusPanel.setErrorMessage(Language.interpolate("contrib.unsupported_operating_system", info.getType()));
+ setErrorMessage(Language.interpolate("contrib.unsupported_operating_system", info.getType()));
} else {
installContribution(info, info.link);
}
}
+ private void finishInstall(boolean error) {
+ resetInstallProgressBarState();
+ installRemoveButton.setEnabled(!contrib.isUpdateFlagged());
+
+ if (error) {
+ setErrorMessage(Language.text("contrib.download_error"));
+ }
+ barButtonCardLayout.show(barButtonCardPane, BUTTON_CONSTRAINT);
+ installInProgress = false;
+ if (updateInProgress) {
+ updateInProgress = false;
+ }
+ updateButton.setVisible(contribListing.hasUpdates(contrib) && !contrib.isUpdateFlagged());
+ setSelected(true);
+ }
+
+
private void installContribution(AvailableContribution ad, String url) {
installRemoveButton.setEnabled(false);
@@ -580,38 +550,17 @@ private void installContribution(AvailableContribution ad, String url) {
ContribProgressBar downloadProgress = new ContribProgressBar(installProgressBar) {
public void finishedAction() {
- // Finished downloading library
+ // nothing?
}
public void cancelAction() {
- // Finished installing library
- resetInstallProgressBarState();
- installRemoveButton.setEnabled(!contrib.isUpdateFlagged());
-
- ((CardLayout) barButtonCardPane.getLayout()).show(barButtonCardPane, BUTTON_CONSTRAINT);
- installInProgress = false;
- if(updateInProgress)
- updateInProgress = !updateInProgress;
- updateButton.setVisible(contribListing.hasUpdates(contrib) && !contrib.isUpdateFlagged());
- setSelected(true);
+ finishInstall(false);
}
};
ContribProgressBar installProgress = new ContribProgressBar(installProgressBar) {
public void finishedAction() {
- // Finished installing library
- resetInstallProgressBarState();
- installRemoveButton.setEnabled(!contrib.isUpdateFlagged());
-
- if (isError()) {
- listPanel.contributionTab.statusPanel.setErrorMessage(Language.text("contrib.download_error"));
- }
- ((CardLayout) barButtonCardPane.getLayout()).show(barButtonCardPane, BUTTON_CONSTRAINT);
- installInProgress = false;
- if(updateInProgress)
- updateInProgress = !updateInProgress;
- updateButton.setVisible(contribListing.hasUpdates(contrib) && !contrib.isUpdateFlagged());
- setSelected(true);
+ finishInstall(isError());
}
public void cancelAction() {
@@ -619,35 +568,19 @@ public void cancelAction() {
}
};
- ContributionManager.downloadAndInstall(listPanel.contributionTab.editor.getBase(),
- downloadUrl, ad,
+ ContributionManager.downloadAndInstall(getBase(), downloadUrl, ad,
downloadProgress, installProgress,
- listPanel.contributionTab.statusPanel);
+ getStatusPanel());
} catch (MalformedURLException e) {
Messages.showWarning(Language.text("contrib.errors.install_failed"),
Language.text("contrib.errors.malformed_url"), e);
// not sure why we'd re-enable the button if it had an error...
-// installRemoveButton.setEnabled(true);
+ //installRemoveButton.setEnabled(true);
}
}
- // This doesn't actually seem to work?
- /*
- static void stripTextSelectionListeners(JEditorPane editorPane) {
- for (MouseListener listener : editorPane.getMouseListeners()) {
- String className = listener.getClass().getName();
- if (className.endsWith("MutableCaretEvent") ||
- className.endsWith("DragListener") ||
- className.endsWith("BasicCaret")) {
- editorPane.removeMouseListener(listener);
- }
- }
- }
- */
-
-
protected void resetInstallProgressBarState() {
installProgressBar.setString(Language.text("contrib.progress.starting"));
installProgressBar.setIndeterminate(false);
@@ -656,9 +589,11 @@ protected void resetInstallProgressBarState() {
}
+ /*
static final HyperlinkListener NULL_HYPERLINK_LISTENER = new HyperlinkListener() {
public void hyperlinkUpdate(HyperlinkEvent e) { }
};
+ */
/**
@@ -679,6 +614,7 @@ public void setSelected(boolean isSelected) {
installRemoveButton.setEnabled(installRemoveButton.getText().equals(Language.text("contrib.remove")) ||!contribListing.hasListDownloadFailed());
reorganizePaneComponents();
+ /*
descriptionPane.removeHyperlinkListener(NULL_HYPERLINK_LISTENER);
descriptionPane.removeHyperlinkListener(conditionalHyperlinkOpener);
if (isSelected()) {
@@ -688,6 +624,7 @@ public void setSelected(boolean isSelected) {
descriptionPane.addHyperlinkListener(NULL_HYPERLINK_LISTENER);
// descriptionPane.setEditable(true);
}
+ */
// Update style of hyperlinks
setSelectionStyle(descriptionPane, isSelected());
@@ -762,29 +699,30 @@ static String toHtmlLinks(String stringIn) {
* Sets coloring based on whether installed or not;
* also makes ugly blue HTML links into the specified color (black).
*/
- static void setForegroundStyle(JTextPane textPane, boolean installed, boolean selected) {
+ static void setForegroundStyle(JTextPane textPane,
+ boolean installed, boolean selected) {
Document doc = textPane.getDocument();
if (doc instanceof HTMLDocument) {
HTMLDocument html = (HTMLDocument) doc;
StyleSheet stylesheet = html.getStyleSheet();
- String c = (installed && !selected) ? "#555555" : "#000000"; // slightly grayed when installed
-// String c = "#000000"; // just make them both black
+ // slightly grayed when installed
+ String c = (installed && !selected) ? "#555555" : "#000000";
stylesheet.addRule("body { color:" + c + "; }");
stylesheet.addRule("a { color:" + c + "; }");
}
}
- static void setTextStyle(JTextPane textPane) {
+ static void setTextStyle(JTextPane textPane, String fontSize) {
Document doc = textPane.getDocument();
if (doc instanceof HTMLDocument) {
HTMLDocument html = (HTMLDocument) doc;
StyleSheet stylesheet = html.getStyleSheet();
stylesheet.addRule("body { " +
" margin: 0; padding: 0;" +
- " font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;" +
- " font-size: 100%;" + "font-size: 0.95em; " +
+ " font-family: " + Toolkit.getSansFontName() + ", Arial, Helvetica, sans-serif;" +
+ " font-size: 100%;" + "font-size: " + fontSize + "; " +
"}");
}
}
@@ -805,9 +743,9 @@ static void setSelectionStyle(JTextPane textPane, boolean selected) {
public void install() {
- listPanel.contributionTab.statusPanel.clearMessage();
+ clearStatusMessage();
installInProgress = true;
- ((CardLayout) barButtonCardPane.getLayout()).show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT);
+ barButtonCardLayout.show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT);
if (contrib instanceof AvailableContribution) {
installContribution((AvailableContribution) contrib);
contribListing.replaceContribution(contrib, contrib);
@@ -816,131 +754,169 @@ public void install() {
public void update() {
-
- listPanel.contributionTab.statusPanel.clearMessage();
+ clearStatusMessage();
updateInProgress = true;
if (contrib.getType().requiresRestart()) {
installRemoveButton.setEnabled(false);
installProgressBar.setVisible(true);
installProgressBar.setIndeterminate(true);
- ContribProgressBar progress = new ContribProgressBar(installProgressBar) {
- public void finishedAction() {
- // Finished uninstalling the library
- resetInstallProgressBarState();
- updateButton.setEnabled(false);
- AvailableContribution ad =
- contribListing.getAvailableContribution(contrib);
- String url = ad.link;
- installContribution(ad, url);
- }
-
- @Override
- public void cancelAction() {
- resetInstallProgressBarState();
- listPanel.contributionTab.statusPanel.setMessage("");
- updateInProgress = false;
- installRemoveButton.setEnabled(true);
- if (contrib.isDeletionFlagged()) {
- ((LocalContribution)contrib).setUpdateFlag(true);
- ((LocalContribution)contrib).setDeletionFlag(false);
- contribListing.replaceContribution(contrib,contrib);
- }
-
- boolean isModeActive = false;
- if (contrib.getType() == ContributionType.MODE) {
- ModeContribution m = (ModeContribution) contrib;
- //Iterator iter = listPanel.contribManager.editor.getBase().getEditors().iterator();
- //while (iter.hasNext()) {
- // TODO there's gotta be a cleaner way to do this accessor
- Base base = listPanel.contributionTab.editor.getBase();
- for (Editor e : base.getEditors()) {
- //Editor e = iter.next();
- if (e.getMode().equals(m.getMode())) {
- isModeActive = true;
- break;
- }
- }
- }
- if (isModeActive) {
- updateButton.setEnabled(true);
- } else {
- // TODO: remove or uncomment if the button was added
- //listPanel.contributionTab.restartButton.setVisible(true);
- }
- }
- };
- ((LocalContribution) contrib)
- .removeContribution(listPanel.contributionTab.editor.getBase(),
- progress, listPanel.contributionTab.statusPanel);
+ ContribProgressBar progress = new UpdateProgressBar(installProgressBar);
+ getLocalContrib().removeContribution(getBase(), progress, getStatusPanel());
} else {
updateButton.setEnabled(false);
installRemoveButton.setEnabled(false);
- AvailableContribution ad = contribListing.getAvailableContribution(contrib);
+ AvailableContribution ad =
+ contribListing.getAvailableContribution(contrib);
+ installContribution(ad, ad.link);
+ }
+ }
+
+
+ class UpdateProgressBar extends ContribProgressBar {
+ public UpdateProgressBar(JProgressBar progressBar) {
+ super(progressBar);
+ }
+
+ public void finishedAction() {
+ resetInstallProgressBarState();
+ updateButton.setEnabled(false);
+ AvailableContribution ad =
+ contribListing.getAvailableContribution(contrib);
String url = ad.link;
installContribution(ad, url);
}
+ @Override
+ public void cancelAction() {
+ resetInstallProgressBarState();
+ //listPanel.contributionTab.statusPanel.setMessage(""); // same as clear?
+ clearStatusMessage();
+ updateInProgress = false;
+ installRemoveButton.setEnabled(true);
+ if (contrib.isDeletionFlagged()) {
+ getLocalContrib().setUpdateFlag(true);
+ getLocalContrib().setDeletionFlag(false);
+ contribListing.replaceContribution(contrib, contrib);
+ }
+
+ if (isModeActive(contrib)) {
+ updateButton.setEnabled(true);
+ } else {
+ // TODO: remove or uncomment if the button was added
+ //listPanel.contributionTab.restartButton.setVisible(true);
+ }
+ }
}
public void remove() {
-
- listPanel.contributionTab.statusPanel.clearMessage();
+ clearStatusMessage();
if (contrib.isInstalled() && contrib instanceof LocalContribution) {
removeInProgress = true;
- ((CardLayout) barButtonCardPane.getLayout()).show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT);
+ barButtonCardLayout.show(barButtonCardPane, PROGRESS_BAR_CONSTRAINT);
updateButton.setEnabled(false);
installRemoveButton.setEnabled(false);
installProgressBar.setVisible(true);
installProgressBar.setIndeterminate(true);
- ContribProgressBar monitor = new ContribProgressBar(installProgressBar) {
- public void finishedAction() {
- // Finished uninstalling the library
- resetInstallProgressBarState();
- removeInProgress = false;
- installRemoveButton.setEnabled(true);
+ ContribProgressBar monitor = new RemoveProgressBar(installProgressBar);
+ getLocalContrib().removeContribution(getBase(), monitor, getStatusPanel());
+ }
+ }
- reorganizePaneComponents();
- setSelected(true); // Needed for smooth working. Dunno why, though...
- }
- public void cancelAction() {
- resetInstallProgressBarState();
- removeInProgress = false;
- installRemoveButton.setEnabled(true);
-
- reorganizePaneComponents();
- setSelected(true);
-
- ContributionTab contributionTab = listPanel.contributionTab;
- boolean isModeActive = false;
- if (contrib.getType() == ContributionType.MODE) {
- ModeContribution m = (ModeContribution) contrib;
- // TODO there's gotta be a cleaner way to do this accessor
- for (Editor e : contributionTab.editor.getBase().getEditors()) {
- //Iterator iter = listPanel.contribManager.editor.getBase().getEditors().iterator();
- //while (iter.hasNext()) {
- //Editor e = iter.next();
- if (e.getMode().equals(m.getMode())) {
- isModeActive = true;
- break;
- }
- }
- }
- if (isModeActive) {
- updateButton.setEnabled(true);
- } else {
- // TODO: remove or uncomment if the button was added
- //contributionTab.restartButton.setVisible(true);
+ class RemoveProgressBar extends ContribProgressBar {
+ public RemoveProgressBar(JProgressBar progressBar) {
+ super(progressBar);
+ }
+
+ private void preAction() {
+ resetInstallProgressBarState();
+ removeInProgress = false;
+ installRemoveButton.setEnabled(true);
+ reorganizePaneComponents();
+ setSelected(true); // Needed for smooth working. Dunno why, though...
+ }
+
+ public void finishedAction() {
+ // Finished uninstalling the library
+ preAction();
+ }
+
+ public void cancelAction() {
+ preAction();
+
+ if (isModeActive(contrib)) {
+ updateButton.setEnabled(true);
+ } else {
+ // TODO: remove or uncomment if the button was added
+ //contributionTab.restartButton.setVisible(true);
+ }
+ }
+ }
+
+
+ private void undo() {
+ clearStatusMessage();
+ if (contrib instanceof LocalContribution) {
+ LocalContribution installed = getLocalContrib();
+ installed.setDeletionFlag(false);
+ contribListing.replaceContribution(contrib, contrib); // ??
+ Iterator contribsListIter = contribListing.allContributions.iterator();
+ boolean toBeRestarted = false;
+ while (contribsListIter.hasNext()) {
+ Contribution contribElement = contribsListIter.next();
+ if (contrib.getType().equals(contribElement.getType())) {
+ if (contribElement.isDeletionFlagged() ||
+ contribElement.isUpdateFlagged()) {
+ toBeRestarted = !toBeRestarted;
+ break;
}
}
- };
- ContributionTab contributionTab = listPanel.contributionTab;
- LocalContribution localContrib = (LocalContribution) contrib;
- localContrib.removeContribution(contributionTab.editor.getBase(), monitor, contributionTab.statusPanel);
+ }
+ // TODO: remove or uncomment if the button was added
+ //listPanel.contributionTab.restartButton.setVisible(toBeRestarted);
+ }
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ // Can't be called from the constructor because the path isn't set all the
+ // way down. However, it's not Base changes over time. More importantly,
+ // though, is that the functions being called in Base are somewhat suspect
+ // since they're contribution-related, and should perhaps live closer.
+ private Base getBase() {
+ return listPanel.contributionTab.editor.getBase();
+ }
+
+
+ private boolean isModeActive(Contribution contrib) {
+ if (contrib.getType() == ContributionType.MODE) {
+ ModeContribution m = (ModeContribution) contrib;
+ for (Editor e : getBase().getEditors()) {
+ if (e.getMode().equals(m.getMode())) {
+ return true;
+ }
+ }
}
+ return false;
+ }
+
+
+ private StatusPanel getStatusPanel() {
+ return listPanel.contributionTab.statusPanel; // TODO this is gross [fry]
+ }
+
+
+ private void clearStatusMessage() {
+ getStatusPanel().clearMessage();
+ }
+
+ private void setErrorMessage(String message) {
+ getStatusPanel().setErrorMessage(message);
}
}
diff --git a/app/src/processing/app/contrib/ExamplesContribution.java b/app/src/processing/app/contrib/ExamplesContribution.java
index aa7a14fb73..2e091c7216 100644
--- a/app/src/processing/app/contrib/ExamplesContribution.java
+++ b/app/src/processing/app/contrib/ExamplesContribution.java
@@ -6,7 +6,7 @@
import java.util.Map;
import processing.app.Base;
-import processing.core.PApplet;
+import processing.app.Mode;
import processing.data.StringDict;
import processing.data.StringList;
import static processing.app.contrib.ContributionType.EXAMPLES;
@@ -30,45 +30,28 @@ private ExamplesContribution(File folder) {
}
- static private StringList parseModeList(StringDict properties) {
- String unparsedModes = properties.get(MODES_PROPERTY);
-
- // Workaround for 3.0 alpha/beta bug for 3.0b2
- if ("null".equals(unparsedModes)) {
- properties.remove(MODES_PROPERTY);
- unparsedModes = null;
- }
-
- StringList outgoing = new StringList();
- if (unparsedModes != null) {
- outgoing.append(PApplet.trim(PApplet.split(unparsedModes, ',')));
- }
- return outgoing;
+ static public boolean isCompatible(Base base, StringDict props) {
+ return isCompatible(base.getActiveEditor().getMode(), props);
}
/**
* Function to determine whether or not the example present in the
* exampleLocation directory is compatible with the current mode.
- *
- * @param base
- * @param exampleFolder
- * @return true if the example is compatible with the mode of the currently
- * active editor
+ * @return true if compatible with the Mode of the currently active editor
*/
- static public boolean isCompatible(Base base, StringDict props) {
- String currentIdentifier =
- base.getActiveEditor().getMode().getIdentifier();
+ static public boolean isCompatible(Mode mode, StringDict props) {
+ String currentIdentifier = mode.getIdentifier();
StringList compatibleList = parseModeList(props);
if (compatibleList.size() == 0) {
- return true; // if no mode specified, assume compatible everywhere
- }
- for (String c : compatibleList) {
- if (c.equals(currentIdentifier)) {
- return true;
+ if (mode.requireExampleCompatibility()) {
+ // for p5js (and maybe Python), examples must specify that they work
+ return false;
}
+ // if no Mode specified, assume compatible everywhere
+ return true;
}
- return false;
+ return compatibleList.hasValue(currentIdentifier);
}
diff --git a/app/src/processing/app/contrib/ListPanel.java b/app/src/processing/app/contrib/ListPanel.java
index 3d9a4fa874..845b7178d4 100644
--- a/app/src/processing/app/contrib/ListPanel.java
+++ b/app/src/processing/app/contrib/ListPanel.java
@@ -45,28 +45,32 @@
public class ListPanel extends JPanel
implements Scrollable, ContributionListing.ChangeListener {
ContributionTab contributionTab;
- TreeMap panelByContribution = new TreeMap(ContributionListing.COMPARATOR);
- Set visibleContributions = new TreeSet(ContributionListing.COMPARATOR);
+ TreeMap panelByContribution = new TreeMap<>(ContributionListing.COMPARATOR);
private DetailPanel selectedPanel;
- protected Contribution.Filter filter;
- protected ContributionListing contribListing = ContributionListing.getInstance();
+ protected ContributionRowFilter filter;
protected JTable table;
- DefaultTableModel model;
+ protected TableRowSorter sorter;
+ ContributionTableModel model;
JScrollPane scrollPane;
static Icon upToDateIcon;
static Icon updateAvailableIcon;
static Icon incompatibleIcon;
static Icon foundationIcon;
-
- static Font plainFont;
- static Font boldFont;
- static Font headerFont;
+ static Icon downloadingIcon;
// Should this be in theme.txt? Of course! Is it? No.
static final Color HEADER_BGCOLOR = new Color(0xffEBEBEB);
+ static final Color SECTION_COLOR = new Color(0xFFf8f8f8);
+ static final Color SELECTION_COLOR = new Color(0xffe0fffd);
+ static final SectionHeaderContribution[] sections = {
+ new SectionHeaderContribution(ContributionType.LIBRARY),
+ new SectionHeaderContribution(ContributionType.MODE),
+ new SectionHeaderContribution(ContributionType.TOOL),
+ new SectionHeaderContribution(ContributionType.EXAMPLES)
+ };
public ListPanel() {
if (upToDateIcon == null) {
@@ -74,47 +78,56 @@ public ListPanel() {
updateAvailableIcon = Toolkit.getLibIconX("manager/update-available");
incompatibleIcon = Toolkit.getLibIconX("manager/incompatible");
foundationIcon = Toolkit.getLibIconX("icons/foundation", 16);
-
- plainFont = Toolkit.getSansFont(14, Font.PLAIN);
- boldFont = Toolkit.getSansFont(14, Font.BOLD);
- headerFont = Toolkit.getSansFont(12, Font.PLAIN);
+ downloadingIcon = Toolkit.getLibIconX("manager/downloading");
}
}
public ListPanel(final ContributionTab contributionTab,
- Contribution.Filter filter) {
+ final Contribution.Filter filter,
+ final boolean enableSections,
+ final ContributionColumn... columns) {
+ this();
this.contributionTab = contributionTab;
- this.filter = filter;
+ this.filter = new ContributionRowFilter(filter);
setLayout(new GridBagLayout());
setOpaque(true);
setBackground(Color.WHITE);
- model = new ContribTableModel();
+ model = new ContributionTableModel(columns);
+ model.enableSections(enableSections);
table = new JTable(model) {
@Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
- if (isRowSelected(row)) {
- c.setBackground(new Color(0xe0fffd));
+ Object rowValue = getValueAt(row, column);
+ if (rowValue instanceof SectionHeaderContribution) {
+ c.setBackground(SECTION_COLOR);
+ } else if (isRowSelected(row)) {
+ c.setBackground(SELECTION_COLOR);
} else {
c.setBackground(Color.white);
}
return c;
}
+
+ @Override
+ public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
+ if (!(getValueAt(rowIndex, columnIndex) instanceof SectionHeaderContribution)) {
+ super.changeSelection(rowIndex, columnIndex, toggle, extend);
+ }
+ }
};
// There is a space before Status
- String[] colName = { " Status", "Name", "Author" };
- model.setColumnIdentifiers(colName);
scrollPane = new JScrollPane(table);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
table.setFillsViewportHeight(true);
table.setDefaultRenderer(Contribution.class, new ContribStatusRenderer());
- table.setFont(plainFont);
- table.setRowHeight(28);
- table.setRowMargin(6);
+ table.setFont(ManagerFrame.NORMAL_PLAIN);
+ table.setRowHeight(Toolkit.zoom(28));
+ table.setRowMargin(Toolkit.zoom(6));
table.getColumnModel().setColumnMargin(0);
table.getColumnModel().getColumn(0).setMaxWidth(ManagerFrame.STATUS_WIDTH);
table.getColumnModel().getColumn(2).setMinWidth(ManagerFrame.AUTHOR_WIDTH);
@@ -134,55 +147,22 @@ public void valueChanged(ListSelectionEvent event) {
setSelectedPanel(panelByContribution.get(table.getValueAt(table
.getSelectedRow(), 0)));
// Preventing the focus to move out of filterField after typing every character
- if (!contributionTab.filterField.hasFocus()) {
+ if (!contributionTab.filterHasFocus()) {
table.requestFocusInWindow();
}
}
}
});
- TableRowSorter sorter = new TableRowSorter(table.getModel());
+ sorter = new TableRowSorter<>(model);
table.setRowSorter(sorter);
- sorter.setComparator(1, ContributionListing.COMPARATOR);
- sorter.setComparator(2, new Comparator() {
-
- @Override
- public int compare(Contribution o1, Contribution o2) {
- return getAuthorNameWithoutMarkup(o1.getAuthorList())
- .compareTo(getAuthorNameWithoutMarkup(o2.getAuthorList()));
+ sorter.setRowFilter(this.filter);
+ for (int i=0; i < model.getColumnCount(); i++) {
+ if (model.columns[i] == ContributionColumn.NAME) {
+ sorter.setSortKeys(Collections.singletonList(new SortKey(i, SortOrder.ASCENDING)));
}
- });
- sorter.setComparator(0, new Comparator() {
-
- @Override
- public int compare(Contribution o1, Contribution o2) {
- int pos1 = 0;
- if (o1.isInstalled()) {
- pos1 = 1;
- if (contribListing.hasUpdates(o1)) {
- pos1 = 2;
- }
- if (!o1.isCompatible(Base.getRevision())) {
- pos1 = 3;
- }
- } else {
- pos1 = 4;
- }
- int pos2 = 0;
- if (o2.isInstalled()) {
- pos2 = 1;
- if (contribListing.hasUpdates(o2)) {
- pos2 = 2;
- }
- if (!o2.isCompatible(Base.getRevision())) {
- pos2 = 3;
- }
- } else {
- pos2 = 4;
- }
- return pos1 - pos2;
- }
- });
+ sorter.setComparator(i, model.columns[i].getComparator());
+ }
table.getTableHeader().setDefaultRenderer(new ContribHeaderRenderer());
GroupLayout layout = new GroupLayout(this);
@@ -193,6 +173,19 @@ public int compare(Contribution o1, Contribution o2) {
table.setVisible(true);
}
+ private static int getContributionStatusRank(Contribution c) {
+ int pos = 4;
+ if (c.isInstalled()) {
+ pos = 1;
+ if (ContributionListing.getInstance().hasUpdates(c)) {
+ pos = 2;
+ }
+ if (!c.isCompatible(Base.getRevision())) {
+ pos = 3;
+ }
+ }
+ return pos;
+ }
class ContribHeaderRenderer extends DefaultTableCellRenderer {
@@ -229,7 +222,7 @@ public Component getTableCellRendererComponent(JTable table, Object value,
if (tableHeader != null) {
setForeground(tableHeader.getForeground());
}
- setFont(headerFont);
+ setFont(ManagerFrame.SMALL_PLAIN);
setIcon(getSortIcon(table, column));
setBackground(HEADER_BGCOLOR);
// if (column % 2 == 0) {
@@ -270,16 +263,11 @@ protected Icon getSortIcon(JTable table, int column) {
* @return the SortKey, or null if the column is unsorted
*/
protected SortKey getSortKey(JTable table, int column) {
- RowSorter rowSorter = table.getRowSorter();
- if (rowSorter == null) {
- return null;
- }
+ return Optional.ofNullable(table.getRowSorter())
+ .map(RowSorter::getSortKeys)
+ .map(columns -> columns.isEmpty() ? null : columns.get(0))
+ .orElse(null);
- List sortedColumns = rowSorter.getSortKeys();
- if (sortedColumns.size() > 0) {
- return (SortKey) sortedColumns.get(0);
- }
- return null;
}
}
@@ -298,123 +286,269 @@ public Component getTableCellRendererComponent(JTable table, Object value,
int column) {
Contribution contribution = (Contribution) value;
JLabel label = new JLabel();
+ ContributionColumn col = model.columns[column];
if (value == null) {
// Working on https://github.com/processing/processing/issues/3667
//System.err.println("null value seen in getTableCellRendererComponent()");
// TODO this is now working, but the underlying issue is not fixed
return label;
}
- if (column == 0) {
- Icon icon = null;
- label.setFont(plainFont);
- if (contribution.isInstalled()) {
- icon = upToDateIcon;
- if (contribListing.hasUpdates(contribution)) {
- icon = updateAvailableIcon;
- }
- if (!contribution.isCompatible(Base.getRevision())) {
- icon = incompatibleIcon;
- }
- }
- label.setIcon(icon);
- label.setHorizontalAlignment(SwingConstants.CENTER);
- if (isSelected) {
- label.setBackground(new Color(0xe0fffd));
- }
- label.setOpaque(true);
-// return table.getDefaultRenderer(Icon.class).getTableCellRendererComponent(table, icon, isSelected, false, row, column);
-
- } else if (column == 1) {
- // Generating ellipses based on fontMetrics
- String fontFace = "";
- FontMetrics fontMetrics = table.getFontMetrics(boldFont); //table.getFont());
- int colSize = table.getColumnModel().getColumn(1).getWidth();
- String sentence = contribution.getSentence();
- //int currentWidth = table.getFontMetrics(table.getFont().deriveFont(Font.BOLD)).stringWidth(contribution.getName() + " | ");
- int currentWidth = table.getFontMetrics(boldFont).stringWidth(contribution.getName() + " | ");
- int ellipsesWidth = fontMetrics.stringWidth("...");
- //String name = "" + contribution.getName();
- String name = "" + fontFace + contribution.getName();
- if (sentence == null) {
- label.setText(name + "");
- } else {
- sentence = " | " + sentence;
- currentWidth += ellipsesWidth;
- int i = 0;
- for (i = 0; i < sentence.length(); i++) {
- currentWidth += fontMetrics.charWidth(sentence.charAt(i));
- if (currentWidth >= colSize) {
- break;
- }
- }
- // Adding ellipses only if text doesn't fits into the column
- if(i != sentence.length()){
- label.setText(name + sentence.substring(0, i) + "...");
- }else {
- label.setText(name + sentence + "