diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..1327eff4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +gradlew text eol=lf \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e9a0d8fb..7bc3f503 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,31 +1,31 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. Windows 10] - - Version [e.g. 2.0.0] - -**Additional context** -Add any other context about the problem here. +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. Windows 10] + - Version [e.g. 2.0.0] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d..72718d5a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,20 +1,20 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.yml b/.github/PULL_REQUEST_TEMPLATE.yml index d743bcf2..16a414ae 100644 --- a/.github/PULL_REQUEST_TEMPLATE.yml +++ b/.github/PULL_REQUEST_TEMPLATE.yml @@ -1,15 +1,15 @@ -# Pull Requests - -Please note that we accept pull requests from anyone, but that does not mean it will be merged. - -## What kind of change does this PR introduce? -* Fix -* Feature -* Codestyle -* Refactor -* Other - -## Did this PR introduce a breaking change? -_A breaking change includes anything that breaks backwards compatibility either at compile or run time._ -* Yes, please list breaking changes -* No +# Pull Requests + +Please note that we accept pull requests from anyone, but that does not mean it will be merged. + +## What kind of change does this PR introduce? +* Fix +* Feature +* Codestyle +* Refactor +* Other + +## Did this PR introduce a breaking change? +_A breaking change includes anything that breaks backwards compatibility either at compile or run time._ +* Yes, please list breaking changes +* No diff --git a/.github/dependabot.yml b/.github/dependabot.yml index edeed94d..dcc3a543 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,12 @@ -version: 2 -updates: - - package-ecosystem: "gradle" # See documentation for possible values - directory: "/" # Location of package manifests - target-branch: "dev" - schedule: - interval: "daily" - - package-ecosystem: "github-actions" - directory: "/" # Location of package manifests - target-branch: "dev" - schedule: - interval: "weekly" +version: 2 +updates: + - package-ecosystem: "gradle" # See documentation for possible values + directory: "/" # Location of package manifests + target-branch: "dev" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + target-branch: "dev" + schedule: + interval: "weekly" diff --git a/.github/workflows/build_ci.yml b/.github/workflows/build_ci.yml index bdd8e1ce..1e0ef849 100644 --- a/.github/workflows/build_ci.yml +++ b/.github/workflows/build_ci.yml @@ -1,49 +1,49 @@ -name: Build and test with Gradle - -on: [push, pull_request] - -jobs: - test_linux: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 8 - uses: actions/setup-java@v2.1.0 - with: - distribution: adopt - java-version: 8 - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build and test with Gradle - run: ./gradlew test - - test_windows: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 8 - uses: actions/setup-java@v2.1.0 - with: - distribution: adopt - java-version: 8 - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build and test with Gradle - run: ./gradlew test - - test_mac: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 8 - uses: actions/setup-java@v2.1.0 - with: - distribution: adopt - java-version: 8 - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build and test with Gradle - run: ./gradlew test +name: Build and test with Gradle + +on: [push, pull_request] + +jobs: + test_linux: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v2.1.0 + with: + distribution: adopt + java-version: 8 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build and test with Gradle + run: ./gradlew test + + test_windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v2.1.0 + with: + distribution: adopt + java-version: 8 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build and test with Gradle + run: ./gradlew test + + test_mac: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v2.1.0 + with: + distribution: adopt + java-version: 8 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build and test with Gradle + run: ./gradlew test diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index fc2861c5..9cf31280 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -1,28 +1,28 @@ -name: "Validate Gradle Wrapper" - -on: - push: - branches: - - master - - dev - pull_request: - branches: - - master - - dev - -jobs: - validationPlugin: - name: "Validation Plugin" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: gradle/wrapper-validation-action@v1 - validationJavaSample: - name: "Validation Java Sample" - runs-on: ubuntu-latest - defaults: - run: - working-directory: demo/java - steps: - - uses: actions/checkout@v2 - - uses: gradle/wrapper-validation-action@v1 +name: "Validate Gradle Wrapper" + +on: + push: + branches: + - master + - dev + pull_request: + branches: + - master + - dev + +jobs: + validationPlugin: + name: "Validation Plugin" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 + validationJavaSample: + name: "Validation Java Sample" + runs-on: ubuntu-latest + defaults: + run: + working-directory: demo/java + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/release_ci.yml b/.github/workflows/release_ci.yml index 7f514dbe..3244c4f1 100644 --- a/.github/workflows/release_ci.yml +++ b/.github/workflows/release_ci.yml @@ -1,47 +1,47 @@ -name: Create GitHub release(s) - -on: - push: - branches: [ master, dev ] - tags: 'v*' - -jobs: - build-and-release: - if: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref != 'ref/heads/master' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 8 - uses: actions/setup-java@v2.1.0 - with: - distribution: adopt - java-version: 8 - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build release shadow jar with Gradle - run: ./gradlew -Penv=release shadowJar -x test - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - - - name: Build dev shadow jar with Gradle - run: | - SHA_SHORT="$(git rev-parse --short HEAD)" - ./gradlew -Phash=$SHA_SHORT shadowJar -x test - if: ${{ !startsWith(github.ref, 'refs/tags/v') && github.ref != 'refs/heads/master' }} - - - uses: eine/tip@master - with: - token: ${{ secrets.GITHUB_TOKEN }} - tag: 'Dev' - rm: true - files: | - EOCV-Sim/build/libs/*.jar - if: ${{ github.event_name == 'push' && github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/tags/v')}} - - - uses: softprops/action-gh-release@v1 - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - with: - files: 'EOCV-Sim/build/libs/*.jar' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +name: Create GitHub release(s) + +on: + push: + branches: [ master, dev ] + tags: 'v*' + +jobs: + build-and-release: + if: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref != 'ref/heads/master' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v2.1.0 + with: + distribution: adopt + java-version: 8 + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build release shadow jar with Gradle + run: ./gradlew -Penv=release shadowJar -x test + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + + - name: Build dev shadow jar with Gradle + run: | + SHA_SHORT="$(git rev-parse --short HEAD)" + ./gradlew -Phash=$SHA_SHORT shadowJar -x test + if: ${{ !startsWith(github.ref, 'refs/tags/v') && github.ref != 'refs/heads/master' }} + + - uses: eine/tip@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: 'Dev' + rm: true + files: | + EOCV-Sim/build/libs/*.jar + if: ${{ github.event_name == 'push' && github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/tags/v')}} + + - uses: softprops/action-gh-release@v1 + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + with: + files: 'EOCV-Sim/build/libs/*.jar' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update-gradle-wrapper.yml b/.github/workflows/update-gradle-wrapper.yml index 4608393c..528180c5 100644 --- a/.github/workflows/update-gradle-wrapper.yml +++ b/.github/workflows/update-gradle-wrapper.yml @@ -1,18 +1,18 @@ -name: Update Gradle Wrapper - -on: - schedule: - - cron: "0 6 * * MON" - -jobs: - update-gradle-wrapper: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2.3.4 - - - name: Update Gradle Wrapper - uses: gradle-update/update-gradle-wrapper-action@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - set-distribution-checksum: false +name: Update Gradle Wrapper + +on: + schedule: + - cron: "0 6 * * MON" + +jobs: + update-gradle-wrapper: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2.3.4 + + - name: Update Gradle Wrapper + uses: gradle-update/update-gradle-wrapper-action@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + set-distribution-checksum: false diff --git a/.gitignore b/.gitignore index 0c79052f..1c4b3d23 100644 --- a/.gitignore +++ b/.gitignore @@ -1,111 +1,113 @@ -# Created by https://www.toptal.com/developers/gitignore/api/intellij -# Edit at https://www.toptal.com/developers/gitignore?templates=intellij - -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -.idea/ -.gradle/ - -# 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 - -# 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/artifacts -.idea/compiler.xml -.idea/jarRepositories.xml -.idea/modules.xml -.idea/*.iml -.idea/modules -*.iml -*.ipr - -# 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 - -### Intellij Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - -# *.iml -# modules.xml -# .idea/misc.xml -# *.ipr - -# Sonarlint plugin -# https://plugins.jetbrains.com/plugin/7973-sonarlint -.idea/**/sonarlint/ - -# SonarQube Plugin -# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin -.idea/**/sonarIssues.xml - -# Markdown Navigator plugin -# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced -.idea/**/markdown-navigator.xml -.idea/**/markdown-navigator-enh.xml -.idea/**/markdown-navigator/ - -# Cache file creation bug -# See https://youtrack.jetbrains.com/issue/JBR-2257 -.idea/$CACHE_FILE$ - -# CodeStream plugin -# https://plugins.jetbrains.com/plugin/12206-codestream -.idea/codestream.xml - -# End of https://www.toptal.com/developers/gitignore/api/intellij - -!/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SkystoneDeterminationPipeline.java -!/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StageSwitchingPipeline.java -!/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StoneOrientationAnalysisPipeline.java -!/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SimpleThresholdPipeline.java - -*.log - -**/Build.java -**/build/* - -*.DS_Store +# Created by https://www.toptal.com/developers/gitignore/api/intellij +# Edit at https://www.toptal.com/developers/gitignore?templates=intellij + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +.idea/ +.gradle/ + +# 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 + +# 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/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# 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 + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# End of https://www.toptal.com/developers/gitignore/api/intellij + +!/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SkystoneDeterminationPipeline.java +!/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StageSwitchingPipeline.java +!/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StoneOrientationAnalysisPipeline.java +!/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SimpleThresholdPipeline.java + +*.log + +**/Build.java +**/build/* + +*.DS_Store + +imgui.ini \ No newline at end of file diff --git a/.replit b/.replit index ab3c8d80..49d4f9bb 100644 --- a/.replit +++ b/.replit @@ -1,2 +1,2 @@ -language = "java10" +language = "java10" run = "chmod +x gradlew && ./gradlew runSim" \ No newline at end of file diff --git a/.run/Run Simulator.run.xml b/.run/Run Simulator.run.xml index 27a12fbb..df0d196d 100644 --- a/.run/Run Simulator.run.xml +++ b/.run/Run Simulator.run.xml @@ -1,23 +1,23 @@ - - - - - - - true - true - false - - + + + + + + + true + true + false + + \ No newline at end of file diff --git a/EOCV-Sim/build.gradle b/EOCV-Sim/build.gradle index 085973c8..7b215f57 100644 --- a/EOCV-Sim/build.gradle +++ b/EOCV-Sim/build.gradle @@ -1,85 +1,89 @@ -import java.nio.file.Paths -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter - -plugins { - id 'java' - id 'org.jetbrains.kotlin.jvm' - id 'com.github.johnrengelman.shadow' version '6.1.0' - id 'maven-publish' -} - -apply from: '../build.common.gradle' - -task sourcesJar(type: Jar) { - from sourceSets.main.allJava - archiveClassifier = "sources" -} - -publishing { - publications { - mavenJava(MavenPublication) { - from components.java - artifact sourcesJar - } - } -} - -ext.kotest_version = '4.4.3' - -test { - useJUnitPlatform() -} - -apply from: '../test-logging.gradle' - -dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - - implementation 'org.openpnp:opencv:4.3.0-2' - implementation 'com.github.sarxos:webcam-capture:0.3.12' - - implementation 'com.google.code.gson:gson:2.8.7' - implementation 'io.github.classgraph:classgraph:4.8.108' - - implementation 'com.formdev:flatlaf:1.2' - implementation 'com.formdev:flatlaf-intellij-themes:1.2' - - implementation 'net.lingala.zip4j:zip4j:2.8.0' - - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-swing:$kotlinx_coroutines_version" - - testImplementation "io.kotest:kotest-runner-junit5:$kotest_version" - testImplementation "io.kotest:kotest-assertions-core:$kotest_version" -} - -task(writeBuildClassJava) { - - String date = DateTimeFormatter.ofPattern("yyyy-M-d hh:mm:ss").format(LocalDateTime.now()) - - File versionFile = Paths.get( - projectDir.absolutePath, 'src', 'main', 'java', - 'com', 'github', 'serivesmejia', 'eocvsim', 'Build.java' - ).toFile() - - versionFile.delete() - - versionFile << "package com.github.serivesmejia.eocvsim;\n" + - "\n" + - "/*\n" + - " * Autogenerated file! Do not manually edit this file. This file\n" + - " * is regenerated any time the build task is run.\n" + - " *\n" + - " * Based from PhotonVision PhotonVersion generator task\n"+ - " */\n" + - "@SuppressWarnings(\"ALL\")\n" + - "public final class Build {\n" + - " public static final String versionString = \"$version\";\n" + - " public static final String standardVersionString = \"$standardVersion\";\n" + - " public static final String buildDate = \"$date\";\n" + - " public static final boolean isDev = ${version.contains("dev")};\n" + - "}" -} - -build.dependsOn writeBuildClassJava \ No newline at end of file +import java.nio.file.Paths +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +plugins { + id 'java' + id 'org.jetbrains.kotlin.jvm' + id 'com.github.johnrengelman.shadow' version '6.1.0' + id 'maven-publish' +} + +apply from: '../build.common.gradle' + +task sourcesJar(type: Jar) { + from sourceSets.main.allJava + archiveClassifier = "sources" +} + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifact sourcesJar + } + } +} + +ext.kotest_version = '4.4.3' + +test { + useJUnitPlatform() +} + +apply from: '../test-logging.gradle' + +dependencies { + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + + implementation "org.openpnp:opencv:$opencv_version" + implementation 'com.github.sarxos:webcam-capture:0.3.12' + implementation "com.github.deltacv:AprilTagDesktop:$apriltag_plugin_version" + + implementation 'info.picocli:picocli:4.6.1' + implementation 'com.google.code.gson:gson:2.8.7' + implementation 'io.github.classgraph:classgraph:4.8.108' + + implementation 'com.formdev:flatlaf:1.2' + implementation 'com.formdev:flatlaf-intellij-themes:1.2' + + implementation 'net.lingala.zip4j:zip4j:2.8.0' + + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-swing:$kotlinx_coroutines_version" + + testImplementation "io.kotest:kotest-runner-junit5:$kotest_version" + testImplementation "io.kotest:kotest-assertions-core:$kotest_version" +} + +task(writeBuildClassJava) { + + String date = DateTimeFormatter.ofPattern("yyyy-M-d hh:mm:ss").format(LocalDateTime.now()) + + File versionFile = Paths.get( + projectDir.absolutePath, 'src', 'main', 'java', + 'com', 'github', 'serivesmejia', 'eocvsim', 'Build.java' + ).toFile() + + versionFile.delete() + + versionFile << "package com.github.serivesmejia.eocvsim;\n" + + "\n" + + "/*\n" + + " * Autogenerated file! Do not manually edit this file, as\n" + + " * it is regenerated any time the build task is run.\n" + + " *\n" + + " * Based from PhotonVision PhotonVersion generator task\n"+ + " */\n" + + "@SuppressWarnings(\"ALL\")\n" + + "public final class Build {\n" + + " public static final String versionString = \"$version\";\n" + + " public static final String standardVersionString = \"$standardVersion\";\n" + + " public static final String buildDate = \"$date\";\n" + + " public static final boolean isDev = ${version.contains("dev")};\n\n" + + " public static final String opencvVersion = \"$opencv_version\";\n" + + " public static final String apriltagPluginVersion = \"$apriltag_plugin_version\";\n" + + "}" +} + +build.dependsOn writeBuildClassJava diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt index 751f1333..7c6828d4 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt @@ -1,362 +1,389 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim - -import com.github.serivesmejia.eocvsim.config.Config -import com.github.serivesmejia.eocvsim.config.ConfigManager -import com.github.serivesmejia.eocvsim.gui.DialogFactory -import com.github.serivesmejia.eocvsim.gui.Visualizer -import com.github.serivesmejia.eocvsim.gui.dialog.FileAlreadyExists -import com.github.serivesmejia.eocvsim.input.InputSourceManager -import com.github.serivesmejia.eocvsim.output.VideoRecordingSession -import com.github.serivesmejia.eocvsim.pipeline.PipelineManager -import com.github.serivesmejia.eocvsim.tuner.TunerManager -import com.github.serivesmejia.eocvsim.util.FileFilters -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import com.github.serivesmejia.eocvsim.util.exception.MaxActiveContextsException -import com.github.serivesmejia.eocvsim.util.exception.handling.EOCVSimUncaughtExceptionHandler -import com.github.serivesmejia.eocvsim.util.extension.plus -import com.github.serivesmejia.eocvsim.util.fps.FpsLimiter -import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder -import com.github.serivesmejia.eocvsim.workspace.WorkspaceManager -import nu.pattern.OpenCV -import org.opencv.core.Size -import java.awt.Dimension -import java.io.File -import javax.swing.SwingUtilities -import javax.swing.filechooser.FileFilter -import javax.swing.filechooser.FileNameExtensionFilter -import kotlin.system.exitProcess - -class EOCVSim(val params: Parameters = Parameters()) { - - companion object { - const val VERSION = Build.versionString - const val DEFAULT_EOCV_WIDTH = 320 - const val DEFAULT_EOCV_HEIGHT = 240 - @JvmField val DEFAULT_EOCV_SIZE = Size(DEFAULT_EOCV_WIDTH.toDouble(), DEFAULT_EOCV_HEIGHT.toDouble()) - - private const val TAG = "EOCVSim" - - private var isNativeLibLoaded = false - - fun loadOpenCvLib() { - if (isNativeLibLoaded) return - - Log.info(TAG, "Loading native lib...") - - try { - OpenCV.loadLocally() - Log.info(TAG, "Successfully loaded native lib") - } catch (ex: Throwable) { - Log.error(TAG, "Failure loading native lib", ex) - Log.info(TAG, "Retrying with old method...") - - if (!SysUtil.loadCvNativeLib()) exitProcess(-1) - } - - isNativeLibLoaded = true - } - } - - @JvmField - val onMainUpdate = EventHandler("OnMainUpdate") - - @JvmField - val visualizer = Visualizer(this) - - @JvmField - val configManager = ConfigManager() - @JvmField - val inputSourceManager = InputSourceManager(this) - @JvmField - val pipelineManager = PipelineManager(this) - @JvmField - val tunerManager = TunerManager(this) - @JvmField - val workspaceManager = WorkspaceManager(this) - - val config: Config - get() = configManager.config - - var currentRecordingSession: VideoRecordingSession? = null - val fpsLimiter = FpsLimiter(30.0) - - lateinit var eocvSimThread: Thread - private set - - private val hexCode = Integer.toHexString(hashCode()) - - enum class DestroyReason { - USER_REQUESTED, RESTART, CRASH - } - - fun init() { - eocvSimThread = Thread.currentThread() - - if(!EOCVSimFolder.couldLock) { - Log.error(TAG, - "Couldn't finally claim lock file in \"${EOCVSimFolder.absolutePath}\"! " + - "Is the folder opened by another EOCV-Sim instance?" - ) - - Log.error(TAG, "Unable to continue with the execution, the sim will exit now.") - exitProcess(-1) - } else { - Log.info(TAG, "Confirmed claiming of the lock file in ${EOCVSimFolder.absolutePath}") - Log.blank() - } - - DialogFactory.createSplashScreen(visualizer.onInitFinished) - - Log.info(TAG, "Initializing EasyOpenCV Simulator v$VERSION ($hexCode)") - Log.blank() - - EOCVSimUncaughtExceptionHandler.register() - - //loading native lib only once in the app runtime - loadOpenCvLib() - Log.blank() - - configManager.init() //load config - - workspaceManager.init() - - visualizer.initAsync(configManager.config.simTheme) //create gui in the EDT - - inputSourceManager.init() //loading user created input sources - pipelineManager.init() //init pipeline manager (scan for pipelines) - tunerManager.init() //init tunable variables manager - - //shows a warning when a pipeline gets "stuck" - pipelineManager.onPipelineTimeout { - visualizer.asyncPleaseWaitDialog( - "Current pipeline took too long to ${pipelineManager.lastPipelineAction}", - "Falling back to DefaultPipeline", - "Close", Dimension(310, 150), true, true - ) - } - - inputSourceManager.inputSourceLoader.saveInputSourcesToFile() - - visualizer.waitForFinishingInit() - - visualizer.sourceSelectorPanel.updateSourcesList() //update sources and pick first one - visualizer.sourceSelectorPanel.sourceSelector.selectedIndex = 0 - visualizer.pipelineSelectorPanel.updatePipelinesList() //update pipelines and pick first one (DefaultPipeline) - visualizer.pipelineSelectorPanel.selectedIndex = 0 - - //post output mats from the pipeline to the visualizer viewport - pipelineManager.pipelineOutputPosters.add(visualizer.viewport.matPoster) - - start() - } - - private fun start() { - Log.info(TAG, "Begin EOCVSim loop") - Log.blank() - - while (!eocvSimThread.isInterrupted) { - //run all pending requested runnables - onMainUpdate.run() - - updateVisualizerTitle() - - inputSourceManager.update(pipelineManager.paused) - tunerManager.update() - - try { - pipelineManager.update(inputSourceManager.lastMatFromSource) - } catch (ex: MaxActiveContextsException) { //handles when a lot of pipelines are stuck in the background - visualizer.asyncPleaseWaitDialog( - "There are many pipelines stuck in processFrame running in the background", - "To avoid further issues, EOCV-Sim will exit now.", - "Ok", - Dimension(450, 150), - true, true - ).onCancel { - destroy(DestroyReason.CRASH) //destroy eocv sim when pressing "exit" - } - - //print exception - Log.error( - TAG, - "Please note that the following exception is likely to be caused by one or more of the user pipelines", - ex - ) - - //block the current thread until the user closes the dialog - try { - //using sleep for avoiding burning cpu cycles - Thread.sleep(Long.MAX_VALUE) - } catch (ignored: InterruptedException) { - //reinterrupt once user closes the dialog - Thread.currentThread().interrupt() - } - - break //bye bye - } - - //updating displayed telemetry - visualizer.telemetryPanel.updateTelemetry(pipelineManager.currentTelemetry) - - //limit FPS - fpsLimiter.maxFPS = config.pipelineMaxFps.fps.toDouble() - fpsLimiter.sync() - } - - Log.warn(TAG, "Main thread interrupted (" + Integer.toHexString(hashCode()) + ")") - } - - fun destroy(reason: DestroyReason) { - Log.warn(TAG, "Destroying current EOCVSim ($hexCode) due to $reason") - - //stop recording session if there's currently an ongoing one - currentRecordingSession?.stopRecordingSession() - currentRecordingSession?.discardVideo() - - Log.info(TAG, "Trying to save config file...") - - inputSourceManager.currentInputSource?.close() - workspaceManager.stopFileWatcher() - configManager.saveToFile() - visualizer.close() - - eocvSimThread.interrupt() - } - - fun destroy() { - destroy(DestroyReason.USER_REQUESTED) - } - - fun restart() { - Log.info(TAG, "Restarting...") - - pipelineManager.captureStaticSnapshot() - - Log.blank() - destroy(DestroyReason.RESTART) - Log.blank() - - Thread( - { EOCVSim().init() }, - "main" - ).start() //run next instance on a separate thread for the old one to get interrupted and ended - } - - fun startRecordingSession() { - if (currentRecordingSession == null) { - currentRecordingSession = VideoRecordingSession( - config.videoRecordingFps.fps.toDouble(), config.videoRecordingSize - ) - - currentRecordingSession!!.startRecordingSession() - - Log.info(TAG, "Recording session started") - - pipelineManager.pipelineOutputPosters.add(currentRecordingSession!!.matPoster) - } - } - - //stopping recording session and saving file - fun stopRecordingSession() { - currentRecordingSession?.let { itVideo -> - - visualizer.pipelineSelectorPanel.buttonsPanel.pipelineRecordBtt.isEnabled = false - - itVideo.stopRecordingSession() - pipelineManager.pipelineOutputPosters.remove(itVideo.matPoster) - - Log.info(TAG, "Recording session stopped") - - DialogFactory.createFileChooser( - visualizer.frame, - DialogFactory.FileChooser.Mode.SAVE_FILE_SELECT, FileFilters.recordedVideoFilter - ).addCloseListener { _: Int, file: File?, selectedFileFilter: FileFilter? -> - onMainUpdate.doOnce { - if (file != null) { - - var correctedFile = File(file.absolutePath) - val extension = SysUtil.getExtensionByStringHandling(file.name) - - if (selectedFileFilter is FileNameExtensionFilter) { //if user selected an extension - //get selected extension - correctedFile = file + "." + selectedFileFilter.extensions[0] - } else if (extension.isPresent) { - if (!extension.get().equals("avi", true)) { - correctedFile = file + ".avi" - } - } else { - correctedFile = file + ".avi" - } - - if (correctedFile.exists()) { - SwingUtilities.invokeLater { - if (DialogFactory.createFileAlreadyExistsDialog(this) == FileAlreadyExists.UserChoice.REPLACE) { - onMainUpdate.doOnce { itVideo.saveTo(correctedFile) } - } - } - } else { - itVideo.saveTo(correctedFile) - } - } else { - itVideo.discardVideo() - } - - currentRecordingSession = null - visualizer.pipelineSelectorPanel.buttonsPanel.pipelineRecordBtt.isEnabled = true - } - } - } - } - - fun isCurrentlyRecording() = currentRecordingSession?.isRecording ?: false - - private fun updateVisualizerTitle() { - val isBuildRunning = if (pipelineManager.compiledPipelineManager.isBuildRunning) "(Building)" else "" - - val workspaceMsg = " - ${config.workspacePath} $isBuildRunning" - - val pipelineFpsMsg = " (${pipelineManager.pipelineFpsCounter.fps} Pipeline FPS)" - val posterFpsMsg = " (${visualizer.viewport.matPoster.fpsCounter.fps} Poster FPS)" - val isPaused = if (pipelineManager.paused) " (Paused)" else "" - val isRecording = if (isCurrentlyRecording()) " RECORDING" else "" - - val msg = isRecording + pipelineFpsMsg + posterFpsMsg + isPaused - - if (pipelineManager.currentPipeline == null) { - visualizer.setTitleMessage("No pipeline$msg${workspaceMsg}") - } else { - visualizer.setTitleMessage("${pipelineManager.currentPipelineName}$msg${workspaceMsg}") - } - } - - class Parameters { - var scanForPipelinesIn = "org.firstinspires" - var scanForTunableFieldsIn = "com.github.serivesmejia" - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim + +import com.github.serivesmejia.eocvsim.config.Config +import com.github.serivesmejia.eocvsim.config.ConfigManager +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.gui.dialog.FileAlreadyExists +import com.github.serivesmejia.eocvsim.input.InputSourceManager +import com.github.serivesmejia.eocvsim.output.VideoRecordingSession +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.pipeline.PipelineSource +import com.github.serivesmejia.eocvsim.tuner.TunerManager +import com.github.serivesmejia.eocvsim.util.ClasspathScan +import com.github.serivesmejia.eocvsim.util.FileFilters +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.exception.MaxActiveContextsException +import com.github.serivesmejia.eocvsim.util.exception.handling.EOCVSimUncaughtExceptionHandler +import com.github.serivesmejia.eocvsim.util.extension.plus +import com.github.serivesmejia.eocvsim.util.fps.FpsLimiter +import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder +import com.github.serivesmejia.eocvsim.workspace.WorkspaceManager +import nu.pattern.OpenCV +import org.opencv.core.Size +import java.awt.Dimension +import java.io.File +import javax.swing.SwingUtilities +import javax.swing.filechooser.FileFilter +import javax.swing.filechooser.FileNameExtensionFilter +import kotlin.concurrent.thread +import kotlin.system.exitProcess + +class EOCVSim(val params: Parameters = Parameters()) { + + companion object { + const val VERSION = Build.versionString + const val DEFAULT_EOCV_WIDTH = 320 + const val DEFAULT_EOCV_HEIGHT = 240 + @JvmField val DEFAULT_EOCV_SIZE = Size(DEFAULT_EOCV_WIDTH.toDouble(), DEFAULT_EOCV_HEIGHT.toDouble()) + + private const val TAG = "EOCVSim" + + private var isNativeLibLoaded = false + + fun loadOpenCvLib() { + if (isNativeLibLoaded) return + + Log.info(TAG, "Loading native lib...") + + try { + OpenCV.loadLocally() + Log.info(TAG, "Successfully loaded native lib") + } catch (ex: Throwable) { + Log.error(TAG, "Failure loading native lib", ex) + Log.info(TAG, "Retrying with old method...") + + if (!SysUtil.loadCvNativeLib()) exitProcess(-1) + } + + isNativeLibLoaded = true + } + } + + @JvmField + val onMainUpdate = EventHandler("OnMainUpdate") + + @JvmField + val visualizer = Visualizer(this) + + @JvmField + val configManager = ConfigManager() + @JvmField + val inputSourceManager = InputSourceManager(this) + @JvmField + val pipelineManager = PipelineManager(this) + @JvmField + val tunerManager = TunerManager(this) + @JvmField + val workspaceManager = WorkspaceManager(this) + + val classpathScan = ClasspathScan() + + val config: Config + get() = configManager.config + + var currentRecordingSession: VideoRecordingSession? = null + val fpsLimiter = FpsLimiter(30.0) + + lateinit var eocvSimThread: Thread + private set + + private val hexCode = Integer.toHexString(hashCode()) + + enum class DestroyReason { + USER_REQUESTED, RESTART, CRASH + } + + fun init() { + eocvSimThread = Thread.currentThread() + + if(!EOCVSimFolder.couldLock) { + Log.error(TAG, + "Couldn't finally claim lock file in \"${EOCVSimFolder.absolutePath}\"! " + + "Is the folder opened by another EOCV-Sim instance?" + ) + + Log.error(TAG, "Unable to continue with the execution, the sim will exit now.") + exitProcess(-1) + } else { + Log.info(TAG, "Confirmed claiming of the lock file in ${EOCVSimFolder.absolutePath}") + Log.blank() + } + + DialogFactory.createSplashScreen(visualizer.onInitFinished) + + Log.info(TAG, "Initializing EasyOpenCV Simulator v$VERSION ($hexCode)") + Log.blank() + + EOCVSimUncaughtExceptionHandler.register() + + //loading native lib only once in the app runtime + loadOpenCvLib() + Log.blank() + + classpathScan.asyncScan() + + configManager.init() //load config + + workspaceManager.init() + + visualizer.initAsync(configManager.config.simTheme) //create gui in the EDT + + inputSourceManager.init() //loading user created input sources + pipelineManager.init() //init pipeline manager (scan for pipelines) + tunerManager.init() //init tunable variables manager + + //shows a warning when a pipeline gets "stuck" + pipelineManager.onPipelineTimeout { + visualizer.asyncPleaseWaitDialog( + "Current pipeline took too long to ${pipelineManager.lastPipelineAction}", + "Falling back to DefaultPipeline", + "Close", Dimension(310, 150), true, true + ) + } + + inputSourceManager.inputSourceLoader.saveInputSourcesToFile() + + visualizer.waitForFinishingInit() + + visualizer.sourceSelectorPanel.updateSourcesList() //update sources and pick first one + visualizer.sourceSelectorPanel.sourceSelector.selectedIndex = 0 + visualizer.sourceSelectorPanel.allowSourceSwitching = true + + visualizer.pipelineSelectorPanel.updatePipelinesList() //update pipelines and pick first one (DefaultPipeline) + visualizer.pipelineSelectorPanel.selectedIndex = 0 + + //post output mats from the pipeline to the visualizer viewport + pipelineManager.pipelineOutputPosters.add(visualizer.viewport.matPoster) + + start() + } + + private fun start() { + Log.info(TAG, "Begin EOCVSim loop") + Log.blank() + + while (!eocvSimThread.isInterrupted) { + //run all pending requested runnables + onMainUpdate.run() + + updateVisualizerTitle() + + inputSourceManager.update(pipelineManager.paused) + tunerManager.update() + + try { + pipelineManager.update( + if(inputSourceManager.lastMatFromSource != null && !inputSourceManager.lastMatFromSource.empty()) { + inputSourceManager.lastMatFromSource + } else null + ) + } catch (ex: MaxActiveContextsException) { //handles when a lot of pipelines are stuck in the background + visualizer.asyncPleaseWaitDialog( + "There are many pipelines stuck in processFrame running in the background", + "To avoid further issues, EOCV-Sim will exit now.", + "Ok", + Dimension(450, 150), + true, true + ).onCancel { + destroy(DestroyReason.CRASH) //destroy eocv sim when pressing "exit" + } + + //print exception + Log.error( + TAG, + "Please note that the following exception is likely to be caused by one or more of the user pipelines", + ex + ) + + //block the current thread until the user closes the dialog + try { + //using sleep for avoiding burning cpu cycles + Thread.sleep(Long.MAX_VALUE) + } catch (ignored: InterruptedException) { + //reinterrupt once user closes the dialog + Thread.currentThread().interrupt() + } + + break //bye bye + } + + //updating displayed telemetry + visualizer.telemetryPanel.updateTelemetry(pipelineManager.currentTelemetry) + + //limit FPG + fpsLimiter.maxFPS = config.pipelineMaxFps.fps.toDouble() + fpsLimiter.sync() + } + + Log.warn(TAG, "Main thread interrupted (" + Integer.toHexString(hashCode()) + ")") + } + + fun destroy(reason: DestroyReason) { + Log.warn(TAG, "Destroying current EOCVSim ($hexCode) due to $reason, it is normal to see InterruptedExceptions and other kinds of stack traces below") + + //stop recording session if there's currently an ongoing one + currentRecordingSession?.stopRecordingSession() + currentRecordingSession?.discardVideo() + + Log.info(TAG, "Trying to save config file...") + + inputSourceManager.currentInputSource?.close() + workspaceManager.stopFileWatcher() + configManager.saveToFile() + visualizer.close() + + eocvSimThread.interrupt() + + if(reason == DestroyReason.USER_REQUESTED || reason == DestroyReason.CRASH) + jvmMainThread.interrupt() + } + + fun destroy() { + destroy(DestroyReason.USER_REQUESTED) + } + + fun restart() { + Log.info(TAG, "Restarting...") + + pipelineManager.captureStaticSnapshot() + + Log.blank() + destroy(DestroyReason.RESTART) + Log.blank() + + currentMainThread = Thread( + { EOCVSim(params).init() }, + "new-main" + ) + currentMainThread.start() //run next instance on a new main thread for the old one to get interrupted and ended + + if(Thread.currentThread() == jvmMainThread) { + Thread.interrupted() // clear interrupt state + Thread.sleep(Long.MAX_VALUE) // hang forever the jvm main thread so that the app doesnt die idk + } + } + + fun startRecordingSession() { + if (currentRecordingSession == null) { + currentRecordingSession = VideoRecordingSession( + config.videoRecordingFps.fps.toDouble(), config.videoRecordingSize + ) + + currentRecordingSession!!.startRecordingSession() + + Log.info(TAG, "Recording session started") + + pipelineManager.pipelineOutputPosters.add(currentRecordingSession!!.matPoster) + } + } + + //stopping recording session and saving file + fun stopRecordingSession() { + currentRecordingSession?.let { itVideo -> + + visualizer.pipelineSelectorPanel.buttonsPanel.pipelineRecordBtt.isEnabled = false + + itVideo.stopRecordingSession() + pipelineManager.pipelineOutputPosters.remove(itVideo.matPoster) + + Log.info(TAG, "Recording session stopped") + + DialogFactory.createFileChooser( + visualizer.frame, + DialogFactory.FileChooser.Mode.SAVE_FILE_SELECT, FileFilters.recordedVideoFilter + ).addCloseListener { _: Int, file: File?, selectedFileFilter: FileFilter? -> + onMainUpdate.doOnce { + if (file != null) { + + var correctedFile = File(file.absolutePath) + val extension = SysUtil.getExtensionByStringHandling(file.name) + + if (selectedFileFilter is FileNameExtensionFilter) { //if user selected an extension + //get selected extension + correctedFile = file + "." + selectedFileFilter.extensions[0] + } else if (extension.isPresent) { + if (!extension.get().equals("avi", true)) { + correctedFile = file + ".avi" + } + } else { + correctedFile = file + ".avi" + } + + if (correctedFile.exists()) { + SwingUtilities.invokeLater { + if (DialogFactory.createFileAlreadyExistsDialog(this) == FileAlreadyExists.UserChoice.REPLACE) { + onMainUpdate.doOnce { itVideo.saveTo(correctedFile) } + } + } + } else { + itVideo.saveTo(correctedFile) + } + } else { + itVideo.discardVideo() + } + + currentRecordingSession = null + visualizer.pipelineSelectorPanel.buttonsPanel.pipelineRecordBtt.isEnabled = true + } + } + } + } + + fun isCurrentlyRecording() = currentRecordingSession?.isRecording ?: false + + private fun updateVisualizerTitle() { + val isBuildRunning = if (pipelineManager.compiledPipelineManager.isBuildRunning) "(Building)" else "" + + val workspaceMsg = " - ${workspaceManager.workspaceFile.absolutePath} $isBuildRunning" + + val pipelineFpsMsg = " (${pipelineManager.pipelineFpsCounter.fps} Pipeline FPS)" + val posterFpsMsg = " (${visualizer.viewport.matPoster.fpsCounter.fps} Poster FPS)" + val isPaused = if (pipelineManager.paused) " (Paused)" else "" + val isRecording = if (isCurrentlyRecording()) " RECORDING" else "" + + val msg = isRecording + pipelineFpsMsg + posterFpsMsg + isPaused + + if (pipelineManager.currentPipeline == null) { + visualizer.setTitleMessage("No pipeline$msg${workspaceMsg}") + } else { + visualizer.setTitleMessage("${pipelineManager.currentPipelineName}$msg${workspaceMsg}") + } + } + + class Parameters { + var scanForPipelinesIn = "org.firstinspires" + var scanForTunableFieldsIn = "com.github.serivesmejia" + + var initialWorkspace: File? = null + + var initialPipelineName: String? = null + var initialPipelineSource: PipelineSource? = null + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Main.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Main.kt index a62c9a79..acaea18b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Main.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/Main.kt @@ -1,4 +1,69 @@ -@file:JvmName("Main") -package com.github.serivesmejia.eocvsim - -fun main() = EOCVSim().init() \ No newline at end of file +@file:JvmName("Main") +package com.github.serivesmejia.eocvsim + +import com.github.serivesmejia.eocvsim.pipeline.PipelineSource +import com.github.serivesmejia.eocvsim.util.ClasspathScan +import com.github.serivesmejia.eocvsim.util.Log +import picocli.CommandLine +import java.io.File +import java.nio.file.Paths +import kotlin.system.exitProcess + +val jvmMainThread: Thread = Thread.currentThread() +var currentMainThread: Thread = jvmMainThread + +fun main(args: Array) { + val result = CommandLine( + EOCVSimCommandInterface() + ).setCaseInsensitiveEnumValuesAllowed(true).execute(*args) + + exitProcess(result) +} + +@CommandLine.Command(name = "eocvsim", mixinStandardHelpOptions = true, version = [Build.versionString]) +class EOCVSimCommandInterface : Runnable { + + @CommandLine.Option(names = ["-w", "--workspace"], description = ["Specifies the workspace that will be used only during this run, path can be relative or absolute"]) + @JvmField var workspacePath = "" + + @CommandLine.Option(names = ["-p", "--pipeline"], description = ["Specifies the pipeline selected when the simulator starts, and the initial runtime build finishes if it was running"]) + @JvmField var initialPipeline = "" + @CommandLine.Option(names = ["-s", "--source"], description = ["Specifies the source of the pipeline that will be selected when the simulator starts, from the --pipeline argument. Defaults to CLASSPATH. Possible values: \${COMPLETION-CANDIDATES}"]) + @JvmField var initialPipelineSource = PipelineSource.CLASSPATH + + override fun run() { + val parameters = EOCVSim.Parameters() + + if(workspacePath.trim() != "") { + var file = File(workspacePath) + + if(!file.exists()) { + file = Paths.get(System.getProperty("user.dir"), workspacePath).toFile() + + if(!file.exists()) { + Log.error("Workspace path is not valid, folder doesn't exist (tried in \"$workspacePath\" and \"${file.absolutePath})\"") + exitProcess(1) + } + } + + if(!file.isDirectory) { + Log.error("Workspace path is not valid, the specified path is not a folder") + exitProcess(1) + } + + Log.info("Workspace from command line: ${file.absolutePath}") + + parameters.initialWorkspace = file + } + + if(initialPipeline.trim() != "") { + parameters.initialPipelineName = initialPipeline + parameters.initialPipelineSource = initialPipelineSource + + Log.info("Initial pipeline from command line: $initialPipeline coming from $initialPipelineSource") + } + + EOCVSim(parameters).init() + } + +} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java index d70fbf7e..a2fb232f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java @@ -1,61 +1,61 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.config; - -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanelConfig; -import com.github.serivesmejia.eocvsim.gui.theme.Theme; -import com.github.serivesmejia.eocvsim.pipeline.PipelineFps; -import com.github.serivesmejia.eocvsim.pipeline.PipelineTimeout; -import com.github.serivesmejia.eocvsim.pipeline.compiler.CompiledPipelineManager; -import org.opencv.core.Size; - -import java.util.HashMap; - -public class Config { - public volatile Theme simTheme = Theme.Light; - - public volatile double zoom = 1; - - public volatile PipelineFps pipelineMaxFps = PipelineFps.MEDIUM; - public volatile PipelineTimeout pipelineTimeout = PipelineTimeout.MEDIUM; - - public volatile boolean pauseOnImages = true; - - public volatile Size videoRecordingSize = new Size(640, 480); - public volatile PipelineFps videoRecordingFps = PipelineFps.MEDIUM; - - public volatile String workspacePath = CompiledPipelineManager.Companion.getDEF_WORKSPACE_FOLDER().getAbsolutePath(); - - public volatile TunableFieldPanelConfig.Config globalTunableFieldsConfig = - new TunableFieldPanelConfig.Config( - new Size(0, 255), - TunableFieldPanelConfig.PickerColorSpace.RGB, - TunableFieldPanel.Mode.TEXTBOXES, - TunableFieldPanelConfig.ConfigSource.GLOBAL_DEFAULT - ); - - public volatile HashMap specificTunableFieldConfig = new HashMap<>(); - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.config; + +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanelConfig; +import com.github.serivesmejia.eocvsim.gui.theme.Theme; +import com.github.serivesmejia.eocvsim.pipeline.PipelineFps; +import com.github.serivesmejia.eocvsim.pipeline.PipelineTimeout; +import com.github.serivesmejia.eocvsim.pipeline.compiler.CompiledPipelineManager; +import org.opencv.core.Size; + +import java.util.HashMap; + +public class Config { + public volatile Theme simTheme = Theme.Light; + + public volatile double zoom = 1; + + public volatile PipelineFps pipelineMaxFps = PipelineFps.MEDIUM; + public volatile PipelineTimeout pipelineTimeout = PipelineTimeout.MEDIUM; + + public volatile boolean pauseOnImages = true; + + public volatile Size videoRecordingSize = new Size(640, 480); + public volatile PipelineFps videoRecordingFps = PipelineFps.MEDIUM; + + public volatile String workspacePath = CompiledPipelineManager.Companion.getDEF_WORKSPACE_FOLDER().getAbsolutePath(); + + public volatile TunableFieldPanelConfig.Config globalTunableFieldsConfig = + new TunableFieldPanelConfig.Config( + new Size(0, 255), + TunableFieldPanelConfig.PickerColorSpace.RGB, + TunableFieldPanel.Mode.TEXTBOXES, + TunableFieldPanelConfig.ConfigSource.GLOBAL_DEFAULT + ); + + public volatile HashMap specificTunableFieldConfig = new HashMap<>(); + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigLoader.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigLoader.java index b6a315cc..e392063d 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigLoader.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigLoader.java @@ -1,73 +1,73 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.config; - -import com.github.serivesmejia.eocvsim.util.Log; -import com.github.serivesmejia.eocvsim.util.SysUtil; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import java.io.File; -import java.io.FileNotFoundException; - -public class ConfigLoader { - - public static final String CONFIG_SAVEFILE_NAME = "eocvsim_config.json"; - - public static final File CONFIG_SAVEFILE = new File(SysUtil.getEOCVSimFolder() + File.separator + CONFIG_SAVEFILE_NAME); - public static final File OLD_CONFIG_SAVEFILE = new File(SysUtil.getAppData() + File.separator + CONFIG_SAVEFILE_NAME); - - static Gson gson = new GsonBuilder().setPrettyPrinting().create(); - - public Config loadFromFile(File file) throws FileNotFoundException { - - if (!file.exists()) throw new FileNotFoundException(); - - String jsonConfig = SysUtil.loadFileStr(file); - if (jsonConfig.trim().equals("")) return null; - - try { - return gson.fromJson(jsonConfig, Config.class); - } catch (Exception ex) { - Log.info("ConfigLoader", "Gson exception while parsing config file", ex); - return null; - } - - } - - public Config loadFromFile() throws FileNotFoundException { - SysUtil.migrateFile(OLD_CONFIG_SAVEFILE, CONFIG_SAVEFILE); - return loadFromFile(CONFIG_SAVEFILE); - } - - public void saveToFile(File file, Config conf) { - String jsonConfig = gson.toJson(conf); - SysUtil.saveFileStr(file, jsonConfig); - } - - public void saveToFile(Config conf) { - saveToFile(CONFIG_SAVEFILE, conf); - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.config; + +import com.github.serivesmejia.eocvsim.util.Log; +import com.github.serivesmejia.eocvsim.util.SysUtil; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.File; +import java.io.FileNotFoundException; + +public class ConfigLoader { + + public static final String CONFIG_SAVEFILE_NAME = "eocvsim_config.json"; + + public static final File CONFIG_SAVEFILE = new File(SysUtil.getEOCVSimFolder() + File.separator + CONFIG_SAVEFILE_NAME); + public static final File OLD_CONFIG_SAVEFILE = new File(SysUtil.getAppData() + File.separator + CONFIG_SAVEFILE_NAME); + + static Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + public Config loadFromFile(File file) throws FileNotFoundException { + + if (!file.exists()) throw new FileNotFoundException(); + + String jsonConfig = SysUtil.loadFileStr(file); + if (jsonConfig.trim().equals("")) return null; + + try { + return gson.fromJson(jsonConfig, Config.class); + } catch (Exception ex) { + Log.info("ConfigLoader", "Gson exception while parsing config file", ex); + return null; + } + + } + + public Config loadFromFile() throws FileNotFoundException { + SysUtil.migrateFile(OLD_CONFIG_SAVEFILE, CONFIG_SAVEFILE); + return loadFromFile(CONFIG_SAVEFILE); + } + + public void saveToFile(File file, Config conf) { + String jsonConfig = gson.toJson(conf); + SysUtil.saveFileStr(file, jsonConfig); + } + + public void saveToFile(Config conf) { + saveToFile(CONFIG_SAVEFILE, conf); + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigManager.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigManager.java index 497dd0b1..852a111a 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigManager.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/ConfigManager.java @@ -1,61 +1,61 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.config; - -import com.github.serivesmejia.eocvsim.util.Log; - -public class ConfigManager { - - public final ConfigLoader configLoader = new ConfigLoader(); - private Config config; - - public void init() { - Log.info("ConfigManager", "Initializing..."); - - try { - config = configLoader.loadFromFile(); - if (config == null) { - Log.error("ConfigManager", "Error while parsing config file, it will be replaced and fixed, but the user configurations will be reset"); - throw new NullPointerException(); //for it to be caught later and handle the creation of a new config - } else { - Log.info("ConfigManager", "Loaded config from file successfully"); - } - } catch (Exception ex) { //handles FileNotFoundException & a NullPointerException thrown above - config = new Config(); - Log.info("ConfigManager", "Creating config file..."); - configLoader.saveToFile(config); - } - - Log.blank(); - } - - public void saveToFile() { - configLoader.saveToFile(config); - } - - public Config getConfig() { - return config; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.config; + +import com.github.serivesmejia.eocvsim.util.Log; + +public class ConfigManager { + + public final ConfigLoader configLoader = new ConfigLoader(); + private Config config; + + public void init() { + Log.info("ConfigManager", "Initializing..."); + + try { + config = configLoader.loadFromFile(); + if (config == null) { + Log.error("ConfigManager", "Error while parsing config file, it will be replaced and fixed, but the user configurations will be reset"); + throw new NullPointerException(); //for it to be caught later and handle the creation of a new config + } else { + Log.info("ConfigManager", "Loaded config from file successfully"); + } + } catch (Exception ex) { //handles FileNotFoundException & a NullPointerException thrown above + config = new Config(); + Log.info("ConfigManager", "Creating config file..."); + configLoader.saveToFile(config); + } + + Log.blank(); + } + + public void saveToFile() { + configLoader.saveToFile(config); + } + + public Config getConfig() { + return config; + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java index 9c495aa2..0936cb4b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java @@ -1,208 +1,229 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.dialog.*; -import com.github.serivesmejia.eocvsim.gui.dialog.SplashScreen; -import com.github.serivesmejia.eocvsim.gui.dialog.source.CreateCameraSource; -import com.github.serivesmejia.eocvsim.gui.dialog.source.CreateImageSource; -import com.github.serivesmejia.eocvsim.gui.dialog.source.CreateSource; -import com.github.serivesmejia.eocvsim.gui.dialog.source.CreateVideoSource; -import com.github.serivesmejia.eocvsim.input.SourceType; -import com.github.serivesmejia.eocvsim.util.event.EventHandler; - -import javax.swing.*; -import javax.swing.filechooser.FileFilter; -import javax.swing.filechooser.FileNameExtensionFilter; -import java.awt.*; -import java.io.File; -import java.util.ArrayList; - -public class DialogFactory { - - private DialogFactory() { } - - public static FileChooser createFileChooser(Component parent, FileChooser.Mode mode, FileFilter... filters) { - FileChooser fileChooser = new FileChooser(parent, mode, filters); - invokeLater(fileChooser::init); - return fileChooser; - } - - public static FileChooser createFileChooser(Component parent, FileFilter... filters) { - return createFileChooser(parent, null, filters); - } - - public static FileChooser createFileChooser(Component parent, FileChooser.Mode mode) { - return createFileChooser(parent, mode, new FileFilter[0]); - } - - public static FileChooser createFileChooser(Component parent) { - return createFileChooser(parent, null, new FileFilter[0]); - } - - public static void createSourceDialog(EOCVSim eocvSim, - SourceType type, - File initialFile) { - invokeLater(() -> { - switch (type) { - case IMAGE: - new CreateImageSource(eocvSim.visualizer.frame, eocvSim, initialFile); - break; - case CAMERA: - new CreateCameraSource(eocvSim.visualizer.frame, eocvSim); - break; - case VIDEO: - new CreateVideoSource(eocvSim.visualizer.frame, eocvSim, initialFile); - } - }); - } - - public static void createSourceDialog(EOCVSim eocvSim, SourceType type) { - createSourceDialog(eocvSim, type, null); - } - - public static void createSourceDialog(EOCVSim eocvSim) { - invokeLater(() -> new CreateSource(eocvSim.visualizer.frame, eocvSim)); - } - - public static void createConfigDialog(EOCVSim eocvSim) { - invokeLater(() -> new Configuration(eocvSim.visualizer.frame, eocvSim)); - } - - public static void createAboutDialog(EOCVSim eocvSim) { - invokeLater(() -> new About(eocvSim.visualizer.frame, eocvSim)); - } - - public static void createOutput(EOCVSim eocvSim, boolean wasManuallyOpened) { - invokeLater(() -> { - if(!Output.Companion.isAlreadyOpened()) - new Output(eocvSim.visualizer.frame, eocvSim, Output.Companion.getLatestIndex(), wasManuallyOpened); - }); - } - - public static void createOutput(EOCVSim eocvSim) { - createOutput(eocvSim, false); - } - - public static void createBuildOutput(EOCVSim eocvSim) { - invokeLater(() -> { - if(!Output.Companion.isAlreadyOpened()) - new Output(eocvSim.visualizer.frame, eocvSim, 1); - }); - } - - public static void createPipelineOutput(EOCVSim eocvSim) { - invokeLater(() -> { - if(!Output.Companion.isAlreadyOpened()) - new Output(eocvSim.visualizer.frame, eocvSim, 0); - }); - } - - public static void createSplashScreen(EventHandler closeHandler) { - invokeLater(() -> new SplashScreen(closeHandler)); - } - - public static FileAlreadyExists.UserChoice createFileAlreadyExistsDialog(EOCVSim eocvSim) { - return new FileAlreadyExists(eocvSim.visualizer.frame, eocvSim).run(); - } - - private static void invokeLater(Runnable runn) { - SwingUtilities.invokeLater(runn); - } - - public static class FileChooser { - - private final JFileChooser chooser; - private final Component parent; - - private final Mode mode; - - private final ArrayList closeListeners = new ArrayList<>(); - - public FileChooser(Component parent, Mode mode, FileFilter... filters) { - - if (mode == null) mode = Mode.FILE_SELECT; - - chooser = new JFileChooser(); - - this.parent = parent; - this.mode = mode; - - if (mode == Mode.DIRECTORY_SELECT) { - chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - // disable the "All files" option. - chooser.setAcceptAllFileFilterUsed(false); - } - - - if (filters != null) { - for (FileFilter filter : filters) { - if(filter != null) chooser.addChoosableFileFilter(filter); - } - if(filters.length > 0) { - chooser.setFileFilter(filters[0]); - } - } - - } - - protected void init() { - - int returnVal; - - if (mode == Mode.SAVE_FILE_SELECT) { - returnVal = chooser.showSaveDialog(parent); - } else { - returnVal = chooser.showOpenDialog(parent); - } - - executeCloseListeners(returnVal, chooser.getSelectedFile(), chooser.getFileFilter()); - - } - - public void addCloseListener(FileChooserCloseListener listener) { - this.closeListeners.add(listener); - } - - private void executeCloseListeners(int OPTION, File selectedFile, FileFilter selectedFileFilter) { - for (FileChooserCloseListener listener : closeListeners) { - listener.onClose(OPTION, selectedFile, selectedFileFilter); - } - } - - public void close() { - chooser.setVisible(false); - executeCloseListeners(JFileChooser.CANCEL_OPTION, new File(""), new FileNameExtensionFilter("", "")); - } - - public enum Mode {FILE_SELECT, DIRECTORY_SELECT, SAVE_FILE_SELECT} - - public interface FileChooserCloseListener { - void onClose(int OPTION, File selectedFile, FileFilter selectedFileFilter); - } - - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.dialog.*; +import com.github.serivesmejia.eocvsim.gui.dialog.SplashScreen; +import com.github.serivesmejia.eocvsim.gui.dialog.source.CreateCameraSource; +import com.github.serivesmejia.eocvsim.gui.dialog.source.CreateImageSource; +import com.github.serivesmejia.eocvsim.gui.dialog.source.CreateSource; +import com.github.serivesmejia.eocvsim.gui.dialog.source.CreateVideoSource; +import com.github.serivesmejia.eocvsim.input.SourceType; +import com.github.serivesmejia.eocvsim.util.event.EventHandler; + +import javax.swing.*; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; +import java.awt.*; +import java.io.File; +import java.util.ArrayList; +import java.util.function.IntConsumer; + +public class DialogFactory { + + private DialogFactory() { } + + public static void createYesOrNo(Component parent, String message, String submessage, IntConsumer result) { + JPanel panel = new JPanel(); + + JLabel label1 = new JLabel(message); + panel.add(label1); + + if (!submessage.trim().equals("")) { + JLabel label2 = new JLabel(submessage); + panel.add(label2); + panel.setLayout(new GridLayout(2, 1)); + } + + SwingUtilities.invokeLater(() -> result.accept( + JOptionPane.showConfirmDialog(parent, panel, "Confirm", + JOptionPane.YES_NO_OPTION, + JOptionPane.PLAIN_MESSAGE + ) + )); + } + + public static FileChooser createFileChooser(Component parent, FileChooser.Mode mode, FileFilter... filters) { + FileChooser fileChooser = new FileChooser(parent, mode, filters); + invokeLater(fileChooser::init); + return fileChooser; + } + + public static FileChooser createFileChooser(Component parent, FileFilter... filters) { + return createFileChooser(parent, null, filters); + } + + public static FileChooser createFileChooser(Component parent, FileChooser.Mode mode) { + return createFileChooser(parent, mode, new FileFilter[0]); + } + + public static FileChooser createFileChooser(Component parent) { + return createFileChooser(parent, null, new FileFilter[0]); + } + + public static void createSourceDialog(EOCVSim eocvSim, + SourceType type, + File initialFile) { + invokeLater(() -> { + switch (type) { + case IMAGE: + new CreateImageSource(eocvSim.visualizer.frame, eocvSim, initialFile); + break; + case CAMERA: + new CreateCameraSource(eocvSim.visualizer.frame, eocvSim); + break; + case VIDEO: + new CreateVideoSource(eocvSim.visualizer.frame, eocvSim, initialFile); + } + }); + } + + public static void createSourceDialog(EOCVSim eocvSim, SourceType type) { + createSourceDialog(eocvSim, type, null); + } + + public static void createSourceDialog(EOCVSim eocvSim) { + invokeLater(() -> new CreateSource(eocvSim.visualizer.frame, eocvSim)); + } + + public static void createConfigDialog(EOCVSim eocvSim) { + invokeLater(() -> new Configuration(eocvSim.visualizer.frame, eocvSim)); + } + + public static void createAboutDialog(EOCVSim eocvSim) { + invokeLater(() -> new About(eocvSim.visualizer.frame, eocvSim)); + } + + public static void createOutput(EOCVSim eocvSim, boolean wasManuallyOpened) { + invokeLater(() -> { + if(!Output.Companion.isAlreadyOpened()) + new Output(eocvSim.visualizer.frame, eocvSim, Output.Companion.getLatestIndex(), wasManuallyOpened); + }); + } + + public static void createOutput(EOCVSim eocvSim) { + createOutput(eocvSim, false); + } + + public static void createBuildOutput(EOCVSim eocvSim) { + invokeLater(() -> { + if(!Output.Companion.isAlreadyOpened()) + new Output(eocvSim.visualizer.frame, eocvSim, 1); + }); + } + + public static void createPipelineOutput(EOCVSim eocvSim) { + invokeLater(() -> { + if(!Output.Companion.isAlreadyOpened()) + new Output(eocvSim.visualizer.frame, eocvSim, 0); + }); + } + + public static void createSplashScreen(EventHandler closeHandler) { + invokeLater(() -> new SplashScreen(closeHandler)); + } + + public static FileAlreadyExists.UserChoice createFileAlreadyExistsDialog(EOCVSim eocvSim) { + return new FileAlreadyExists(eocvSim.visualizer.frame, eocvSim).run(); + } + + private static void invokeLater(Runnable runn) { + SwingUtilities.invokeLater(runn); + } + + public static class FileChooser { + + private final JFileChooser chooser; + private final Component parent; + + private final Mode mode; + + private final ArrayList closeListeners = new ArrayList<>(); + + public FileChooser(Component parent, Mode mode, FileFilter... filters) { + + if (mode == null) mode = Mode.FILE_SELECT; + + chooser = new JFileChooser(); + + this.parent = parent; + this.mode = mode; + + if (mode == Mode.DIRECTORY_SELECT) { + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + // disable the "All files" option. + chooser.setAcceptAllFileFilterUsed(false); + } + + + if (filters != null) { + for (FileFilter filter : filters) { + if(filter != null) chooser.addChoosableFileFilter(filter); + } + if(filters.length > 0) { + chooser.setFileFilter(filters[0]); + } + } + + } + + protected void init() { + + int returnVal; + + if (mode == Mode.SAVE_FILE_SELECT) { + returnVal = chooser.showSaveDialog(parent); + } else { + returnVal = chooser.showOpenDialog(parent); + } + + executeCloseListeners(returnVal, chooser.getSelectedFile(), chooser.getFileFilter()); + + } + + public void addCloseListener(FileChooserCloseListener listener) { + this.closeListeners.add(listener); + } + + private void executeCloseListeners(int OPTION, File selectedFile, FileFilter selectedFileFilter) { + for (FileChooserCloseListener listener : closeListeners) { + listener.onClose(OPTION, selectedFile, selectedFileFilter); + } + } + + public void close() { + chooser.setVisible(false); + executeCloseListeners(JFileChooser.CANCEL_OPTION, new File(""), new FileNameExtensionFilter("", "")); + } + + public enum Mode {FILE_SELECT, DIRECTORY_SELECT, SAVE_FILE_SELECT} + + public interface FileChooserCloseListener { + void onClose(int OPTION, File selectedFile, FileFilter selectedFileFilter); + } + + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Icons.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Icons.kt index 6f30dbd9..06cfc6ba 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Icons.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Icons.kt @@ -1,140 +1,144 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui - -import com.github.serivesmejia.eocvsim.gui.util.GuiUtil -import com.github.serivesmejia.eocvsim.util.Log -import java.awt.image.BufferedImage -import java.util.NoSuchElementException -import javax.swing.ImageIcon - -object Icons { - - private val bufferedImages = HashMap() - - private val icons = HashMap() - private val resizedIcons = HashMap() - - private val futureIcons = mutableListOf() - - private var colorsInverted = false - - private const val TAG = "Icons" - - init { - addFutureImage("ico_eocvsim", "/images/icon/ico_eocvsim.png", false) - - addFutureImage("ico_img", "/images/icon/ico_img.png") - addFutureImage("ico_cam", "/images/icon/ico_cam.png") - addFutureImage("ico_vid", "/images/icon/ico_vid.png") - - addFutureImage("ico_config", "/images/icon/ico_config.png") - addFutureImage("ico_slider", "/images/icon/ico_slider.png") - addFutureImage("ico_textbox", "/images/icon/ico_textbox.png") - addFutureImage("ico_colorpick", "/images/icon/ico_colorpick.png") - - addFutureImage("ico_gears", "/images/icon/ico_gears.png") - addFutureImage("ico_hammer", "/images/icon/ico_hammer.png") - - addFutureImage("ico_colorpick_pointer", "/images/icon/ico_colorpick_pointer.png") - } - - fun getImage(name: String): ImageIcon { - for(futureIcon in futureIcons.toTypedArray()) { - if(futureIcon.name == name) { - Log.info(TAG, "Loading future icon $name") - addImage(futureIcon.name, futureIcon.resourcePath, futureIcon.allowInvert) - - futureIcons.remove(futureIcon) - } - } - - if(!icons.containsKey(name)) { - throw NoSuchElementException("Image $name is not loaded into memory") - } - return icons[name]!! - } - - fun getImageResized(name: String, width: Int, height: Int): ImageIcon { - //determines the icon name from the: - //name, widthxheight, is inverted or is original - val resIconName = "$name-${width}x${height}${ - if(colorsInverted) { - "-inverted" - } else { - "" - } - }" - - val icon = if(resizedIcons.contains(resIconName)) { - resizedIcons[resIconName] - } else { - resizedIcons[resIconName] = GuiUtil.scaleImage(getImage(name), width, height) - resizedIcons[resIconName] - } - - return icon!! - } - - fun addFutureImage(name: String, path: String, allowInvert: Boolean = true) = futureIcons.add( - FutureIcon(name, path, allowInvert) - ) - - fun addImage(name: String, path: String, allowInvert: Boolean = true) { - val buffImg = GuiUtil.loadBufferedImage(path) - if(colorsInverted && allowInvert) { - GuiUtil.invertBufferedImageColors(buffImg) - } - - bufferedImages[name] = Image(buffImg, allowInvert) - icons[name] = ImageIcon(buffImg) - } - - fun setDark(dark: Boolean) { - if(dark) { - if(!colorsInverted) { - invertAll() - colorsInverted = true - } - } else { - if(colorsInverted) { - invertAll() - colorsInverted = false - } - } - } - - private fun invertAll() { - for((_, image) in bufferedImages) { - if(image.allowInvert) { - GuiUtil.invertBufferedImageColors(image.img) - } - } - } - - data class Image(val img: BufferedImage, val allowInvert: Boolean) - - data class FutureIcon(val name: String, val resourcePath: String, val allowInvert: Boolean) - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui + +import com.github.serivesmejia.eocvsim.gui.util.GuiUtil +import com.github.serivesmejia.eocvsim.util.Log +import java.awt.image.BufferedImage +import java.util.NoSuchElementException +import javax.swing.ImageIcon + +object Icons { + + private val bufferedImages = HashMap() + + private val icons = HashMap() + private val resizedIcons = HashMap() + + private val futureIcons = mutableListOf() + + private var colorsInverted = false + + private const val TAG = "Icons" + + init { + addFutureImage("ico_eocvsim", "/images/icon/ico_eocvsim.png", false) + + addFutureImage("ico_img", "/images/icon/ico_img.png") + addFutureImage("ico_cam", "/images/icon/ico_cam.png") + addFutureImage("ico_vid", "/images/icon/ico_vid.png") + + addFutureImage("ico_config", "/images/icon/ico_config.png") + addFutureImage("ico_slider", "/images/icon/ico_slider.png") + addFutureImage("ico_textbox", "/images/icon/ico_textbox.png") + addFutureImage("ico_colorpick", "/images/icon/ico_colorpick.png") + + addFutureImage("ico_gears", "/images/icon/ico_gears.png") + addFutureImage("ico_hammer", "/images/icon/ico_hammer.png") + + addFutureImage("ico_colorpick_pointer", "/images/icon/ico_colorpick_pointer.png") + } + + fun getImage(name: String): ImageIcon { + for(futureIcon in futureIcons.toTypedArray()) { + if(futureIcon.name == name) { + Log.info(TAG, "Loading future icon $name") + addImage(futureIcon.name, futureIcon.resourcePath, futureIcon.allowInvert) + + futureIcons.remove(futureIcon) + } + } + + if(!icons.containsKey(name)) { + throw NoSuchElementException("Image $name is not loaded into memory") + } + return icons[name]!! + } + + fun lazyGetImageResized(name: String, width: Int, height: Int) = lazy { + getImageResized(name, width, height) + } + + fun getImageResized(name: String, width: Int, height: Int): ImageIcon { + //determines the icon name from the: + //name, widthxheight, is inverted or is original + val resIconName = "$name-${width}x${height}${ + if(colorsInverted) { + "-inverted" + } else { + "" + } + }" + + val icon = if(resizedIcons.contains(resIconName)) { + resizedIcons[resIconName] + } else { + resizedIcons[resIconName] = GuiUtil.scaleImage(getImage(name), width, height) + resizedIcons[resIconName] + } + + return icon!! + } + + fun addFutureImage(name: String, path: String, allowInvert: Boolean = true) = futureIcons.add( + FutureIcon(name, path, allowInvert) + ) + + fun addImage(name: String, path: String, allowInvert: Boolean = true) { + val buffImg = GuiUtil.loadBufferedImage(path) + if(colorsInverted && allowInvert) { + GuiUtil.invertBufferedImageColors(buffImg) + } + + bufferedImages[name] = Image(buffImg, allowInvert) + icons[name] = ImageIcon(buffImg) + } + + fun setDark(dark: Boolean) { + if(dark) { + if(!colorsInverted) { + invertAll() + colorsInverted = true + } + } else { + if(colorsInverted) { + invertAll() + colorsInverted = false + } + } + } + + private fun invertAll() { + for((_, image) in bufferedImages) { + if(image.allowInvert) { + GuiUtil.invertBufferedImageColors(image.img) + } + } + } + + data class Image(val img: BufferedImage, val allowInvert: Boolean) + + data class FutureIcon(val name: String, val resourcePath: String, val allowInvert: Boolean) + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java index 732b6f61..b65285ce 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java @@ -1,584 +1,609 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui; - -import com.formdev.flatlaf.FlatLaf; -import com.github.serivesmejia.eocvsim.Build; -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.Viewport; -import com.github.serivesmejia.eocvsim.gui.component.tuner.ColorPicker; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.InputSourceDropTarget; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.SourceSelectorPanel; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.TelemetryPanel; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.TopMenuBar; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.PipelineSelectorPanel; -import com.github.serivesmejia.eocvsim.gui.theme.Theme; -import com.github.serivesmejia.eocvsim.gui.util.ReflectTaskbar; -import com.github.serivesmejia.eocvsim.pipeline.compiler.PipelineCompiler; -import com.github.serivesmejia.eocvsim.util.Log; -import com.github.serivesmejia.eocvsim.util.event.EventHandler; -import com.github.serivesmejia.eocvsim.workspace.util.template.GradleWorkspaceTemplate; -import kotlin.Unit; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.awt.event.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class Visualizer { - - public final EventHandler onInitFinished = new EventHandler("OnVisualizerInitFinish"); - - public final ArrayList pleaseWaitDialogs = new ArrayList<>(); - - public final ArrayList childFrames = new ArrayList<>(); - public final ArrayList childDialogs = new ArrayList<>(); - - private final EOCVSim eocvSim; - public JFrame frame; - - public Viewport viewport = null; - public TopMenuBar menuBar = null; - public JPanel tunerMenuPanel = new JPanel(); - - public JScrollPane imgScrollPane = null; - - public JPanel rightContainer = null; - public JSplitPane globalSplitPane = null; - public JSplitPane imageTunerSplitPane = null; - - public PipelineSelectorPanel pipelineSelectorPanel = null; - public SourceSelectorPanel sourceSelectorPanel = null; - public TelemetryPanel telemetryPanel; - - private String title = "EasyOpenCV Simulator v" + Build.standardVersionString; - private String titleMsg = "No pipeline"; - private String beforeTitle = ""; - private String beforeTitleMsg = ""; - - public ColorPicker colorPicker = null; - - //stuff for zooming handling - private volatile boolean isCtrlPressed = false; - - private volatile boolean hasFinishedInitializing = false; - - public Visualizer(EOCVSim eocvSim) { - this.eocvSim = eocvSim; - } - - public void init(Theme theme) { - if(ReflectTaskbar.INSTANCE.isUsable()){ - try { - //set icon for mac os (and other systems which do support this method) - ReflectTaskbar.INSTANCE.setIconImage(Icons.INSTANCE.getImage("ico_eocvsim").getImage()); - } catch (final UnsupportedOperationException e) { - Log.warn("Visualizer", "Setting the Taskbar icon image is not supported on this platform"); - } catch (final SecurityException e) { - Log.error("Visualizer", "Security exception while setting TaskBar icon", e); - } - } - - try { - theme.install(); - } catch (Exception e) { - Log.error("Visualizer", "Failed to install theme " + theme.name(), e); - } - - Icons.INSTANCE.setDark(FlatLaf.isLafDark()); - - if(Build.isDev) { - title += "-dev "; - } - - //instantiate all swing elements after theme installation - frame = new JFrame(); - viewport = new Viewport(eocvSim, eocvSim.getConfig().pipelineMaxFps.getFps()); - - menuBar = new TopMenuBar(this, eocvSim); - - tunerMenuPanel = new JPanel(); - - pipelineSelectorPanel = new PipelineSelectorPanel(eocvSim); - sourceSelectorPanel = new SourceSelectorPanel(eocvSim); - telemetryPanel = new TelemetryPanel(); - - rightContainer = new JPanel(); - - /* - * TOP MENU BAR - */ - - frame.setJMenuBar(menuBar); - - /* - * IMG VISUALIZER & SCROLL PANE - */ - - imgScrollPane = new JScrollPane(viewport); - - imgScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); - imgScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); - - imgScrollPane.getHorizontalScrollBar().setUnitIncrement(16); - imgScrollPane.getVerticalScrollBar().setUnitIncrement(16); - - rightContainer.setLayout(new BoxLayout(rightContainer, BoxLayout.Y_AXIS)); - - /* - * PIPELINE SELECTOR - */ - pipelineSelectorPanel.setBorder(new EmptyBorder(0, 20, 0, 20)); - rightContainer.add(pipelineSelectorPanel); - - /* - * SOURCE SELECTOR - */ - sourceSelectorPanel.setBorder(new EmptyBorder(0, 20, 0, 20)); - rightContainer.add(sourceSelectorPanel); - - /* - * TELEMETRY - */ - telemetryPanel.setBorder(new EmptyBorder(0, 20, 20, 20)); - rightContainer.add(telemetryPanel); - - /* - * SPLIT - */ - - //left side, image scroll & tuner menu split panel - imageTunerSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, imgScrollPane, tunerMenuPanel); - - imageTunerSplitPane.setResizeWeight(1); - imageTunerSplitPane.setOneTouchExpandable(false); - imageTunerSplitPane.setContinuousLayout(true); - - //global - globalSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, imageTunerSplitPane, rightContainer); - - globalSplitPane.setResizeWeight(1); - globalSplitPane.setOneTouchExpandable(false); - globalSplitPane.setContinuousLayout(true); - - globalSplitPane.setDropTarget(new InputSourceDropTarget(eocvSim)); - - frame.add(globalSplitPane, BorderLayout.CENTER); - - //initialize other various stuff of the frame - frame.setSize(780, 645); - frame.setMinimumSize(frame.getSize()); - frame.setTitle("EasyOpenCV Simulator - No Pipeline"); - - frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); - - frame.setIconImage(Icons.INSTANCE.getImage("ico_eocvsim").getImage()); - - frame.setLocationRelativeTo(null); - frame.setExtendedState(JFrame.MAXIMIZED_BOTH); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - - globalSplitPane.setDividerLocation(1070); - - colorPicker = new ColorPicker(viewport.image); - - frame.setVisible(true); - - onInitFinished.run(); - onInitFinished.setCallRightAway(true); - - registerListeners(); - - hasFinishedInitializing = true; - } - - public void initAsync(Theme simTheme) { - SwingUtilities.invokeLater(() -> init(simTheme)); - } - - private void registerListeners() { - - frame.addWindowListener(new WindowAdapter() { - public void windowClosing(WindowEvent e) { - eocvSim.onMainUpdate.doOnce((Runnable) eocvSim::destroy); - } - }); - - //handling onViewportTapped evts - viewport.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if(!colorPicker.isPicking()) - eocvSim.pipelineManager.callViewportTapped(); - } - }); - - //VIEWPORT RESIZE HANDLING - imgScrollPane.addMouseWheelListener(e -> { - if (isCtrlPressed) { //check if control key is pressed - double scale = viewport.getViewportScale() - (0.15 * e.getPreciseWheelRotation()); - viewport.setViewportScale(scale); - } - }); - - //listening for keyboard presses and releases, to check if ctrl key was pressed or released (handling zoom) - KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(ke -> { - switch (ke.getID()) { - case KeyEvent.KEY_PRESSED: - if (ke.getKeyCode() == KeyEvent.VK_CONTROL) { - isCtrlPressed = true; - imgScrollPane.setWheelScrollingEnabled(false); //lock scrolling if ctrl is pressed - } - break; - case KeyEvent.KEY_RELEASED: - if (ke.getKeyCode() == KeyEvent.VK_CONTROL) { - isCtrlPressed = false; - imgScrollPane.setWheelScrollingEnabled(true); //unlock - } - break; - } - return false; //idk let's just return false 'cause keyboard input doesn't work otherwise - }); - - //resizes all three JLists in right panel to make buttons visible in smaller resolutions - frame.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent evt) { - double ratioH = frame.getSize().getHeight() / 645; - - double fontSize = 17 * ratioH; - Font font = pipelineSelectorPanel.getPipelineSelectorLabel().getFont().deriveFont((float)fontSize); - - pipelineSelectorPanel.getPipelineSelectorLabel().setFont(font); - pipelineSelectorPanel.revalAndRepaint(); - - sourceSelectorPanel.getSourceSelectorLabel().setFont(font); - sourceSelectorPanel.revalAndRepaint(); - - telemetryPanel.getTelemetryLabel().setFont(font); - telemetryPanel.revalAndRepaint(); - - rightContainer.revalidate(); - rightContainer.repaint(); - } - }); - - // stop color-picking mode when changing pipeline - // TODO: find out why this breaks everything????? - // eocvSim.pipelineManager.onPipelineChange.doPersistent(() -> colorPicker.stopPicking()); - } - - public boolean hasFinishedInit() { return hasFinishedInitializing; } - - public void waitForFinishingInit() { - while (!hasFinishedInitializing) { - Thread.yield(); - } - } - - public void close() { - SwingUtilities.invokeLater(() -> { - frame.setVisible(false); - viewport.stop(); - - //close all asyncpleasewait dialogs - for (AsyncPleaseWaitDialog dialog : pleaseWaitDialogs) { - if (dialog != null) { - dialog.destroyDialog(); - } - } - - pleaseWaitDialogs.clear(); - - //close all opened frames - for (JFrame frame : childFrames) { - if (frame != null && frame.isVisible()) { - frame.setVisible(false); - frame.dispose(); - } - } - - childFrames.clear(); - - //close all opened dialogs - for (JDialog dialog : childDialogs) { - if (dialog != null && dialog.isVisible()) { - dialog.setVisible(false); - dialog.dispose(); - } - } - - childDialogs.clear(); - frame.dispose(); - viewport.flush(); - }); - } - - private void setFrameTitle(String title, String titleMsg) { - frame.setTitle(title + " - " + titleMsg); - } - - public void setTitle(String title) { - this.title = title; - if (!beforeTitle.equals(title)) setFrameTitle(title, titleMsg); - beforeTitle = title; - } - - public void setTitleMessage(String titleMsg) { - this.titleMsg = titleMsg; - if (!beforeTitleMsg.equals(title)) setFrameTitle(title, titleMsg); - beforeTitleMsg = titleMsg; - } - - public void updateTunerFields(List fields) { - tunerMenuPanel.removeAll(); - tunerMenuPanel.repaint(); - - for (TunableFieldPanel fieldPanel : fields) { - tunerMenuPanel.add(fieldPanel); - fieldPanel.showFieldPanel(); - } - } - - public void asyncCompilePipelines() { - if(PipelineCompiler.Companion.getIS_USABLE()) { - menuBar.workspCompile.setEnabled(false); - pipelineSelectorPanel.getButtonsPanel().getPipelineCompileBtt().setEnabled(false); - - eocvSim.pipelineManager.compiledPipelineManager.asyncCompile(true, (result) -> { - menuBar.workspCompile.setEnabled(true); - pipelineSelectorPanel.getButtonsPanel().getPipelineCompileBtt().setEnabled(true); - - return Unit.INSTANCE; - }); - } else { - asyncPleaseWaitDialog( - "Runtime compilation is not supported on this JVM", - "For further info, check the EOCV-Sim GitHub repo", - "Close", - new Dimension(320, 160), - true, true - ); - } - } - - public void selectPipelinesWorkspace() { - DialogFactory.createFileChooser( - frame, DialogFactory.FileChooser.Mode.DIRECTORY_SELECT - ).addCloseListener((OPTION, selectedFile, selectedFileFilter) -> { - if (OPTION == JFileChooser.APPROVE_OPTION) { - if(!selectedFile.exists()) selectedFile.mkdir(); - - eocvSim.onMainUpdate.doOnce(() -> - eocvSim.workspaceManager.setWorkspaceFile(selectedFile) - ); - } - }); - } - - public void createVSCodeWorkspace() { - DialogFactory.createFileChooser(frame, DialogFactory.FileChooser.Mode.DIRECTORY_SELECT) - .addCloseListener((OPTION, selectedFile, selectedFileFilter) -> { - if(OPTION == JFileChooser.APPROVE_OPTION) { - if(!selectedFile.exists()) selectedFile.mkdir(); - - if(selectedFile.isDirectory() && - Objects.requireNonNull(selectedFile.listFiles()).length == 0) { - eocvSim.workspaceManager.createWorkspaceWithTemplateAsync(selectedFile, GradleWorkspaceTemplate.INSTANCE); - } else { - asyncPleaseWaitDialog( - "The selected directory must be empty", "Select an empty directory or create a new one", - "Retry", new Dimension(320, 160), true, true - ).onCancel(this::createVSCodeWorkspace); - } - } - }); - } - - // PLEASE WAIT DIALOGS - - public boolean pleaseWaitDialog(JDialog diag, String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable, AsyncPleaseWaitDialog apwd, boolean isError) { - final JDialog dialog = diag == null ? new JDialog(this.frame) : diag; - - boolean addSubMessage = subMessage != null; - - int rows = 3; - if (!addSubMessage) { - rows--; - } - - dialog.setModal(true); - dialog.setLayout(new GridLayout(rows, 1)); - - if (isError) { - dialog.setTitle("Operation failed"); - } else { - dialog.setTitle("Operation in progress"); - } - - JLabel msg = new JLabel(message); - msg.setHorizontalAlignment(JLabel.CENTER); - msg.setVerticalAlignment(JLabel.CENTER); - - dialog.add(msg); - - JLabel subMsg = null; - if (addSubMessage) { - - subMsg = new JLabel(subMessage); - subMsg.setHorizontalAlignment(JLabel.CENTER); - subMsg.setVerticalAlignment(JLabel.CENTER); - - dialog.add(subMsg); - - } - - JPanel exitBttPanel = new JPanel(new FlowLayout()); - JButton cancelBtt = new JButton(cancelBttText); - - cancelBtt.setEnabled(cancellable); - - exitBttPanel.add(cancelBtt); - - boolean[] cancelled = {false}; - - cancelBtt.addActionListener(e -> { - cancelled[0] = true; - dialog.setVisible(false); - dialog.dispose(); - }); - - dialog.add(exitBttPanel); - - if (apwd != null) { - apwd.msg = msg; - apwd.subMsg = subMsg; - apwd.cancelBtt = cancelBtt; - } - - if(size == null) size = new Dimension(400, 200); - dialog.setSize(size); - - dialog.setLocationRelativeTo(null); - dialog.setResizable(false); - dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); - - dialog.setVisible(true); - - return cancelled[0]; - } - - public void pleaseWaitDialog(JDialog dialog, String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable) { - pleaseWaitDialog(dialog, message, subMessage, cancelBttText, size, cancellable, null, false); - } - - public void pleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable) { - pleaseWaitDialog(null, message, subMessage, cancelBttText, size, cancellable, null, false); - } - - public AsyncPleaseWaitDialog asyncPleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable, boolean isError) { - AsyncPleaseWaitDialog rPWD = new AsyncPleaseWaitDialog(message, subMessage, cancelBttText, size, cancellable, isError, eocvSim); - SwingUtilities.invokeLater(rPWD); - - return rPWD; - } - - public AsyncPleaseWaitDialog asyncPleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable) { - AsyncPleaseWaitDialog rPWD = new AsyncPleaseWaitDialog(message, subMessage, cancelBttText, size, cancellable, false, eocvSim); - SwingUtilities.invokeLater(rPWD); - - return rPWD; - } - - public class AsyncPleaseWaitDialog implements Runnable { - - public volatile JDialog dialog = new JDialog(frame); - - public volatile JLabel msg = null; - public volatile JLabel subMsg = null; - - public volatile JButton cancelBtt = null; - - public volatile boolean wasCancelled = false; - public volatile boolean isError; - - public volatile String initialMessage; - public volatile String initialSubMessage; - - public volatile boolean isDestroyed = false; - - String message; - String subMessage; - String cancelBttText; - - Dimension size; - - boolean cancellable; - - private final ArrayList onCancelRunnables = new ArrayList<>(); - - public AsyncPleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable, boolean isError, EOCVSim eocvSim) { - this.message = message; - this.subMessage = subMessage; - this.initialMessage = message; - this.initialSubMessage = subMessage; - this.cancelBttText = cancelBttText; - - this.size = size; - this.cancellable = cancellable; - - this.isError = isError; - - eocvSim.visualizer.pleaseWaitDialogs.add(this); - } - - public void onCancel(Runnable runn) { - onCancelRunnables.add(runn); - } - - @Override - public void run() { - wasCancelled = pleaseWaitDialog(dialog, message, subMessage, cancelBttText, size, cancellable, this, isError); - - if (wasCancelled) { - for (Runnable runn : onCancelRunnables) { - runn.run(); - } - } - } - - public void destroyDialog() { - if (!isDestroyed) { - dialog.setVisible(false); - dialog.dispose(); - isDestroyed = true; - } - } - - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui; + +import com.formdev.flatlaf.FlatLaf; +import com.github.serivesmejia.eocvsim.Build; +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.component.Viewport; +import com.github.serivesmejia.eocvsim.gui.component.tuner.ColorPicker; +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; +import com.github.serivesmejia.eocvsim.gui.component.visualizer.InputSourceDropTarget; +import com.github.serivesmejia.eocvsim.gui.component.visualizer.SourceSelectorPanel; +import com.github.serivesmejia.eocvsim.gui.component.visualizer.TelemetryPanel; +import com.github.serivesmejia.eocvsim.gui.component.visualizer.TopMenuBar; +import com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline.PipelineSelectorPanel; +import com.github.serivesmejia.eocvsim.gui.theme.Theme; +import com.github.serivesmejia.eocvsim.gui.util.ReflectTaskbar; +import com.github.serivesmejia.eocvsim.pipeline.compiler.PipelineCompiler; +import com.github.serivesmejia.eocvsim.util.Log; +import com.github.serivesmejia.eocvsim.util.event.EventHandler; +import com.github.serivesmejia.eocvsim.workspace.util.VSCodeLauncher; +import com.github.serivesmejia.eocvsim.workspace.util.template.GradleWorkspaceTemplate; +import kotlin.Unit; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class Visualizer { + + public final EventHandler onInitFinished = new EventHandler("OnVisualizerInitFinish"); + + public final ArrayList pleaseWaitDialogs = new ArrayList<>(); + + public final ArrayList childFrames = new ArrayList<>(); + public final ArrayList childDialogs = new ArrayList<>(); + + private final EOCVSim eocvSim; + public JFrame frame; + + public Viewport viewport = null; + public TopMenuBar menuBar = null; + public JPanel tunerMenuPanel = new JPanel(); + + public JScrollPane imgScrollPane = null; + + public JPanel rightContainer = null; + public JSplitPane globalSplitPane = null; + public JSplitPane imageTunerSplitPane = null; + + public PipelineSelectorPanel pipelineSelectorPanel = null; + public SourceSelectorPanel sourceSelectorPanel = null; + public TelemetryPanel telemetryPanel; + + private String title = "EasyOpenCV Simulator v" + Build.standardVersionString; + private String titleMsg = "No pipeline"; + private String beforeTitle = ""; + private String beforeTitleMsg = ""; + + public ColorPicker colorPicker = null; + + //stuff for zooming handling + private volatile boolean isCtrlPressed = false; + + private volatile boolean hasFinishedInitializing = false; + + public Visualizer(EOCVSim eocvSim) { + this.eocvSim = eocvSim; + } + + public void init(Theme theme) { + if(ReflectTaskbar.INSTANCE.isUsable()){ + try { + //set icon for mac os (and other systems which do support this method) + ReflectTaskbar.INSTANCE.setIconImage(Icons.INSTANCE.getImage("ico_eocvsim").getImage()); + } catch (final UnsupportedOperationException e) { + Log.warn("Visualizer", "Setting the Taskbar icon image is not supported on this platform"); + } catch (final SecurityException e) { + Log.error("Visualizer", "Security exception while setting TaskBar icon", e); + } + } + + try { + theme.install(); + } catch (Exception e) { + Log.error("Visualizer", "Failed to install theme " + theme.name(), e); + } + + Icons.INSTANCE.setDark(FlatLaf.isLafDark()); + + if(Build.isDev) { + title += "-dev "; + } + + //instantiate all swing elements after theme installation + frame = new JFrame(); + viewport = new Viewport(eocvSim, eocvSim.getConfig().pipelineMaxFps.getFps()); + + menuBar = new TopMenuBar(this, eocvSim); + + tunerMenuPanel = new JPanel(); + + pipelineSelectorPanel = new PipelineSelectorPanel(eocvSim); + sourceSelectorPanel = new SourceSelectorPanel(eocvSim); + telemetryPanel = new TelemetryPanel(); + + rightContainer = new JPanel(); + + /* + * TOP MENU BAR + */ + + frame.setJMenuBar(menuBar); + + /* + * IMG VISUALIZER & SCROLL PANE + */ + + imgScrollPane = new JScrollPane(viewport); + + imgScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + imgScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + + imgScrollPane.getHorizontalScrollBar().setUnitIncrement(16); + imgScrollPane.getVerticalScrollBar().setUnitIncrement(16); + + rightContainer.setLayout(new BoxLayout(rightContainer, BoxLayout.Y_AXIS)); + + /* + * PIPELINE SELECTOR + */ + pipelineSelectorPanel.setBorder(new EmptyBorder(0, 20, 0, 20)); + rightContainer.add(pipelineSelectorPanel); + + /* + * SOURCE SELECTOR + */ + sourceSelectorPanel.setBorder(new EmptyBorder(0, 20, 0, 20)); + rightContainer.add(sourceSelectorPanel); + + /* + * TELEMETRY + */ + telemetryPanel.setBorder(new EmptyBorder(0, 20, 20, 20)); + rightContainer.add(telemetryPanel); + + /* + * SPLIT + */ + + //left side, image scroll & tuner menu split panel + imageTunerSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, imgScrollPane, tunerMenuPanel); + + imageTunerSplitPane.setResizeWeight(1); + imageTunerSplitPane.setOneTouchExpandable(false); + imageTunerSplitPane.setContinuousLayout(true); + + //global + globalSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, imageTunerSplitPane, rightContainer); + + globalSplitPane.setResizeWeight(1); + globalSplitPane.setOneTouchExpandable(false); + globalSplitPane.setContinuousLayout(true); + + globalSplitPane.setDropTarget(new InputSourceDropTarget(eocvSim)); + + frame.add(globalSplitPane, BorderLayout.CENTER); + + //initialize other various stuff of the frame + frame.setSize(780, 645); + frame.setMinimumSize(frame.getSize()); + frame.setTitle("EasyOpenCV Simulator - No Pipeline"); + + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + frame.setIconImage(Icons.INSTANCE.getImage("ico_eocvsim").getImage()); + + frame.setLocationRelativeTo(null); + frame.setExtendedState(JFrame.MAXIMIZED_BOTH); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + globalSplitPane.setDividerLocation(1070); + + colorPicker = new ColorPicker(viewport.image); + + frame.setVisible(true); + + onInitFinished.run(); + onInitFinished.setCallRightAway(true); + + registerListeners(); + + hasFinishedInitializing = true; + + if(!PipelineCompiler.Companion.getIS_USABLE()) { + compilingUnsupported(); + } + } + + public void initAsync(Theme simTheme) { + SwingUtilities.invokeLater(() -> init(simTheme)); + } + + private void registerListeners() { + + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + eocvSim.onMainUpdate.doOnce((Runnable) eocvSim::destroy); + } + }); + + //handling onViewportTapped evts + viewport.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if(!colorPicker.isPicking()) + eocvSim.pipelineManager.callViewportTapped(); + } + }); + + //VIEWPORT RESIZE HANDLING + imgScrollPane.addMouseWheelListener(e -> { + if (isCtrlPressed) { //check if control key is pressed + double scale = viewport.getViewportScale() - (0.15 * e.getPreciseWheelRotation()); + viewport.setViewportScale(scale); + } + }); + + //listening for keyboard presses and releases, to check if ctrl key was pressed or released (handling zoom) + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(ke -> { + switch (ke.getID()) { + case KeyEvent.KEY_PRESSED: + if (ke.getKeyCode() == KeyEvent.VK_CONTROL) { + isCtrlPressed = true; + imgScrollPane.setWheelScrollingEnabled(false); //lock scrolling if ctrl is pressed + } + break; + case KeyEvent.KEY_RELEASED: + if (ke.getKeyCode() == KeyEvent.VK_CONTROL) { + isCtrlPressed = false; + imgScrollPane.setWheelScrollingEnabled(true); //unlock + } + break; + } + return false; //idk let's just return false 'cause keyboard input doesn't work otherwise + }); + + //resizes all three JLists in right panel to make buttons visible in smaller resolutions + frame.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent evt) { + double ratioH = frame.getSize().getHeight() / 645; + + double fontSize = 17 * ratioH; + Font font = pipelineSelectorPanel.getPipelineSelectorLabel().getFont().deriveFont((float)fontSize); + + pipelineSelectorPanel.getPipelineSelectorLabel().setFont(font); + pipelineSelectorPanel.revalAndRepaint(); + + sourceSelectorPanel.getSourceSelectorLabel().setFont(font); + sourceSelectorPanel.revalAndRepaint(); + + telemetryPanel.getTelemetryLabel().setFont(font); + telemetryPanel.revalAndRepaint(); + + rightContainer.revalidate(); + rightContainer.repaint(); + } + }); + + // stop color-picking mode when changing pipeline + // TODO: find out why this breaks everything????? + // eocvSim.pipelineManager.onPipelineChange.doPersistent(() -> colorPicker.stopPicking()); + } + + public boolean hasFinishedInit() { return hasFinishedInitializing; } + + public void waitForFinishingInit() { + while (!hasFinishedInitializing) { + Thread.yield(); + } + } + + public void close() { + SwingUtilities.invokeLater(() -> { + frame.setVisible(false); + viewport.stop(); + + //close all asyncpleasewait dialogs + for (AsyncPleaseWaitDialog dialog : pleaseWaitDialogs) { + if (dialog != null) { + dialog.destroyDialog(); + } + } + + pleaseWaitDialogs.clear(); + + //close all opened frames + for (JFrame frame : childFrames) { + if (frame != null && frame.isVisible()) { + frame.setVisible(false); + frame.dispose(); + } + } + + childFrames.clear(); + + //close all opened dialogs + for (JDialog dialog : childDialogs) { + if (dialog != null && dialog.isVisible()) { + dialog.setVisible(false); + dialog.dispose(); + } + } + + childDialogs.clear(); + frame.dispose(); + viewport.flush(); + }); + } + + private void setFrameTitle(String title, String titleMsg) { + frame.setTitle(title + " - " + titleMsg); + } + + public void setTitle(String title) { + this.title = title; + if (!beforeTitle.equals(title)) setFrameTitle(title, titleMsg); + beforeTitle = title; + } + + public void setTitleMessage(String titleMsg) { + this.titleMsg = titleMsg; + if (!beforeTitleMsg.equals(title)) setFrameTitle(title, titleMsg); + beforeTitleMsg = titleMsg; + } + + public void updateTunerFields(List fields) { + tunerMenuPanel.removeAll(); + tunerMenuPanel.repaint(); + + for (TunableFieldPanel fieldPanel : fields) { + tunerMenuPanel.add(fieldPanel); + fieldPanel.showFieldPanel(); + } + } + + public void asyncCompilePipelines() { + if(PipelineCompiler.Companion.getIS_USABLE()) { + menuBar.workspCompile.setEnabled(false); + pipelineSelectorPanel.getButtonsPanel().getPipelineCompileBtt().setEnabled(false); + + eocvSim.pipelineManager.compiledPipelineManager.asyncCompile(true, (result) -> { + menuBar.workspCompile.setEnabled(true); + pipelineSelectorPanel.getButtonsPanel().getPipelineCompileBtt().setEnabled(true); + + return Unit.INSTANCE; + }); + } else { + compilingUnsupported(); + } + } + + public void compilingUnsupported() { + asyncPleaseWaitDialog( + "Runtime compiling is not supported on this JVM", + "For further info, check the EOCV-Sim GitHub repo", + "Close", + new Dimension(320, 160), + true, true + ); + } + + public void selectPipelinesWorkspace() { + DialogFactory.createFileChooser( + frame, DialogFactory.FileChooser.Mode.DIRECTORY_SELECT + ).addCloseListener((OPTION, selectedFile, selectedFileFilter) -> { + if (OPTION == JFileChooser.APPROVE_OPTION) { + if(!selectedFile.exists()) selectedFile.mkdir(); + + eocvSim.onMainUpdate.doOnce(() -> + eocvSim.workspaceManager.setWorkspaceFile(selectedFile) + ); + } + }); + } + + public void createVSCodeWorkspace() { + DialogFactory.createFileChooser(frame, DialogFactory.FileChooser.Mode.DIRECTORY_SELECT) + .addCloseListener((OPTION, selectedFile, selectedFileFilter) -> { + if(OPTION == JFileChooser.APPROVE_OPTION) { + if(!selectedFile.exists()) selectedFile.mkdir(); + + if(selectedFile.isDirectory() && selectedFile.listFiles().length == 0) { + eocvSim.workspaceManager.createWorkspaceWithTemplateAsync( + selectedFile, GradleWorkspaceTemplate.INSTANCE, + + () -> { + askOpenVSCode(); + return Unit.INSTANCE; // weird kotlin interop + } + ); + } else { + asyncPleaseWaitDialog( + "The selected directory must be empty", "Select an empty directory or create a new one", + "Retry", new Dimension(320, 160), true, true + ).onCancel(this::createVSCodeWorkspace); + } + } + }); + } + + public void askOpenVSCode() { + DialogFactory.createYesOrNo(frame, "A new workspace was created. Do you wanna open VS Code?", "", + (result) -> { + if(result == 0) { + VSCodeLauncher.INSTANCE.asyncLaunch(eocvSim.workspaceManager.getWorkspaceFile()); + } + } + ); + } + + // PLEASE WAIT DIALOGS + + public boolean pleaseWaitDialog(JDialog diag, String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable, AsyncPleaseWaitDialog apwd, boolean isError) { + final JDialog dialog = diag == null ? new JDialog(this.frame) : diag; + + boolean addSubMessage = subMessage != null; + + int rows = 3; + if (!addSubMessage) { + rows--; + } + + dialog.setModal(true); + dialog.setLayout(new GridLayout(rows, 1)); + + if (isError) { + dialog.setTitle("Operation failed"); + } else { + dialog.setTitle("Operation in progress"); + } + + JLabel msg = new JLabel(message); + msg.setHorizontalAlignment(JLabel.CENTER); + msg.setVerticalAlignment(JLabel.CENTER); + + dialog.add(msg); + + JLabel subMsg = null; + if (addSubMessage) { + + subMsg = new JLabel(subMessage); + subMsg.setHorizontalAlignment(JLabel.CENTER); + subMsg.setVerticalAlignment(JLabel.CENTER); + + dialog.add(subMsg); + + } + + JPanel exitBttPanel = new JPanel(new FlowLayout()); + JButton cancelBtt = new JButton(cancelBttText); + + cancelBtt.setEnabled(cancellable); + + exitBttPanel.add(cancelBtt); + + boolean[] cancelled = {false}; + + cancelBtt.addActionListener(e -> { + cancelled[0] = true; + dialog.setVisible(false); + dialog.dispose(); + }); + + dialog.add(exitBttPanel); + + if (apwd != null) { + apwd.msg = msg; + apwd.subMsg = subMsg; + apwd.cancelBtt = cancelBtt; + } + + if(size == null) size = new Dimension(400, 200); + dialog.setSize(size); + + dialog.setLocationRelativeTo(null); + dialog.setResizable(false); + dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + + dialog.setVisible(true); + + return cancelled[0]; + } + + public void pleaseWaitDialog(JDialog dialog, String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable) { + pleaseWaitDialog(dialog, message, subMessage, cancelBttText, size, cancellable, null, false); + } + + public void pleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable) { + pleaseWaitDialog(null, message, subMessage, cancelBttText, size, cancellable, null, false); + } + + public AsyncPleaseWaitDialog asyncPleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable, boolean isError) { + AsyncPleaseWaitDialog rPWD = new AsyncPleaseWaitDialog(message, subMessage, cancelBttText, size, cancellable, isError, eocvSim); + SwingUtilities.invokeLater(rPWD); + + return rPWD; + } + + public AsyncPleaseWaitDialog asyncPleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable) { + AsyncPleaseWaitDialog rPWD = new AsyncPleaseWaitDialog(message, subMessage, cancelBttText, size, cancellable, false, eocvSim); + SwingUtilities.invokeLater(rPWD); + + return rPWD; + } + + public class AsyncPleaseWaitDialog implements Runnable { + + public volatile JDialog dialog = new JDialog(frame); + + public volatile JLabel msg = null; + public volatile JLabel subMsg = null; + + public volatile JButton cancelBtt = null; + + public volatile boolean wasCancelled = false; + public volatile boolean isError; + + public volatile String initialMessage; + public volatile String initialSubMessage; + + public volatile boolean isDestroyed = false; + + String message; + String subMessage; + String cancelBttText; + + Dimension size; + + boolean cancellable; + + private final ArrayList onCancelRunnables = new ArrayList<>(); + + public AsyncPleaseWaitDialog(String message, String subMessage, String cancelBttText, Dimension size, boolean cancellable, boolean isError, EOCVSim eocvSim) { + this.message = message; + this.subMessage = subMessage; + this.initialMessage = message; + this.initialSubMessage = subMessage; + this.cancelBttText = cancelBttText; + + this.size = size; + this.cancellable = cancellable; + + this.isError = isError; + + eocvSim.visualizer.pleaseWaitDialogs.add(this); + } + + public void onCancel(Runnable runn) { + onCancelRunnables.add(runn); + } + + @Override + public void run() { + wasCancelled = pleaseWaitDialog(dialog, message, subMessage, cancelBttText, size, cancellable, this, isError); + + if (wasCancelled) { + for (Runnable runn : onCancelRunnables) { + runn.run(); + } + } + } + + public void destroyDialog() { + if (!isDestroyed) { + dialog.setVisible(false); + dialog.dispose(); + isDestroyed = true; + } + } + + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/ImageX.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/ImageX.java index 52c29319..7a79b711 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/ImageX.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/ImageX.java @@ -1,88 +1,88 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component; - -import com.github.serivesmejia.eocvsim.gui.util.GuiUtil; -import com.github.serivesmejia.eocvsim.util.CvUtil; -import org.opencv.core.Mat; - -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; - -public class ImageX extends JLabel { - - volatile ImageIcon icon; - - public ImageX() { - super(); - } - - public ImageX(ImageIcon img) { - this(); - setImage(img); - } - - public ImageX(BufferedImage img) { - this(); - setImage(img); - } - - public void setImage(ImageIcon img) { - if (icon != null) - icon.getImage().flush(); //flush old image :p - - icon = img; - - setIcon(icon); //set to the new image - } - - public synchronized void setImage(BufferedImage img) { - Graphics2D g2d = (Graphics2D) getGraphics(); - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - setImage(new ImageIcon(img)); //set to the new image - } - - public synchronized void setImageMat(Mat m) { - setImage(CvUtil.matToBufferedImage(m)); - } - - public synchronized BufferedImage getImage() { - return (BufferedImage)icon.getImage(); - } - - @Override - public void setSize(int width, int height) { - super.setSize(width, height); - setImage(GuiUtil.scaleImage(icon, width, height)); //set to the new image - } - - @Override - public void setSize(Dimension dimension) { - super.setSize(dimension); - setImage(GuiUtil.scaleImage(icon, (int)dimension.getWidth(), (int)dimension.getHeight())); //set to the new image - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component; + +import com.github.serivesmejia.eocvsim.gui.util.GuiUtil; +import com.github.serivesmejia.eocvsim.util.CvUtil; +import org.opencv.core.Mat; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; + +public class ImageX extends JLabel { + + volatile ImageIcon icon; + + public ImageX() { + super(); + } + + public ImageX(ImageIcon img) { + this(); + setImage(img); + } + + public ImageX(BufferedImage img) { + this(); + setImage(img); + } + + public void setImage(ImageIcon img) { + if (icon != null) + icon.getImage().flush(); //flush old image :p + + icon = img; + + setIcon(icon); //set to the new image + } + + public synchronized void setImage(BufferedImage img) { + Graphics2D g2d = (Graphics2D) getGraphics(); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + setImage(new ImageIcon(img)); //set to the new image + } + + public synchronized void setImageMat(Mat m) { + setImage(CvUtil.matToBufferedImage(m)); + } + + public synchronized BufferedImage getImage() { + return (BufferedImage)icon.getImage(); + } + + @Override + public void setSize(int width, int height) { + super.setSize(width, height); + setImage(GuiUtil.scaleImage(icon, width, height)); //set to the new image + } + + @Override + public void setSize(Dimension dimension) { + super.setSize(dimension); + setImage(GuiUtil.scaleImage(icon, (int)dimension.getWidth(), (int)dimension.getHeight())); //set to the new image + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/PopupX.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/PopupX.kt index 848954d5..15cf5bd3 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/PopupX.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/PopupX.kt @@ -1,100 +1,100 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component - -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import java.awt.Window -import java.awt.event.KeyAdapter -import java.awt.event.KeyEvent -import java.awt.event.WindowEvent -import java.awt.event.WindowFocusListener -import javax.swing.JPanel -import javax.swing.JPopupMenu -import javax.swing.JWindow -import javax.swing.Popup - -class PopupX @JvmOverloads constructor(windowAncestor: Window, - private val panel: JPanel, - private var x: Int, - private var y: Int, - var closeOnFocusLost: Boolean = true, - private val fixX: Boolean = false, - private val fixY: Boolean = true) : Popup(), WindowFocusListener { - - val window = JWindow(windowAncestor) - - @JvmField val onShow = EventHandler("PopupX-OnShow") - @JvmField val onHide = EventHandler("PopupX-OnHide") - - init { - window.isFocusable = true - window.setLocation(x, y) - window.contentPane = panel - - panel.border = JPopupMenu().border - - window.size = panel.preferredSize - - windowAncestor.addKeyListener(object: KeyAdapter() { - override fun keyPressed(e: KeyEvent?) { - if(e?.keyCode == KeyEvent.VK_ESCAPE) { - hide() - windowAncestor.removeKeyListener(this) - } - } - }) - } - - override fun show() { - window.addWindowFocusListener(this) - window.isVisible = true - - //fixes position since our panel dimensions - //aren't known until it's set visible (above) - if(fixX) x -= panel.width / 4 - if(fixY) y -= panel.height - setLocation(x, y) - - onShow.run() - } - - override fun hide() { - if(!window.isVisible) return - - window.removeWindowFocusListener(this) - window.isVisible = false - onHide.run() - } - - override fun windowGainedFocus(e: WindowEvent?) {} - - override fun windowLostFocus(e: WindowEvent?) { - if(closeOnFocusLost) { - hide() - } - } - - fun setLocation(width: Int, height: Int) = window.setLocation(width, height) - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component + +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import java.awt.Window +import java.awt.event.KeyAdapter +import java.awt.event.KeyEvent +import java.awt.event.WindowEvent +import java.awt.event.WindowFocusListener +import javax.swing.JPanel +import javax.swing.JPopupMenu +import javax.swing.JWindow +import javax.swing.Popup + +class PopupX @JvmOverloads constructor(windowAncestor: Window, + private val panel: JPanel, + private var x: Int, + private var y: Int, + var closeOnFocusLost: Boolean = true, + private val fixX: Boolean = false, + private val fixY: Boolean = true) : Popup(), WindowFocusListener { + + val window = JWindow(windowAncestor) + + @JvmField val onShow = EventHandler("PopupX-OnShow") + @JvmField val onHide = EventHandler("PopupX-OnHide") + + init { + window.isFocusable = true + window.setLocation(x, y) + window.contentPane = panel + + panel.border = JPopupMenu().border + + window.size = panel.preferredSize + + windowAncestor.addKeyListener(object: KeyAdapter() { + override fun keyPressed(e: KeyEvent?) { + if(e?.keyCode == KeyEvent.VK_ESCAPE) { + hide() + windowAncestor.removeKeyListener(this) + } + } + }) + } + + override fun show() { + window.addWindowFocusListener(this) + window.isVisible = true + + //fixes position since our panel dimensions + //aren't known until it's set visible (above) + if(fixX) x -= panel.width / 4 + if(fixY) y -= panel.height + setLocation(x, y) + + onShow.run() + } + + override fun hide() { + if(!window.isVisible) return + + window.removeWindowFocusListener(this) + window.isVisible = false + onHide.run() + } + + override fun windowGainedFocus(e: WindowEvent?) {} + + override fun windowLostFocus(e: WindowEvent?) { + if(closeOnFocusLost) { + hide() + } + } + + fun setLocation(width: Int, height: Int) = window.setLocation(width, height) + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/SliderX.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/SliderX.kt index 98d617f0..bfb5e775 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/SliderX.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/SliderX.kt @@ -1,72 +1,72 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component - -import com.qualcomm.robotcore.util.Range -import javax.swing.JSlider -import kotlin.math.roundToInt - -/** - * Allows for a slider to take a range of type double - * and return a value of type double, instead of int. - * - * Achieved by upscaling the input bounds and the input - * value by a certain amount (multiplier of 10), and - * downscaling the value when getting it - */ -open class SliderX(private var minBound: Double, - private var maxBound: Double, - private val scale: Int) : JSlider() { - - var scaledValue: Double = 0.0 - set(value) { - field = Range.clip(value * scale, minimum.toDouble(), maximum.toDouble()) - this.value = field.roundToInt() - } - get() { - return Range.clip(this.value.toDouble() / scale, minBound, maxBound) - } - - init { - setScaledBounds(minBound, maxBound) - setMajorTickSpacing(scale) - setMinorTickSpacing(scale / 4) - } - - fun setScaledBounds(minBound: Double, maxBound: Double) { - //for some reason we have to scale min bound when - //going negative... but not when going positive - this.minBound = if(minBound > 0) { - minBound - } else { - minBound * scale - } - - this.maxBound = maxBound * scale - - minimum = this.minBound.roundToInt() - maximum = this.maxBound.roundToInt() - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component + +import com.qualcomm.robotcore.util.Range +import javax.swing.JSlider +import kotlin.math.roundToInt + +/** + * Allows for a slider to take a range of type double + * and return a value of type double, instead of int. + * + * Achieved by upscaling the input bounds and the input + * value by a certain amount (multiplier of 10), and + * downscaling the value when getting it + */ +open class SliderX(private var minBound: Double, + private var maxBound: Double, + private val scale: Int) : JSlider() { + + var scaledValue: Double = 0.0 + set(value) { + field = Range.clip(value * scale, minimum.toDouble(), maximum.toDouble()) + this.value = field.roundToInt() + } + get() { + return Range.clip(this.value.toDouble() / scale, minBound, maxBound) + } + + init { + setScaledBounds(minBound, maxBound) + setMajorTickSpacing(scale) + setMinorTickSpacing(scale / 4) + } + + fun setScaledBounds(minBound: Double, maxBound: Double) { + //for some reason we have to scale min bound when + //going negative... but not when going positive + this.minBound = if(minBound > 0) { + minBound + } else { + minBound * scale + } + + this.maxBound = maxBound * scale + + minimum = this.minBound.roundToInt() + maximum = this.maxBound.roundToInt() + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/Viewport.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/Viewport.java index 33349f1d..a5f25c10 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/Viewport.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/Viewport.java @@ -1,144 +1,144 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.config.Config; -import com.github.serivesmejia.eocvsim.gui.util.MatPoster; -import com.github.serivesmejia.eocvsim.util.image.DynamicBufferedImageRecycler; -import com.github.serivesmejia.eocvsim.util.CvUtil; -import com.github.serivesmejia.eocvsim.util.Log; -import com.qualcomm.robotcore.util.Range; -import org.opencv.core.Mat; -import org.opencv.core.Size; -import org.opencv.imgproc.Imgproc; - -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; - -public class Viewport extends JPanel { - - public final ImageX image = new ImageX(); - public final MatPoster matPoster; - - private Mat lastVisualizedMat = null; - private Mat lastVisualizedScaledMat = null; - - private final DynamicBufferedImageRecycler buffImgGiver = new DynamicBufferedImageRecycler(); - - private volatile BufferedImage lastBuffImage; - private volatile Dimension lastDimension; - - private double scale; - - private final EOCVSim eocvSim; - - public Viewport(EOCVSim eocvSim, int maxQueueItems) { - super(new GridBagLayout()); - - this.eocvSim = eocvSim; - setViewportScale(eocvSim.configManager.getConfig().zoom); - - add(image, new GridBagConstraints()); - - matPoster = new MatPoster("Viewport", maxQueueItems); - attachToPoster(matPoster); - } - - public void postMatAsync(Mat mat) { - matPoster.post(mat); - } - - public synchronized void postMat(Mat mat) { - if(lastVisualizedMat == null) lastVisualizedMat = new Mat(); //create latest mat if we have null reference - if(lastVisualizedScaledMat == null) lastVisualizedScaledMat = new Mat(); //create last scaled mat if null reference - - JFrame frame = (JFrame) SwingUtilities.getWindowAncestor(this); - - mat.copyTo(lastVisualizedMat); //copy given mat to viewport latest one - - double wScale = (double) frame.getWidth() / mat.width(); - double hScale = (double) frame.getHeight() / mat.height(); - - double calcScale = (wScale / hScale) * 1.5; - double finalScale = Math.max(0.1, Math.min(3, scale * calcScale)); - - Size size = new Size(mat.width() * finalScale, mat.height() * finalScale); - Imgproc.resize(mat, lastVisualizedScaledMat, size, 0.0, 0.0, Imgproc.INTER_AREA); //resize mat to lastVisualizedScaledMat - - Dimension newDimension = new Dimension(lastVisualizedScaledMat.width(), lastVisualizedScaledMat.height()); - - if(lastBuffImage != null) buffImgGiver.returnBufferedImage(lastBuffImage); - - lastBuffImage = buffImgGiver.giveBufferedImage(newDimension, 2); - lastDimension = newDimension; - - CvUtil.matToBufferedImage(lastVisualizedScaledMat, lastBuffImage); - - image.setImage(lastBuffImage); //set buff image to ImageX component - - eocvSim.configManager.getConfig().zoom = scale; //store latest scale if store setting turned on - } - - public void attachToPoster(MatPoster poster) { - poster.addPostable((m) -> { - try { - Imgproc.cvtColor(m, m, Imgproc.COLOR_RGB2BGR); - postMat(m); - } catch(Exception ex) { - Log.error("Viewport-Postable", "Couldn't visualize last mat", ex); - } - }); - } - - public void flush() { - buffImgGiver.flushAll(); - } - - public void stop() { - matPoster.stop(); - flush(); - } - - public synchronized void setViewportScale(double scale) { - scale = Range.clip(scale, 0.1, 3); - - boolean scaleChanged = this.scale != scale; - this.scale = scale; - - if(lastVisualizedMat != null && scaleChanged) - postMat(lastVisualizedMat); - - } - - public synchronized Mat getLastVisualizedMat() { - return lastVisualizedMat; - } - - public synchronized double getViewportScale() { - return scale; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.config.Config; +import com.github.serivesmejia.eocvsim.gui.util.MatPoster; +import com.github.serivesmejia.eocvsim.util.image.DynamicBufferedImageRecycler; +import com.github.serivesmejia.eocvsim.util.CvUtil; +import com.github.serivesmejia.eocvsim.util.Log; +import com.qualcomm.robotcore.util.Range; +import org.opencv.core.Mat; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; + +public class Viewport extends JPanel { + + public final ImageX image = new ImageX(); + public final MatPoster matPoster; + + private Mat lastVisualizedMat = null; + private Mat lastVisualizedScaledMat = null; + + private final DynamicBufferedImageRecycler buffImgGiver = new DynamicBufferedImageRecycler(); + + private volatile BufferedImage lastBuffImage; + private volatile Dimension lastDimension; + + private double scale; + + private final EOCVSim eocvSim; + + public Viewport(EOCVSim eocvSim, int maxQueueItems) { + super(new GridBagLayout()); + + this.eocvSim = eocvSim; + setViewportScale(eocvSim.configManager.getConfig().zoom); + + add(image, new GridBagConstraints()); + + matPoster = new MatPoster("Viewport", maxQueueItems); + attachToPoster(matPoster); + } + + public void postMatAsync(Mat mat) { + matPoster.post(mat); + } + + public synchronized void postMat(Mat mat) { + if(lastVisualizedMat == null) lastVisualizedMat = new Mat(); //create latest mat if we have null reference + if(lastVisualizedScaledMat == null) lastVisualizedScaledMat = new Mat(); //create last scaled mat if null reference + + JFrame frame = (JFrame) SwingUtilities.getWindowAncestor(this); + + mat.copyTo(lastVisualizedMat); //copy given mat to viewport latest one + + double wScale = (double) frame.getWidth() / mat.width(); + double hScale = (double) frame.getHeight() / mat.height(); + + double calcScale = (wScale / hScale) * 1.5; + double finalScale = Math.max(0.1, Math.min(3, scale * calcScale)); + + Size size = new Size(mat.width() * finalScale, mat.height() * finalScale); + Imgproc.resize(mat, lastVisualizedScaledMat, size, 0.0, 0.0, Imgproc.INTER_AREA); //resize mat to lastVisualizedScaledMat + + Dimension newDimension = new Dimension(lastVisualizedScaledMat.width(), lastVisualizedScaledMat.height()); + + if(lastBuffImage != null) buffImgGiver.returnBufferedImage(lastBuffImage); + + lastBuffImage = buffImgGiver.giveBufferedImage(newDimension, 2); + lastDimension = newDimension; + + CvUtil.matToBufferedImage(lastVisualizedScaledMat, lastBuffImage); + + image.setImage(lastBuffImage); //set buff image to ImageX component + + eocvSim.configManager.getConfig().zoom = scale; //store latest scale if store setting turned on + } + + public void attachToPoster(MatPoster poster) { + poster.addPostable((m) -> { + try { + Imgproc.cvtColor(m, m, Imgproc.COLOR_RGB2BGR); + postMat(m); + } catch(Exception ex) { + Log.error("Viewport-Postable", "Couldn't visualize last mat", ex); + } + }); + } + + public void flush() { + buffImgGiver.flushAll(); + } + + public void stop() { + matPoster.stop(); + flush(); + } + + public synchronized void setViewportScale(double scale) { + scale = Range.clip(scale, 0.1, 3); + + boolean scaleChanged = this.scale != scale; + this.scale = scale; + + if(lastVisualizedMat != null && scaleChanged) + postMat(lastVisualizedMat); + + } + + public synchronized Mat getLastVisualizedMat() { + return lastVisualizedMat; + } + + public synchronized double getViewportScale() { + return scale; + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/EnumComboBox.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/EnumComboBox.kt index a06bd938..3530403c 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/EnumComboBox.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/EnumComboBox.kt @@ -1,78 +1,78 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.input - -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import javax.swing.JComboBox -import javax.swing.JLabel -import javax.swing.JPanel - -class EnumComboBox> @JvmOverloads constructor( - descriptiveText: String = "Select a value:", - private val clazz: Class, - values: Array, - private val nameSupplier: (T) -> String = { it.name }, - private val enumSupplier: (String) -> T = { - java.lang.Enum.valueOf(clazz, it) as T - } -) : JPanel() { - - val descriptiveLabel = JLabel(descriptiveText) - val comboBox = JComboBox() - - var selectedEnum: T? - set(value) { - value?.let { - comboBox.selectedItem = nameSupplier(it) - } - } - get() { - comboBox.selectedItem?.let { - return enumSupplier(comboBox.selectedItem!!.toString()) - } - return null - } - - val onSelect = EventHandler("EnumComboBox-OnSelect") - - init { - if(descriptiveText.trim() != "") { - descriptiveLabel.horizontalAlignment = JLabel.LEFT - add(descriptiveLabel) - } - - for(value in values) { - comboBox.addItem(nameSupplier(value)) - } - add(comboBox) - - comboBox.addActionListener { onSelect.run() } - } - - - fun removeEnumOption(enum: T) { - comboBox.removeItem(nameSupplier(enum)) - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.input + +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import javax.swing.JComboBox +import javax.swing.JLabel +import javax.swing.JPanel + +class EnumComboBox> @JvmOverloads constructor( + descriptiveText: String = "Select a value:", + private val clazz: Class, + values: Array, + private val nameSupplier: (T) -> String = { it.name }, + private val enumSupplier: (String) -> T = { + java.lang.Enum.valueOf(clazz, it) as T + } +) : JPanel() { + + val descriptiveLabel = JLabel(descriptiveText) + val comboBox = JComboBox() + + var selectedEnum: T? + set(value) { + value?.let { + comboBox.selectedItem = nameSupplier(it) + } + } + get() { + comboBox.selectedItem?.let { + return enumSupplier(comboBox.selectedItem!!.toString()) + } + return null + } + + val onSelect = EventHandler("EnumComboBox-OnSelect") + + init { + if(descriptiveText.trim() != "") { + descriptiveLabel.horizontalAlignment = JLabel.LEFT + add(descriptiveLabel) + } + + for(value in values) { + comboBox.addItem(nameSupplier(value)) + } + add(comboBox) + + comboBox.addActionListener { onSelect.run() } + } + + + fun removeEnumOption(enum: T) { + comboBox.removeItem(nameSupplier(enum)) + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/FileSelector.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/FileSelector.kt index 73340b65..16a47144 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/FileSelector.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/FileSelector.kt @@ -1,75 +1,75 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.input - -import com.github.serivesmejia.eocvsim.gui.DialogFactory -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import java.awt.FlowLayout -import java.io.File -import javax.swing.* -import javax.swing.filechooser.FileFilter - -class FileSelector(columns: Int = 18, - mode: DialogFactory.FileChooser.Mode, - vararg fileFilters: FileFilter?) : JPanel(FlowLayout()) { - - constructor(columns: Int) : this(columns, DialogFactory.FileChooser.Mode.FILE_SELECT) - - constructor(columns: Int, vararg fileFilters: FileFilter?) : this(columns, DialogFactory.FileChooser.Mode.FILE_SELECT, *fileFilters) - - constructor(columns: Int, mode: DialogFactory.FileChooser.Mode) : this(columns, mode, null) - - @JvmField val onFileSelect = EventHandler("OnFileSelect") - - val dirTextField = JTextField(columns) - val selectDirButton = JButton("Select file...") - - var lastSelectedFile: File? = null - set(value) { - dirTextField.text = value?.absolutePath ?: "" - field = value - onFileSelect.run() - } - - var lastSelectedFileFilter: FileFilter? = null - private set - - init { - dirTextField.isEditable = false - - selectDirButton.addActionListener { - val frame = SwingUtilities.getWindowAncestor(this) - DialogFactory.createFileChooser(frame, mode, *fileFilters).addCloseListener { returnVal: Int, selectedFile: File?, selectedFileFilter: FileFilter? -> - if (returnVal == JFileChooser.APPROVE_OPTION) { - lastSelectedFileFilter = selectedFileFilter - lastSelectedFile = selectedFile - } - } - } - - add(dirTextField) - add(selectDirButton) - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.input + +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import java.awt.FlowLayout +import java.io.File +import javax.swing.* +import javax.swing.filechooser.FileFilter + +class FileSelector(columns: Int = 18, + mode: DialogFactory.FileChooser.Mode, + vararg fileFilters: FileFilter?) : JPanel(FlowLayout()) { + + constructor(columns: Int) : this(columns, DialogFactory.FileChooser.Mode.FILE_SELECT) + + constructor(columns: Int, vararg fileFilters: FileFilter?) : this(columns, DialogFactory.FileChooser.Mode.FILE_SELECT, *fileFilters) + + constructor(columns: Int, mode: DialogFactory.FileChooser.Mode) : this(columns, mode, null) + + @JvmField val onFileSelect = EventHandler("OnFileSelect") + + val dirTextField = JTextField(columns) + val selectDirButton = JButton("Select file...") + + var lastSelectedFile: File? = null + set(value) { + dirTextField.text = value?.absolutePath ?: "" + field = value + onFileSelect.run() + } + + var lastSelectedFileFilter: FileFilter? = null + private set + + init { + dirTextField.isEditable = false + + selectDirButton.addActionListener { + val frame = SwingUtilities.getWindowAncestor(this) + DialogFactory.createFileChooser(frame, mode, *fileFilters).addCloseListener { returnVal: Int, selectedFile: File?, selectedFileFilter: FileFilter? -> + if (returnVal == JFileChooser.APPROVE_OPTION) { + lastSelectedFileFilter = selectedFileFilter + lastSelectedFile = selectedFile + } + } + } + + add(dirTextField) + add(selectDirButton) + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/SizeFields.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/SizeFields.kt index d2500b30..a3fc9faa 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/SizeFields.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/input/SizeFields.kt @@ -1,141 +1,141 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.input - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.util.ValidCharactersDocumentFilter -import com.github.serivesmejia.eocvsim.gui.util.extension.SwingExt.documentFilter -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import org.opencv.core.Size -import java.awt.Color -import java.awt.FlowLayout -import java.util.* -import javax.swing.JLabel -import javax.swing.JPanel -import javax.swing.JTextField -import javax.swing.border.LineBorder -import javax.swing.event.DocumentEvent -import javax.swing.event.DocumentListener -import kotlin.math.roundToInt - -class SizeFields(initialSize: Size = EOCVSim.DEFAULT_EOCV_SIZE, - allowDecimalValues: Boolean = false, - allowNegativeValues: Boolean = false, - descriptiveText: String = "Size: ", - middleText: String = " x ") : JPanel(FlowLayout()) { - - constructor(initialSize: Size, allowDecimalValues: Boolean, descriptiveText: String) - : this(initialSize, allowDecimalValues, false, descriptiveText, " x ") - - val widthTextField = JTextField(4) - val heightTextField = JTextField(4) - - private val widthValidator: ValidCharactersDocumentFilter - private val heightValidator: ValidCharactersDocumentFilter - - @get:Synchronized - val lastValidWidth: Double - get() = widthValidator.lastValid - - @get:Synchronized - val currentWidth: Double - get() = widthTextField.text.toDouble() - - @get:Synchronized - val lastValidHeight: Double - get() = heightValidator.lastValid - - @get:Synchronized - val currentHeight: Double - get() = heightTextField.text.toDouble() - - @get:Synchronized - val lastValidSize: Size - get() = Size(lastValidWidth, lastValidHeight) - - @get:Synchronized - val currentSize: Size - get() = Size(currentWidth, currentHeight) - - private val validChars = ArrayList() - - val valid: Boolean - get() = widthValidator.valid && heightValidator.valid && widthTextField.text != "" && heightTextField.text != "" - - @JvmField val onChange = EventHandler("SizeFields-OnChange") - - init { - //add all valid characters for non decimal numeric fields - Collections.addAll(validChars, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9') - if(allowDecimalValues) { - validChars.add('.') - } - if(allowNegativeValues) { - validChars.add('-') - } - - widthValidator = ValidCharactersDocumentFilter(validChars.toTypedArray()) - heightValidator = ValidCharactersDocumentFilter(validChars.toTypedArray()) - - widthTextField.documentFilter = widthValidator - widthTextField.document.addDocumentListener(BorderChangerListener(widthTextField, widthValidator, onChange)) - widthTextField.text = "${ if(allowDecimalValues) { initialSize.width } else { initialSize.width.roundToInt() } }" - - heightTextField.documentFilter = heightValidator - heightTextField.document.addDocumentListener(BorderChangerListener(heightTextField, heightValidator, onChange)) - heightTextField.text = "${ if(allowDecimalValues) { initialSize.height } else { initialSize.height.roundToInt() } }" - - val sizeLabel = JLabel(descriptiveText) - sizeLabel.horizontalAlignment = JLabel.LEFT - add(sizeLabel) - - add(widthTextField) - - val xLabel = JLabel(middleText) - xLabel.horizontalAlignment = JLabel.CENTER - add(xLabel) - - add(heightTextField) - } - - private class BorderChangerListener(val field: JTextField, val validator: ValidCharactersDocumentFilter, val onChange: EventHandler? = null): DocumentListener { - - val initialBorder = field.border - val redBorder = LineBorder(Color(255, 79, 79), 2) - - override fun insertUpdate(e: DocumentEvent?) = change() - override fun removeUpdate(e: DocumentEvent?) = change() - override fun changedUpdate(e: DocumentEvent?) = change() - fun change() { - if(validator.valid && field.text != "") { - field.border = initialBorder - } else { - field.border = redBorder - } - - onChange?.run() - } - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.input + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.util.ValidCharactersDocumentFilter +import com.github.serivesmejia.eocvsim.gui.util.extension.SwingExt.documentFilter +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.opencv.core.Size +import java.awt.Color +import java.awt.FlowLayout +import java.util.* +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.JTextField +import javax.swing.border.LineBorder +import javax.swing.event.DocumentEvent +import javax.swing.event.DocumentListener +import kotlin.math.roundToInt + +class SizeFields(initialSize: Size = EOCVSim.DEFAULT_EOCV_SIZE, + allowDecimalValues: Boolean = false, + allowNegativeValues: Boolean = false, + descriptiveText: String = "Size: ", + middleText: String = " x ") : JPanel(FlowLayout()) { + + constructor(initialSize: Size, allowDecimalValues: Boolean, descriptiveText: String) + : this(initialSize, allowDecimalValues, false, descriptiveText, " x ") + + val widthTextField = JTextField(4) + val heightTextField = JTextField(4) + + private val widthValidator: ValidCharactersDocumentFilter + private val heightValidator: ValidCharactersDocumentFilter + + @get:Synchronized + val lastValidWidth: Double + get() = widthValidator.lastValid + + @get:Synchronized + val currentWidth: Double + get() = widthTextField.text.toDouble() + + @get:Synchronized + val lastValidHeight: Double + get() = heightValidator.lastValid + + @get:Synchronized + val currentHeight: Double + get() = heightTextField.text.toDouble() + + @get:Synchronized + val lastValidSize: Size + get() = Size(lastValidWidth, lastValidHeight) + + @get:Synchronized + val currentSize: Size + get() = Size(currentWidth, currentHeight) + + private val validChars = ArrayList() + + val valid: Boolean + get() = widthValidator.valid && heightValidator.valid && widthTextField.text != "" && heightTextField.text != "" + + @JvmField val onChange = EventHandler("SizeFields-OnChange") + + init { + //add all valid characters for non decimal numeric fields + Collections.addAll(validChars, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9') + if(allowDecimalValues) { + validChars.add('.') + } + if(allowNegativeValues) { + validChars.add('-') + } + + widthValidator = ValidCharactersDocumentFilter(validChars.toTypedArray()) + heightValidator = ValidCharactersDocumentFilter(validChars.toTypedArray()) + + widthTextField.documentFilter = widthValidator + widthTextField.document.addDocumentListener(BorderChangerListener(widthTextField, widthValidator, onChange)) + widthTextField.text = "${ if(allowDecimalValues) { initialSize.width } else { initialSize.width.roundToInt() } }" + + heightTextField.documentFilter = heightValidator + heightTextField.document.addDocumentListener(BorderChangerListener(heightTextField, heightValidator, onChange)) + heightTextField.text = "${ if(allowDecimalValues) { initialSize.height } else { initialSize.height.roundToInt() } }" + + val sizeLabel = JLabel(descriptiveText) + sizeLabel.horizontalAlignment = JLabel.LEFT + add(sizeLabel) + + add(widthTextField) + + val xLabel = JLabel(middleText) + xLabel.horizontalAlignment = JLabel.CENTER + add(xLabel) + + add(heightTextField) + } + + private class BorderChangerListener(val field: JTextField, val validator: ValidCharactersDocumentFilter, val onChange: EventHandler? = null): DocumentListener { + + val initialBorder = field.border + val redBorder = LineBorder(Color(255, 79, 79), 2) + + override fun insertUpdate(e: DocumentEvent?) = change() + override fun removeUpdate(e: DocumentEvent?) = change() + override fun changedUpdate(e: DocumentEvent?) = change() + fun change() { + if(validator.valid && field.text != "") { + field.border = initialBorder + } else { + field.border = redBorder + } + + onChange?.run() + } + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/ColorPicker.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/ColorPicker.kt index daf3153f..3244f25a 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/ColorPicker.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/ColorPicker.kt @@ -1,115 +1,115 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.tuner - -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.gui.Icons -import com.github.serivesmejia.eocvsim.gui.component.ImageX -import com.github.serivesmejia.eocvsim.gui.component.Viewport -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import org.opencv.core.Scalar -import java.awt.Color -import java.awt.Cursor -import java.awt.Point -import java.awt.event.MouseAdapter -import java.awt.event.MouseEvent -import java.awt.Toolkit - -class ColorPicker(private val imageX: ImageX) { - - companion object { - private val size = if(SysUtil.OS == SysUtil.OperatingSystem.WINDOWS) { - 200 - } else { 35 } - - val colorPickIco = Icons.getImageResized("ico_colorpick_pointer", size, size).image - - val colorPickCursor = Toolkit.getDefaultToolkit().createCustomCursor( - colorPickIco, Point(0, 0), "Color Pick Pointer" - ) - } - - var isPicking = false - private set - - var hasPicked = false - private set - - val onPick = EventHandler("ColorPicker-OnPick") - val onCancel = EventHandler("ColorPicker-OnCancel") - - private var initialCursor: Cursor? = null - - var colorRgb = Scalar(0.0, 0.0, 0.0) - private set - - val clickListener = object: MouseAdapter() { - override fun mouseClicked(e: MouseEvent) { - //if clicked with primary button... - if(e.button == MouseEvent.BUTTON1) { - //get the "packed" (in a single int value) color from the image at mouse position's pixel - val packedColor = imageX.image.getRGB(e.x, e.y) - //parse the "packed" color into four separate channels - val color = Color(packedColor, true) - - //wrap Java's color to OpenCV's Scalar since we're EOCV-Sim not JavaCv-Sim right? - colorRgb = Scalar( - color.red.toDouble(), color.green.toDouble(), color.blue.toDouble() - ) - - hasPicked = true - onPick.run() //run all oick listeners - } else { - onCancel.run() - } - - stopPicking() - } - } - - fun startPicking() { - if(isPicking) return - isPicking = true - hasPicked = false - - imageX.addMouseListener(clickListener) - - initialCursor = imageX.cursor - imageX.cursor = colorPickCursor - } - - fun stopPicking() { - if(!isPicking) return - isPicking = false - - if(!hasPicked) { - onPick.removeAllListeners() - onCancel.run() - } - - imageX.removeMouseListener(clickListener) - imageX.cursor = initialCursor - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.tuner + +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.gui.Icons +import com.github.serivesmejia.eocvsim.gui.component.ImageX +import com.github.serivesmejia.eocvsim.gui.component.Viewport +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import org.opencv.core.Scalar +import java.awt.Color +import java.awt.Cursor +import java.awt.Point +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import java.awt.Toolkit + +class ColorPicker(private val imageX: ImageX) { + + companion object { + private val size = if(SysUtil.OS == SysUtil.OperatingSystem.WINDOWS) { + 200 + } else { 35 } + + val colorPickIco = Icons.getImageResized("ico_colorpick_pointer", size, size).image + + val colorPickCursor = Toolkit.getDefaultToolkit().createCustomCursor( + colorPickIco, Point(0, 0), "Color Pick Pointer" + ) + } + + var isPicking = false + private set + + var hasPicked = false + private set + + val onPick = EventHandler("ColorPicker-OnPick") + val onCancel = EventHandler("ColorPicker-OnCancel") + + private var initialCursor: Cursor? = null + + var colorRgb = Scalar(0.0, 0.0, 0.0) + private set + + val clickListener = object: MouseAdapter() { + override fun mouseClicked(e: MouseEvent) { + //if clicked with primary button... + if(e.button == MouseEvent.BUTTON1) { + //get the "packed" (in a single int value) color from the image at mouse position's pixel + val packedColor = imageX.image.getRGB(e.x, e.y) + //parse the "packed" color into four separate channels + val color = Color(packedColor, true) + + //wrap Java's color to OpenCV's Scalar since we're EOCV-Sim not JavaCv-Sim right? + colorRgb = Scalar( + color.red.toDouble(), color.green.toDouble(), color.blue.toDouble() + ) + + hasPicked = true + onPick.run() //run all oick listeners + } else { + onCancel.run() + } + + stopPicking() + } + } + + fun startPicking() { + if(isPicking) return + isPicking = true + hasPicked = false + + imageX.addMouseListener(clickListener) + + initialCursor = imageX.cursor + imageX.cursor = colorPickCursor + } + + fun stopPicking() { + if(!isPicking) return + isPicking = false + + if(!hasPicked) { + onPick.removeAllListeners() + onCancel.run() + } + + imageX.removeMouseListener(clickListener) + imageX.cursor = initialCursor + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java index 71efcf45..37b70b63 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java @@ -1,219 +1,219 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.tuner; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableComboBox; -import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableSlider; -import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableTextField; -import com.github.serivesmejia.eocvsim.tuner.TunableField; - -import javax.swing.*; -import javax.swing.border.SoftBevelBorder; -import java.awt.*; - -public class TunableFieldPanel extends JPanel { - - public final TunableField tunableField; - - public TunableTextField[] fields; - public JPanel fieldsPanel; - - public TunableSlider[] sliders; - public JPanel slidersPanel; - - public JComboBox[] comboBoxes; - - public TunableFieldPanelOptions panelOptions = null; - private final EOCVSim eocvSim; - - private Mode mode; - private boolean reevalConfigRequested = false; - - private boolean hasBeenShown = false; - - public enum Mode { TEXTBOXES, SLIDERS } - - public TunableFieldPanel(TunableField tunableField, EOCVSim eocvSim) { - super(); - - this.tunableField = tunableField; - this.eocvSim = eocvSim; - - tunableField.setTunableFieldPanel(this); - - init(); - } - - private void init() { - //nice look - setBorder(new SoftBevelBorder(SoftBevelBorder.RAISED)); - - panelOptions = new TunableFieldPanelOptions(this, eocvSim); - - if(tunableField.getGuiFieldAmount() > 0) { - add(panelOptions); - } - - JLabel fieldNameLabel = new JLabel(); - fieldNameLabel.setText(tunableField.getFieldName()); - - add(fieldNameLabel); - - int fieldAmount = tunableField.getGuiFieldAmount(); - - fields = new TunableTextField[fieldAmount]; - sliders = new TunableSlider[fieldAmount]; - - fieldsPanel = new JPanel(); - slidersPanel = new JPanel(new GridBagLayout()); - - for (int i = 0 ; i < tunableField.getGuiFieldAmount() ; i++) { - //add the tunable field as a field - TunableTextField field = new TunableTextField(i, tunableField, eocvSim); - fields[i] = field; - - field.setEditable(true); - fieldsPanel.add(field); - - //add the tunable field as a slider - JLabel sliderLabel = new JLabel("0"); - TunableSlider slider = new TunableSlider(i, tunableField, eocvSim, sliderLabel); - sliders[i] = slider; - - GridBagConstraints cSlider = new GridBagConstraints(); - cSlider.gridx = 0; - cSlider.gridy = i; - - GridBagConstraints cLabel = new GridBagConstraints(); - cLabel.gridy = 1; - cLabel.gridy = i; - - slidersPanel.add(slider, cSlider); - slidersPanel.add(sliderLabel, cLabel); - } - - setMode(Mode.TEXTBOXES); - - comboBoxes = new JComboBox[tunableField.getGuiComboBoxAmount()]; - - for (int i = 0; i < comboBoxes.length; i++) { - TunableComboBox comboBox = new TunableComboBox(i, tunableField, eocvSim); - add(comboBox); - - comboBoxes[i] = comboBox; - } - } - - //method that should be called when this panel is added to the visualizer gui - public void showFieldPanel() { - if(hasBeenShown) return; - hasBeenShown = true; - - //updates the slider ranges from config - panelOptions.getConfigPanel().updateFieldGuiFromConfig(); - tunableField.evalRecommendedPanelMode(); - } - - public void setFieldValue(int index, Object value) { - if(index >= fields.length) return; - - String text; - if(tunableField.getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS) { - text = String.valueOf((int) Math.round(Double.parseDouble(value.toString()))); - } else { - text = value.toString(); - } - - fields[index].setText(text); - - try { - sliders[index].setScaledValue(Double.parseDouble(value.toString())); - } catch(NumberFormatException ignored) {} - } - - public void setComboBoxSelection(int index, Object selection) { - comboBoxes[index].setSelectedItem(selection.toString()); - } - - protected void requestAllConfigReeval() { - reevalConfigRequested = true; - } - - public void setMode(Mode mode) { - switch(mode) { - case TEXTBOXES: - if(this.mode == Mode.SLIDERS) { - remove(slidersPanel); - } - - for(int i = 0 ; i < tunableField.getGuiFieldAmount() ; i++) { - fields[i].setInControl(true); - sliders[i].setInControl(false); - setFieldValue(i, tunableField.getGuiFieldValue(i)); - } - - add(fieldsPanel); - break; - case SLIDERS: - if(this.mode == Mode.TEXTBOXES) { - remove(fieldsPanel); - } - - for(int i = 0 ; i < tunableField.getGuiFieldAmount() ; i++) { - fields[i].setInControl(false); - sliders[i].setInControl(true); - setFieldValue(i, tunableField.getGuiFieldValue(i)); - } - - add(slidersPanel); - break; - } - - this.mode = mode; - - if(panelOptions.getMode() != mode) { - panelOptions.setMode(mode); - } - - revalidate(); repaint(); - } - - public void setSlidersRange(double min, double max) { - if(sliders == null) return; - for(TunableSlider slider : sliders) { - slider.setScaledBounds(min, max); - } - } - - public Mode getMode() { return this.mode; } - - public boolean hasRequestedAllConfigReeval() { - boolean current = reevalConfigRequested; - reevalConfigRequested = false; - - return current; - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.tuner; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableComboBox; +import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableSlider; +import com.github.serivesmejia.eocvsim.gui.component.tuner.element.TunableTextField; +import com.github.serivesmejia.eocvsim.tuner.TunableField; + +import javax.swing.*; +import javax.swing.border.SoftBevelBorder; +import java.awt.*; + +public class TunableFieldPanel extends JPanel { + + public final TunableField tunableField; + + public TunableTextField[] fields; + public JPanel fieldsPanel; + + public TunableSlider[] sliders; + public JPanel slidersPanel; + + public JComboBox[] comboBoxes; + + public TunableFieldPanelOptions panelOptions = null; + private final EOCVSim eocvSim; + + private Mode mode; + private boolean reevalConfigRequested = false; + + private boolean hasBeenShown = false; + + public enum Mode { TEXTBOXES, SLIDERS } + + public TunableFieldPanel(TunableField tunableField, EOCVSim eocvSim) { + super(); + + this.tunableField = tunableField; + this.eocvSim = eocvSim; + + tunableField.setTunableFieldPanel(this); + + init(); + } + + private void init() { + //nice look + setBorder(new SoftBevelBorder(SoftBevelBorder.RAISED)); + + panelOptions = new TunableFieldPanelOptions(this, eocvSim); + + if(tunableField.getGuiFieldAmount() > 0) { + add(panelOptions); + } + + JLabel fieldNameLabel = new JLabel(); + fieldNameLabel.setText(tunableField.getFieldName()); + + add(fieldNameLabel); + + int fieldAmount = tunableField.getGuiFieldAmount(); + + fields = new TunableTextField[fieldAmount]; + sliders = new TunableSlider[fieldAmount]; + + fieldsPanel = new JPanel(); + slidersPanel = new JPanel(new GridBagLayout()); + + for (int i = 0 ; i < tunableField.getGuiFieldAmount() ; i++) { + //add the tunable field as a field + TunableTextField field = new TunableTextField(i, tunableField, eocvSim); + fields[i] = field; + + field.setEditable(true); + fieldsPanel.add(field); + + //add the tunable field as a slider + JLabel sliderLabel = new JLabel("0"); + TunableSlider slider = new TunableSlider(i, tunableField, eocvSim, sliderLabel); + sliders[i] = slider; + + GridBagConstraints cSlider = new GridBagConstraints(); + cSlider.gridx = 0; + cSlider.gridy = i; + + GridBagConstraints cLabel = new GridBagConstraints(); + cLabel.gridy = 1; + cLabel.gridy = i; + + slidersPanel.add(slider, cSlider); + slidersPanel.add(sliderLabel, cLabel); + } + + setMode(Mode.TEXTBOXES); + + comboBoxes = new JComboBox[tunableField.getGuiComboBoxAmount()]; + + for (int i = 0; i < comboBoxes.length; i++) { + TunableComboBox comboBox = new TunableComboBox(i, tunableField, eocvSim); + add(comboBox); + + comboBoxes[i] = comboBox; + } + } + + //method that should be called when this panel is added to the visualizer gui + public void showFieldPanel() { + if(hasBeenShown) return; + hasBeenShown = true; + + //updates the slider ranges from config + panelOptions.getConfigPanel().updateFieldGuiFromConfig(); + tunableField.evalRecommendedPanelMode(); + } + + public void setFieldValue(int index, Object value) { + if(index >= fields.length) return; + + String text; + if(tunableField.getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS) { + text = String.valueOf((int) Math.round(Double.parseDouble(value.toString()))); + } else { + text = value.toString(); + } + + fields[index].setText(text); + + try { + sliders[index].setScaledValue(Double.parseDouble(value.toString())); + } catch(NumberFormatException ignored) {} + } + + public void setComboBoxSelection(int index, Object selection) { + comboBoxes[index].setSelectedItem(selection.toString()); + } + + protected void requestAllConfigReeval() { + reevalConfigRequested = true; + } + + public void setMode(Mode mode) { + switch(mode) { + case TEXTBOXES: + if(this.mode == Mode.SLIDERS) { + remove(slidersPanel); + } + + for(int i = 0 ; i < tunableField.getGuiFieldAmount() ; i++) { + fields[i].setInControl(true); + sliders[i].setInControl(false); + setFieldValue(i, tunableField.getGuiFieldValue(i)); + } + + add(fieldsPanel); + break; + case SLIDERS: + if(this.mode == Mode.TEXTBOXES) { + remove(fieldsPanel); + } + + for(int i = 0 ; i < tunableField.getGuiFieldAmount() ; i++) { + fields[i].setInControl(false); + sliders[i].setInControl(true); + setFieldValue(i, tunableField.getGuiFieldValue(i)); + } + + add(slidersPanel); + break; + } + + this.mode = mode; + + if(panelOptions.getMode() != mode) { + panelOptions.setMode(mode); + } + + revalidate(); repaint(); + } + + public void setSlidersRange(double min, double max) { + if(sliders == null) return; + for(TunableSlider slider : sliders) { + slider.setScaledBounds(min, max); + } + } + + public Mode getMode() { return this.mode; } + + public boolean hasRequestedAllConfigReeval() { + boolean current = reevalConfigRequested; + reevalConfigRequested = false; + + return current; + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelConfig.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelConfig.kt index 6facd93a..3b4b37a2 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelConfig.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelConfig.kt @@ -1,331 +1,332 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.tuner - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.component.PopupX -import com.github.serivesmejia.eocvsim.gui.component.input.EnumComboBox -import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields -import com.github.serivesmejia.eocvsim.tuner.TunableField -import kotlinx.coroutines.* -import kotlinx.coroutines.swing.Swing -import org.opencv.core.Size -import org.opencv.imgproc.Imgproc -import java.awt.Dimension -import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import javax.swing.* -import javax.swing.border.EmptyBorder - -class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions, - private val eocvSim: EOCVSim) : JPanel() { - - var localConfig = eocvSim.config.globalTunableFieldsConfig.copy() - private set - - private var lastApplyPopup: PopupX? = null - - val currentConfig: Config - get() { - val config = localConfig.copy() - applyToConfig(config) - return config - } - - private val sliderRangeFieldsPanel = JPanel() - - private var sliderRangeFields = createRangeFields() - private val colorSpaceComboBox = EnumComboBox("Color space: ", PickerColorSpace::class.java, PickerColorSpace.values()) - - private val applyToAllButtonPanel = JPanel(GridBagLayout()) - private val applyToAllButton = JToggleButton("Apply to all fields...") - - private val applyModesPanel = JPanel() - private val applyToAllGloballyButton = JButton("Globally") - private val applyToAllOfSameTypeButton = JButton("Of this type") - - private val constCenterBottom = GridBagConstraints() - - private val configSourceLabel = JLabel(localConfig.source.description) - - private val allowsDecimals - get() = fieldOptions.fieldPanel.tunableField.allowMode == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL - - private val fieldTypeClass = fieldOptions.fieldPanel.tunableField::class.java - - //represents a color space conversion when picking from the viewport. always - //convert from rgb to the desired color space since that's the color space of - //the scalar the ColorPicker returns from the viewport after picking. - enum class PickerColorSpace(val cvtCode: Int) { - YCrCb(Imgproc.COLOR_RGB2YCrCb), - HSV(Imgproc.COLOR_RGB2HSV), - RGB(Imgproc.COLOR_RGBA2RGB), - Lab(Imgproc.COLOR_RGB2Lab) - } - - enum class ConfigSource(val description: String) { - LOCAL("From local config"), - GLOBAL("From global config"), - GLOBAL_DEFAULT("From default global config"), - TYPE_SPECIFIC("From specific config") - } - - data class Config(var sliderRange: Size, - var pickerColorSpace: PickerColorSpace, - var fieldPanelMode: TunableFieldPanel.Mode, - var source: ConfigSource) - - init { - layout = GridBagLayout() - - val mConstraints = GridBagConstraints() - mConstraints.ipady = 10 - - //adding into an individual panel so that we can add - //and remove later when recreating without much problem - sliderRangeFieldsPanel.add(sliderRangeFields) - - mConstraints.gridy = 0 - add(sliderRangeFieldsPanel, mConstraints) - - colorSpaceComboBox.onSelect { updateConfigSourceLabel(currentConfig) } - //combo box to select color space - colorSpaceComboBox.selectedEnum = localConfig.pickerColorSpace - - mConstraints.gridy = 1 - add(colorSpaceComboBox, mConstraints) - - //centering apply to all button... - val constCenter = GridBagConstraints() - constCenter.anchor = GridBagConstraints.CENTER - constCenter.fill = GridBagConstraints.HORIZONTAL - constCenter.gridy = 0 - - //add apply to all button to a centered pane - applyToAllButtonPanel.add(applyToAllButton, constCenter) - - mConstraints.gridy = 2 - add(applyToAllButtonPanel, mConstraints) - - //display or hide apply to all mode buttons - applyToAllButton.addActionListener { - //create a new popup for displaying the apply modes button - if(applyToAllButton.isSelected && (lastApplyPopup == null || lastApplyPopup?.window?.isVisible == false)) { - val window = SwingUtilities.getWindowAncestor(fieldOptions) //gets the parent frame - val location = applyToAllButton.locationOnScreen - - val popup = PopupX(window, applyModesPanel, location.x, location.y) - lastApplyPopup = popup //set to a "last" variable so that we can hide it later - - //so that the main config popup doesn't get closed - //when it gets unfocused in favour of this new frame - fieldOptions.lastConfigPopup?.closeOnFocusLost = false - - popup.onShow { - popup.setLocation( - popup.window.location.x - applyModesPanel.width / 8, - popup.window.location.y + applyModesPanel.height + applyToAllButton.height - ) - } - - //untoggle the apply to all button if the popup closes - popup.onHide { - applyToAllButton.isSelected = false - - fieldOptions.lastConfigPopup?.let { - //allow the main config popup to close when losing focus now - it.closeOnFocusLost = true - - //launch the waiting in the background - GlobalScope.launch { - delay(100) - //close config popup if still hasn't focused after a bit - launch(Dispatchers.Swing) { - if (!it.window.isFocused && (lastApplyPopup == null || lastApplyPopup?.window?.isFocused == false)) { - it.hide() - } - } - } - } - } - - popup.show() - } else { - lastApplyPopup?.hide() //close the popup if user un-toggled button - } - } - - applyModesPanel.layout = BoxLayout(applyModesPanel, BoxLayout.LINE_AXIS) - - //apply globally button and disable toggle for apply to all button - applyToAllGloballyButton.addActionListener { - lastApplyPopup?.hide() - applyGlobally() - } - - applyModesPanel.add(applyToAllGloballyButton) - - //creates a space between the apply mode buttons - applyModesPanel.add(Box.createRigidArea(Dimension(5, 0))) - - //apply of same type button and disable toggle for apply to all button - applyToAllOfSameTypeButton.addActionListener { - lastApplyPopup?.hide() - applyOfSameType() - } - applyModesPanel.add(applyToAllOfSameTypeButton) - - //add a bit of space between the upper and lower apply to all buttons - applyModesPanel.border = EmptyBorder(5, 0, 0, 0) - - //add two apply to all modes buttons to the bottom center - constCenterBottom.anchor = GridBagConstraints.CENTER - constCenterBottom.fill = GridBagConstraints.HORIZONTAL - constCenterBottom.gridy = 1 - - configSourceLabel.horizontalAlignment = JLabel.CENTER - configSourceLabel.verticalAlignment = JLabel.CENTER - - mConstraints.gridy = 3 - add(configSourceLabel, mConstraints) - - applyFromEOCVSimConfig() - } - - //set the current config values and hide apply modes panel when panel show - fun panelShow() { - updateConfigGuiFromConfig() - applyToAllButton.isSelected = false - } - - //set the slider bounds when the popup gets closed - fun panelHide() { - applyToConfig() - updateFieldGuiFromConfig() - lastApplyPopup?.hide() - } - - //applies the config of this tunable field panel globally - private fun applyGlobally() { - applyToConfig() //saves the current values to the current local config - - localConfig.source = ConfigSource.GLOBAL //changes the source of the local config to global - eocvSim.config.globalTunableFieldsConfig = localConfig.copy() - - updateConfigSourceLabel() - fieldOptions.fieldPanel.requestAllConfigReeval() - } - - //applies the config of this tunable field to this type specifically - private fun applyOfSameType() { - applyToConfig() //saves the current values to the current local config - val typeClass = fieldOptions.fieldPanel.tunableField::class.java - - localConfig.source = ConfigSource.TYPE_SPECIFIC //changes the source of the local config to type specific - eocvSim.config.specificTunableFieldConfig[typeClass.name] = localConfig.copy() - - updateConfigSourceLabel() - fieldOptions.fieldPanel.requestAllConfigReeval() - } - - //loads the config from global eocv sim config file - internal fun applyFromEOCVSimConfig() { - val specificConfigs = eocvSim.config.specificTunableFieldConfig - - //apply specific config if we have one, or else, apply global - localConfig = if(specificConfigs.containsKey(fieldTypeClass.name)) { - specificConfigs[fieldTypeClass.name]!!.copy() - } else { - eocvSim.config.globalTunableFieldsConfig.copy() - } - - updateConfigGuiFromConfig() - updateConfigSourceLabel() - } - - //applies the current values to the specified config, defaults to local - @Suppress("UNNECESSARY_SAFE_CALL") - private fun applyToConfig(config: Config = localConfig) { - //if user entered a valid number and our max value is bigger than the minimum... - if(sliderRangeFields.valid) { - config.sliderRange = sliderRangeFields.currentSize - //update slider range in gui sliders... - if(config.sliderRange.height > config.sliderRange.width && config !== localConfig) - updateFieldGuiFromConfig() - } - - //set the color space enum to the config if it's not null - colorSpaceComboBox.selectedEnum?.let { - config.pickerColorSpace = it - } - - //sets the panel mode (sliders or textboxes) to config from the current mode - if(fieldOptions.fieldPanel?.mode != null) { - config.fieldPanelMode = fieldOptions.fieldPanel.mode - } - } - - private fun updateConfigSourceLabel(currentConfig: Config = localConfig) { - //sets to local if user changed values and hasn't applied locally or globally - if(currentConfig != localConfig) { - localConfig.source = ConfigSource.LOCAL - } - - configSourceLabel.text = localConfig.source.description - } - - //updates the actual configuration displayed on the field panel gui - @Suppress("UNNECESSARY_SAFE_CALL") - fun updateFieldGuiFromConfig() { - //sets the slider range from config - fieldOptions.fieldPanel.setSlidersRange(localConfig.sliderRange.width, localConfig.sliderRange.height) - //sets the panel mode (sliders or textboxes) to config from the current mode - if(fieldOptions.fieldPanel?.fields != null){ - fieldOptions.fieldPanel.mode = localConfig.fieldPanelMode - } - } - - //updates the values displayed in this config's ui to the current config values - private fun updateConfigGuiFromConfig() { - sliderRangeFieldsPanel.remove(sliderRangeFields) //remove old fields - sliderRangeFields = createRangeFields() //need to recreate in order to set new values - sliderRangeFieldsPanel.add(sliderRangeFields) //add new fields - - //need to reval&repaint as always - sliderRangeFieldsPanel.revalidate(); sliderRangeFieldsPanel.repaint() - - colorSpaceComboBox.selectedEnum = localConfig.pickerColorSpace - } - - //simple short hand for a repetitive instantiation... - private fun createRangeFields(): SizeFields { - val fields = SizeFields(localConfig.sliderRange, allowsDecimals, true,"Slider range:", " to ") - fields.onChange { - updateConfigSourceLabel(currentConfig) - } - - return fields - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.tuner + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.component.PopupX +import com.github.serivesmejia.eocvsim.gui.component.input.EnumComboBox +import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields +import com.github.serivesmejia.eocvsim.tuner.TunableField +import kotlinx.coroutines.* +import kotlinx.coroutines.swing.Swing +import org.opencv.core.Size +import org.opencv.imgproc.Imgproc +import java.awt.Dimension +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import javax.swing.* +import javax.swing.border.EmptyBorder + +@OptIn(DelicateCoroutinesApi::class) +class TunableFieldPanelConfig(private val fieldOptions: TunableFieldPanelOptions, + private val eocvSim: EOCVSim) : JPanel() { + + var localConfig = eocvSim.config.globalTunableFieldsConfig.copy() + private set + + private var lastApplyPopup: PopupX? = null + + val currentConfig: Config + get() { + val config = localConfig.copy() + applyToConfig(config) + return config + } + + private val sliderRangeFieldsPanel = JPanel() + + private var sliderRangeFields = createRangeFields() + private val colorSpaceComboBox = EnumComboBox("Color space: ", PickerColorSpace::class.java, PickerColorSpace.values()) + + private val applyToAllButtonPanel = JPanel(GridBagLayout()) + private val applyToAllButton = JToggleButton("Apply to all fields...") + + private val applyModesPanel = JPanel() + private val applyToAllGloballyButton = JButton("Globally") + private val applyToAllOfSameTypeButton = JButton("Of this type") + + private val constCenterBottom = GridBagConstraints() + + private val configSourceLabel = JLabel(localConfig.source.description) + + private val allowsDecimals + get() = fieldOptions.fieldPanel.tunableField.allowMode == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL + + private val fieldTypeClass = fieldOptions.fieldPanel.tunableField::class.java + + //represents a color space conversion when picking from the viewport. always + //convert from rgb to the desired color space since that's the color space of + //the scalar the ColorPicker returns from the viewport after picking. + enum class PickerColorSpace(val cvtCode: Int) { + YCrCb(Imgproc.COLOR_RGB2YCrCb), + HSV(Imgproc.COLOR_RGB2HSV), + RGB(Imgproc.COLOR_RGBA2RGB), + Lab(Imgproc.COLOR_RGB2Lab) + } + + enum class ConfigSource(val description: String) { + LOCAL("From local config"), + GLOBAL("From global config"), + GLOBAL_DEFAULT("From default global config"), + TYPE_SPECIFIC("From specific config") + } + + data class Config(var sliderRange: Size, + var pickerColorSpace: PickerColorSpace, + var fieldPanelMode: TunableFieldPanel.Mode, + var source: ConfigSource) + + init { + layout = GridBagLayout() + + val mConstraints = GridBagConstraints() + mConstraints.ipady = 10 + + //adding into an individual panel so that we can add + //and remove later when recreating without much problem + sliderRangeFieldsPanel.add(sliderRangeFields) + + mConstraints.gridy = 0 + add(sliderRangeFieldsPanel, mConstraints) + + colorSpaceComboBox.onSelect { updateConfigSourceLabel(currentConfig) } + //combo box to select color space + colorSpaceComboBox.selectedEnum = localConfig.pickerColorSpace + + mConstraints.gridy = 1 + add(colorSpaceComboBox, mConstraints) + + //centering apply to all button... + val constCenter = GridBagConstraints() + constCenter.anchor = GridBagConstraints.CENTER + constCenter.fill = GridBagConstraints.HORIZONTAL + constCenter.gridy = 0 + + //add apply to all button to a centered pane + applyToAllButtonPanel.add(applyToAllButton, constCenter) + + mConstraints.gridy = 2 + add(applyToAllButtonPanel, mConstraints) + + //display or hide apply to all mode buttons + applyToAllButton.addActionListener { + //create a new popup for displaying the apply modes button + if(applyToAllButton.isSelected && (lastApplyPopup == null || lastApplyPopup?.window?.isVisible == false)) { + val window = SwingUtilities.getWindowAncestor(fieldOptions) //gets the parent frame + val location = applyToAllButton.locationOnScreen + + val popup = PopupX(window, applyModesPanel, location.x, location.y) + lastApplyPopup = popup //set to a "last" variable so that we can hide it later + + //so that the main config popup doesn't get closed + //when it gets unfocused in favour of this new frame + fieldOptions.lastConfigPopup?.closeOnFocusLost = false + + popup.onShow { + popup.setLocation( + popup.window.location.x - applyModesPanel.width / 8, + popup.window.location.y + applyModesPanel.height + applyToAllButton.height + ) + } + + //untoggle the apply to all button if the popup closes + popup.onHide { + applyToAllButton.isSelected = false + + fieldOptions.lastConfigPopup?.let { + //allow the main config popup to close when losing focus now + it.closeOnFocusLost = true + + //launch the waiting in the background + GlobalScope.launch { + delay(100) + //close config popup if still hasn't focused after a bit + launch(Dispatchers.Swing) { + if (!it.window.isFocused && (lastApplyPopup == null || lastApplyPopup?.window?.isFocused == false)) { + it.hide() + } + } + } + } + } + + popup.show() + } else { + lastApplyPopup?.hide() //close the popup if user un-toggled button + } + } + + applyModesPanel.layout = BoxLayout(applyModesPanel, BoxLayout.LINE_AXIS) + + //apply globally button and disable toggle for apply to all button + applyToAllGloballyButton.addActionListener { + lastApplyPopup?.hide() + applyGlobally() + } + + applyModesPanel.add(applyToAllGloballyButton) + + //creates a space between the apply mode buttons + applyModesPanel.add(Box.createRigidArea(Dimension(5, 0))) + + //apply of same type button and disable toggle for apply to all button + applyToAllOfSameTypeButton.addActionListener { + lastApplyPopup?.hide() + applyOfSameType() + } + applyModesPanel.add(applyToAllOfSameTypeButton) + + //add a bit of space between the upper and lower apply to all buttons + applyModesPanel.border = EmptyBorder(5, 0, 0, 0) + + //add two apply to all modes buttons to the bottom center + constCenterBottom.anchor = GridBagConstraints.CENTER + constCenterBottom.fill = GridBagConstraints.HORIZONTAL + constCenterBottom.gridy = 1 + + configSourceLabel.horizontalAlignment = JLabel.CENTER + configSourceLabel.verticalAlignment = JLabel.CENTER + + mConstraints.gridy = 3 + add(configSourceLabel, mConstraints) + + applyFromEOCVSimConfig() + } + + //set the current config values and hide apply modes panel when panel show + fun panelShow() { + updateConfigGuiFromConfig() + applyToAllButton.isSelected = false + } + + //set the slider bounds when the popup gets closed + fun panelHide() { + applyToConfig() + updateFieldGuiFromConfig() + lastApplyPopup?.hide() + } + + //applies the config of this tunable field panel globally + private fun applyGlobally() { + applyToConfig() //saves the current values to the current local config + + localConfig.source = ConfigSource.GLOBAL //changes the source of the local config to global + eocvSim.config.globalTunableFieldsConfig = localConfig.copy() + + updateConfigSourceLabel() + fieldOptions.fieldPanel.requestAllConfigReeval() + } + + //applies the config of this tunable field to this type specifically + private fun applyOfSameType() { + applyToConfig() //saves the current values to the current local config + val typeClass = fieldOptions.fieldPanel.tunableField::class.java + + localConfig.source = ConfigSource.TYPE_SPECIFIC //changes the source of the local config to type specific + eocvSim.config.specificTunableFieldConfig[typeClass.name] = localConfig.copy() + + updateConfigSourceLabel() + fieldOptions.fieldPanel.requestAllConfigReeval() + } + + //loads the config from global eocv sim config file + internal fun applyFromEOCVSimConfig() { + val specificConfigs = eocvSim.config.specificTunableFieldConfig + + //apply specific config if we have one, or else, apply global + localConfig = if(specificConfigs.containsKey(fieldTypeClass.name)) { + specificConfigs[fieldTypeClass.name]!!.copy() + } else { + eocvSim.config.globalTunableFieldsConfig.copy() + } + + updateConfigGuiFromConfig() + updateConfigSourceLabel() + } + + //applies the current values to the specified config, defaults to local + @Suppress("UNNECESSARY_SAFE_CALL") + private fun applyToConfig(config: Config = localConfig) { + //if user entered a valid number and our max value is bigger than the minimum... + if(sliderRangeFields.valid) { + config.sliderRange = sliderRangeFields.currentSize + //update slider range in gui sliders... + if(config.sliderRange.height > config.sliderRange.width && config !== localConfig) + updateFieldGuiFromConfig() + } + + //set the color space enum to the config if it's not null + colorSpaceComboBox.selectedEnum?.let { + config.pickerColorSpace = it + } + + //sets the panel mode (sliders or textboxes) to config from the current mode + if(fieldOptions.fieldPanel?.mode != null) { + config.fieldPanelMode = fieldOptions.fieldPanel.mode + } + } + + private fun updateConfigSourceLabel(currentConfig: Config = localConfig) { + //sets to local if user changed values and hasn't applied locally or globally + if(currentConfig != localConfig) { + localConfig.source = ConfigSource.LOCAL + } + + configSourceLabel.text = localConfig.source.description + } + + //updates the actual configuration displayed on the field panel gui + @Suppress("UNNECESSARY_SAFE_CALL") + fun updateFieldGuiFromConfig() { + //sets the slider range from config + fieldOptions.fieldPanel.setSlidersRange(localConfig.sliderRange.width, localConfig.sliderRange.height) + //sets the panel mode (sliders or textboxes) to config from the current mode + if(fieldOptions.fieldPanel?.fields != null){ + fieldOptions.fieldPanel.mode = localConfig.fieldPanelMode + } + } + + //updates the values displayed in this config's ui to the current config values + private fun updateConfigGuiFromConfig() { + sliderRangeFieldsPanel.remove(sliderRangeFields) //remove old fields + sliderRangeFields = createRangeFields() //need to recreate in order to set new values + sliderRangeFieldsPanel.add(sliderRangeFields) //add new fields + + //need to reval&repaint as always + sliderRangeFieldsPanel.revalidate(); sliderRangeFieldsPanel.repaint() + + colorSpaceComboBox.selectedEnum = localConfig.pickerColorSpace + } + + //simple short hand for a repetitive instantiation... + private fun createRangeFields(): SizeFields { + val fields = SizeFields(localConfig.sliderRange, allowsDecimals, true,"Slider range:", " to ") + fields.onChange { + updateConfigSourceLabel(currentConfig) + } + + return fields + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelOptions.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelOptions.kt index 18679ea8..9be6461a 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelOptions.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanelOptions.kt @@ -1,195 +1,195 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.tuner - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.Icons -import com.github.serivesmejia.eocvsim.gui.component.PopupX -import com.github.serivesmejia.eocvsim.util.extension.cvtColor -import com.github.serivesmejia.eocvsim.util.extension.clipUpperZero -import java.awt.FlowLayout -import java.awt.GridLayout -import java.awt.event.ComponentAdapter -import java.awt.event.ComponentEvent -import javax.swing.* -import javax.swing.event.AncestorEvent -import javax.swing.event.AncestorListener - -class TunableFieldPanelOptions(val fieldPanel: TunableFieldPanel, - eocvSim: EOCVSim) : JPanel() { - - private val sliderIco = Icons.getImageResized("ico_slider", 15, 15) - private val textBoxIco = Icons.getImageResized("ico_textbox", 15, 15) - private val configIco = Icons.getImageResized("ico_config", 15, 15) - private val colorPickIco = Icons.getImageResized("ico_colorpick", 15, 15) - - private val textBoxSliderToggle = JToggleButton() - private val configButton = JButton() - private val colorPickButton = JToggleButton() - - val configPanel = TunableFieldPanelConfig(this, eocvSim) - var lastConfigPopup: PopupX? = null - private set - - //toggle between textbox and slider ico - var mode = TunableFieldPanel.Mode.TEXTBOXES - set(value) { - when(value) { - TunableFieldPanel.Mode.SLIDERS -> { - textBoxSliderToggle.icon = textBoxIco - textBoxSliderToggle.isSelected = true - } - TunableFieldPanel.Mode.TEXTBOXES -> { - textBoxSliderToggle.icon = sliderIco - textBoxSliderToggle.isSelected = false - } - } - - handleResize() - - if(fieldPanel.mode != value) fieldPanel.mode = value - configPanel.localConfig.fieldPanelMode = value - - field = value - } - - init { - //set initial icon for buttons - textBoxSliderToggle.icon = sliderIco - configButton.icon = configIco - colorPickButton.icon = colorPickIco - - add(textBoxSliderToggle) - add(configButton) - add(colorPickButton) - - textBoxSliderToggle.addActionListener { - mode = if(textBoxSliderToggle.isSelected) { - TunableFieldPanel.Mode.SLIDERS - } else { - TunableFieldPanel.Mode.TEXTBOXES - } - configPanel.localConfig.fieldPanelMode = mode - } - - configButton.addActionListener { - val configLocation = configButton.locationOnScreen - val buttonHeight = configButton.height / 2 - - val window = SwingUtilities.getWindowAncestor(this) - val popup = PopupX(window, configPanel, configLocation.x, configLocation.y - buttonHeight) - - popup.onShow.doOnce { configPanel.panelShow() } - popup.onHide.doOnce { configPanel.panelHide() } - - //make sure we hide last config so - //that we don't get a "stuck" popup - //if the silly user is pressing the - //button wayy too fast - lastConfigPopup?.hide() - lastConfigPopup = popup - - popup.show() - } - - colorPickButton.addActionListener { - val colorPicker = fieldPanel.tunableField.eocvSim.visualizer.colorPicker - - //start picking if global color picker is not being used by other panel - if(!colorPicker.isPicking && colorPickButton.isSelected) { - startPicking(colorPicker) - } else { //handles cases when cancelling picking - colorPicker.stopPicking() - //if we weren't the ones controlling the last picking, - //start picking again to gain control for this panel - if(colorPickButton.isSelected) startPicking(colorPicker) - } - } - - fieldPanel.addComponentListener(object: ComponentAdapter() { - override fun componentResized(e: ComponentEvent?) = handleResize() - }) - - addAncestorListener(object: AncestorListener { - override fun ancestorRemoved(event: AncestorEvent?) {} - override fun ancestorMoved(event: AncestorEvent?) {} - - override fun ancestorAdded(event: AncestorEvent?) = handleResize() - }) - } - - private fun startPicking(colorPicker: ColorPicker) { - //when user picks a color - colorPicker.onPick.doOnce { - val colorScalar = colorPicker.colorRgb.cvtColor(configPanel.localConfig.pickerColorSpace.cvtCode) - - //setting the scalar value in order from first to fourth field - for(i in 0..(fieldPanel.fields.size - 1).clipUpperZero()) { - //if we're still in range of the scalar values amount - if(i < colorScalar.`val`.size) { - val colorVal = colorScalar.`val`[i] - fieldPanel.setFieldValue(i, colorVal) - fieldPanel.tunableField.setGuiFieldValue(i, colorVal.toString()) - } else { break } //keep looping until we write the entire scalar value - } - colorPickButton.isSelected = false - } - - //handles cancel cases, mostly when passing control to another panel - colorPicker.onCancel.doOnce { colorPickButton.isSelected = false } - - //might want to start picking to this panel here... - colorPicker.startPicking() - } - - //handling resizes for responsive buttons arrangement - private fun handleResize() { - val buttonsHeight = textBoxSliderToggle.height + colorPickButton.height + configButton.height - - layout = if(fieldPanel.height > buttonsHeight && mode == TunableFieldPanel.Mode.SLIDERS) { - GridLayout(3, 1) - } else { - FlowLayout() - } - - revalAndRepaint() - } - - //reevaluates the config of this field panel from the eocv sim config - fun reevaluateConfig() { - //only reevaluate if our config is not local - if(configPanel.localConfig.source != TunableFieldPanelConfig.ConfigSource.LOCAL) { - configPanel.applyFromEOCVSimConfig() - } - } - - private fun revalAndRepaint() { - textBoxSliderToggle.revalidate() - textBoxSliderToggle.repaint() - - revalidate() - repaint() - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.tuner + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.Icons +import com.github.serivesmejia.eocvsim.gui.component.PopupX +import com.github.serivesmejia.eocvsim.util.extension.cvtColor +import com.github.serivesmejia.eocvsim.util.extension.clipUpperZero +import java.awt.FlowLayout +import java.awt.GridLayout +import java.awt.event.ComponentAdapter +import java.awt.event.ComponentEvent +import javax.swing.* +import javax.swing.event.AncestorEvent +import javax.swing.event.AncestorListener + +class TunableFieldPanelOptions(val fieldPanel: TunableFieldPanel, + eocvSim: EOCVSim) : JPanel() { + + private val sliderIco by Icons.lazyGetImageResized("ico_slider", 15, 15) + private val textBoxIco by Icons.lazyGetImageResized("ico_textbox", 15, 15) + private val configIco by Icons.lazyGetImageResized("ico_config", 15, 15) + private val colorPickIco by Icons.lazyGetImageResized("ico_colorpick", 15, 15) + + private val textBoxSliderToggle = JToggleButton() + private val configButton = JButton() + private val colorPickButton = JToggleButton() + + val configPanel = TunableFieldPanelConfig(this, eocvSim) + var lastConfigPopup: PopupX? = null + private set + + //toggle between textbox and slider ico + var mode = TunableFieldPanel.Mode.TEXTBOXES + set(value) { + when(value) { + TunableFieldPanel.Mode.SLIDERS -> { + textBoxSliderToggle.icon = textBoxIco + textBoxSliderToggle.isSelected = true + } + TunableFieldPanel.Mode.TEXTBOXES -> { + textBoxSliderToggle.icon = sliderIco + textBoxSliderToggle.isSelected = false + } + } + + handleResize() + + if(fieldPanel.mode != value) fieldPanel.mode = value + configPanel.localConfig.fieldPanelMode = value + + field = value + } + + init { + //set initial icon for buttons + textBoxSliderToggle.icon = sliderIco + configButton.icon = configIco + colorPickButton.icon = colorPickIco + + add(textBoxSliderToggle) + add(configButton) + add(colorPickButton) + + textBoxSliderToggle.addActionListener { + mode = if(textBoxSliderToggle.isSelected) { + TunableFieldPanel.Mode.SLIDERS + } else { + TunableFieldPanel.Mode.TEXTBOXES + } + configPanel.localConfig.fieldPanelMode = mode + } + + configButton.addActionListener { + val configLocation = configButton.locationOnScreen + val buttonHeight = configButton.height / 2 + + val window = SwingUtilities.getWindowAncestor(this) + val popup = PopupX(window, configPanel, configLocation.x, configLocation.y - buttonHeight) + + popup.onShow.doOnce { configPanel.panelShow() } + popup.onHide.doOnce { configPanel.panelHide() } + + //make sure we hide last config so + //that we don't get a "stuck" popup + //if the silly user is pressing the + //button wayy too fast + lastConfigPopup?.hide() + lastConfigPopup = popup + + popup.show() + } + + colorPickButton.addActionListener { + val colorPicker = fieldPanel.tunableField.eocvSim.visualizer.colorPicker + + //start picking if global color picker is not being used by other panel + if(!colorPicker.isPicking && colorPickButton.isSelected) { + startPicking(colorPicker) + } else { //handles cases when cancelling picking + colorPicker.stopPicking() + //if we weren't the ones controlling the last picking, + //start picking again to gain control for this panel + if(colorPickButton.isSelected) startPicking(colorPicker) + } + } + + fieldPanel.addComponentListener(object: ComponentAdapter() { + override fun componentResized(e: ComponentEvent?) = handleResize() + }) + + addAncestorListener(object: AncestorListener { + override fun ancestorRemoved(event: AncestorEvent?) {} + override fun ancestorMoved(event: AncestorEvent?) {} + + override fun ancestorAdded(event: AncestorEvent?) = handleResize() + }) + } + + private fun startPicking(colorPicker: ColorPicker) { + //when user picks a color + colorPicker.onPick.doOnce { + val colorScalar = colorPicker.colorRgb.cvtColor(configPanel.localConfig.pickerColorSpace.cvtCode) + + //setting the scalar value in order from first to fourth field + for(i in 0..(fieldPanel.fields.size - 1).clipUpperZero()) { + //if we're still in range of the scalar values amount + if(i < colorScalar.`val`.size) { + val colorVal = colorScalar.`val`[i] + fieldPanel.setFieldValue(i, colorVal) + fieldPanel.tunableField.setGuiFieldValue(i, colorVal.toString()) + } else { break } //keep looping until we write the entire scalar value + } + colorPickButton.isSelected = false + } + + //handles cancel cases, mostly when passing control to another panel + colorPicker.onCancel.doOnce { colorPickButton.isSelected = false } + + //might want to start picking to this panel here... + colorPicker.startPicking() + } + + //handling resizes for responsive buttons arrangement + private fun handleResize() { + val buttonsHeight = textBoxSliderToggle.height + colorPickButton.height + configButton.height + + layout = if(fieldPanel.height > buttonsHeight && mode == TunableFieldPanel.Mode.SLIDERS) { + GridLayout(3, 1) + } else { + FlowLayout() + } + + revalAndRepaint() + } + + //reevaluates the config of this field panel from the eocv sim config + fun reevaluateConfig() { + //only reevaluate if our config is not local + if(configPanel.localConfig.source != TunableFieldPanelConfig.ConfigSource.LOCAL) { + configPanel.applyFromEOCVSimConfig() + } + } + + private fun revalAndRepaint() { + textBoxSliderToggle.revalidate() + textBoxSliderToggle.repaint() + + revalidate() + repaint() + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableComboBox.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableComboBox.java index acfae9cd..7e0ef8ed 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableComboBox.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableComboBox.java @@ -1,67 +1,67 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.tuner.element; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.TunableField; - -import javax.swing.*; -import java.util.Objects; - -public class TunableComboBox extends JComboBox { - - private final TunableField tunableField; - private final int index; - - private final EOCVSim eocvSim; - - public TunableComboBox(int index, TunableField tunableField, EOCVSim eocvSim) { - super(); - - this.tunableField = tunableField; - this.index = index; - this.eocvSim = eocvSim; - - init(); - } - - private void init() { - for (Object obj : tunableField.getGuiComboBoxValues(index)) { - this.addItem(obj.toString()); - } - - addItemListener(evt -> eocvSim.onMainUpdate.doOnce(() -> { - try { - tunableField.setGuiComboBoxValue(index, Objects.requireNonNull(getSelectedItem()).toString()); - } catch (IllegalAccessException ex) { - ex.printStackTrace(); - } - - if (eocvSim.pipelineManager.getPaused()) { - eocvSim.pipelineManager.requestSetPaused(false); - } - })); - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.tuner.element; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.tuner.TunableField; + +import javax.swing.*; +import java.util.Objects; + +public class TunableComboBox extends JComboBox { + + private final TunableField tunableField; + private final int index; + + private final EOCVSim eocvSim; + + public TunableComboBox(int index, TunableField tunableField, EOCVSim eocvSim) { + super(); + + this.tunableField = tunableField; + this.index = index; + this.eocvSim = eocvSim; + + init(); + } + + private void init() { + for (Object obj : tunableField.getGuiComboBoxValues(index)) { + this.addItem(obj.toString()); + } + + addItemListener(evt -> eocvSim.onMainUpdate.doOnce(() -> { + try { + tunableField.setGuiComboBoxValue(index, Objects.requireNonNull(getSelectedItem()).toString()); + } catch (IllegalAccessException ex) { + ex.printStackTrace(); + } + + if (eocvSim.pipelineManager.getPaused()) { + eocvSim.pipelineManager.requestSetPaused(false); + } + })); + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableSlider.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableSlider.kt index c630e6ba..170a1f6d 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableSlider.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableSlider.kt @@ -1,76 +1,76 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.tuner.element - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.component.SliderX -import com.github.serivesmejia.eocvsim.tuner.TunableField -import com.github.serivesmejia.eocvsim.util.event.EventListener -import javax.swing.JLabel -import kotlin.math.roundToInt - -class TunableSlider(val index: Int, - val tunableField: TunableField<*>, - val eocvSim: EOCVSim, - val valueLabel: JLabel? = null, - minBound: Double = 0.0, - maxBound: Double = 255.0) : SliderX(minBound, maxBound, 10) { - - var inControl = false - - constructor(i: Int, tunableField: TunableField, eocvSim: EOCVSim, valueLabel: JLabel) : this(i, tunableField, eocvSim, valueLabel, 0.0, 255.0) - - constructor(i: Int, tunableField: TunableField, eocvSim: EOCVSim) : this(i, tunableField, eocvSim, null, 0.0, 255.0) - - private val changeFieldValue = EventListener { - if(inControl) { - tunableField.setGuiFieldValue(index, scaledValue.toString()) - - if (eocvSim.pipelineManager.paused) - eocvSim.pipelineManager.setPaused(false) - } - } - - init { - - addChangeListener { - eocvSim.onMainUpdate.doOnce(changeFieldValue) - - valueLabel?.text = if (tunableField.allowMode == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL) { - scaledValue.toString() - } else { - scaledValue.roundToInt().toString() - } - } - - tunableField.onValueChange { - if (!inControl) { - scaledValue = try { - tunableField.getGuiFieldValue(index).toString().toDouble() - } catch(ignored: NumberFormatException) { 0.0 } - } - } - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.tuner.element + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.component.SliderX +import com.github.serivesmejia.eocvsim.tuner.TunableField +import com.github.serivesmejia.eocvsim.util.event.EventListener +import javax.swing.JLabel +import kotlin.math.roundToInt + +class TunableSlider(val index: Int, + val tunableField: TunableField<*>, + val eocvSim: EOCVSim, + val valueLabel: JLabel? = null, + minBound: Double = 0.0, + maxBound: Double = 255.0) : SliderX(minBound, maxBound, 10) { + + var inControl = false + + constructor(i: Int, tunableField: TunableField, eocvSim: EOCVSim, valueLabel: JLabel) : this(i, tunableField, eocvSim, valueLabel, 0.0, 255.0) + + constructor(i: Int, tunableField: TunableField, eocvSim: EOCVSim) : this(i, tunableField, eocvSim, null, 0.0, 255.0) + + private val changeFieldValue = EventListener { + if(inControl) { + tunableField.setGuiFieldValue(index, scaledValue.toString()) + + if (eocvSim.pipelineManager.paused) + eocvSim.pipelineManager.setPaused(false) + } + } + + init { + + addChangeListener { + eocvSim.onMainUpdate.doOnce(changeFieldValue) + + valueLabel?.text = if (tunableField.allowMode == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL) { + scaledValue.toString() + } else { + scaledValue.roundToInt().toString() + } + } + + tunableField.onValueChange { + if (!inControl) { + scaledValue = try { + tunableField.getGuiFieldValue(index).toString().toDouble() + } catch(ignored: NumberFormatException) { 0.0 } + } + } + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableTextField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableTextField.java index b5a479fb..5088cee1 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableTextField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/element/TunableTextField.java @@ -1,191 +1,191 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.tuner.element; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.TunableField; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.LineBorder; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.AbstractDocument; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.DocumentFilter; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.util.ArrayList; -import java.util.Collections; - -public class TunableTextField extends JTextField { - - private final ArrayList validCharsIfNumber = new ArrayList<>(); - - private final TunableField tunableField; - private final int index; - private final EOCVSim eocvSim; - - private final Border initialBorder; - - private volatile boolean hasValidText = true; - - private boolean inControl = false; - - public TunableTextField(int index, TunableField tunableField, EOCVSim eocvSim) { - super(); - - this.initialBorder = this.getBorder(); - - this.tunableField = tunableField; - this.index = index; - this.eocvSim = eocvSim; - - setText(tunableField.getGuiFieldValue(index).toString()); - - int plusW = Math.round(getText().length() / 5f) * 10; - this.setPreferredSize(new Dimension(40 + plusW, getPreferredSize().height)); - - tunableField.onValueChange.doPersistent(() -> { - if(!inControl) { - setText(tunableField.getGuiFieldValue(index).toString()); - } - }); - - if (tunableField.isOnlyNumbers()) { - - //add all valid characters for non decimal numeric fields - Collections.addAll(validCharsIfNumber, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'); - - //allow dots for decimal numeric fields - if (tunableField.getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL) { - validCharsIfNumber.add('.'); - } - - ((AbstractDocument) getDocument()).setDocumentFilter(new DocumentFilter() { - - @Override - public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { - text = text.replace(" ", ""); - - for (char c : text.toCharArray()) { - if (!isNumberCharacter(c)) return; - } - - boolean invalidNumber = false; - - try { //check if entered text is valid number - Double.valueOf(text); - } catch (NumberFormatException ex) { - invalidNumber = true; - } - - hasValidText = !invalidNumber || !text.isEmpty(); - - if (hasValidText) { - setNormalBorder(); - } else { - setRedBorder(); - } - - super.replace(fb, offset, length, text, attrs); - } - - }); - - } - - getDocument().addDocumentListener(new DocumentListener() { - - Runnable changeFieldValue = () -> { - if ((!hasValidText || !tunableField.isOnlyNumbers() || !getText().trim().equals(""))) { - try { - tunableField.setGuiFieldValue(index, getText()); - } catch (Exception e) { - setRedBorder(); - } - } else { - setRedBorder(); - } - }; - - @Override - public void insertUpdate(DocumentEvent e) { - change(); - } - @Override - public void removeUpdate(DocumentEvent e) { change(); } - @Override - public void changedUpdate(DocumentEvent e) { change(); } - - public void change() { - eocvSim.onMainUpdate.doOnce(changeFieldValue); - } - }); - - //unpausing when typing on any tunable text box - addKeyListener(new KeyListener() { - @Override - public void keyTyped(KeyEvent e) { - execute(); - } - @Override - public void keyPressed(KeyEvent e) { - execute(); - } - @Override - public void keyReleased(KeyEvent e) { execute(); } - - public void execute() { - if (eocvSim.pipelineManager.getPaused()) { - eocvSim.pipelineManager.requestSetPaused(false); - } - } - }); - } - - public void setNormalBorder() { - setBorder(initialBorder); - } - - public void setRedBorder() { - setBorder(new LineBorder(new Color(255, 79, 79), 2)); - } - - public void setInControl(boolean inControl) { - this.inControl = inControl; - } - - private boolean isNumberCharacter(char c) { - for (char validC : validCharsIfNumber) { - if (c == validC) return true; - } - return false; - } - - public boolean isInControl() { return inControl; } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.tuner.element; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.tuner.TunableField; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.LineBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.DocumentFilter; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.ArrayList; +import java.util.Collections; + +public class TunableTextField extends JTextField { + + private final ArrayList validCharsIfNumber = new ArrayList<>(); + + private final TunableField tunableField; + private final int index; + private final EOCVSim eocvSim; + + private final Border initialBorder; + + private volatile boolean hasValidText = true; + + private boolean inControl = false; + + public TunableTextField(int index, TunableField tunableField, EOCVSim eocvSim) { + super(); + + this.initialBorder = this.getBorder(); + + this.tunableField = tunableField; + this.index = index; + this.eocvSim = eocvSim; + + setText(tunableField.getGuiFieldValue(index).toString()); + + int plusW = Math.round(getText().length() / 5f) * 10; + this.setPreferredSize(new Dimension(40 + plusW, getPreferredSize().height)); + + tunableField.onValueChange.doPersistent(() -> { + if(!inControl) { + setText(tunableField.getGuiFieldValue(index).toString()); + } + }); + + if (tunableField.isOnlyNumbers()) { + + //add all valid characters for non decimal numeric fields + Collections.addAll(validCharsIfNumber, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'); + + //allow dots for decimal numeric fields + if (tunableField.getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL) { + validCharsIfNumber.add('.'); + } + + ((AbstractDocument) getDocument()).setDocumentFilter(new DocumentFilter() { + + @Override + public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { + text = text.replace(" ", ""); + + for (char c : text.toCharArray()) { + if (!isNumberCharacter(c)) return; + } + + boolean invalidNumber = false; + + try { //check if entered text is valid number + Double.valueOf(text); + } catch (NumberFormatException ex) { + invalidNumber = true; + } + + hasValidText = !invalidNumber || !text.isEmpty(); + + if (hasValidText) { + setNormalBorder(); + } else { + setRedBorder(); + } + + super.replace(fb, offset, length, text, attrs); + } + + }); + + } + + getDocument().addDocumentListener(new DocumentListener() { + + Runnable changeFieldValue = () -> { + if ((!hasValidText || !tunableField.isOnlyNumbers() || !getText().trim().equals(""))) { + try { + tunableField.setGuiFieldValue(index, getText()); + } catch (Exception e) { + setRedBorder(); + } + } else { + setRedBorder(); + } + }; + + @Override + public void insertUpdate(DocumentEvent e) { + change(); + } + @Override + public void removeUpdate(DocumentEvent e) { change(); } + @Override + public void changedUpdate(DocumentEvent e) { change(); } + + public void change() { + eocvSim.onMainUpdate.doOnce(changeFieldValue); + } + }); + + //unpausing when typing on any tunable text box + addKeyListener(new KeyListener() { + @Override + public void keyTyped(KeyEvent e) { + execute(); + } + @Override + public void keyPressed(KeyEvent e) { + execute(); + } + @Override + public void keyReleased(KeyEvent e) { execute(); } + + public void execute() { + if (eocvSim.pipelineManager.getPaused()) { + eocvSim.pipelineManager.requestSetPaused(false); + } + } + }); + } + + public void setNormalBorder() { + setBorder(initialBorder); + } + + public void setRedBorder() { + setBorder(new LineBorder(new Color(255, 79, 79), 2)); + } + + public void setInControl(boolean inControl) { + this.inControl = inControl; + } + + private boolean isNumberCharacter(char c) { + for (char validC : validCharsIfNumber) { + if (c == validC) return true; + } + return false; + } + + public boolean isInControl() { return inControl; } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/CreateSourcePanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/CreateSourcePanel.kt index 80f1324d..e80521fe 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/CreateSourcePanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/CreateSourcePanel.kt @@ -1,65 +1,65 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.visualizer - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.DialogFactory -import com.github.serivesmejia.eocvsim.gui.component.PopupX -import com.github.serivesmejia.eocvsim.gui.component.input.EnumComboBox -import com.github.serivesmejia.eocvsim.input.SourceType -import java.awt.FlowLayout -import java.awt.GridLayout -import javax.swing.JButton -import javax.swing.JPanel - -class CreateSourcePanel(eocvSim: EOCVSim) : JPanel(GridLayout(2, 1)) { - - private val sourceSelectComboBox = EnumComboBox( - "", SourceType::class.java, SourceType.values(), - { it.coolName }, { SourceType.fromCoolName(it) } - ) - - private val sourceSelectPanel = JPanel(FlowLayout(FlowLayout.CENTER)) - - private val nextButton = JButton("Next") - private val nextPanel = JPanel() - - var popup: PopupX? = null - - init { - sourceSelectComboBox.removeEnumOption(SourceType.UNKNOWN) //removes the UNKNOWN enum - sourceSelectPanel.add(sourceSelectComboBox) //add to separate panel to center element - add(sourceSelectPanel) //add centered panel to this - - nextButton.addActionListener { - //creates "create source" dialog from selected enum - DialogFactory.createSourceDialog(eocvSim, sourceSelectComboBox.selectedEnum!!) - popup?.hide() - } - nextPanel.add(nextButton) - - add(nextPanel) - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.visualizer + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.gui.component.PopupX +import com.github.serivesmejia.eocvsim.gui.component.input.EnumComboBox +import com.github.serivesmejia.eocvsim.input.SourceType +import java.awt.FlowLayout +import java.awt.GridLayout +import javax.swing.JButton +import javax.swing.JPanel + +class CreateSourcePanel(eocvSim: EOCVSim) : JPanel(GridLayout(2, 1)) { + + private val sourceSelectComboBox = EnumComboBox( + "", SourceType::class.java, SourceType.values(), + { it.coolName }, { SourceType.fromCoolName(it) } + ) + + private val sourceSelectPanel = JPanel(FlowLayout(FlowLayout.CENTER)) + + private val nextButton = JButton("Next") + private val nextPanel = JPanel() + + var popup: PopupX? = null + + init { + sourceSelectComboBox.removeEnumOption(SourceType.UNKNOWN) //removes the UNKNOWN enum + sourceSelectPanel.add(sourceSelectComboBox) //add to separate panel to center element + add(sourceSelectPanel) //add centered panel to this + + nextButton.addActionListener { + //creates "create source" dialog from selected enum + DialogFactory.createSourceDialog(eocvSim, sourceSelectComboBox.selectedEnum!!) + popup?.hide() + } + nextPanel.add(nextButton) + + add(nextPanel) + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/InputSourceDropTarget.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/InputSourceDropTarget.kt index 03a85db7..9c1ee748 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/InputSourceDropTarget.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/InputSourceDropTarget.kt @@ -1,42 +1,42 @@ -package com.github.serivesmejia.eocvsim.gui.component.visualizer - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.DialogFactory -import com.github.serivesmejia.eocvsim.input.SourceType -import com.github.serivesmejia.eocvsim.util.Log -import java.awt.datatransfer.DataFlavor -import java.awt.dnd.DnDConstants -import java.awt.dnd.DropTarget -import java.awt.dnd.DropTargetDropEvent -import java.io.File - -class InputSourceDropTarget(val eocvSim: EOCVSim) : DropTarget() { - - companion object { - private const val TAG = "InputSourceDropTarget" - } - - @Suppress("UNCHECKED_CAST") - override fun drop(evt: DropTargetDropEvent) { - try { - evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE) - val droppedFiles = evt.transferable.getTransferData( - DataFlavor.javaFileListFlavor - ) as List - - for(file in droppedFiles) { - val sourceType = SourceType.isFileUsableForSource(file) - - if(sourceType != SourceType.UNKNOWN) { - DialogFactory.createSourceDialog(eocvSim, sourceType, file) - break - } - } - } catch (e: Exception) { - Log.warn(TAG, "Drag n' drop failed", e) - } finally { - evt.dropComplete(true) - } - } - -} +package com.github.serivesmejia.eocvsim.gui.component.visualizer + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.input.SourceType +import com.github.serivesmejia.eocvsim.util.Log +import java.awt.datatransfer.DataFlavor +import java.awt.dnd.DnDConstants +import java.awt.dnd.DropTarget +import java.awt.dnd.DropTargetDropEvent +import java.io.File + +class InputSourceDropTarget(val eocvSim: EOCVSim) : DropTarget() { + + companion object { + private const val TAG = "InputSourceDropTarget" + } + + @Suppress("UNCHECKED_CAST") + override fun drop(evt: DropTargetDropEvent) { + try { + evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE) + val droppedFiles = evt.transferable.getTransferData( + DataFlavor.javaFileListFlavor + ) as List + + for(file in droppedFiles) { + val sourceType = SourceType.isFileUsableForSource(file) + + if(sourceType != SourceType.UNKNOWN) { + DialogFactory.createSourceDialog(eocvSim, sourceType, file) + break + } + } + } catch (e: Exception) { + Log.warn(TAG, "Drag n' drop failed", e) + } finally { + evt.dropComplete(true) + } + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/SourceSelectorPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/SourceSelectorPanel.kt index a46bd996..12830556 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/SourceSelectorPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/SourceSelectorPanel.kt @@ -1,179 +1,179 @@ -package com.github.serivesmejia.eocvsim.gui.component.visualizer - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.component.PopupX -import com.github.serivesmejia.eocvsim.gui.util.icon.SourcesListIconRenderer -import com.github.serivesmejia.eocvsim.pipeline.PipelineManager -import com.github.serivesmejia.eocvsim.util.extension.clipUpperZero -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.swing.Swing -import java.awt.FlowLayout -import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import javax.swing.* - -class SourceSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { - - val sourceSelector = JList() - val sourceSelectorScroll = JScrollPane() - var sourceSelectorButtonsContainer = JPanel() - val sourceSelectorCreateBtt = JButton("Create") - val sourceSelectorDeleteBtt = JButton("Delete") - - val sourceSelectorLabel = JLabel("Sources") - - private var beforeSelectedSource = "" - private var beforeSelectedSourceIndex = 0 - - private var lastCreateSourcePopup: PopupX? = null - - var allowSourceSwitching = true - - init { - layout = GridBagLayout() - - sourceSelectorLabel.font = sourceSelectorLabel.font.deriveFont(20.0f) - sourceSelectorLabel.horizontalAlignment = JLabel.CENTER - - add(sourceSelectorLabel, GridBagConstraints().apply { - gridy = 0 - ipady = 20 - }) - - sourceSelector.selectionMode = ListSelectionModel.SINGLE_SELECTION - - sourceSelectorScroll.setViewportView(sourceSelector) - sourceSelectorScroll.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS - sourceSelectorScroll.horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED - - add(sourceSelectorScroll, GridBagConstraints().apply { - gridy = 1 - - weightx = 0.5 - weighty = 1.0 - fill = GridBagConstraints.BOTH - - ipadx = 120 - ipady = 20 - }) - - //different icons - sourceSelector.cellRenderer = SourcesListIconRenderer(eocvSim.inputSourceManager) - - sourceSelectorCreateBtt.addActionListener { - lastCreateSourcePopup?.hide() - - val panel = CreateSourcePanel(eocvSim) - val location = sourceSelectorCreateBtt.locationOnScreen - - val frame = SwingUtilities.getWindowAncestor(this) - - val popup = PopupX(frame, panel, location.x, location.y, true) - lastCreateSourcePopup = popup - - popup.show() - } - - sourceSelectorButtonsContainer = JPanel(FlowLayout(FlowLayout.CENTER)) - - sourceSelectorButtonsContainer.add(sourceSelectorCreateBtt) - sourceSelectorButtonsContainer.add(sourceSelectorDeleteBtt) - - add(sourceSelectorButtonsContainer, GridBagConstraints().apply { - gridy = 2 - ipady = 20 - }) - - registerListeners() - } - - private fun registerListeners() { - //listener for changing input sources - sourceSelector.addListSelectionListener { evt -> - if(!allowSourceSwitching) return@addListSelectionListener - - try { - if (sourceSelector.selectedIndex != -1) { - val model = sourceSelector.model - val source = model.getElementAt(sourceSelector.selectedIndex) - - //enable or disable source delete button depending if source is default or not - eocvSim.visualizer.sourceSelectorPanel.sourceSelectorDeleteBtt - .isEnabled = !(eocvSim.inputSourceManager.sources[source]?.isDefault ?: true) - - if (!evt.valueIsAdjusting && source != beforeSelectedSource) { - if (!eocvSim.pipelineManager.paused) { - eocvSim.inputSourceManager.requestSetInputSource(source) - beforeSelectedSource = source - beforeSelectedSourceIndex = sourceSelector.selectedIndex - } else { - //check if the user requested the pause or if it was due to one shoot analysis when selecting images - if (eocvSim.pipelineManager.pauseReason !== PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS) { - sourceSelector.setSelectedIndex(beforeSelectedSourceIndex) - } else { //handling pausing - eocvSim.pipelineManager.requestSetPaused(false) - eocvSim.inputSourceManager.requestSetInputSource(source) - beforeSelectedSource = source - beforeSelectedSourceIndex = sourceSelector.selectedIndex - } - } - } - } else { - sourceSelector.setSelectedIndex(1) - } - } catch (ignored: ArrayIndexOutOfBoundsException) { - } - } - - // delete selected input source - sourceSelectorDeleteBtt.addActionListener { - val index = sourceSelector.selectedIndex - val source = sourceSelector.model.getElementAt(index) - - eocvSim.onMainUpdate.doOnce { - eocvSim.inputSourceManager.deleteInputSource(source) - updateSourcesList() - - sourceSelector.selectedIndex = (index - 1).clipUpperZero() - } - } - } - - fun updateSourcesList() = runBlocking { - launch(Dispatchers.Swing) { - val listModel = DefaultListModel() - - for (source in eocvSim.inputSourceManager.sortedInputSources) { - listModel.addElement(source.name) - } - - sourceSelector.fixedCellWidth = 240 - - sourceSelector.model = listModel - sourceSelector.revalidate() - sourceSelectorScroll.revalidate() - - sourceSelector.selectedIndex = 0 - } - } - - fun getIndexOf(name: String): Int { - for(i in 0..sourceSelector.model.size) { - if(sourceSelector.model.getElementAt(i) == name) - return i - } - - return 0 - } - - fun revalAndRepaint() { - sourceSelector.revalidate() - sourceSelector.repaint() - sourceSelectorScroll.revalidate() - sourceSelectorScroll.repaint() - revalidate(); repaint() - } - -} +package com.github.serivesmejia.eocvsim.gui.component.visualizer + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.component.PopupX +import com.github.serivesmejia.eocvsim.gui.util.icon.SourcesListIconRenderer +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.util.extension.clipUpperZero +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.swing.Swing +import java.awt.FlowLayout +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import javax.swing.* + +class SourceSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { + + val sourceSelector = JList() + val sourceSelectorScroll = JScrollPane() + var sourceSelectorButtonsContainer = JPanel() + val sourceSelectorCreateBtt = JButton("Create") + val sourceSelectorDeleteBtt = JButton("Delete") + + val sourceSelectorLabel = JLabel("Sources") + + private var beforeSelectedSource = "" + private var beforeSelectedSourceIndex = 0 + + private var lastCreateSourcePopup: PopupX? = null + + var allowSourceSwitching = false + + init { + layout = GridBagLayout() + + sourceSelectorLabel.font = sourceSelectorLabel.font.deriveFont(20.0f) + sourceSelectorLabel.horizontalAlignment = JLabel.CENTER + + add(sourceSelectorLabel, GridBagConstraints().apply { + gridy = 0 + ipady = 20 + }) + + sourceSelector.selectionMode = ListSelectionModel.SINGLE_SELECTION + + sourceSelectorScroll.setViewportView(sourceSelector) + sourceSelectorScroll.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS + sourceSelectorScroll.horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED + + add(sourceSelectorScroll, GridBagConstraints().apply { + gridy = 1 + + weightx = 0.5 + weighty = 1.0 + fill = GridBagConstraints.BOTH + + ipadx = 120 + ipady = 20 + }) + + //different icons + sourceSelector.cellRenderer = SourcesListIconRenderer(eocvSim.inputSourceManager) + + sourceSelectorCreateBtt.addActionListener { + lastCreateSourcePopup?.hide() + + val panel = CreateSourcePanel(eocvSim) + val location = sourceSelectorCreateBtt.locationOnScreen + + val frame = SwingUtilities.getWindowAncestor(this) + + val popup = PopupX(frame, panel, location.x, location.y, true) + lastCreateSourcePopup = popup + + popup.show() + } + + sourceSelectorButtonsContainer = JPanel(FlowLayout(FlowLayout.CENTER)) + + sourceSelectorButtonsContainer.add(sourceSelectorCreateBtt) + sourceSelectorButtonsContainer.add(sourceSelectorDeleteBtt) + + add(sourceSelectorButtonsContainer, GridBagConstraints().apply { + gridy = 2 + ipady = 20 + }) + + registerListeners() + } + + private fun registerListeners() { + //listener for changing input sources + sourceSelector.addListSelectionListener { evt -> + if(allowSourceSwitching && !evt.valueIsAdjusting) { + try { + if (sourceSelector.selectedIndex != -1) { + val model = sourceSelector.model + val source = model.getElementAt(sourceSelector.selectedIndex) + + //enable or disable source delete button depending if source is default or not + eocvSim.visualizer.sourceSelectorPanel.sourceSelectorDeleteBtt + .isEnabled = !(eocvSim.inputSourceManager.sources[source]?.isDefault ?: true) + + if (!evt.valueIsAdjusting && source != beforeSelectedSource) { + if (!eocvSim.pipelineManager.paused) { + eocvSim.inputSourceManager.requestSetInputSource(source) + beforeSelectedSource = source + beforeSelectedSourceIndex = sourceSelector.selectedIndex + } else { + //check if the user requested the pause or if it was due to one shoot analysis when selecting images + if (eocvSim.pipelineManager.pauseReason !== PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS) { + sourceSelector.setSelectedIndex(beforeSelectedSourceIndex) + } else { //handling pausing + eocvSim.pipelineManager.requestSetPaused(false) + eocvSim.inputSourceManager.requestSetInputSource(source) + beforeSelectedSource = source + beforeSelectedSourceIndex = sourceSelector.selectedIndex + } + } + } + } else { + sourceSelector.setSelectedIndex(1) + } + } catch (ignored: ArrayIndexOutOfBoundsException) { + } + } + } + + // delete selected input source + sourceSelectorDeleteBtt.addActionListener { + val index = sourceSelector.selectedIndex + val source = sourceSelector.model.getElementAt(index) + + eocvSim.onMainUpdate.doOnce { + eocvSim.inputSourceManager.deleteInputSource(source) + updateSourcesList() + + sourceSelector.selectedIndex = (index - 1).clipUpperZero() + } + } + } + + fun updateSourcesList() = runBlocking { + launch(Dispatchers.Swing) { + val listModel = DefaultListModel() + + for (source in eocvSim.inputSourceManager.sortedInputSources) { + listModel.addElement(source.name) + } + + sourceSelector.fixedCellWidth = 240 + + sourceSelector.model = listModel + sourceSelector.revalidate() + sourceSelectorScroll.revalidate() + + sourceSelector.selectedIndex = 0 + } + } + + fun getIndexOf(name: String): Int { + for(i in 0..sourceSelector.model.size) { + if(sourceSelector.model.getElementAt(i) == name) + return i + } + + return 0 + } + + fun revalAndRepaint() { + sourceSelector.revalidate() + sourceSelector.repaint() + sourceSelectorScroll.revalidate() + sourceSelectorScroll.repaint() + revalidate(); repaint() + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TelemetryPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TelemetryPanel.kt index 0b314b4b..8db3f295 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TelemetryPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TelemetryPanel.kt @@ -1,100 +1,100 @@ -package com.github.serivesmejia.eocvsim.gui.component.visualizer - -import org.firstinspires.ftc.robotcore.external.Telemetry -import java.awt.FlowLayout -import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import java.awt.GridLayout -import java.awt.event.MouseEvent -import java.awt.event.MouseMotionListener -import javax.swing.* - -class TelemetryPanel : JPanel() { - - val telemetryScroll = JScrollPane() - val telemetryList = JList() - - val telemetryLabel = JLabel("Telemetry") - - init { - layout = GridBagLayout() - - /* - * TELEMETRY - */ - - telemetryLabel.font = telemetryLabel.font.deriveFont(20.0f) - telemetryLabel.horizontalAlignment = JLabel.CENTER - - add(telemetryLabel, GridBagConstraints().apply { - gridy = 0 - ipady = 20 - }) - - telemetryScroll.setViewportView(telemetryList) - telemetryScroll.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS - telemetryScroll.horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS - - //tooltips for the telemetry list items (thnx stackoverflow) - telemetryList.addMouseMotionListener(object : MouseMotionListener { - override fun mouseDragged(e: MouseEvent) {} - override fun mouseMoved(e: MouseEvent) { - val l = e.source as JList<*> - val m = l.model - val index = l.locationToIndex(e.point) - if (index > -1) { - l.toolTipText = m.getElementAt(index).toString() - } - } - }) - - telemetryList.selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION - - add(telemetryScroll, GridBagConstraints().apply { - gridy = 1 - - weightx = 0.5 - weighty = 1.0 - fill = GridBagConstraints.BOTH - - ipadx = 120 - ipady = 20 - }) - } - - fun revalAndRepaint() { - telemetryList.revalidate() - telemetryList.repaint() - telemetryScroll.revalidate() - telemetryScroll.repaint() - } - - fun updateTelemetry(telemetry: Telemetry?) { - val cacheTelemetryText = telemetry.toString() - - var telemetryText: String? = null - - if (telemetry != null && telemetry.hasChanged()) { - telemetryText = cacheTelemetryText - - val listModel = DefaultListModel() - for (line in telemetryText.split("\n").toTypedArray()) { - listModel.addElement(line) - } - - telemetryList.model = listModel - } - - if (telemetryList.model.size <= 0 || (telemetryText != null && telemetryText.trim { it <= ' ' } == "")) { - val listModel = DefaultListModel() - - listModel.addElement("") - telemetryList.model = listModel - } - - telemetryList.fixedCellWidth = 240 - - revalAndRepaint() - } - -} +package com.github.serivesmejia.eocvsim.gui.component.visualizer + +import org.firstinspires.ftc.robotcore.external.Telemetry +import java.awt.FlowLayout +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import java.awt.GridLayout +import java.awt.event.MouseEvent +import java.awt.event.MouseMotionListener +import javax.swing.* + +class TelemetryPanel : JPanel() { + + val telemetryScroll = JScrollPane() + val telemetryList = JList() + + val telemetryLabel = JLabel("Telemetry") + + init { + layout = GridBagLayout() + + /* + * TELEMETRY + */ + + telemetryLabel.font = telemetryLabel.font.deriveFont(20.0f) + telemetryLabel.horizontalAlignment = JLabel.CENTER + + add(telemetryLabel, GridBagConstraints().apply { + gridy = 0 + ipady = 20 + }) + + telemetryScroll.setViewportView(telemetryList) + telemetryScroll.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS + telemetryScroll.horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS + + //tooltips for the telemetry list items (thnx stackoverflow) + telemetryList.addMouseMotionListener(object : MouseMotionListener { + override fun mouseDragged(e: MouseEvent) {} + override fun mouseMoved(e: MouseEvent) { + val l = e.source as JList<*> + val m = l.model + val index = l.locationToIndex(e.point) + if (index > -1) { + l.toolTipText = m.getElementAt(index).toString() + } + } + }) + + telemetryList.selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION + + add(telemetryScroll, GridBagConstraints().apply { + gridy = 1 + + weightx = 0.5 + weighty = 1.0 + fill = GridBagConstraints.BOTH + + ipadx = 120 + ipady = 20 + }) + } + + fun revalAndRepaint() { + telemetryList.revalidate() + telemetryList.repaint() + telemetryScroll.revalidate() + telemetryScroll.repaint() + } + + fun updateTelemetry(telemetry: Telemetry?) { + val cacheTelemetryText = telemetry.toString() + + var telemetryText: String? = null + + if (telemetry != null && telemetry.hasChanged()) { + telemetryText = cacheTelemetryText + + val listModel = DefaultListModel() + for (line in telemetryText.split("\n").toTypedArray()) { + listModel.addElement(line) + } + + telemetryList.model = listModel + } + + if (telemetryList.model.size <= 0 || (telemetryText != null && telemetryText.trim { it <= ' ' } == "")) { + val listModel = DefaultListModel() + + listModel.addElement("") + telemetryList.model = listModel + } + + telemetryList.fixedCellWidth = 240 + + revalAndRepaint() + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TopMenuBar.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TopMenuBar.kt index 6fe3093b..f5d0e861 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TopMenuBar.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/TopMenuBar.kt @@ -1,156 +1,162 @@ - -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.visualizer - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.DialogFactory -import com.github.serivesmejia.eocvsim.gui.Visualizer -import com.github.serivesmejia.eocvsim.gui.dialog.Output -import com.github.serivesmejia.eocvsim.gui.util.GuiUtil -import com.github.serivesmejia.eocvsim.input.SourceType -import com.github.serivesmejia.eocvsim.workspace.util.VSCodeLauncher -import java.awt.Desktop -import java.net.URI -import javax.swing.JMenu -import javax.swing.JMenuBar -import javax.swing.JMenuItem - -class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { - - @JvmField val mFileMenu = JMenu("File") - @JvmField val mWorkspMenu = JMenu("Workspace") - @JvmField val mEditMenu = JMenu("Edit") - @JvmField val mHelpMenu = JMenu("Help") - - @JvmField val workspCompile = JMenuItem("Build java files") - - init { - // FILE - - val fileNew = JMenu("New") - mFileMenu.add(fileNew) - - val fileNewInputSourceSubmenu = JMenu("Input Source") - fileNew.add(fileNewInputSourceSubmenu) - - //add all input source types to top bar menu - for (type in SourceType.values()) { - if (type == SourceType.UNKNOWN) continue //exclude unknown type - - val fileNewInputSourceItem = JMenuItem(type.coolName) - - fileNewInputSourceItem.addActionListener { - DialogFactory.createSourceDialog(eocvSim, type) - } - - fileNewInputSourceSubmenu.add(fileNewInputSourceItem) - } - - val fileSaveMat = JMenuItem("Save current image") - - fileSaveMat.addActionListener { - GuiUtil.saveMatFileChooser( - visualizer.frame, - visualizer.viewport.lastVisualizedMat, - eocvSim - ) - } - mFileMenu.add(fileSaveMat) - - mFileMenu.addSeparator() - - val fileRestart = JMenuItem("Restart") - - fileRestart.addActionListener { eocvSim.onMainUpdate.doOnce { eocvSim.restart() } } - mFileMenu.add(fileRestart) - - add(mFileMenu) - - //WORKSPACE - - val workspSetWorkspace = JMenuItem("Select workspace") - - workspSetWorkspace.addActionListener { visualizer.selectPipelinesWorkspace() } - mWorkspMenu.add(workspSetWorkspace) - - workspCompile.addActionListener { visualizer.asyncCompilePipelines() } - mWorkspMenu.add(workspCompile) - - val workspBuildOutput = JMenuItem("Output") - - workspBuildOutput.addActionListener { - if(!Output.isAlreadyOpened) - DialogFactory.createOutput(eocvSim, true) - } - mWorkspMenu.add(workspBuildOutput) - - mWorkspMenu.addSeparator() - - val workspVSCode = JMenu("VS Code") - - val workspVSCodeOpen = JMenuItem("Open in current workspace") - - workspVSCodeOpen.addActionListener { - VSCodeLauncher.asyncLaunch(eocvSim.workspaceManager.workspaceFile) - } - workspVSCode.add(workspVSCodeOpen) - - val workspVSCodeCreate = JMenuItem("Create VS Code workspace") - - workspVSCodeCreate.addActionListener { visualizer.createVSCodeWorkspace() } - workspVSCode.add(workspVSCodeCreate) - - mWorkspMenu.add(workspVSCode) - - add(mWorkspMenu) - - // EDIT - - val editSettings = JMenuItem("Settings") - editSettings.addActionListener { DialogFactory.createConfigDialog(eocvSim) } - - mEditMenu.add(editSettings) - add(mEditMenu) - - // HELP - - val helpUsage = JMenuItem("Usage") - helpUsage.addActionListener { - Desktop.getDesktop().browse(URI("https://github.com/deltacv/EOCV-Sim/blob/master/USAGE.md")) - } - - helpUsage.isEnabled = Desktop.isDesktopSupported() - mHelpMenu.add(helpUsage) - - mHelpMenu.addSeparator() - - val helpAbout = JMenuItem("About") - helpAbout.addActionListener { DialogFactory.createAboutDialog(eocvSim) } - - mHelpMenu.add(helpAbout) - add(mHelpMenu) - } - -} + +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.visualizer + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.gui.Visualizer +import com.github.serivesmejia.eocvsim.gui.dialog.Output +import com.github.serivesmejia.eocvsim.gui.util.GuiUtil +import com.github.serivesmejia.eocvsim.input.SourceType +import com.github.serivesmejia.eocvsim.workspace.util.VSCodeLauncher +import java.awt.Desktop +import java.net.URI +import javax.swing.JMenu +import javax.swing.JMenuBar +import javax.swing.JMenuItem + +class TopMenuBar(visualizer: Visualizer, eocvSim: EOCVSim) : JMenuBar() { + + companion object { + val docsUrl = URI("https://deltacv.gitbook.io/eocv-sim/") + } + + @JvmField val mFileMenu = JMenu("File") + @JvmField val mWorkspMenu = JMenu("Workspace") + @JvmField val mEditMenu = JMenu("Edit") + @JvmField val mHelpMenu = JMenu("Help") + + @JvmField val workspCompile = JMenuItem("Build java files") + + init { + // FILE + + val fileNew = JMenu("New") + mFileMenu.add(fileNew) + + val fileNewInputSourceSubmenu = JMenu("Input Source") + fileNew.add(fileNewInputSourceSubmenu) + + //add all input source types to top bar menu + for (type in SourceType.values()) { + if (type == SourceType.UNKNOWN) continue //exclude unknown type + + val fileNewInputSourceItem = JMenuItem(type.coolName) + + fileNewInputSourceItem.addActionListener { + DialogFactory.createSourceDialog(eocvSim, type) + } + + fileNewInputSourceSubmenu.add(fileNewInputSourceItem) + } + + val fileSaveMat = JMenuItem("Save current image") + + fileSaveMat.addActionListener { + GuiUtil.saveMatFileChooser( + visualizer.frame, + visualizer.viewport.lastVisualizedMat, + eocvSim + ) + } + mFileMenu.add(fileSaveMat) + + mFileMenu.addSeparator() + + val fileRestart = JMenuItem("Restart") + + fileRestart.addActionListener { eocvSim.onMainUpdate.doOnce { eocvSim.restart() } } + mFileMenu.add(fileRestart) + + add(mFileMenu) + + //WORKSPACE + + val workspSetWorkspace = JMenuItem("Select workspace") + + workspSetWorkspace.addActionListener { visualizer.selectPipelinesWorkspace() } + mWorkspMenu.add(workspSetWorkspace) + + workspCompile.addActionListener { visualizer.asyncCompilePipelines() } + mWorkspMenu.add(workspCompile) + + val workspBuildOutput = JMenuItem("Output") + + workspBuildOutput.addActionListener { + if(!Output.isAlreadyOpened) + DialogFactory.createOutput(eocvSim, true) + } + mWorkspMenu.add(workspBuildOutput) + + mWorkspMenu.addSeparator() + + val workspVSCode = JMenu("External") + + val workspVSCodeCreate = JMenuItem("Create Gradle workspace") + + workspVSCodeCreate.addActionListener { visualizer.createVSCodeWorkspace() } + workspVSCode.add(workspVSCodeCreate) + + workspVSCode.addSeparator() + + val workspVSCodeOpen = JMenuItem("Open VS Code here") + + workspVSCodeOpen.addActionListener { + VSCodeLauncher.asyncLaunch(eocvSim.workspaceManager.workspaceFile) + } + workspVSCode.add(workspVSCodeOpen) + + mWorkspMenu.add(workspVSCode) + + add(mWorkspMenu) + + // EDIT + + val editSettings = JMenuItem("Settings") + editSettings.addActionListener { DialogFactory.createConfigDialog(eocvSim) } + + mEditMenu.add(editSettings) + add(mEditMenu) + + // HELP + + val helpUsage = JMenuItem("Documentation") + helpUsage.addActionListener { + Desktop.getDesktop().browse(docsUrl) + } + + helpUsage.isEnabled = Desktop.isDesktopSupported() + mHelpMenu.add(helpUsage) + + mHelpMenu.addSeparator() + + val helpAbout = JMenuItem("About") + helpAbout.addActionListener { DialogFactory.createAboutDialog(eocvSim) } + + mHelpMenu.add(helpAbout) + add(mHelpMenu) + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorButtonsPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorButtonsPanel.kt index 1ec80142..11bd34e7 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorButtonsPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorButtonsPanel.kt @@ -1,110 +1,110 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.component.PopupX -import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import java.awt.Insets -import javax.swing.JButton -import javax.swing.JPanel -import javax.swing.JToggleButton -import javax.swing.SwingUtilities - -class PipelineSelectorButtonsPanel(eocvSim: EOCVSim) : JPanel(GridBagLayout()) { - - val pipelinePauseBtt = JToggleButton("Pause") - val pipelineRecordBtt = JToggleButton("Record") - - val pipelineWorkspaceBtt = JButton("Workspace") - - val workspaceButtonsPanel = JPanel(GridBagLayout()) - val pipelineCompileBtt = JButton("Build java files") - - private var lastWorkspacePopup: PopupX? = null - - init { - //listener for changing pause state - pipelinePauseBtt.addActionListener { - eocvSim.onMainUpdate.doOnce { eocvSim.pipelineManager.setPaused(pipelinePauseBtt.isSelected) } - } - pipelinePauseBtt.addChangeListener { - pipelinePauseBtt.text = if(pipelinePauseBtt.isSelected) "Resume" else "Pause" - } - - add(pipelinePauseBtt, GridBagConstraints().apply { - insets = Insets(0, 0, 0, 5) - }) - - pipelineRecordBtt.addActionListener { - eocvSim.onMainUpdate.doOnce { - if (pipelineRecordBtt.isSelected) { - if (!eocvSim.isCurrentlyRecording()) eocvSim.startRecordingSession() - } else { - if (eocvSim.isCurrentlyRecording()) eocvSim.stopRecordingSession() - } - } - } - add(pipelineRecordBtt, GridBagConstraints().apply { gridx = 1 }) - - pipelineWorkspaceBtt.addActionListener { - val workspaceLocation = pipelineWorkspaceBtt.locationOnScreen - - val window = SwingUtilities.getWindowAncestor(this) - val popup = PopupX(window, workspaceButtonsPanel, workspaceLocation.x, workspaceLocation.y) - - popup.onShow { - popup.setLocation( - popup.window.location.x - workspaceButtonsPanel.width / 5, - popup.window.location.y - ) - } - - lastWorkspacePopup?.hide() - lastWorkspacePopup = popup - popup.show() - } - add(pipelineWorkspaceBtt, GridBagConstraints().apply { - gridwidth = 2 - gridy = 1 - - insets = Insets(5, 0, 0, 0) - weightx = 1.0 - - fill = GridBagConstraints.HORIZONTAL - anchor = GridBagConstraints.CENTER - }) - - // WORKSPACE BUTTONS POPUP - pipelineCompileBtt.addActionListener { eocvSim.visualizer.asyncCompilePipelines() } - workspaceButtonsPanel.add(pipelineCompileBtt, GridBagConstraints()) - - val selectWorkspBtt = JButton("Select workspace") - - selectWorkspBtt.addActionListener { eocvSim.visualizer.selectPipelinesWorkspace() } - workspaceButtonsPanel.add(selectWorkspBtt, GridBagConstraints().apply { gridx = 1 }) - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.component.PopupX +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import java.awt.Insets +import javax.swing.JButton +import javax.swing.JPanel +import javax.swing.JToggleButton +import javax.swing.SwingUtilities + +class PipelineSelectorButtonsPanel(eocvSim: EOCVSim) : JPanel(GridBagLayout()) { + + val pipelinePauseBtt = JToggleButton("Pause") + val pipelineRecordBtt = JToggleButton("Record") + + val pipelineWorkspaceBtt = JButton("Workspace") + + val workspaceButtonsPanel = JPanel(GridBagLayout()) + val pipelineCompileBtt = JButton("Build java files") + + private var lastWorkspacePopup: PopupX? = null + + init { + //listener for changing pause state + pipelinePauseBtt.addActionListener { + eocvSim.onMainUpdate.doOnce { eocvSim.pipelineManager.setPaused(pipelinePauseBtt.isSelected) } + } + pipelinePauseBtt.addChangeListener { + pipelinePauseBtt.text = if(pipelinePauseBtt.isSelected) "Resume" else "Pause" + } + + add(pipelinePauseBtt, GridBagConstraints().apply { + insets = Insets(0, 0, 0, 5) + }) + + pipelineRecordBtt.addActionListener { + eocvSim.onMainUpdate.doOnce { + if (pipelineRecordBtt.isSelected) { + if (!eocvSim.isCurrentlyRecording()) eocvSim.startRecordingSession() + } else { + if (eocvSim.isCurrentlyRecording()) eocvSim.stopRecordingSession() + } + } + } + add(pipelineRecordBtt, GridBagConstraints().apply { gridx = 1 }) + + pipelineWorkspaceBtt.addActionListener { + val workspaceLocation = pipelineWorkspaceBtt.locationOnScreen + + val window = SwingUtilities.getWindowAncestor(this) + val popup = PopupX(window, workspaceButtonsPanel, workspaceLocation.x, workspaceLocation.y) + + popup.onShow { + popup.setLocation( + popup.window.location.x - workspaceButtonsPanel.width / 5, + popup.window.location.y + ) + } + + lastWorkspacePopup?.hide() + lastWorkspacePopup = popup + popup.show() + } + add(pipelineWorkspaceBtt, GridBagConstraints().apply { + gridwidth = 2 + gridy = 1 + + insets = Insets(5, 0, 0, 0) + weightx = 1.0 + + fill = GridBagConstraints.HORIZONTAL + anchor = GridBagConstraints.CENTER + }) + + // WORKSPACE BUTTONS POPUP + pipelineCompileBtt.addActionListener { eocvSim.visualizer.asyncCompilePipelines() } + workspaceButtonsPanel.add(pipelineCompileBtt, GridBagConstraints()) + + val selectWorkspBtt = JButton("Select workspace") + + selectWorkspBtt.addActionListener { eocvSim.visualizer.selectPipelinesWorkspace() } + workspaceButtonsPanel.add(selectWorkspBtt, GridBagConstraints().apply { gridx = 1 }) + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorPanel.kt index b0a62702..73b2c559 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/pipeline/PipelineSelectorPanel.kt @@ -1,150 +1,150 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.util.icon.PipelineListIconRenderer -import com.github.serivesmejia.eocvsim.pipeline.PipelineManager -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.swing.Swing -import java.awt.FlowLayout -import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import javax.swing.* -import javax.swing.event.ListSelectionEvent - -class PipelineSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { - - var selectedIndex: Int - get() = pipelineSelector.selectedIndex - set(value) { - runBlocking { - launch(Dispatchers.Swing) { - pipelineSelector.selectedIndex = value - } - } - } - - val pipelineSelector = JList() - val pipelineSelectorScroll = JScrollPane() - - val pipelineSelectorLabel = JLabel("Pipelines") - - val buttonsPanel = PipelineSelectorButtonsPanel(eocvSim) - - var allowPipelineSwitching = false - - private var beforeSelectedPipeline = -1 - - init { - layout = GridBagLayout() - - pipelineSelectorLabel.font = pipelineSelectorLabel.font.deriveFont(20.0f) - - pipelineSelectorLabel.horizontalAlignment = JLabel.CENTER - - add(pipelineSelectorLabel, GridBagConstraints().apply { - gridy = 0 - ipady = 20 - }) - - pipelineSelector.cellRenderer = PipelineListIconRenderer(eocvSim.pipelineManager) - pipelineSelector.selectionMode = ListSelectionModel.SINGLE_SELECTION - - pipelineSelectorScroll.setViewportView(pipelineSelector) - pipelineSelectorScroll.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS - pipelineSelectorScroll.horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED - - add(pipelineSelectorScroll, GridBagConstraints().apply { - gridy = 1 - - weightx = 0.5 - weighty = 1.0 - fill = GridBagConstraints.BOTH - - ipadx = 120 - ipady = 20 - }) - - add(buttonsPanel, GridBagConstraints().apply { - gridy = 2 - ipady = 20 - }) - - registerListeners() - } - - private fun registerListeners() { - - //listener for changing pipeline - pipelineSelector.addListSelectionListener { evt: ListSelectionEvent -> - if(!allowPipelineSwitching) return@addListSelectionListener - - if (pipelineSelector.selectedIndex != -1) { - val pipeline = pipelineSelector.selectedIndex - - if (!evt.valueIsAdjusting && pipeline != beforeSelectedPipeline) { - if (!eocvSim.pipelineManager.paused) { - eocvSim.pipelineManager.requestChangePipeline(pipeline) - beforeSelectedPipeline = pipeline - } else { - if (eocvSim.pipelineManager.pauseReason !== PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS) { - pipelineSelector.setSelectedIndex(beforeSelectedPipeline) - } else { //handling pausing - eocvSim.pipelineManager.requestSetPaused(false) - eocvSim.pipelineManager.requestChangePipeline(pipeline) - beforeSelectedPipeline = pipeline - } - } - } - } else { - pipelineSelector.setSelectedIndex(1) - } - } - } - - fun updatePipelinesList() = runBlocking { - launch(Dispatchers.Swing) { - val listModel = DefaultListModel() - for (pipeline in eocvSim.pipelineManager.pipelines) { - listModel.addElement(pipeline.clazz.simpleName) - } - - pipelineSelector.fixedCellWidth = 240 - pipelineSelector.model = listModel - - revalAndRepaint() - } - } - - fun revalAndRepaint() { - pipelineSelector.revalidate() - pipelineSelector.repaint() - pipelineSelectorScroll.revalidate() - pipelineSelectorScroll.repaint() - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.component.visualizer.pipeline + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.util.icon.PipelineListIconRenderer +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.swing.Swing +import java.awt.FlowLayout +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import javax.swing.* +import javax.swing.event.ListSelectionEvent + +class PipelineSelectorPanel(private val eocvSim: EOCVSim) : JPanel() { + + var selectedIndex: Int + get() = pipelineSelector.selectedIndex + set(value) { + runBlocking { + launch(Dispatchers.Swing) { + pipelineSelector.selectedIndex = value + } + } + } + + val pipelineSelector = JList() + val pipelineSelectorScroll = JScrollPane() + + val pipelineSelectorLabel = JLabel("Pipelines") + + val buttonsPanel = PipelineSelectorButtonsPanel(eocvSim) + + var allowPipelineSwitching = false + + private var beforeSelectedPipeline = -1 + + init { + layout = GridBagLayout() + + pipelineSelectorLabel.font = pipelineSelectorLabel.font.deriveFont(20.0f) + + pipelineSelectorLabel.horizontalAlignment = JLabel.CENTER + + add(pipelineSelectorLabel, GridBagConstraints().apply { + gridy = 0 + ipady = 20 + }) + + pipelineSelector.cellRenderer = PipelineListIconRenderer(eocvSim.pipelineManager) + pipelineSelector.selectionMode = ListSelectionModel.SINGLE_SELECTION + + pipelineSelectorScroll.setViewportView(pipelineSelector) + pipelineSelectorScroll.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS + pipelineSelectorScroll.horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED + + add(pipelineSelectorScroll, GridBagConstraints().apply { + gridy = 1 + + weightx = 0.5 + weighty = 1.0 + fill = GridBagConstraints.BOTH + + ipadx = 120 + ipady = 20 + }) + + add(buttonsPanel, GridBagConstraints().apply { + gridy = 2 + ipady = 20 + }) + + registerListeners() + } + + private fun registerListeners() { + + //listener for changing pipeline + pipelineSelector.addListSelectionListener { evt: ListSelectionEvent -> + if(!allowPipelineSwitching) return@addListSelectionListener + + if (pipelineSelector.selectedIndex != -1) { + val pipeline = pipelineSelector.selectedIndex + + if (!evt.valueIsAdjusting && pipeline != beforeSelectedPipeline) { + if (!eocvSim.pipelineManager.paused) { + eocvSim.pipelineManager.requestChangePipeline(pipeline) + beforeSelectedPipeline = pipeline + } else { + if (eocvSim.pipelineManager.pauseReason !== PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS) { + pipelineSelector.setSelectedIndex(beforeSelectedPipeline) + } else { //handling pausing + eocvSim.pipelineManager.requestSetPaused(false) + eocvSim.pipelineManager.requestChangePipeline(pipeline) + beforeSelectedPipeline = pipeline + } + } + } + } else { + pipelineSelector.setSelectedIndex(1) + } + } + } + + fun updatePipelinesList() = runBlocking { + launch(Dispatchers.Swing) { + val listModel = DefaultListModel() + for (pipeline in eocvSim.pipelineManager.pipelines) { + listModel.addElement(pipeline.clazz.simpleName) + } + + pipelineSelector.fixedCellWidth = 240 + pipelineSelector.model = listModel + + revalAndRepaint() + } + } + + fun revalAndRepaint() { + pipelineSelector.revalidate() + pipelineSelector.repaint() + pipelineSelectorScroll.revalidate() + pipelineSelectorScroll.repaint() + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/About.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/About.java index b18a523f..65b36e24 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/About.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/About.java @@ -1,179 +1,179 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.Icons; -import com.github.serivesmejia.eocvsim.gui.Visualizer; -import com.github.serivesmejia.eocvsim.gui.component.ImageX; -import com.github.serivesmejia.eocvsim.gui.util.GuiUtil; -import com.github.serivesmejia.eocvsim.util.StrUtil; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.nio.charset.StandardCharsets; - -public class About { - - public JDialog about = null; - - public static ListModel CONTRIBS_LIST_MODEL; - public static ListModel OSL_LIST_MODEL; - - static { - try { - CONTRIBS_LIST_MODEL = GuiUtil.isToListModel(About.class.getResourceAsStream("/contributors.txt"), StandardCharsets.UTF_8); - OSL_LIST_MODEL = GuiUtil.isToListModel(About.class.getResourceAsStream("/opensourcelibs.txt"), StandardCharsets.UTF_8); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - } - - public About(JFrame parent, EOCVSim eocvSim) { - - about = new JDialog(parent); - - eocvSim.visualizer.childDialogs.add(about); - initAbout(); - - } - - private void initAbout() { - - about.setModal(true); - - about.setTitle("About"); - about.setSize(445, 290); - - JPanel contents = new JPanel(new GridLayout(2, 1)); - contents.setAlignmentX(Component.CENTER_ALIGNMENT); - - ImageX icon = new ImageX(Icons.INSTANCE.getImage("ico_eocvsim")); - icon.setSize(50, 50); - icon.setAlignmentX(Component.CENTER_ALIGNMENT); - - icon.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - - JLabel appInfo = new JLabel("EasyOpenCV Simulator v" + EOCVSim.VERSION); - appInfo.setFont(appInfo.getFont().deriveFont(appInfo.getFont().getStyle() | Font.BOLD)); //set font to bold - - JPanel appInfoLogo = new JPanel(new FlowLayout()); - - appInfoLogo.add(icon); - appInfoLogo.add(appInfo); - - appInfoLogo.setBorder(BorderFactory.createEmptyBorder(10, 10, -30, 10)); - - contents.add(appInfoLogo); - - JTabbedPane tabbedPane = new JTabbedPane(); - - JPanel contributors = new JPanel(new FlowLayout(FlowLayout.CENTER)); - - JList contribsList = new JList<>(); - contribsList.setModel(CONTRIBS_LIST_MODEL); - contribsList.setSelectionModel(new GuiUtil.NoSelectionModel()); - contribsList.setLayout(new FlowLayout(FlowLayout.CENTER)); - contribsList.setAlignmentY(Component.TOP_ALIGNMENT); - - contribsList.setVisibleRowCount(4); - - JPanel contributorsList = new JPanel(new FlowLayout(FlowLayout.CENTER)); - contributorsList.setAlignmentY(Component.TOP_ALIGNMENT); - - JScrollPane contribsListScroll = new JScrollPane(); - contribsListScroll.setBorder(new EmptyBorder(0,0,20,10)); - contribsListScroll.setAlignmentX(Component.CENTER_ALIGNMENT); - contribsListScroll.setAlignmentY(Component.TOP_ALIGNMENT); - contribsListScroll.setViewportView(contribsList); - - contributors.setAlignmentY(Component.TOP_ALIGNMENT); - contents.setAlignmentY(Component.TOP_ALIGNMENT); - - contributorsList.add(contribsListScroll); - contributors.add(contributorsList); - - tabbedPane.addTab("Contributors", contributors); - - JPanel osLibs = new JPanel(new FlowLayout(FlowLayout.CENTER)); - - JList osLibsList = new JList<>(); - osLibsList.setModel(OSL_LIST_MODEL); - osLibsList.setLayout(new FlowLayout(FlowLayout.CENTER)); - osLibsList.setAlignmentY(Component.TOP_ALIGNMENT); - - osLibsList.setVisibleRowCount(4); - - osLibsList.addListSelectionListener(e -> { - if(!e.getValueIsAdjusting()) { - - String text = osLibsList.getModel().getElementAt(osLibsList.getSelectedIndex()); - String[] urls = StrUtil.findUrlsInString(text); - - if(urls.length > 0) { - try { - Desktop.getDesktop().browse(new URI(urls[0])); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - osLibsList.clearSelection(); - - } - }); - - JPanel osLibsListPane = new JPanel(new FlowLayout(FlowLayout.CENTER)); - osLibsList.setAlignmentY(Component.TOP_ALIGNMENT); - - JScrollPane osLibsListScroll = new JScrollPane(); - osLibsListScroll.setBorder(new EmptyBorder(0,0,20,10)); - osLibsListScroll.setAlignmentX(Component.CENTER_ALIGNMENT); - osLibsListScroll.setAlignmentY(Component.TOP_ALIGNMENT); - osLibsListScroll.setViewportView(osLibsList); - - osLibs.setAlignmentY(Component.TOP_ALIGNMENT); - - osLibsListPane.add(osLibsListScroll); - osLibs.add(osLibsListPane); - - tabbedPane.addTab("Open Source Libraries", osLibs); - - contents.add(tabbedPane); - - contents.setBorder(new EmptyBorder(10,10,10,10)); - - about.add(contents); - - about.setResizable(false); - - about.setLocationRelativeTo(null); - about.setVisible(true); - - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.dialog; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.Icons; +import com.github.serivesmejia.eocvsim.gui.Visualizer; +import com.github.serivesmejia.eocvsim.gui.component.ImageX; +import com.github.serivesmejia.eocvsim.gui.util.GuiUtil; +import com.github.serivesmejia.eocvsim.util.StrUtil; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.nio.charset.StandardCharsets; + +public class About { + + public JDialog about = null; + + public static ListModel CONTRIBS_LIST_MODEL; + public static ListModel OSL_LIST_MODEL; + + static { + try { + CONTRIBS_LIST_MODEL = GuiUtil.isToListModel(About.class.getResourceAsStream("/contributors.txt"), StandardCharsets.UTF_8); + OSL_LIST_MODEL = GuiUtil.isToListModel(About.class.getResourceAsStream("/opensourcelibs.txt"), StandardCharsets.UTF_8); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + public About(JFrame parent, EOCVSim eocvSim) { + + about = new JDialog(parent); + + eocvSim.visualizer.childDialogs.add(about); + initAbout(); + + } + + private void initAbout() { + + about.setModal(true); + + about.setTitle("About"); + about.setSize(490, 320); + + JPanel contents = new JPanel(new GridLayout(2, 1)); + contents.setAlignmentX(Component.CENTER_ALIGNMENT); + + ImageX icon = new ImageX(Icons.INSTANCE.getImage("ico_eocvsim")); + icon.setSize(50, 50); + icon.setAlignmentX(Component.CENTER_ALIGNMENT); + + icon.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + JLabel appInfo = new JLabel("EasyOpenCV Simulator v" + EOCVSim.VERSION); + appInfo.setFont(appInfo.getFont().deriveFont(appInfo.getFont().getStyle() | Font.BOLD)); //set font to bold + + JPanel appInfoLogo = new JPanel(new FlowLayout()); + + appInfoLogo.add(icon); + appInfoLogo.add(appInfo); + + appInfoLogo.setBorder(BorderFactory.createEmptyBorder(10, 10, -30, 10)); + + contents.add(appInfoLogo); + + JTabbedPane tabbedPane = new JTabbedPane(); + + JPanel contributors = new JPanel(new FlowLayout(FlowLayout.CENTER)); + + JList contribsList = new JList<>(); + contribsList.setModel(CONTRIBS_LIST_MODEL); + contribsList.setSelectionModel(new GuiUtil.NoSelectionModel()); + contribsList.setLayout(new FlowLayout(FlowLayout.CENTER)); + contribsList.setAlignmentY(Component.TOP_ALIGNMENT); + + contribsList.setVisibleRowCount(4); + + JPanel contributorsList = new JPanel(new FlowLayout(FlowLayout.CENTER)); + contributorsList.setAlignmentY(Component.TOP_ALIGNMENT); + + JScrollPane contribsListScroll = new JScrollPane(); + contribsListScroll.setBorder(new EmptyBorder(0,0,20,10)); + contribsListScroll.setAlignmentX(Component.CENTER_ALIGNMENT); + contribsListScroll.setAlignmentY(Component.TOP_ALIGNMENT); + contribsListScroll.setViewportView(contribsList); + + contributors.setAlignmentY(Component.TOP_ALIGNMENT); + contents.setAlignmentY(Component.TOP_ALIGNMENT); + + contributorsList.add(contribsListScroll); + contributors.add(contributorsList); + + tabbedPane.addTab("Contributors", contributors); + + JPanel osLibs = new JPanel(new FlowLayout(FlowLayout.CENTER)); + + JList osLibsList = new JList<>(); + osLibsList.setModel(OSL_LIST_MODEL); + osLibsList.setLayout(new FlowLayout(FlowLayout.CENTER)); + osLibsList.setAlignmentY(Component.TOP_ALIGNMENT); + + osLibsList.setVisibleRowCount(4); + + osLibsList.addListSelectionListener(e -> { + if(!e.getValueIsAdjusting()) { + + String text = osLibsList.getModel().getElementAt(osLibsList.getSelectedIndex()); + String[] urls = StrUtil.findUrlsInString(text); + + if(urls.length > 0) { + try { + Desktop.getDesktop().browse(new URI(urls[0])); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + osLibsList.clearSelection(); + + } + }); + + JPanel osLibsListPane = new JPanel(new FlowLayout(FlowLayout.CENTER)); + osLibsList.setAlignmentY(Component.TOP_ALIGNMENT); + + JScrollPane osLibsListScroll = new JScrollPane(); + osLibsListScroll.setBorder(new EmptyBorder(0,0,20,10)); + osLibsListScroll.setAlignmentX(Component.CENTER_ALIGNMENT); + osLibsListScroll.setAlignmentY(Component.TOP_ALIGNMENT); + osLibsListScroll.setViewportView(osLibsList); + + osLibs.setAlignmentY(Component.TOP_ALIGNMENT); + + osLibsListPane.add(osLibsListScroll); + osLibs.add(osLibsListPane); + + tabbedPane.addTab("Open Source Libraries", osLibs); + + contents.add(tabbedPane); + + contents.setBorder(new EmptyBorder(10,10,10,10)); + + about.add(contents); + + about.setResizable(false); + + about.setLocationRelativeTo(null); + about.setVisible(true); + + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.java index f8883d67..301d06cc 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.java @@ -1,169 +1,169 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.config.Config; -import com.github.serivesmejia.eocvsim.gui.component.input.EnumComboBox; -import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields; -import com.github.serivesmejia.eocvsim.gui.theme.Theme; -import com.github.serivesmejia.eocvsim.pipeline.PipelineFps; -import com.github.serivesmejia.eocvsim.pipeline.PipelineTimeout; - -import javax.swing.*; -import java.awt.*; - -public class Configuration { - - private final EOCVSim eocvSim; - public JPanel contents = new JPanel(new GridLayout(7, 1, 1, 8)); - public JComboBox themeComboBox = new JComboBox<>(); - - public JButton acceptButton = new JButton("Accept"); - - public JCheckBox pauseOnImageCheckBox = new JCheckBox(); - - public EnumComboBox pipelineTimeoutComboBox = null; - public EnumComboBox pipelineFpsComboBox = null; - - public SizeFields videoRecordingSize = null; - public EnumComboBox videoRecordingFpsComboBox = null; - - JDialog configuration; - - public Configuration(JFrame parent, EOCVSim eocvSim) { - configuration = new JDialog(parent); - this.eocvSim = eocvSim; - - eocvSim.visualizer.childDialogs.add(configuration); - - initConfiguration(); - } - - private void initConfiguration() { - - Config config = this.eocvSim.configManager.getConfig(); - configuration.setModal(true); - configuration.setTitle("Settings"); - configuration.setSize(350, 320); - - JPanel themePanel = new JPanel(new FlowLayout()); - JLabel themeLabel = new JLabel("Theme: "); - - themeLabel.setHorizontalAlignment(0); - - for (Theme theme : Theme.values()) { - this.themeComboBox.addItem(theme.toString().replace("_", " ")); - } - - themeComboBox.setSelectedIndex(eocvSim.getConfig().simTheme.ordinal()); - themePanel.add(themeLabel); - themePanel.add(this.themeComboBox); - contents.add(themePanel); - - JPanel pauseOnImagePanel = new JPanel(new FlowLayout()); - JLabel pauseOnImageLabel = new JLabel("Pause with Image Sources"); - - pauseOnImageCheckBox.setSelected(config.pauseOnImages); - - pauseOnImagePanel.add(pauseOnImageCheckBox); - pauseOnImagePanel.add(pauseOnImageLabel); - - contents.add(pauseOnImagePanel); - - pipelineTimeoutComboBox = new EnumComboBox<>( - "Pipeline Process Timeout: ", PipelineTimeout.class, - PipelineTimeout.values(), PipelineTimeout::getCoolName, PipelineTimeout::fromCoolName - ); - pipelineTimeoutComboBox.setSelectedEnum(config.pipelineTimeout); - contents.add(pipelineTimeoutComboBox); - - pipelineFpsComboBox = new EnumComboBox<>( - "Pipeline Max FPS: ", PipelineFps.class, - PipelineFps.values(), PipelineFps::getCoolName, PipelineFps::fromCoolName - ); - pipelineFpsComboBox.setSelectedEnum(config.pipelineMaxFps); - contents.add(this.pipelineFpsComboBox); - - videoRecordingSize = new SizeFields( - config.videoRecordingSize, false, - "Video Recording Size: " - ); - videoRecordingSize.onChange.doPersistent(() -> - acceptButton.setEnabled(this.videoRecordingSize.getValid()) - ); - contents.add(this.videoRecordingSize); - - // video fps - - videoRecordingFpsComboBox = new EnumComboBox<>( - "Video Recording FPS: ", PipelineFps.class, - PipelineFps.values(), PipelineFps::getCoolName, PipelineFps::fromCoolName - ); - videoRecordingFpsComboBox.setSelectedEnum(config.videoRecordingFps); - contents.add(videoRecordingFpsComboBox); - - JPanel acceptPanel = new JPanel(new FlowLayout()); - acceptPanel.add(acceptButton); - - acceptButton.addActionListener(e -> { - this.eocvSim.onMainUpdate.doOnce(this::applyChanges); - close(); - }); - - contents.add(acceptPanel); - contents.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); - - configuration.add(this.contents); - configuration.setResizable(false); - configuration.setLocationRelativeTo(null); - configuration.setVisible(true); - } - - private void applyChanges() { - Config config = eocvSim.configManager.getConfig(); - - Theme userSelectedTheme = Theme.valueOf(themeComboBox.getSelectedItem().toString().replace(" ", "_")); - Theme beforeTheme = config.simTheme; - - //save user modifications to config - config.simTheme = userSelectedTheme; - config.pauseOnImages = pauseOnImageCheckBox.isSelected(); - config.pipelineTimeout = pipelineTimeoutComboBox.getSelectedEnum(); - config.pipelineMaxFps = pipelineFpsComboBox.getSelectedEnum(); - config.videoRecordingSize = videoRecordingSize.getCurrentSize(); - config.videoRecordingFps = videoRecordingFpsComboBox.getSelectedEnum(); - - eocvSim.configManager.saveToFile(); //update config file - - if (userSelectedTheme != beforeTheme) - eocvSim.restart(); - } - - public void close() { - configuration.setVisible(false); - configuration.dispose(); - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.dialog; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.config.Config; +import com.github.serivesmejia.eocvsim.gui.component.input.EnumComboBox; +import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields; +import com.github.serivesmejia.eocvsim.gui.theme.Theme; +import com.github.serivesmejia.eocvsim.pipeline.PipelineFps; +import com.github.serivesmejia.eocvsim.pipeline.PipelineTimeout; + +import javax.swing.*; +import java.awt.*; + +public class Configuration { + + private final EOCVSim eocvSim; + public JPanel contents = new JPanel(new GridLayout(7, 1, 1, 8)); + public JComboBox themeComboBox = new JComboBox<>(); + + public JButton acceptButton = new JButton("Accept"); + + public JCheckBox pauseOnImageCheckBox = new JCheckBox(); + + public EnumComboBox pipelineTimeoutComboBox = null; + public EnumComboBox pipelineFpsComboBox = null; + + public SizeFields videoRecordingSize = null; + public EnumComboBox videoRecordingFpsComboBox = null; + + JDialog configuration; + + public Configuration(JFrame parent, EOCVSim eocvSim) { + configuration = new JDialog(parent); + this.eocvSim = eocvSim; + + eocvSim.visualizer.childDialogs.add(configuration); + + initConfiguration(); + } + + private void initConfiguration() { + + Config config = this.eocvSim.configManager.getConfig(); + configuration.setModal(true); + configuration.setTitle("Settings"); + configuration.setSize(350, 320); + + JPanel themePanel = new JPanel(new FlowLayout()); + JLabel themeLabel = new JLabel("Theme: "); + + themeLabel.setHorizontalAlignment(0); + + for (Theme theme : Theme.values()) { + this.themeComboBox.addItem(theme.toString().replace("_", " ")); + } + + themeComboBox.setSelectedIndex(eocvSim.getConfig().simTheme.ordinal()); + themePanel.add(themeLabel); + themePanel.add(this.themeComboBox); + contents.add(themePanel); + + JPanel pauseOnImagePanel = new JPanel(new FlowLayout()); + JLabel pauseOnImageLabel = new JLabel("Pause with Image Sources"); + + pauseOnImageCheckBox.setSelected(config.pauseOnImages); + + pauseOnImagePanel.add(pauseOnImageCheckBox); + pauseOnImagePanel.add(pauseOnImageLabel); + + contents.add(pauseOnImagePanel); + + pipelineTimeoutComboBox = new EnumComboBox<>( + "Pipeline Process Timeout: ", PipelineTimeout.class, + PipelineTimeout.values(), PipelineTimeout::getCoolName, PipelineTimeout::fromCoolName + ); + pipelineTimeoutComboBox.setSelectedEnum(config.pipelineTimeout); + contents.add(pipelineTimeoutComboBox); + + pipelineFpsComboBox = new EnumComboBox<>( + "Pipeline Max FPS: ", PipelineFps.class, + PipelineFps.values(), PipelineFps::getCoolName, PipelineFps::fromCoolName + ); + pipelineFpsComboBox.setSelectedEnum(config.pipelineMaxFps); + contents.add(this.pipelineFpsComboBox); + + videoRecordingSize = new SizeFields( + config.videoRecordingSize, false, + "Video Recording Size: " + ); + videoRecordingSize.onChange.doPersistent(() -> + acceptButton.setEnabled(this.videoRecordingSize.getValid()) + ); + contents.add(this.videoRecordingSize); + + // video fps + + videoRecordingFpsComboBox = new EnumComboBox<>( + "Video Recording FPS: ", PipelineFps.class, + PipelineFps.values(), PipelineFps::getCoolName, PipelineFps::fromCoolName + ); + videoRecordingFpsComboBox.setSelectedEnum(config.videoRecordingFps); + contents.add(videoRecordingFpsComboBox); + + JPanel acceptPanel = new JPanel(new FlowLayout()); + acceptPanel.add(acceptButton); + + acceptButton.addActionListener(e -> { + this.eocvSim.onMainUpdate.doOnce(this::applyChanges); + close(); + }); + + contents.add(acceptPanel); + contents.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); + + configuration.add(this.contents); + configuration.setResizable(false); + configuration.setLocationRelativeTo(null); + configuration.setVisible(true); + } + + private void applyChanges() { + Config config = eocvSim.configManager.getConfig(); + + Theme userSelectedTheme = Theme.valueOf(themeComboBox.getSelectedItem().toString().replace(" ", "_")); + Theme beforeTheme = config.simTheme; + + //save user modifications to config + config.simTheme = userSelectedTheme; + config.pauseOnImages = pauseOnImageCheckBox.isSelected(); + config.pipelineTimeout = pipelineTimeoutComboBox.getSelectedEnum(); + config.pipelineMaxFps = pipelineFpsComboBox.getSelectedEnum(); + config.videoRecordingSize = videoRecordingSize.getCurrentSize(); + config.videoRecordingFps = videoRecordingFpsComboBox.getSelectedEnum(); + + eocvSim.configManager.saveToFile(); //update config file + + if (userSelectedTheme != beforeTheme) + eocvSim.restart(); + } + + public void close() { + configuration.setVisible(false); + configuration.dispose(); + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/FileAlreadyExists.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/FileAlreadyExists.java index df7867b0..a5565fb1 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/FileAlreadyExists.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/FileAlreadyExists.java @@ -1,97 +1,97 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog; - -import com.github.serivesmejia.eocvsim.EOCVSim; - -import javax.swing.*; -import java.awt.*; - -public class FileAlreadyExists { - - public static volatile boolean alreadyOpened = false; - public volatile JDialog fileAlreadyExists = null; - public volatile JPanel contentsPanel = new JPanel(); - public volatile UserChoice userChoice; - EOCVSim eocvSim = null; - - public FileAlreadyExists(JFrame parent, EOCVSim eocvSim) { - - fileAlreadyExists = new JDialog(parent); - - this.eocvSim = eocvSim; - - eocvSim.visualizer.childDialogs.add(fileAlreadyExists); - - } - - public UserChoice run() { - - fileAlreadyExists.setModal(true); - - fileAlreadyExists.setTitle("Warning"); - fileAlreadyExists.setSize(300, 120); - - JPanel alreadyExistsPanel = new JPanel(new FlowLayout()); - - JLabel alreadyExistsLabel = new JLabel("File already exists in the selected directory"); - alreadyExistsPanel.add(alreadyExistsLabel); - - contentsPanel.add(alreadyExistsPanel); - - JPanel replaceCancelPanel = new JPanel(new FlowLayout()); - - JButton replaceButton = new JButton("Replace"); - replaceCancelPanel.add(replaceButton); - - replaceButton.addActionListener((e) -> { - userChoice = UserChoice.REPLACE; - fileAlreadyExists.setVisible(false); - }); - - JButton cancelButton = new JButton("Cancel"); - replaceCancelPanel.add(cancelButton); - - cancelButton.addActionListener((e) -> { - userChoice = UserChoice.CANCEL; - fileAlreadyExists.setVisible(false); - }); - - contentsPanel.add(replaceCancelPanel); - - fileAlreadyExists.add(contentsPanel); - - fileAlreadyExists.setResizable(false); - fileAlreadyExists.setLocationRelativeTo(null); - fileAlreadyExists.setVisible(true); - - while (userChoice == UserChoice.NA) ; - - return userChoice; - - } - - public enum UserChoice {NA, REPLACE, CANCEL} - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.dialog; + +import com.github.serivesmejia.eocvsim.EOCVSim; + +import javax.swing.*; +import java.awt.*; + +public class FileAlreadyExists { + + public static volatile boolean alreadyOpened = false; + public volatile JDialog fileAlreadyExists = null; + public volatile JPanel contentsPanel = new JPanel(); + public volatile UserChoice userChoice; + EOCVSim eocvSim = null; + + public FileAlreadyExists(JFrame parent, EOCVSim eocvSim) { + + fileAlreadyExists = new JDialog(parent); + + this.eocvSim = eocvSim; + + eocvSim.visualizer.childDialogs.add(fileAlreadyExists); + + } + + public UserChoice run() { + + fileAlreadyExists.setModal(true); + + fileAlreadyExists.setTitle("Warning"); + fileAlreadyExists.setSize(300, 120); + + JPanel alreadyExistsPanel = new JPanel(new FlowLayout()); + + JLabel alreadyExistsLabel = new JLabel("File already exists in the selected directory"); + alreadyExistsPanel.add(alreadyExistsLabel); + + contentsPanel.add(alreadyExistsPanel); + + JPanel replaceCancelPanel = new JPanel(new FlowLayout()); + + JButton replaceButton = new JButton("Replace"); + replaceCancelPanel.add(replaceButton); + + replaceButton.addActionListener((e) -> { + userChoice = UserChoice.REPLACE; + fileAlreadyExists.setVisible(false); + }); + + JButton cancelButton = new JButton("Cancel"); + replaceCancelPanel.add(cancelButton); + + cancelButton.addActionListener((e) -> { + userChoice = UserChoice.CANCEL; + fileAlreadyExists.setVisible(false); + }); + + contentsPanel.add(replaceCancelPanel); + + fileAlreadyExists.add(contentsPanel); + + fileAlreadyExists.setResizable(false); + fileAlreadyExists.setLocationRelativeTo(null); + fileAlreadyExists.setVisible(true); + + while (userChoice == UserChoice.NA) ; + + return userChoice; + + } + + public enum UserChoice {NA, REPLACE, CANCEL} + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Output.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Output.kt index c58ca77c..bf6a0cec 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Output.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Output.kt @@ -1,239 +1,241 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.dialog.component.OutputPanel -import com.github.serivesmejia.eocvsim.pipeline.compiler.PipelineCompileStatus -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.swing.Swing -import java.awt.Dimension -import java.awt.event.WindowAdapter -import java.awt.event.WindowEvent -import javax.swing.* - -class Output @JvmOverloads constructor( - parent: JFrame, - private val eocvSim: EOCVSim, - private val tabbedPaneIndex: Int = latestIndex, - private val wasManuallyOpened: Boolean = false -) { - - companion object { - var isAlreadyOpened = false - private set - - var latestIndex = 0 - private set - } - - private val output = JDialog(parent) - - private val buildBottomButtonsPanel = BuildOutputBottomButtonsPanel(::close) - private val buildOutputPanel = OutputPanel(buildBottomButtonsPanel) - - private val pipelineBottomButtonsPanel = PipelineBottomButtonsPanel(::close) - private val pipelineOutputPanel = OutputPanel(pipelineBottomButtonsPanel) - - private val tabbedPane = JTabbedPane() - - private val compiledPipelineManager = eocvSim.pipelineManager.compiledPipelineManager - private val pipelineExceptionTracker = eocvSim.pipelineManager.pipelineExceptionTracker - - init { - isAlreadyOpened = true - - eocvSim.visualizer.childDialogs.add(output) - - output.isModal = true - output.title = "Output" - output.setSize(500, 350) - - tabbedPane.add("Pipeline Output", pipelineOutputPanel) - tabbedPane.add("Build Output", buildOutputPanel) - - tabbedPane.selectedIndex = tabbedPaneIndex - - output.contentPane.add(tabbedPane) - - output.setLocationRelativeTo(null) - - updatePipelineOutput() - - if(eocvSim.pipelineManager.paused) { - pipelinePaused() - } else { - pipelineResumed() - } - - buildEnded(true) - if(compiledPipelineManager.isBuildRunning) { - buildRunning() - } - - registerListeners() - - output.isVisible = true - } - - private fun registerListeners() = GlobalScope.launch(Dispatchers.Swing) { - output.addWindowListener(object: WindowAdapter() { - override fun windowClosing(e: WindowEvent) { - close() - } - }) - - pipelineExceptionTracker.onUpdate { - if(!output.isVisible) { - it.removeThis() - } else { - updatePipelineOutput() - } - } - - compiledPipelineManager.onBuildStart { - if(!output.isVisible) { - it.removeThis() - } else { - buildRunning() - } - } - - compiledPipelineManager.onBuildEnd { - if(!output.isVisible) { - it.removeThis() - } else { - buildEnded() - tabbedPane.selectedIndex = 1 - } - } - - eocvSim.pipelineManager.onPause { - if(!output.isVisible) { - it.removeThis() - } else { - pipelinePaused() - } - } - - eocvSim.pipelineManager.onResume { - if(!output.isVisible) { - it.removeThis() - } else { - pipelineResumed() - } - } - - pipelineBottomButtonsPanel.pauseButton.addActionListener { - eocvSim.pipelineManager.setPaused(pipelineBottomButtonsPanel.pauseButton.isSelected) - - if(pipelineBottomButtonsPanel.pauseButton.isSelected) { - pipelinePaused() - } else { - pipelineResumed() - } - } - - pipelineBottomButtonsPanel.clearButton.addActionListener { - eocvSim.pipelineManager.pipelineExceptionTracker.clear() - } - - buildBottomButtonsPanel.buildAgainButton.addActionListener { - eocvSim.visualizer.asyncCompilePipelines() - } - } - - private fun updatePipelineOutput() { - pipelineOutputPanel.outputArea.text = pipelineExceptionTracker.message - } - - private fun buildRunning() { - buildBottomButtonsPanel.buildAgainButton.isEnabled = false - buildOutputPanel.outputArea.text = "Build running..." - } - - private fun buildEnded(calledOnInit: Boolean = false) { - compiledPipelineManager.run { - if(!wasManuallyOpened && - compiledPipelineManager.lastBuildResult!!.status == PipelineCompileStatus.SUCCESS && - tabbedPaneIndex == 1 && !calledOnInit - ) { - // close if the dialog was automatically opened in the - // "build output" tab and a new build was successful - close() - return@buildEnded - } - - buildOutputPanel.outputArea.text = when { - lastBuildOutputMessage != null -> lastBuildOutputMessage!! - lastBuildResult != null -> lastBuildResult!!.message - else -> "No output" - } - } - - buildBottomButtonsPanel.buildAgainButton.isEnabled = true - } - - private fun pipelineResumed() { - pipelineBottomButtonsPanel.pauseButton.isSelected = false - pipelineBottomButtonsPanel.pauseButton.text = "Pause" - } - - private fun pipelinePaused() { - pipelineBottomButtonsPanel.pauseButton.isSelected = true - pipelineBottomButtonsPanel.pauseButton.text = "Resume" - } - - fun close() { - output.isVisible = false - isAlreadyOpened = false - latestIndex = tabbedPane.selectedIndex - } - - class PipelineBottomButtonsPanel( - closeCallback: () -> Unit - ) : OutputPanel.DefaultBottomButtonsPanel(closeCallback) { - val pauseButton = JToggleButton("Pause") - - override fun create(panel: OutputPanel) { - add(Box.createRigidArea(Dimension(4, 0))) - add(pauseButton) - super.create(panel) - } - } - - class BuildOutputBottomButtonsPanel( - closeCallback: () -> Unit, - ) : OutputPanel.DefaultBottomButtonsPanel(closeCallback) { - val buildAgainButton = JButton("Build again") - - override fun create(panel: OutputPanel) { - add(Box.createRigidArea(Dimension(4, 0))) - add(buildAgainButton) - super.create(panel) - } - } -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.dialog + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.dialog.component.OutputPanel +import com.github.serivesmejia.eocvsim.pipeline.compiler.PipelineCompileStatus +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.swing.Swing +import java.awt.Dimension +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent +import javax.swing.* + +class Output @JvmOverloads constructor( + parent: JFrame, + private val eocvSim: EOCVSim, + private val tabbedPaneIndex: Int = latestIndex, + private val wasManuallyOpened: Boolean = false +) { + + companion object { + var isAlreadyOpened = false + private set + + var latestIndex = 0 + private set + } + + private val output = JDialog(parent) + + private val buildBottomButtonsPanel = BuildOutputBottomButtonsPanel(::close) + private val buildOutputPanel = OutputPanel(buildBottomButtonsPanel) + + private val pipelineBottomButtonsPanel = PipelineBottomButtonsPanel(::close) + private val pipelineOutputPanel = OutputPanel(pipelineBottomButtonsPanel) + + private val tabbedPane = JTabbedPane() + + private val compiledPipelineManager = eocvSim.pipelineManager.compiledPipelineManager + private val pipelineExceptionTracker = eocvSim.pipelineManager.pipelineExceptionTracker + + init { + isAlreadyOpened = true + + eocvSim.visualizer.childDialogs.add(output) + + output.isModal = true + output.title = "Output" + output.setSize(500, 350) + + tabbedPane.add("Pipeline Output", pipelineOutputPanel) + tabbedPane.add("Build Output", buildOutputPanel) + + tabbedPane.selectedIndex = tabbedPaneIndex + + output.contentPane.add(tabbedPane) + + output.setLocationRelativeTo(null) + + updatePipelineOutput() + + if(eocvSim.pipelineManager.paused) { + pipelinePaused() + } else { + pipelineResumed() + } + + buildEnded(true) + if(compiledPipelineManager.isBuildRunning) { + buildRunning() + } + + registerListeners() + + output.isVisible = true + } + + @OptIn(DelicateCoroutinesApi::class) + private fun registerListeners() = GlobalScope.launch(Dispatchers.Swing) { + output.addWindowListener(object: WindowAdapter() { + override fun windowClosing(e: WindowEvent) { + close() + } + }) + + pipelineExceptionTracker.onUpdate { + if(!output.isVisible) { + it.removeThis() + } else { + updatePipelineOutput() + } + } + + compiledPipelineManager.onBuildStart { + if(!output.isVisible) { + it.removeThis() + } else { + buildRunning() + } + } + + compiledPipelineManager.onBuildEnd { + if(!output.isVisible) { + it.removeThis() + } else { + buildEnded() + tabbedPane.selectedIndex = 1 + } + } + + eocvSim.pipelineManager.onPause { + if(!output.isVisible) { + it.removeThis() + } else { + pipelinePaused() + } + } + + eocvSim.pipelineManager.onResume { + if(!output.isVisible) { + it.removeThis() + } else { + pipelineResumed() + } + } + + pipelineBottomButtonsPanel.pauseButton.addActionListener { + eocvSim.pipelineManager.setPaused(pipelineBottomButtonsPanel.pauseButton.isSelected) + + if(pipelineBottomButtonsPanel.pauseButton.isSelected) { + pipelinePaused() + } else { + pipelineResumed() + } + } + + pipelineBottomButtonsPanel.clearButton.addActionListener { + eocvSim.pipelineManager.pipelineExceptionTracker.clear() + } + + buildBottomButtonsPanel.buildAgainButton.addActionListener { + eocvSim.visualizer.asyncCompilePipelines() + } + } + + private fun updatePipelineOutput() { + pipelineOutputPanel.outputArea.text = pipelineExceptionTracker.message + } + + private fun buildRunning() { + buildBottomButtonsPanel.buildAgainButton.isEnabled = false + buildOutputPanel.outputArea.text = "Build running..." + } + + private fun buildEnded(calledOnInit: Boolean = false) { + compiledPipelineManager.run { + if(!wasManuallyOpened && + compiledPipelineManager.lastBuildResult!!.status == PipelineCompileStatus.SUCCESS && + tabbedPaneIndex == 1 && !calledOnInit + ) { + // close if the dialog was automatically opened in the + // "build output" tab and a new build was successful + close() + return@buildEnded + } + + buildOutputPanel.outputArea.text = when { + lastBuildOutputMessage != null -> lastBuildOutputMessage!! + lastBuildResult != null -> lastBuildResult!!.message + else -> "No output" + } + } + + buildBottomButtonsPanel.buildAgainButton.isEnabled = true + } + + private fun pipelineResumed() { + pipelineBottomButtonsPanel.pauseButton.isSelected = false + pipelineBottomButtonsPanel.pauseButton.text = "Pause" + } + + private fun pipelinePaused() { + pipelineBottomButtonsPanel.pauseButton.isSelected = true + pipelineBottomButtonsPanel.pauseButton.text = "Resume" + } + + fun close() { + output.isVisible = false + isAlreadyOpened = false + latestIndex = tabbedPane.selectedIndex + } + + class PipelineBottomButtonsPanel( + closeCallback: () -> Unit + ) : OutputPanel.DefaultBottomButtonsPanel(closeCallback) { + val pauseButton = JToggleButton("Pause") + + override fun create(panel: OutputPanel) { + add(Box.createRigidArea(Dimension(4, 0))) + add(pauseButton) + super.create(panel) + } + } + + class BuildOutputBottomButtonsPanel( + closeCallback: () -> Unit, + ) : OutputPanel.DefaultBottomButtonsPanel(closeCallback) { + val buildAgainButton = JButton("Build again") + + override fun create(panel: OutputPanel) { + add(Box.createRigidArea(Dimension(4, 0))) + add(buildAgainButton) + super.create(panel) + } + } +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/SplashScreen.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/SplashScreen.kt index 3161311e..3987a033 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/SplashScreen.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/SplashScreen.kt @@ -1,57 +1,57 @@ -package com.github.serivesmejia.eocvsim.gui.dialog - -import com.github.serivesmejia.eocvsim.gui.Icons -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import java.awt.* -import javax.swing.JDialog -import javax.swing.JPanel -import javax.swing.SwingUtilities - -class SplashScreen(closeHandler: EventHandler? = null) : JDialog() { - - init { - if(closeHandler != null) { - closeHandler { - SwingUtilities.invokeLater { - isVisible = false - dispose() - } - } - } - - val image = ImagePanel() - add(image) - - setLocationRelativeTo(null) - isUndecorated = true - isAlwaysOnTop = true - background = Color(0, 0, 0, 0) - - cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR) - - pack() - - val screenSize = Toolkit.getDefaultToolkit().screenSize - val x = (screenSize.width - image.width) / 2 - val y = (screenSize.height - image.height) / 2 - - setLocation(x, y) - isVisible = true - } - - class ImagePanel : JPanel(GridBagLayout()) { - val img = Icons.getImage("ico_eocvsim") - - init { - isOpaque = false - } - - override fun paintComponent(g: Graphics) { - super.paintComponent(g) - g.drawImage(img.image, 0, 0, width, height, this) - } - - override fun getPreferredSize() = Dimension(img.iconWidth / 2, img.iconHeight / 2) - } - +package com.github.serivesmejia.eocvsim.gui.dialog + +import com.github.serivesmejia.eocvsim.gui.Icons +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import java.awt.* +import javax.swing.JDialog +import javax.swing.JPanel +import javax.swing.SwingUtilities + +class SplashScreen(closeHandler: EventHandler? = null) : JDialog() { + + init { + if(closeHandler != null) { + closeHandler { + SwingUtilities.invokeLater { + isVisible = false + dispose() + } + } + } + + val image = ImagePanel() + add(image) + + setLocationRelativeTo(null) + isUndecorated = true + isAlwaysOnTop = true + background = Color(0, 0, 0, 0) + + cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR) + + pack() + + val screenSize = Toolkit.getDefaultToolkit().screenSize + val x = (screenSize.width - image.width) / 2 + val y = (screenSize.height - image.height) / 2 + + setLocation(x, y) + isVisible = true + } + + class ImagePanel : JPanel(GridBagLayout()) { + val img = Icons.getImage("ico_eocvsim") + + init { + isOpaque = false + } + + override fun paintComponent(g: Graphics) { + super.paintComponent(g) + g.drawImage(img.image, 0, 0, width, height, this) + } + + override fun getPreferredSize() = Dimension(img.iconWidth / 2, img.iconHeight / 2) + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/component/OutputPanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/component/OutputPanel.kt index 50f999d4..9814c5a1 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/component/OutputPanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/component/OutputPanel.kt @@ -1,111 +1,111 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog.component - -import java.awt.Dimension -import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import java.awt.Toolkit -import java.awt.datatransfer.StringSelection -import javax.swing.* - -class OutputPanel( - private val bottomButtonsPanel: BottomButtonsPanel -) : JPanel(GridBagLayout()) { - - val outputArea = JTextArea("") - - constructor(closeCallback: () -> Unit) : this(DefaultBottomButtonsPanel(closeCallback)) - - init { - if(bottomButtonsPanel is DefaultBottomButtonsPanel) { - bottomButtonsPanel.outputTextSupplier = { outputArea.text } - } - - outputArea.isEditable = false - outputArea.highlighter = null - outputArea.lineWrap = true - - val outputScroll = JScrollPane(outputArea) - outputScroll.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS - outputScroll.horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED - - add(outputScroll, GridBagConstraints().apply { - fill = GridBagConstraints.BOTH - weightx = 0.5 - weighty = 1.0 - }) - - bottomButtonsPanel.create(this) - - add(bottomButtonsPanel, GridBagConstraints().apply { - fill = GridBagConstraints.HORIZONTAL - gridy = 1 - - weightx = 1.0 - ipadx = 10 - ipady = 10 - }) - } - - open class DefaultBottomButtonsPanel( - override val closeCallback: () -> Unit - ) : BottomButtonsPanel() { - val copyButton = JButton("Copy") - val clearButton = JButton("Clear") - val closeButton = JButton("Close") - - var outputTextSupplier: () -> String = { "" } - - override fun create(panel: OutputPanel){ - layout = BoxLayout(this, BoxLayout.LINE_AXIS) - - add(Box.createHorizontalGlue()) - copyButton.addActionListener { - Toolkit.getDefaultToolkit().systemClipboard.setContents(StringSelection(outputTextSupplier()), null) - } - - add(copyButton) - add(Box.createRigidArea(Dimension(4, 0))) - - clearButton.addActionListener { panel.outputArea.text = "" } - - add(clearButton) - add(Box.createRigidArea(Dimension(4, 0))) - - closeButton.addActionListener { closeCallback() } - - add(closeButton) - add(Box.createRigidArea(Dimension(4, 0))) - } - - } - -} - -abstract class BottomButtonsPanel : JPanel() { - abstract val closeCallback: () -> Unit - - abstract fun create(panel: OutputPanel) -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.dialog.component + +import java.awt.Dimension +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import java.awt.Toolkit +import java.awt.datatransfer.StringSelection +import javax.swing.* + +class OutputPanel( + private val bottomButtonsPanel: BottomButtonsPanel +) : JPanel(GridBagLayout()) { + + val outputArea = JTextArea("") + + constructor(closeCallback: () -> Unit) : this(DefaultBottomButtonsPanel(closeCallback)) + + init { + if(bottomButtonsPanel is DefaultBottomButtonsPanel) { + bottomButtonsPanel.outputTextSupplier = { outputArea.text } + } + + outputArea.isEditable = false + outputArea.highlighter = null + outputArea.lineWrap = true + + val outputScroll = JScrollPane(outputArea) + outputScroll.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS + outputScroll.horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED + + add(outputScroll, GridBagConstraints().apply { + fill = GridBagConstraints.BOTH + weightx = 0.5 + weighty = 1.0 + }) + + bottomButtonsPanel.create(this) + + add(bottomButtonsPanel, GridBagConstraints().apply { + fill = GridBagConstraints.HORIZONTAL + gridy = 1 + + weightx = 1.0 + ipadx = 10 + ipady = 10 + }) + } + + open class DefaultBottomButtonsPanel( + override val closeCallback: () -> Unit + ) : BottomButtonsPanel() { + val copyButton = JButton("Copy") + val clearButton = JButton("Clear") + val closeButton = JButton("Close") + + var outputTextSupplier: () -> String = { "" } + + override fun create(panel: OutputPanel){ + layout = BoxLayout(this, BoxLayout.LINE_AXIS) + + add(Box.createHorizontalGlue()) + copyButton.addActionListener { + Toolkit.getDefaultToolkit().systemClipboard.setContents(StringSelection(outputTextSupplier()), null) + } + + add(copyButton) + add(Box.createRigidArea(Dimension(4, 0))) + + clearButton.addActionListener { panel.outputArea.text = "" } + + add(clearButton) + add(Box.createRigidArea(Dimension(4, 0))) + + closeButton.addActionListener { closeCallback() } + + add(closeButton) + add(Box.createRigidArea(Dimension(4, 0))) + } + + } + +} + +abstract class BottomButtonsPanel : JPanel() { + abstract val closeCallback: () -> Unit + + abstract fun create(panel: OutputPanel) +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateCameraSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateCameraSource.java index e946b79d..c1639654 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateCameraSource.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateCameraSource.java @@ -1,282 +1,310 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog.source; - -import com.github.sarxos.webcam.Webcam; -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields; -import com.github.serivesmejia.eocvsim.input.source.CameraSource; -import com.github.serivesmejia.eocvsim.util.CvUtil; -import com.github.serivesmejia.eocvsim.util.Log; -import org.opencv.core.Mat; -import org.opencv.core.Size; -import org.opencv.videoio.VideoCapture; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import java.awt.*; - -public class CreateCameraSource { - - public JDialog createCameraSource = null; - - public JComboBox camerasComboBox = null; - public SizeFields sizeFieldsInput = null; - public JTextField nameTextField = null; - - public JButton createButton = null; - - public boolean wasCancelled = false; - - private final EOCVSim eocvSim; - - private State state = State.INITIAL; - - JLabel statusLabel = new JLabel(); - - enum State { INITIAL, CLICKED_TEST, TEST_SUCCESSFUL, TEST_FAILED } - - public CreateCameraSource(JFrame parent, EOCVSim eocvSim) { - createCameraSource = new JDialog(parent); - - this.eocvSim = eocvSim; - eocvSim.visualizer.childDialogs.add(createCameraSource); - - initCreateImageSource(); - } - - public void initCreateImageSource() { - java.util.List webcams = Webcam.getWebcams(); - - createCameraSource.setModal(true); - - createCameraSource.setTitle("Create camera source"); - createCameraSource.setSize(350, 250); - - JPanel contentsPanel = new JPanel(new GridLayout(5, 1)); - - // Camera id part - JPanel idPanel = new JPanel(new FlowLayout()); - - JLabel idLabel = new JLabel("Camera: "); - idLabel.setHorizontalAlignment(JLabel.LEFT); - - camerasComboBox = new JComboBox<>(); - for(Webcam webcam : webcams) { - camerasComboBox.addItem(webcam.getName()); - } - - SwingUtilities.invokeLater(() -> camerasComboBox.setSelectedIndex(0)); - - idPanel.add(idLabel); - idPanel.add(camerasComboBox); - - contentsPanel.add(idPanel); - - //Name part - - JPanel namePanel = new JPanel(new FlowLayout()); - - JLabel nameLabel = new JLabel("Source Name: "); - - nameTextField = new JTextField("CameraSource-" + (eocvSim.inputSourceManager.sources.size() + 1), 15); - - namePanel.add(nameLabel); - namePanel.add(nameTextField); - - contentsPanel.add(namePanel); - - // Size part - sizeFieldsInput = new SizeFields(); - sizeFieldsInput.onChange.doPersistent(this::updateCreateBtt); - - contentsPanel.add(sizeFieldsInput); - - contentsPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); - - // Status label part - statusLabel.setHorizontalAlignment(JLabel.CENTER); - - contentsPanel.add(statusLabel); - - // Bottom buttons - JPanel buttonsPanel = new JPanel(new FlowLayout()); - createButton = new JButton(); - - buttonsPanel.add(createButton); - - JButton cancelButton = new JButton("Cancel"); - buttonsPanel.add(cancelButton); - - contentsPanel.add(buttonsPanel); - - //Add contents - contentsPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); - - createCameraSource.getContentPane().add(contentsPanel, BorderLayout.CENTER); - - // Additional stuff & events - createButton.addActionListener(e -> { - if(state == State.TEST_SUCCESSFUL) { - createSource( - nameTextField.getText(), - camerasComboBox.getSelectedIndex(), - sizeFieldsInput.getCurrentSize() - ); - close(); - } else { - state = State.CLICKED_TEST; - updateState(); - - eocvSim.onMainUpdate.doOnce(() -> { - if (testCamera(camerasComboBox.getSelectedIndex())) { - if (wasCancelled) return; - - SwingUtilities.invokeLater(() -> { - state = State.TEST_SUCCESSFUL; - updateState(); - }); - } else { - SwingUtilities.invokeLater(() -> { - state = State.TEST_FAILED; - updateState(); - }); - } - }); - } - }); - - camerasComboBox.addActionListener((e) -> { - String sourceName = (String)camerasComboBox.getSelectedItem(); - if(!eocvSim.inputSourceManager.isNameOnUse(sourceName)) { - nameTextField.setText(sourceName); - } - - state = State.INITIAL; - updateCreateBtt(); - }); - - nameTextField.getDocument().addDocumentListener(new DocumentListener() { - public void changedUpdate(DocumentEvent e) { - changed(); - } - public void removeUpdate(DocumentEvent e) { changed(); } - public void insertUpdate(DocumentEvent e) { - changed(); - } - public void changed() { - updateCreateBtt(); - } - }); - - cancelButton.addActionListener(e -> { - wasCancelled = true; - close(); - }); - - createCameraSource.setResizable(false); - createCameraSource.setLocationRelativeTo(null); - createCameraSource.setVisible(true); - } - - public void close() { - createCameraSource.setVisible(false); - createCameraSource.dispose(); - } - - public boolean testCamera(int camIndex) { - VideoCapture camera = new VideoCapture(); - camera.open(camerasComboBox.getSelectedIndex()); - - boolean wasOpened = camera.isOpened(); - - if(wasOpened) { - Mat m = new Mat(); - try { - camera.read(m); - Size size = CvUtil.scaleToFit(m.size(), EOCVSim.DEFAULT_EOCV_SIZE); - - SwingUtilities.invokeLater(() -> { - sizeFieldsInput.getWidthTextField().setText(String.format("%.0f", size.width)); - sizeFieldsInput.getHeightTextField().setText(String.format("%.0f", size.height)); - }); - } catch (Exception e) { - Log.warn("CreateCameraSource", "Threw exception when trying to open camera", e); - wasOpened = false; - } - - m.release(); - } - - camera.release(); - - return wasOpened; - } - - private void updateState() { - switch(state) { - case INITIAL: - statusLabel.setText("Click \"test\" to test camera."); - createButton.setText("Test"); - break; - - case CLICKED_TEST: - statusLabel.setText("Trying to open camera, please wait..."); - camerasComboBox.setEnabled(false); - createButton.setEnabled(false); - break; - - case TEST_SUCCESSFUL: - camerasComboBox.setEnabled(true); - createButton.setEnabled(true); - statusLabel.setText("Camera was opened successfully."); - createButton.setText("Create"); - break; - - case TEST_FAILED: - camerasComboBox.setEnabled(true); - createButton.setEnabled(true); - statusLabel.setText("Failed to open camera, try another one."); - createButton.setText("Test"); - break; - } - } - - public void createSource(String sourceName, int index, Size size) { - eocvSim.onMainUpdate.doOnce(() -> eocvSim.inputSourceManager.addInputSource( - sourceName, - new CameraSource(index, size) - )); - } - - public void updateCreateBtt() { - createButton.setEnabled(!nameTextField.getText().trim().equals("") - && sizeFieldsInput.getValid() - && !eocvSim.inputSourceManager.isNameOnUse(nameTextField.getText())); - - updateState(); - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.dialog.source; + +import com.github.sarxos.webcam.Webcam; +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields; +import com.github.serivesmejia.eocvsim.input.source.CameraSource; +import com.github.serivesmejia.eocvsim.util.CvUtil; +import com.github.serivesmejia.eocvsim.util.Log; +import org.opencv.core.Mat; +import org.opencv.core.Size; +import org.opencv.videoio.VideoCapture; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; + +public class CreateCameraSource { + + public static int VISIBLE_CHARACTERS_COMBO_BOX = 22; + + public JDialog createCameraSource = null; + + public JComboBox camerasComboBox = null; + public SizeFields sizeFieldsInput = null; + public JTextField nameTextField = null; + + public JButton createButton = null; + + public boolean wasCancelled = false; + + private final EOCVSim eocvSim; + + private State state = State.INITIAL; + + JLabel statusLabel = new JLabel(); + + enum State { INITIAL, CLICKED_TEST, TEST_SUCCESSFUL, TEST_FAILED, NO_WEBCAMS } + + public CreateCameraSource(JFrame parent, EOCVSim eocvSim) { + createCameraSource = new JDialog(parent); + + this.eocvSim = eocvSim; + eocvSim.visualizer.childDialogs.add(createCameraSource); + + initCreateImageSource(); + } + + public void initCreateImageSource() { + java.util.List webcams = Webcam.getWebcams(); + + createCameraSource.setModal(true); + + createCameraSource.setTitle("Create camera source"); + createCameraSource.setSize(350, 280); + + JPanel contentsPanel = new JPanel(new GridLayout(5, 1)); + + // Camera id part + JPanel idPanel = new JPanel(new FlowLayout()); + + JLabel idLabel = new JLabel("Camera: "); + idLabel.setHorizontalAlignment(JLabel.LEFT); + + camerasComboBox = new JComboBox<>(); + if(webcams.isEmpty()) { + camerasComboBox.addItem("No Cameras Detected"); + state = State.NO_WEBCAMS; + } else { + for(Webcam webcam : webcams) { + // limit the webcam name to certain characters and append dots in the end if needed + String dots = webcam.getName().length() > VISIBLE_CHARACTERS_COMBO_BOX ? "..." : ""; + + camerasComboBox.addItem( + // https://stackoverflow.com/a/27060643 + String.format("%1." + VISIBLE_CHARACTERS_COMBO_BOX + "s", webcam.getName()).trim() + dots + ); + } + + SwingUtilities.invokeLater(() -> camerasComboBox.setSelectedIndex(0)); + } + + idPanel.add(idLabel); + idPanel.add(camerasComboBox); + + contentsPanel.add(idPanel); + + //Name part + + JPanel namePanel = new JPanel(new FlowLayout()); + + JLabel nameLabel = new JLabel("Source Name: "); + + nameTextField = new JTextField("CameraSource-" + (eocvSim.inputSourceManager.sources.size() + 1), 15); + + namePanel.add(nameLabel); + namePanel.add(nameTextField); + + contentsPanel.add(namePanel); + + // Size part + sizeFieldsInput = new SizeFields(); + sizeFieldsInput.onChange.doPersistent(this::updateCreateBtt); + + contentsPanel.add(sizeFieldsInput); + + contentsPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); + + // Status label part + statusLabel.setHorizontalAlignment(JLabel.CENTER); + + contentsPanel.add(statusLabel); + + // Bottom buttons + JPanel buttonsPanel = new JPanel(new FlowLayout()); + createButton = new JButton(); + + buttonsPanel.add(createButton); + + JButton cancelButton = new JButton("Cancel"); + buttonsPanel.add(cancelButton); + + contentsPanel.add(buttonsPanel); + + //Add contents + contentsPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); + + createCameraSource.getContentPane().add(contentsPanel, BorderLayout.CENTER); + + // Additional stuff & events + createButton.addActionListener(e -> { + if(state == State.TEST_SUCCESSFUL) { + createSource( + nameTextField.getText(), + camerasComboBox.getSelectedIndex(), + sizeFieldsInput.getCurrentSize() + ); + close(); + } else { + state = State.CLICKED_TEST; + updateState(); + + eocvSim.onMainUpdate.doOnce(() -> { + if (testCamera(camerasComboBox.getSelectedIndex())) { + if (wasCancelled) return; + + SwingUtilities.invokeLater(() -> { + state = State.TEST_SUCCESSFUL; + updateState(); + }); + } else { + SwingUtilities.invokeLater(() -> { + state = State.TEST_FAILED; + updateState(); + }); + } + }); + } + }); + + camerasComboBox.addActionListener((e) -> { + String sourceName = webcams.get(camerasComboBox.getSelectedIndex()).getName(); + if(!eocvSim.inputSourceManager.isNameOnUse(sourceName)) { + nameTextField.setText(sourceName); + } + + state = State.INITIAL; + updateCreateBtt(); + }); + + nameTextField.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { + changed(); + } + public void removeUpdate(DocumentEvent e) { changed(); } + public void insertUpdate(DocumentEvent e) { + changed(); + } + public void changed() { + updateCreateBtt(); + } + }); + + cancelButton.addActionListener(e -> { + wasCancelled = true; + close(); + }); + + updateState(); + + createCameraSource.setResizable(false); + createCameraSource.setLocationRelativeTo(null); + createCameraSource.setVisible(true); + } + + public void close() { + createCameraSource.setVisible(false); + createCameraSource.dispose(); + } + + public boolean testCamera(int camIndex) { + VideoCapture camera = new VideoCapture(); + camera.open(camerasComboBox.getSelectedIndex()); + + boolean wasOpened = camera.isOpened(); + + if(wasOpened) { + Mat m = new Mat(); + try { + camera.read(m); + Size size = CvUtil.scaleToFit(m.size(), EOCVSim.DEFAULT_EOCV_SIZE); + + SwingUtilities.invokeLater(() -> { + sizeFieldsInput.getWidthTextField().setText(String.format("%.0f", size.width)); + sizeFieldsInput.getHeightTextField().setText(String.format("%.0f", size.height)); + }); + } catch (Exception e) { + Log.warn("CreateCameraSource", "Threw exception when trying to open camera", e); + wasOpened = false; + } + + m.release(); + } + + camera.release(); + + return wasOpened; + } + + private void updateState() { + switch(state) { + case INITIAL: + statusLabel.setText("Click \"test\" to test camera."); + createButton.setText("Test"); + break; + + case CLICKED_TEST: + statusLabel.setText("Trying to open camera, please wait..."); + camerasComboBox.setEnabled(false); + createButton.setEnabled(false); + break; + + case TEST_SUCCESSFUL: + camerasComboBox.setEnabled(true); + createButton.setEnabled(true); + statusLabel.setText("Camera was opened successfully."); + createButton.setText("Create"); + break; + + case TEST_FAILED: + camerasComboBox.setEnabled(true); + createButton.setEnabled(true); + statusLabel.setText("Failed to open camera, try another one."); + createButton.setText("Test"); + break; + + case NO_WEBCAMS: + statusLabel.setText("No cameras detected."); + createButton.setText("Test"); + nameTextField.setText(""); + + createButton.setEnabled(false); + nameTextField.setEnabled(false); + camerasComboBox.setEnabled(false); + sizeFieldsInput.getWidthTextField().setEnabled(false); + sizeFieldsInput.getHeightTextField().setEnabled(false); + break; + } + } + + public void createSource(String sourceName, int index, Size size) { + eocvSim.onMainUpdate.doOnce(() -> eocvSim.inputSourceManager.addInputSource( + sourceName, + new CameraSource(index, size), + true + )); + } + + public void updateCreateBtt() { + createButton.setEnabled(!nameTextField.getText().trim().equals("") + && sizeFieldsInput.getValid() + && !eocvSim.inputSourceManager.isNameOnUse(nameTextField.getText())); + + updateState(); + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateImageSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateImageSource.java index 3c5cf2a9..0d157698 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateImageSource.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateImageSource.java @@ -1,208 +1,211 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog.source; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.input.FileSelector; -import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields; -import com.github.serivesmejia.eocvsim.input.source.ImageSource; -import com.github.serivesmejia.eocvsim.util.CvUtil; -import com.github.serivesmejia.eocvsim.util.FileFilters; -import com.github.serivesmejia.eocvsim.util.StrUtil; -import org.opencv.core.Size; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import java.awt.*; -import java.io.File; - -public class CreateImageSource { - - public JDialog createImageSource; - - public JTextField nameTextField = null; - - public SizeFields sizeFieldsInput = null; - - public FileSelector imageFileSelector = null; - - private File initialFile = null; - - public JButton createButton = null; - public boolean selectedValidImage = false; - private EOCVSim eocvSim = null; - - public CreateImageSource(JFrame parent, EOCVSim eocvSim, File initialFile) { - createImageSource = new JDialog(parent); - - this.eocvSim = eocvSim; - this.initialFile = initialFile; - - eocvSim.visualizer.childDialogs.add(createImageSource); - - initCreateImageSource(); - } - - public void initCreateImageSource() { - - createImageSource.setModal(true); - - createImageSource.setTitle("Create image source"); - createImageSource.setSize(370, 200); - - JPanel contentsPanel = new JPanel(new GridLayout(4, 1)); - - //file select part - - imageFileSelector = new FileSelector(18, FileFilters.imagesFilter); - - imageFileSelector.onFileSelect.doPersistent(() -> - imageFileSelected(imageFileSelector.getLastSelectedFile()) - ); - - - if(initialFile != null) - SwingUtilities.invokeLater(() -> - imageFileSelector.setLastSelectedFile(initialFile) - ); - - contentsPanel.add(imageFileSelector); - - // Size part - - sizeFieldsInput = new SizeFields(); - sizeFieldsInput.onChange.doPersistent(this::updateCreateBtt); - - contentsPanel.add(sizeFieldsInput); - contentsPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); - - //Name part - - JPanel namePanel = new JPanel(new FlowLayout()); - - JLabel nameLabel = new JLabel("Source name: "); - nameLabel.setHorizontalAlignment(JLabel.LEFT); - - nameTextField = new JTextField("ImageSource-" + (eocvSim.inputSourceManager.sources.size() + 1), 15); - - namePanel.add(nameLabel); - namePanel.add(nameTextField); - - contentsPanel.add(namePanel); - - // Bottom buttons - - JPanel buttonsPanel = new JPanel(new FlowLayout()); - createButton = new JButton("Create"); - createButton.setEnabled(selectedValidImage); - - buttonsPanel.add(createButton); - - JButton cancelButton = new JButton("Cancel"); - buttonsPanel.add(cancelButton); - - contentsPanel.add(buttonsPanel); - - //Add contents - - createImageSource.getContentPane().add(contentsPanel, BorderLayout.CENTER); - - // Additional stuff & events - - nameTextField.getDocument().addDocumentListener(new DocumentListener() { - public void changedUpdate(DocumentEvent e) { - changed(); - } - - public void removeUpdate(DocumentEvent e) { - changed(); - } - - public void insertUpdate(DocumentEvent e) { - changed(); - } - - public void changed() { - updateCreateBtt(); - } - }); - - createButton.addActionListener(e -> { - createSource(nameTextField.getText(), imageFileSelector.getLastSelectedFile().getAbsolutePath(), sizeFieldsInput.getCurrentSize()); - close(); - }); - - cancelButton.addActionListener(e -> close()); - - createImageSource.setResizable(false); - createImageSource.setLocationRelativeTo(null); - createImageSource.setVisible(true); - - } - - public void imageFileSelected(File f) { - String fileAbsPath = f.getAbsolutePath(); - - if (CvUtil.checkImageValid(fileAbsPath)) { - - String fileName = StrUtil.getFileBaseName(f.getName()); - if(!fileName.trim().equals("") && !eocvSim.inputSourceManager.isNameOnUse(fileName)) { - nameTextField.setText(fileName); - } - - Size size = CvUtil.scaleToFit(CvUtil.getImageSize(fileAbsPath), EOCVSim.DEFAULT_EOCV_SIZE); - - sizeFieldsInput.getWidthTextField().setText(String.valueOf(Math.round(size.width))); - sizeFieldsInput.getHeightTextField().setText(String.valueOf(Math.round(size.height))); - - selectedValidImage = true; - } else { - imageFileSelector.getDirTextField().setText("Unable to load selected file."); - selectedValidImage = false; - } - - updateCreateBtt(); - } - - public void close() { - createImageSource.setVisible(false); - createImageSource.dispose(); - } - - public void createSource(String sourceName, String imgPath, Size size) { - eocvSim.onMainUpdate.doOnce(() -> eocvSim.inputSourceManager.addInputSource( - sourceName, - new ImageSource(imgPath, size) - )); - } - - public void updateCreateBtt() { - createButton.setEnabled(!nameTextField.getText().trim().equals("") - && sizeFieldsInput.getValid() - && selectedValidImage - && !eocvSim.inputSourceManager.isNameOnUse(nameTextField.getText())); - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.dialog.source; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.component.input.FileSelector; +import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields; +import com.github.serivesmejia.eocvsim.input.source.ImageSource; +import com.github.serivesmejia.eocvsim.util.CvUtil; +import com.github.serivesmejia.eocvsim.util.FileFilters; +import com.github.serivesmejia.eocvsim.util.StrUtil; +import org.opencv.core.Size; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.io.File; + +public class CreateImageSource { + + public JDialog createImageSource; + + public JTextField nameTextField = null; + + public SizeFields sizeFieldsInput = null; + + public FileSelector imageFileSelector = null; + + private File initialFile = null; + + public JButton createButton = null; + public boolean selectedValidImage = false; + private EOCVSim eocvSim = null; + + public CreateImageSource(JFrame parent, EOCVSim eocvSim, File initialFile) { + createImageSource = new JDialog(parent); + + this.eocvSim = eocvSim; + this.initialFile = initialFile; + + eocvSim.visualizer.childDialogs.add(createImageSource); + + initCreateImageSource(); + } + + public void initCreateImageSource() { + + createImageSource.setModal(true); + + createImageSource.setTitle("Create image source"); + createImageSource.setSize(370, 200); + + JPanel contentsPanel = new JPanel(new GridLayout(4, 1)); + + //file select part + + imageFileSelector = new FileSelector(18, FileFilters.imagesFilter); + + imageFileSelector.onFileSelect.doPersistent(() -> + imageFileSelected(imageFileSelector.getLastSelectedFile()) + ); + + + if(initialFile != null) + SwingUtilities.invokeLater(() -> + imageFileSelector.setLastSelectedFile(initialFile) + ); + + contentsPanel.add(imageFileSelector); + + // Size part + + sizeFieldsInput = new SizeFields(); + sizeFieldsInput.onChange.doPersistent(this::updateCreateBtt); + + contentsPanel.add(sizeFieldsInput); + contentsPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); + + //Name part + + JPanel namePanel = new JPanel(new FlowLayout()); + + JLabel nameLabel = new JLabel("Source name: "); + nameLabel.setHorizontalAlignment(JLabel.LEFT); + + nameTextField = new JTextField("ImageSource-" + (eocvSim.inputSourceManager.sources.size() + 1), 15); + + namePanel.add(nameLabel); + namePanel.add(nameTextField); + + contentsPanel.add(namePanel); + + // Bottom buttons + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + createButton = new JButton("Create"); + createButton.setEnabled(selectedValidImage); + + buttonsPanel.add(createButton); + + JButton cancelButton = new JButton("Cancel"); + buttonsPanel.add(cancelButton); + + contentsPanel.add(buttonsPanel); + + //Add contents + + createImageSource.getContentPane().add(contentsPanel, BorderLayout.CENTER); + + // Additional stuff & events + + nameTextField.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { + changed(); + } + + public void removeUpdate(DocumentEvent e) { + changed(); + } + + public void insertUpdate(DocumentEvent e) { + changed(); + } + + public void changed() { + updateCreateBtt(); + } + }); + + createButton.addActionListener(e -> { + createSource(nameTextField.getText(), imageFileSelector.getLastSelectedFile().getAbsolutePath(), sizeFieldsInput.getCurrentSize()); + close(); + }); + + cancelButton.addActionListener(e -> close()); + + createImageSource.setResizable(false); + createImageSource.setLocationRelativeTo(null); + createImageSource.setVisible(true); + + } + + public void imageFileSelected(File f) { + String fileAbsPath = f.getAbsolutePath(); + + if (CvUtil.checkImageValid(fileAbsPath)) { + + String fileName = StrUtil.getFileBaseName(f.getName()); + if(!fileName.trim().equals("") && !eocvSim.inputSourceManager.isNameOnUse(fileName)) { + nameTextField.setText(fileName); + } + + Size size = CvUtil.scaleToFit(CvUtil.getImageSize(fileAbsPath), EOCVSim.DEFAULT_EOCV_SIZE); + + sizeFieldsInput.getWidthTextField().setText(String.valueOf(Math.round(size.width))); + sizeFieldsInput.getHeightTextField().setText(String.valueOf(Math.round(size.height))); + + selectedValidImage = true; + } else { + imageFileSelector.getDirTextField().setText("Unable to load selected file."); + selectedValidImage = false; + } + + updateCreateBtt(); + } + + public void close() { + createImageSource.setVisible(false); + createImageSource.dispose(); + } + + public void createSource(String sourceName, String imgPath, Size size) { + eocvSim.onMainUpdate.doOnce(() -> + eocvSim.inputSourceManager.addInputSource( + sourceName, + new ImageSource(imgPath, size), + false + ) + ); + } + + public void updateCreateBtt() { + createButton.setEnabled(!nameTextField.getText().trim().equals("") + && sizeFieldsInput.getValid() + && selectedValidImage + && !eocvSim.inputSourceManager.isNameOnUse(nameTextField.getText())); + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSource.java index 7cedd0ab..abc88ad5 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSource.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateSource.java @@ -1,112 +1,112 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog.source; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.DialogFactory; -import com.github.serivesmejia.eocvsim.input.SourceType; - -import javax.swing.*; -import java.awt.*; - -public class CreateSource { - - public static volatile boolean alreadyOpened = false; - public volatile JDialog chooseSource = null; - - EOCVSim eocvSim = null; - - private volatile JFrame parent = null; - - public CreateSource(JFrame parent, EOCVSim eocvSim) { - - chooseSource = new JDialog(parent); - - this.parent = parent; - this.eocvSim = eocvSim; - - eocvSim.visualizer.childDialogs.add(chooseSource); - initChooseSource(); - - } - - private void initChooseSource() { - - alreadyOpened = true; - - chooseSource.setModal(true); - - chooseSource.setTitle("Select source type"); - chooseSource.setSize(300, 150); - - JPanel contentsPane = new JPanel(new GridLayout(2, 1)); - - JPanel dropDownPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - - SourceType[] sourceTypes = SourceType.values(); - String[] sourceTypesStr = new String[sourceTypes.length - 1]; - - for (int i = 0; i < sourceTypes.length - 1; i++) { - sourceTypesStr[i] = sourceTypes[i].coolName; - } - - JComboBox dropDown = new JComboBox<>(sourceTypesStr); - dropDownPanel.add(dropDown); - contentsPane.add(dropDownPanel); - - JPanel buttonsPanel = new JPanel(new FlowLayout()); - JButton nextButton = new JButton("Next"); - - buttonsPanel.add(nextButton); - - JButton cancelButton = new JButton("Cancel"); - buttonsPanel.add(cancelButton); - - contentsPane.add(buttonsPanel); - - contentsPane.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); - - chooseSource.getContentPane().add(contentsPane, BorderLayout.CENTER); - - cancelButton.addActionListener(e -> close()); - - nextButton.addActionListener(e -> { - close(); - SourceType sourceType = SourceType.fromCoolName(dropDown.getSelectedItem().toString()); - DialogFactory.createSourceDialog(eocvSim, sourceType); - }); - - chooseSource.setResizable(false); - chooseSource.setLocationRelativeTo(null); - chooseSource.setVisible(true); - - } - - public void close() { - alreadyOpened = false; - chooseSource.setVisible(false); - chooseSource.dispose(); - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.dialog.source; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.DialogFactory; +import com.github.serivesmejia.eocvsim.input.SourceType; + +import javax.swing.*; +import java.awt.*; + +public class CreateSource { + + public static volatile boolean alreadyOpened = false; + public volatile JDialog chooseSource = null; + + EOCVSim eocvSim = null; + + private volatile JFrame parent = null; + + public CreateSource(JFrame parent, EOCVSim eocvSim) { + + chooseSource = new JDialog(parent); + + this.parent = parent; + this.eocvSim = eocvSim; + + eocvSim.visualizer.childDialogs.add(chooseSource); + initChooseSource(); + + } + + private void initChooseSource() { + + alreadyOpened = true; + + chooseSource.setModal(true); + + chooseSource.setTitle("Select source type"); + chooseSource.setSize(300, 150); + + JPanel contentsPane = new JPanel(new GridLayout(2, 1)); + + JPanel dropDownPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + + SourceType[] sourceTypes = SourceType.values(); + String[] sourceTypesStr = new String[sourceTypes.length - 1]; + + for (int i = 0; i < sourceTypes.length - 1; i++) { + sourceTypesStr[i] = sourceTypes[i].coolName; + } + + JComboBox dropDown = new JComboBox<>(sourceTypesStr); + dropDownPanel.add(dropDown); + contentsPane.add(dropDownPanel); + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + JButton nextButton = new JButton("Next"); + + buttonsPanel.add(nextButton); + + JButton cancelButton = new JButton("Cancel"); + buttonsPanel.add(cancelButton); + + contentsPane.add(buttonsPanel); + + contentsPane.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); + + chooseSource.getContentPane().add(contentsPane, BorderLayout.CENTER); + + cancelButton.addActionListener(e -> close()); + + nextButton.addActionListener(e -> { + close(); + SourceType sourceType = SourceType.fromCoolName(dropDown.getSelectedItem().toString()); + DialogFactory.createSourceDialog(eocvSim, sourceType); + }); + + chooseSource.setResizable(false); + chooseSource.setLocationRelativeTo(null); + chooseSource.setVisible(true); + + } + + public void close() { + alreadyOpened = false; + chooseSource.setVisible(false); + chooseSource.dispose(); + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateVideoSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateVideoSource.java index ec94b739..6290705f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateVideoSource.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/source/CreateVideoSource.java @@ -1,222 +1,221 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.dialog.source; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.DialogFactory; -import com.github.serivesmejia.eocvsim.gui.component.input.FileSelector; -import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields; -import com.github.serivesmejia.eocvsim.gui.util.GuiUtil; -import com.github.serivesmejia.eocvsim.input.source.VideoSource; -import com.github.serivesmejia.eocvsim.util.CvUtil; -import com.github.serivesmejia.eocvsim.util.FileFilters; -import com.github.serivesmejia.eocvsim.util.Log; -import com.github.serivesmejia.eocvsim.util.StrUtil; -import org.opencv.core.Mat; -import org.opencv.core.Size; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.filechooser.FileNameExtensionFilter; -import java.awt.*; -import java.io.File; - -public class CreateVideoSource { - - public JDialog createVideoSource = null; - - public JTextField nameTextField = null; - - public FileSelector fileSelectorVideo = null; - - public SizeFields sizeFieldsInput = null; - - public JButton createButton = null; - public boolean selectedValidVideo = false; - - public File initialFile = null; - - private EOCVSim eocvSim = null; - - public CreateVideoSource(JFrame parent, EOCVSim eocvSim, File initialFile) { - - createVideoSource = new JDialog(parent); - - this.eocvSim = eocvSim; - this.initialFile = initialFile; - - eocvSim.visualizer.childDialogs.add(createVideoSource); - - initCreateImageSource(); - - } - - public void initCreateImageSource() { - - createVideoSource.setModal(true); - - createVideoSource.setTitle("Create video source"); - createVideoSource.setSize(370, 200); - - JPanel contentsPanel = new JPanel(new GridLayout(4, 1)); - - //file select - fileSelectorVideo = new FileSelector(18, FileFilters.videoMediaFilter); - - fileSelectorVideo.onFileSelect.doPersistent(() -> - videoFileSelected(fileSelectorVideo.getLastSelectedFile()) - ); - - if(initialFile != null) - SwingUtilities.invokeLater(() -> - fileSelectorVideo.setLastSelectedFile(initialFile) - ); - - contentsPanel.add(fileSelectorVideo); - - // Size part - - sizeFieldsInput = new SizeFields(); - sizeFieldsInput.onChange.doPersistent(this::updateCreateBtt); - - contentsPanel.add(sizeFieldsInput); - - contentsPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); - - //Name part - - JPanel namePanel = new JPanel(new FlowLayout()); - - JLabel nameLabel = new JLabel("Source name: "); - nameLabel.setHorizontalAlignment(JLabel.LEFT); - - nameTextField = new JTextField("VideoSource-" + (eocvSim.inputSourceManager.sources.size() + 1), 15); - - namePanel.add(nameLabel); - namePanel.add(nameTextField); - - contentsPanel.add(namePanel); - - // Bottom buttons - JPanel buttonsPanel = new JPanel(new FlowLayout()); - createButton = new JButton("Create"); - createButton.setEnabled(selectedValidVideo); - - buttonsPanel.add(createButton); - - JButton cancelButton = new JButton("Cancel"); - buttonsPanel.add(cancelButton); - - contentsPanel.add(buttonsPanel); - - //Add contents - createVideoSource.getContentPane().add(contentsPanel, BorderLayout.CENTER); - - nameTextField.getDocument().addDocumentListener(new DocumentListener() { - public void changedUpdate(DocumentEvent e) { - changed(); - } - - public void removeUpdate(DocumentEvent e) { - changed(); - } - - public void insertUpdate(DocumentEvent e) { - changed(); - } - - public void changed() { - updateCreateBtt(); - } - }); - - createButton.addActionListener(e -> { - createSource(nameTextField.getText(), fileSelectorVideo.getLastSelectedFile().getAbsolutePath(), sizeFieldsInput.getCurrentSize()); - close(); - }); - - // Status label part - cancelButton.addActionListener(e -> close()); - - createVideoSource.setResizable(false); - createVideoSource.setLocationRelativeTo(null); - createVideoSource.setVisible(true); - - } - - public void videoFileSelected(File f) { - - String fileAbsPath = f.getAbsolutePath(); - - Mat videoMat = CvUtil.readOnceFromVideo(fileAbsPath); - - if (videoMat != null && !videoMat.empty()) { - - String fileName = StrUtil.getFileBaseName(f.getName()); - if(!fileName.trim().equals("") && !eocvSim.inputSourceManager.isNameOnUse(fileName)) { - nameTextField.setText(fileName); - } - - Size newSize = CvUtil.scaleToFit(videoMat.size(), EOCVSim.DEFAULT_EOCV_SIZE); - - this.sizeFieldsInput.getWidthTextField().setText(String.valueOf(Math.round(newSize.width))); - this.sizeFieldsInput.getHeightTextField().setText(String.valueOf(Math.round(newSize.height))); - - selectedValidVideo = true; - } else { - fileSelectorVideo.getDirTextField().setText("Unable to load selected file."); - selectedValidVideo = false; - } - - if(videoMat != null) videoMat.release(); - - updateCreateBtt(); - - } - - public void close() { - createVideoSource.setVisible(false); - createVideoSource.dispose(); - } - - public void createSource(String sourceName, String videoPath, Size size) { - eocvSim.onMainUpdate.doOnce(() -> { - eocvSim.inputSourceManager.addInputSource( - sourceName, - new VideoSource(videoPath, size) - ); - - eocvSim.inputSourceManager.requestSetInputSource(sourceName); - }); - } - - public void updateCreateBtt() { - createButton.setEnabled(!nameTextField.getText().trim().equals("") - && sizeFieldsInput.getValid() - && selectedValidVideo - && !eocvSim.inputSourceManager.isNameOnUse(nameTextField.getText())); - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.dialog.source; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.DialogFactory; +import com.github.serivesmejia.eocvsim.gui.component.input.FileSelector; +import com.github.serivesmejia.eocvsim.gui.component.input.SizeFields; +import com.github.serivesmejia.eocvsim.gui.util.GuiUtil; +import com.github.serivesmejia.eocvsim.input.source.VideoSource; +import com.github.serivesmejia.eocvsim.util.CvUtil; +import com.github.serivesmejia.eocvsim.util.FileFilters; +import com.github.serivesmejia.eocvsim.util.Log; +import com.github.serivesmejia.eocvsim.util.StrUtil; +import org.opencv.core.Mat; +import org.opencv.core.Size; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.filechooser.FileNameExtensionFilter; +import java.awt.*; +import java.io.File; + +public class CreateVideoSource { + + public JDialog createVideoSource = null; + + public JTextField nameTextField = null; + + public FileSelector fileSelectorVideo = null; + + public SizeFields sizeFieldsInput = null; + + public JButton createButton = null; + public boolean selectedValidVideo = false; + + public File initialFile = null; + + private EOCVSim eocvSim = null; + + public CreateVideoSource(JFrame parent, EOCVSim eocvSim, File initialFile) { + + createVideoSource = new JDialog(parent); + + this.eocvSim = eocvSim; + this.initialFile = initialFile; + + eocvSim.visualizer.childDialogs.add(createVideoSource); + + initCreateImageSource(); + + } + + public void initCreateImageSource() { + + createVideoSource.setModal(true); + + createVideoSource.setTitle("Create video source"); + createVideoSource.setSize(370, 200); + + JPanel contentsPanel = new JPanel(new GridLayout(4, 1)); + + //file select + fileSelectorVideo = new FileSelector(18, FileFilters.videoMediaFilter); + + fileSelectorVideo.onFileSelect.doPersistent(() -> + videoFileSelected(fileSelectorVideo.getLastSelectedFile()) + ); + + if(initialFile != null) + SwingUtilities.invokeLater(() -> + fileSelectorVideo.setLastSelectedFile(initialFile) + ); + + contentsPanel.add(fileSelectorVideo); + + // Size part + + sizeFieldsInput = new SizeFields(); + sizeFieldsInput.onChange.doPersistent(this::updateCreateBtt); + + contentsPanel.add(sizeFieldsInput); + + contentsPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0)); + + //Name part + + JPanel namePanel = new JPanel(new FlowLayout()); + + JLabel nameLabel = new JLabel("Source name: "); + nameLabel.setHorizontalAlignment(JLabel.LEFT); + + nameTextField = new JTextField("VideoSource-" + (eocvSim.inputSourceManager.sources.size() + 1), 15); + + namePanel.add(nameLabel); + namePanel.add(nameTextField); + + contentsPanel.add(namePanel); + + // Bottom buttons + JPanel buttonsPanel = new JPanel(new FlowLayout()); + createButton = new JButton("Create"); + createButton.setEnabled(selectedValidVideo); + + buttonsPanel.add(createButton); + + JButton cancelButton = new JButton("Cancel"); + buttonsPanel.add(cancelButton); + + contentsPanel.add(buttonsPanel); + + //Add contents + createVideoSource.getContentPane().add(contentsPanel, BorderLayout.CENTER); + + nameTextField.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { + changed(); + } + + public void removeUpdate(DocumentEvent e) { + changed(); + } + + public void insertUpdate(DocumentEvent e) { + changed(); + } + + public void changed() { + updateCreateBtt(); + } + }); + + createButton.addActionListener(e -> { + createSource(nameTextField.getText(), fileSelectorVideo.getLastSelectedFile().getAbsolutePath(), sizeFieldsInput.getCurrentSize()); + close(); + }); + + // Status label part + cancelButton.addActionListener(e -> close()); + + createVideoSource.setResizable(false); + createVideoSource.setLocationRelativeTo(null); + createVideoSource.setVisible(true); + + } + + public void videoFileSelected(File f) { + + String fileAbsPath = f.getAbsolutePath(); + + Mat videoMat = CvUtil.readOnceFromVideo(fileAbsPath); + + if (videoMat != null && !videoMat.empty()) { + + String fileName = StrUtil.getFileBaseName(f.getName()); + if(!fileName.trim().equals("") && !eocvSim.inputSourceManager.isNameOnUse(fileName)) { + nameTextField.setText(fileName); + } + + Size newSize = CvUtil.scaleToFit(videoMat.size(), EOCVSim.DEFAULT_EOCV_SIZE); + + this.sizeFieldsInput.getWidthTextField().setText(String.valueOf(Math.round(newSize.width))); + this.sizeFieldsInput.getHeightTextField().setText(String.valueOf(Math.round(newSize.height))); + + selectedValidVideo = true; + } else { + fileSelectorVideo.getDirTextField().setText("Unable to load selected file."); + selectedValidVideo = false; + } + + if(videoMat != null) videoMat.release(); + + updateCreateBtt(); + + } + + public void close() { + createVideoSource.setVisible(false); + createVideoSource.dispose(); + } + + public void createSource(String sourceName, String videoPath, Size size) { + eocvSim.onMainUpdate.doOnce(() -> { + eocvSim.inputSourceManager.addInputSource( + sourceName, + new VideoSource(videoPath, size), + true + ); + }); + } + + public void updateCreateBtt() { + createButton.setEnabled(!nameTextField.getText().trim().equals("") + && sizeFieldsInput.getValid() + && selectedValidVideo + && !eocvSim.inputSourceManager.isNameOnUse(nameTextField.getText())); + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/Theme.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/Theme.java index 390aa801..98fb99c0 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/Theme.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/Theme.java @@ -1,57 +1,57 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.theme; - -import com.formdev.flatlaf.*; -import com.formdev.flatlaf.intellijthemes.*; - -import javax.swing.*; - -public enum Theme { - - Default(() -> { - UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); - }), - Light(FlatLightLaf::setup), - Dark(FlatDarkLaf::setup), - Darcula(FlatDarculaLaf::setup), - Light_Intellij(FlatIntelliJLaf::setup), - Light_Flat_Intellij(FlatLightFlatIJTheme::setup), - Cyan_Light_Intellij(FlatCyanLightIJTheme::setup), - High_Contrast_Intellij(FlatHighContrastIJTheme::setup), - Dracula_Intellij(FlatDraculaIJTheme::setup), - Dark_Flat_Intellij(FlatDarkFlatIJTheme::setup), - Spacegray_Intellij(FlatSpacegrayIJTheme::setup), - Material_Dark_Intellij(FlatMaterialDesignDarkIJTheme::setup); - - ThemeInstaller installRunn; - - Theme(ThemeInstaller installRunn) { - this.installRunn = installRunn; - } - - public void install() throws ClassNotFoundException, UnsupportedLookAndFeelException, InstantiationException, IllegalAccessException { - installRunn.install(); - } +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.theme; + +import com.formdev.flatlaf.*; +import com.formdev.flatlaf.intellijthemes.*; + +import javax.swing.*; + +public enum Theme { + + Default(() -> { + UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + }), + Light(FlatLightLaf::setup), + Dark(FlatDarkLaf::setup), + Darcula(FlatDarculaLaf::setup), + Light_Intellij(FlatIntelliJLaf::setup), + Light_Flat_Intellij(FlatLightFlatIJTheme::setup), + Cyan_Light_Intellij(FlatCyanLightIJTheme::setup), + High_Contrast_Intellij(FlatHighContrastIJTheme::setup), + Dracula_Intellij(FlatDraculaIJTheme::setup), + Dark_Flat_Intellij(FlatDarkFlatIJTheme::setup), + Spacegray_Intellij(FlatSpacegrayIJTheme::setup), + Material_Dark_Intellij(FlatMaterialDesignDarkIJTheme::setup); + + ThemeInstaller installRunn; + + Theme(ThemeInstaller installRunn) { + this.installRunn = installRunn; + } + + public void install() throws ClassNotFoundException, UnsupportedLookAndFeelException, InstantiationException, IllegalAccessException { + installRunn.install(); + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/ThemeInstaller.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/ThemeInstaller.java index 9635ec5e..43d079f1 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/ThemeInstaller.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/theme/ThemeInstaller.java @@ -1,7 +1,7 @@ -package com.github.serivesmejia.eocvsim.gui.theme; - -import javax.swing.*; - -public interface ThemeInstaller { - void install() throws ClassNotFoundException, UnsupportedLookAndFeelException, InstantiationException, IllegalAccessException; -} +package com.github.serivesmejia.eocvsim.gui.theme; + +import javax.swing.*; + +public interface ThemeInstaller { + void install() throws ClassNotFoundException, UnsupportedLookAndFeelException, InstantiationException, IllegalAccessException; +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/GuiUtil.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/GuiUtil.java index c419bea7..a2beb6f5 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/GuiUtil.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/GuiUtil.java @@ -1,245 +1,245 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.util; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.DialogFactory; -import com.github.serivesmejia.eocvsim.gui.dialog.FileAlreadyExists; -import com.github.serivesmejia.eocvsim.util.CvUtil; -import com.github.serivesmejia.eocvsim.util.Log; -import com.github.serivesmejia.eocvsim.util.SysUtil; -import org.opencv.core.Mat; - -import javax.imageio.ImageIO; -import javax.swing.*; -import javax.swing.filechooser.FileNameExtensionFilter; -import javax.swing.text.AbstractDocument; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.DocumentFilter; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class GuiUtil { - - public static void jTextFieldOnlyNumbers(JTextField field, int minNumber, int onMinNumberChangeTo) { - - ((AbstractDocument) field.getDocument()).setDocumentFilter(new DocumentFilter() { - final Pattern regEx = Pattern.compile("\\d*"); - - @Override - public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { - Matcher matcher = regEx.matcher(text); - if (!matcher.matches()) { - return; - } - - if (field.getText().length() == 0) { - try { - int number = Integer.parseInt(text); - if (number <= minNumber) { - text = String.valueOf(onMinNumberChangeTo); - } - } catch (NumberFormatException ex) { - } - } - - super.replace(fb, offset, length, text, attrs); - } - }); - - } - - public static ImageIcon scaleImage(ImageIcon icon, int w, int h) { - - int nw = icon.getIconWidth(); - int nh = icon.getIconHeight(); - - if (icon.getIconWidth() > w) { - nw = w; - nh = (nw * icon.getIconHeight()) / icon.getIconWidth(); - } - - if (nh > h) { - nh = h; - nw = (icon.getIconWidth() * nh) / icon.getIconHeight(); - } - - return new ImageIcon(icon.getImage().getScaledInstance(nw, nh, Image.SCALE_SMOOTH)); - - } - - public static ImageIcon loadImageIcon(String path) throws IOException { - return new ImageIcon(loadBufferedImage(path)); - } - - public static BufferedImage loadBufferedImage(String path) throws IOException { - return ImageIO.read(GuiUtil.class.getResourceAsStream(path)); - } - - public static void saveBufferedImage(File file, BufferedImage bufferedImage, String format) throws IOException { - ImageIO.write(bufferedImage, format, file); - } - - public static void saveBufferedImage(File file, BufferedImage bufferedImage) throws IOException { - saveBufferedImage(file, bufferedImage, "jpg"); - } - - public static void catchSaveBufferedImage(File file, BufferedImage bufferedImage, String format) { - try { - saveBufferedImage(file, bufferedImage, format); - } catch (IOException e) { - Log.error("GuiUtil", "Failed to save buffered image", e); - } - } - - public static void catchSaveBufferedImage(File file, BufferedImage bufferedImage) { - catchSaveBufferedImage(file, bufferedImage, "jpg"); - } - - public static void invertBufferedImageColors(BufferedImage input) { - - for (int x = 0; x < input.getWidth(); x++) { - for (int y = 0; y < input.getHeight(); y++) { - - int rgba = input.getRGB(x, y); - Color col = new Color(rgba, true); - - if (col.getAlpha() <= 0) continue; - - col = new Color(255 - col.getRed(), - 255 - col.getGreen(), - 255 - col.getBlue()); - - input.setRGB(x, y, col.getRGB()); - - } - } - - } - - public static void saveBufferedImageFileChooser(Component parent, BufferedImage bufferedImage, EOCVSim eocvSim) { - - FileNameExtensionFilter jpegFilter = new FileNameExtensionFilter("JPEG (*.jpg)", "jpg", "jpeg"); - FileNameExtensionFilter pngFilter = new FileNameExtensionFilter("PNG (*.png)", "png"); - - String[] validExts = {"jpg", "jpeg", "png"}; - - DialogFactory.createFileChooser(parent, DialogFactory.FileChooser.Mode.SAVE_FILE_SELECT, jpegFilter, pngFilter) - - .addCloseListener((MODE, selectedFile, selectedFileFilter) -> { - if (MODE == JFileChooser.APPROVE_OPTION) { - - Optional extension = SysUtil.getExtensionByStringHandling(selectedFile.getName()); - - boolean saveImage; - - if (!selectedFile.exists()) { - saveImage = true; - } else { - FileAlreadyExists.UserChoice userChoice = DialogFactory.createFileAlreadyExistsDialog(eocvSim); //create confirm dialog - saveImage = userChoice == FileAlreadyExists.UserChoice.REPLACE; - } - - String ext = ""; - - if (saveImage) { - if (selectedFileFilter instanceof FileNameExtensionFilter) { //if user selected an extension - - //get selected extension - ext = ((FileNameExtensionFilter) selectedFileFilter).getExtensions()[0]; - - selectedFile = new File(selectedFile + "." + ext); //append extension to file - catchSaveBufferedImage(selectedFile, bufferedImage, ext); - - } else if (extension.isPresent() && Arrays.asList(validExts).contains(extension.get())) { //if user gave a extension to file and it's valid (jpg, jpeg or png) - - ext = extension.get(); //get the extension - catchSaveBufferedImage(selectedFile, bufferedImage, ext); - - } else { //default to jpg if the conditions are not met - - selectedFile = new File(selectedFile + ".jpg"); //append default extension to file - catchSaveBufferedImage(selectedFile, bufferedImage); - - } - } - - } - }); - - } - - public static void saveMatFileChooser(Component parent, Mat mat, EOCVSim eocvSim) { - - Mat clonedMat = mat.clone(); - - BufferedImage img = CvUtil.matToBufferedImage(clonedMat); - clonedMat.release(); - - saveBufferedImageFileChooser(parent, img, eocvSim); - - } - - public static ListModel isToListModel(InputStream is, Charset charset) throws UnsupportedEncodingException { - - DefaultListModel listModel = new DefaultListModel<>(); - String isStr = SysUtil.loadIsStr(is, charset); - - String[] lines = isStr.split("\n"); - - for(int i = 0 ; i < lines.length ; i++) { - listModel.add(i, lines[i]); - } - - return listModel; - - } - - public static class NoSelectionModel extends DefaultListSelectionModel { - - @Override - public void setAnchorSelectionIndex(final int anchorIndex) {} - - @Override - public void setLeadAnchorNotificationEnabled(final boolean flag) {} - - @Override - public void setLeadSelectionIndex(final int leadIndex) {} - - @Override - public void setSelectionInterval(final int index0, final int index1) { } - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.util; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.DialogFactory; +import com.github.serivesmejia.eocvsim.gui.dialog.FileAlreadyExists; +import com.github.serivesmejia.eocvsim.util.CvUtil; +import com.github.serivesmejia.eocvsim.util.Log; +import com.github.serivesmejia.eocvsim.util.SysUtil; +import org.opencv.core.Mat; + +import javax.imageio.ImageIO; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.DocumentFilter; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class GuiUtil { + + public static void jTextFieldOnlyNumbers(JTextField field, int minNumber, int onMinNumberChangeTo) { + + ((AbstractDocument) field.getDocument()).setDocumentFilter(new DocumentFilter() { + final Pattern regEx = Pattern.compile("\\d*"); + + @Override + public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { + Matcher matcher = regEx.matcher(text); + if (!matcher.matches()) { + return; + } + + if (field.getText().length() == 0) { + try { + int number = Integer.parseInt(text); + if (number <= minNumber) { + text = String.valueOf(onMinNumberChangeTo); + } + } catch (NumberFormatException ex) { + } + } + + super.replace(fb, offset, length, text, attrs); + } + }); + + } + + public static ImageIcon scaleImage(ImageIcon icon, int w, int h) { + + int nw = icon.getIconWidth(); + int nh = icon.getIconHeight(); + + if (icon.getIconWidth() > w) { + nw = w; + nh = (nw * icon.getIconHeight()) / icon.getIconWidth(); + } + + if (nh > h) { + nh = h; + nw = (icon.getIconWidth() * nh) / icon.getIconHeight(); + } + + return new ImageIcon(icon.getImage().getScaledInstance(nw, nh, Image.SCALE_SMOOTH)); + + } + + public static ImageIcon loadImageIcon(String path) throws IOException { + return new ImageIcon(loadBufferedImage(path)); + } + + public static BufferedImage loadBufferedImage(String path) throws IOException { + return ImageIO.read(GuiUtil.class.getResourceAsStream(path)); + } + + public static void saveBufferedImage(File file, BufferedImage bufferedImage, String format) throws IOException { + ImageIO.write(bufferedImage, format, file); + } + + public static void saveBufferedImage(File file, BufferedImage bufferedImage) throws IOException { + saveBufferedImage(file, bufferedImage, "jpg"); + } + + public static void catchSaveBufferedImage(File file, BufferedImage bufferedImage, String format) { + try { + saveBufferedImage(file, bufferedImage, format); + } catch (IOException e) { + Log.error("GuiUtil", "Failed to save buffered image", e); + } + } + + public static void catchSaveBufferedImage(File file, BufferedImage bufferedImage) { + catchSaveBufferedImage(file, bufferedImage, "jpg"); + } + + public static void invertBufferedImageColors(BufferedImage input) { + + for (int x = 0; x < input.getWidth(); x++) { + for (int y = 0; y < input.getHeight(); y++) { + + int rgba = input.getRGB(x, y); + Color col = new Color(rgba, true); + + if (col.getAlpha() <= 0) continue; + + col = new Color(255 - col.getRed(), + 255 - col.getGreen(), + 255 - col.getBlue()); + + input.setRGB(x, y, col.getRGB()); + + } + } + + } + + public static void saveBufferedImageFileChooser(Component parent, BufferedImage bufferedImage, EOCVSim eocvSim) { + + FileNameExtensionFilter jpegFilter = new FileNameExtensionFilter("JPEG (*.jpg)", "jpg", "jpeg"); + FileNameExtensionFilter pngFilter = new FileNameExtensionFilter("PNG (*.png)", "png"); + + String[] validExts = {"jpg", "jpeg", "png"}; + + DialogFactory.createFileChooser(parent, DialogFactory.FileChooser.Mode.SAVE_FILE_SELECT, jpegFilter, pngFilter) + + .addCloseListener((MODE, selectedFile, selectedFileFilter) -> { + if (MODE == JFileChooser.APPROVE_OPTION) { + + Optional extension = SysUtil.getExtensionByStringHandling(selectedFile.getName()); + + boolean saveImage; + + if (!selectedFile.exists()) { + saveImage = true; + } else { + FileAlreadyExists.UserChoice userChoice = DialogFactory.createFileAlreadyExistsDialog(eocvSim); //create confirm dialog + saveImage = userChoice == FileAlreadyExists.UserChoice.REPLACE; + } + + String ext = ""; + + if (saveImage) { + if (selectedFileFilter instanceof FileNameExtensionFilter) { //if user selected an extension + + //get selected extension + ext = ((FileNameExtensionFilter) selectedFileFilter).getExtensions()[0]; + + selectedFile = new File(selectedFile + "." + ext); //append extension to file + catchSaveBufferedImage(selectedFile, bufferedImage, ext); + + } else if (extension.isPresent() && Arrays.asList(validExts).contains(extension.get())) { //if user gave a extension to file and it's valid (jpg, jpeg or png) + + ext = extension.get(); //get the extension + catchSaveBufferedImage(selectedFile, bufferedImage, ext); + + } else { //default to jpg if the conditions are not met + + selectedFile = new File(selectedFile + ".jpg"); //append default extension to file + catchSaveBufferedImage(selectedFile, bufferedImage); + + } + } + + } + }); + + } + + public static void saveMatFileChooser(Component parent, Mat mat, EOCVSim eocvSim) { + + Mat clonedMat = mat.clone(); + + BufferedImage img = CvUtil.matToBufferedImage(clonedMat); + clonedMat.release(); + + saveBufferedImageFileChooser(parent, img, eocvSim); + + } + + public static ListModel isToListModel(InputStream is, Charset charset) throws UnsupportedEncodingException { + + DefaultListModel listModel = new DefaultListModel<>(); + String isStr = SysUtil.loadIsStr(is, charset); + + String[] lines = isStr.split("\n"); + + for(int i = 0 ; i < lines.length ; i++) { + listModel.add(i, lines[i]); + } + + return listModel; + + } + + public static class NoSelectionModel extends DefaultListSelectionModel { + + @Override + public void setAnchorSelectionIndex(final int anchorIndex) {} + + @Override + public void setLeadAnchorNotificationEnabled(final boolean flag) {} + + @Override + public void setLeadSelectionIndex(final int leadIndex) {} + + @Override + public void setSelectionInterval(final int index0, final int index1) { } + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/MatPoster.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/MatPoster.java index f761e2ab..d8f93ef3 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/MatPoster.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/MatPoster.java @@ -1,216 +1,216 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.util; - -import com.github.serivesmejia.eocvsim.util.Log; -import com.github.serivesmejia.eocvsim.util.fps.FpsCounter; -import org.firstinspires.ftc.robotcore.internal.collections.EvictingBlockingQueue; -import org.opencv.core.Mat; -import org.openftc.easyopencv.MatRecycler; - -import java.util.ArrayList; -import java.util.concurrent.ArrayBlockingQueue; - -public class MatPoster { - - private final ArrayList postables = new ArrayList<>(); - - private final EvictingBlockingQueue postQueue; - private final MatRecycler matRecycler; - - private final String name; - - private final Thread posterThread; - - public final FpsCounter fpsCounter = new FpsCounter(); - - private final Object lock = new Object(); - - private volatile boolean paused = false; - - private volatile boolean hasPosterThreadStarted = false; - - public static MatPoster createWithoutRecycler(String name, int maxQueueItems) { - return new MatPoster(name, maxQueueItems, null); - } - - public MatPoster(String name, int maxQueueItems) { - this(name, new MatRecycler(maxQueueItems + 2)); - } - - public MatPoster(String name, MatRecycler recycler) { - this(name, recycler.getSize(), recycler); - } - - public MatPoster(String name, int maxQueueItems, MatRecycler recycler) { - postQueue = new EvictingBlockingQueue<>(new ArrayBlockingQueue<>(maxQueueItems)); - matRecycler = recycler; - posterThread = new Thread(new PosterRunnable(), "MatPoster-" + name + "-Thread"); - - this.name = name; - - postQueue.setEvictAction(this::evict); //release mat and return it to recycler if it's dropped by the EvictingBlockingQueue - } - - public void post(Mat m) { - if (m == null || m.empty()) { - Log.warn("MatPoster-" + name, "Tried to post empty or null mat, skipped this frame."); - return; - } - - if (matRecycler != null) { - if(matRecycler.getAvailableMatsAmount() < 1) { - //evict one if we don't have any available mats in the recycler - evict(postQueue.poll()); - } - - MatRecycler.RecyclableMat recycledMat = matRecycler.takeMat(); - m.copyTo(recycledMat); - - postQueue.offer(recycledMat); - } else { - postQueue.offer(m); - } - } - - public void synchronizedPost(Mat m) { - synchronize(() -> post(m)); - } - - public Mat pull() throws InterruptedException { - synchronized(lock) { - return postQueue.take(); - } - } - - public void clearQueue() { - if(postQueue.size() == 0) return; - - synchronized(lock) { - postQueue.clear(); - } - } - - public void synchronize(Runnable runn) { - synchronized(lock) { - runn.run(); - } - } - - public void addPostable(Postable postable) { - //start mat posting thread if it hasn't been started yet - if (!posterThread.isAlive() && !hasPosterThreadStarted) { - posterThread.start(); - } - - postables.add(postable); - } - - public void stop() { - Log.info("MatPoster-" + name, "Destroying..."); - - posterThread.interrupt(); - - for (Mat m : postQueue) { - if (m != null) { - if(m instanceof MatRecycler.RecyclableMat) { - ((MatRecycler.RecyclableMat)m).returnMat(); - } - } - } - - matRecycler.releaseAll(); - } - - private void evict(Mat m) { - if (m instanceof MatRecycler.RecyclableMat) { - ((MatRecycler.RecyclableMat) m).returnMat(); - } - m.release(); - } - - public void setPaused(boolean paused) { - this.paused = paused; - } - - public boolean getPaused() { - synchronized(lock) { - return paused; - } - } - - public String getName() { - return name; - } - - public interface Postable { - void post(Mat m); - } - - private class PosterRunnable implements Runnable { - - private Mat postableMat = new Mat(); - - @Override - public void run() { - hasPosterThreadStarted = true; - - while (!Thread.interrupted()) { - - while(paused && !Thread.currentThread().isInterrupted()) { - Thread.yield(); - } - - if (postQueue.size() == 0 || postables.size() == 0) continue; //skip if we have no queued frames - - synchronized(lock) { - fpsCounter.update(); - - try { - Mat takenMat = postQueue.take(); - - for (Postable postable : postables) { - takenMat.copyTo(postableMat); - postable.post(postableMat); - } - - takenMat.release(); - - if (takenMat instanceof MatRecycler.RecyclableMat) { - ((MatRecycler.RecyclableMat) takenMat).returnMat(); - } - } catch (InterruptedException e) { - e.printStackTrace(); - break; - } catch (Exception ex) { } - } - - } - - Log.warn("MatPoster-" + name +"-Thread", "Thread interrupted (" + Integer.toHexString(hashCode()) + ")"); - - } - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.util; + +import com.github.serivesmejia.eocvsim.util.Log; +import com.github.serivesmejia.eocvsim.util.fps.FpsCounter; +import org.firstinspires.ftc.robotcore.internal.collections.EvictingBlockingQueue; +import org.opencv.core.Mat; +import org.openftc.easyopencv.MatRecycler; + +import java.util.ArrayList; +import java.util.concurrent.ArrayBlockingQueue; + +public class MatPoster { + + private final ArrayList postables = new ArrayList<>(); + + private final EvictingBlockingQueue postQueue; + private final MatRecycler matRecycler; + + private final String name; + + private final Thread posterThread; + + public final FpsCounter fpsCounter = new FpsCounter(); + + private final Object lock = new Object(); + + private volatile boolean paused = false; + + private volatile boolean hasPosterThreadStarted = false; + + public static MatPoster createWithoutRecycler(String name, int maxQueueItems) { + return new MatPoster(name, maxQueueItems, null); + } + + public MatPoster(String name, int maxQueueItems) { + this(name, new MatRecycler(maxQueueItems + 2)); + } + + public MatPoster(String name, MatRecycler recycler) { + this(name, recycler.getSize(), recycler); + } + + public MatPoster(String name, int maxQueueItems, MatRecycler recycler) { + postQueue = new EvictingBlockingQueue<>(new ArrayBlockingQueue<>(maxQueueItems)); + matRecycler = recycler; + posterThread = new Thread(new PosterRunnable(), "MatPoster-" + name + "-Thread"); + + this.name = name; + + postQueue.setEvictAction(this::evict); //release mat and return it to recycler if it's dropped by the EvictingBlockingQueue + } + + public void post(Mat m) { + if (m == null || m.empty()) { + Log.warn("MatPoster-" + name, "Tried to post empty or null mat, skipped this frame."); + return; + } + + if (matRecycler != null) { + if(matRecycler.getAvailableMatsAmount() < 1) { + //evict one if we don't have any available mats in the recycler + evict(postQueue.poll()); + } + + MatRecycler.RecyclableMat recycledMat = matRecycler.takeMat(); + m.copyTo(recycledMat); + + postQueue.offer(recycledMat); + } else { + postQueue.offer(m); + } + } + + public void synchronizedPost(Mat m) { + synchronize(() -> post(m)); + } + + public Mat pull() throws InterruptedException { + synchronized(lock) { + return postQueue.take(); + } + } + + public void clearQueue() { + if(postQueue.size() == 0) return; + + synchronized(lock) { + postQueue.clear(); + } + } + + public void synchronize(Runnable runn) { + synchronized(lock) { + runn.run(); + } + } + + public void addPostable(Postable postable) { + //start mat posting thread if it hasn't been started yet + if (!posterThread.isAlive() && !hasPosterThreadStarted) { + posterThread.start(); + } + + postables.add(postable); + } + + public void stop() { + Log.info("MatPoster-" + name, "Destroying..."); + + posterThread.interrupt(); + + for (Mat m : postQueue) { + if (m != null) { + if(m instanceof MatRecycler.RecyclableMat) { + ((MatRecycler.RecyclableMat)m).returnMat(); + } + } + } + + matRecycler.releaseAll(); + } + + private void evict(Mat m) { + if (m instanceof MatRecycler.RecyclableMat) { + ((MatRecycler.RecyclableMat) m).returnMat(); + } + m.release(); + } + + public void setPaused(boolean paused) { + this.paused = paused; + } + + public boolean getPaused() { + synchronized(lock) { + return paused; + } + } + + public String getName() { + return name; + } + + public interface Postable { + void post(Mat m); + } + + private class PosterRunnable implements Runnable { + + private Mat postableMat = new Mat(); + + @Override + public void run() { + hasPosterThreadStarted = true; + + while (!Thread.interrupted()) { + + while(paused && !Thread.currentThread().isInterrupted()) { + Thread.yield(); + } + + if (postQueue.size() == 0 || postables.size() == 0) continue; //skip if we have no queued frames + + synchronized(lock) { + fpsCounter.update(); + + try { + Mat takenMat = postQueue.take(); + + for (Postable postable : postables) { + takenMat.copyTo(postableMat); + postable.post(postableMat); + } + + takenMat.release(); + + if (takenMat instanceof MatRecycler.RecyclableMat) { + ((MatRecycler.RecyclableMat) takenMat).returnMat(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + break; + } catch (Exception ex) { } + } + + } + + Log.warn("MatPoster-" + name +"-Thread", "Thread interrupted (" + Integer.toHexString(hashCode()) + ")"); + + } + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ReflectTaskbar.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ReflectTaskbar.kt index e72609e3..f9c68161 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ReflectTaskbar.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ReflectTaskbar.kt @@ -1,33 +1,33 @@ -package com.github.serivesmejia.eocvsim.gui.util - -import java.awt.Image -import java.lang.reflect.InvocationTargetException - -object ReflectTaskbar { - - private val taskbarClass by lazy { Class.forName("java.awt.Taskbar") } - - private val isTaskbarSupportedMethod by lazy { taskbarClass.getDeclaredMethod("isTaskbarSupported") } - - private val getTaskbarMethod by lazy { taskbarClass.getDeclaredMethod("getTaskbar") } - private val setIconImageMethod by lazy { taskbarClass.getDeclaredMethod("setIconImage", Image::class.java) } - - val isUsable by lazy { - try { - isTaskbarSupported - } catch(ex: ClassNotFoundException) { false } - } - val isTaskbarSupported get() = isTaskbarSupportedMethod.invoke(null) as Boolean - - val taskbar by lazy { getTaskbarMethod.invoke(null) } - - @Throws(SecurityException::class, UnsupportedOperationException::class) - fun setIconImage(image: Image) { - try { - setIconImageMethod.invoke(taskbar, image) - } catch(e: InvocationTargetException) { - throw e.cause ?: e - } - } - +package com.github.serivesmejia.eocvsim.gui.util + +import java.awt.Image +import java.lang.reflect.InvocationTargetException + +object ReflectTaskbar { + + private val taskbarClass by lazy { Class.forName("java.awt.Taskbar") } + + private val isTaskbarSupportedMethod by lazy { taskbarClass.getDeclaredMethod("isTaskbarSupported") } + + private val getTaskbarMethod by lazy { taskbarClass.getDeclaredMethod("getTaskbar") } + private val setIconImageMethod by lazy { taskbarClass.getDeclaredMethod("setIconImage", Image::class.java) } + + val isUsable by lazy { + try { + isTaskbarSupported + } catch(ex: ClassNotFoundException) { false } + } + val isTaskbarSupported get() = isTaskbarSupportedMethod.invoke(null) as Boolean + + val taskbar by lazy { getTaskbarMethod.invoke(null) } + + @Throws(SecurityException::class, UnsupportedOperationException::class) + fun setIconImage(image: Image) { + try { + setIconImageMethod.invoke(taskbar, image) + } catch(e: InvocationTargetException) { + throw e.cause ?: e + } + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ValidCharactersDocumentFilter.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ValidCharactersDocumentFilter.kt index 24c957ae..cd589573 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ValidCharactersDocumentFilter.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/ValidCharactersDocumentFilter.kt @@ -1,69 +1,69 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.util - -import javax.swing.JTextField -import javax.swing.text.AttributeSet -import javax.swing.text.BadLocationException -import javax.swing.text.DocumentFilter - -class ValidCharactersDocumentFilter(val validCharacters: Array) : DocumentFilter() { - - @get:Synchronized - @Volatile var valid = false - private set - - @get:Synchronized - @Volatile var lastValid = 0.0 - private set - - @Volatile private var lastText = "" - - @Throws(BadLocationException::class) - @Synchronized override fun replace(fb: FilterBypass?, offset: Int, length: Int, text: String, attrs: AttributeSet?) { - val newText = text.replace(" ", "") - - for (c in newText.toCharArray()) { - if(!isValidCharacter(c)) return - } - - valid = try { - lastValid = newText.toDouble() - lastText = newText - newText != "" - } catch (ex: NumberFormatException) { - false - } - - super.replace(fb, offset, length, newText, attrs) - } - - private fun isValidCharacter(c: Char): Boolean { - for (validC in validCharacters) { - if (c == validC) return true - } - return false - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.util + +import javax.swing.JTextField +import javax.swing.text.AttributeSet +import javax.swing.text.BadLocationException +import javax.swing.text.DocumentFilter + +class ValidCharactersDocumentFilter(val validCharacters: Array) : DocumentFilter() { + + @get:Synchronized + @Volatile var valid = false + private set + + @get:Synchronized + @Volatile var lastValid = 0.0 + private set + + @Volatile private var lastText = "" + + @Throws(BadLocationException::class) + @Synchronized override fun replace(fb: FilterBypass?, offset: Int, length: Int, text: String, attrs: AttributeSet?) { + val newText = text.replace(" ", "") + + for (c in newText.toCharArray()) { + if(!isValidCharacter(c)) return + } + + valid = try { + lastValid = newText.toDouble() + lastText = newText + newText != "" + } catch (ex: NumberFormatException) { + false + } + + super.replace(fb, offset, length, newText, attrs) + } + + private fun isValidCharacter(c: Char): Boolean { + for (validC in validCharacters) { + if (c == validC) return true + } + return false + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/extension/SwingExt.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/extension/SwingExt.kt index e034e9b9..b5081f23 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/extension/SwingExt.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/extension/SwingExt.kt @@ -1,45 +1,45 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.util.extension - -import javax.swing.JTextField -import javax.swing.text.AbstractDocument -import javax.swing.text.DocumentFilter - -object SwingExt { - - val JTextField.abstractDocument: AbstractDocument - get() { - return (document as AbstractDocument) - } - - var JTextField.documentFilter: DocumentFilter - get() { - return abstractDocument.documentFilter - } - set(value) { - abstractDocument.documentFilter = value - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.util.extension + +import javax.swing.JTextField +import javax.swing.text.AbstractDocument +import javax.swing.text.DocumentFilter + +object SwingExt { + + val JTextField.abstractDocument: AbstractDocument + get() { + return (document as AbstractDocument) + } + + var JTextField.documentFilter: DocumentFilter + get() { + return abstractDocument.documentFilter + } + set(value) { + abstractDocument.documentFilter = value + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/PipelineListIconRenderer.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/PipelineListIconRenderer.kt index 1c1a2e82..94f6e107 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/PipelineListIconRenderer.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/PipelineListIconRenderer.kt @@ -1,45 +1,45 @@ -package com.github.serivesmejia.eocvsim.gui.util.icon - -import com.github.serivesmejia.eocvsim.gui.Icons -import com.github.serivesmejia.eocvsim.input.InputSourceManager -import com.github.serivesmejia.eocvsim.pipeline.PipelineManager -import com.github.serivesmejia.eocvsim.pipeline.PipelineSource - -import javax.swing.* -import java.awt.* - -class PipelineListIconRenderer( - private val pipelineManager: PipelineManager -) : DefaultListCellRenderer() { - - private val gearsIcon = Icons.getImageResized("ico_gears", 15, 15) - private val hammerIcon = Icons.getImageResized("ico_hammer", 15, 15) - - override fun getListCellRendererComponent( - list: JList<*>, - value: Any, - index: Int, - isSelected: Boolean, - cellHasFocus: Boolean - ): Component { - val label = super.getListCellRendererComponent( - list, value, index, isSelected, cellHasFocus - ) as JLabel - - val runtimePipelinesAmount = pipelineManager.getPipelinesFrom( - PipelineSource.COMPILED_ON_RUNTIME - ).size - - if(runtimePipelinesAmount > 0) { - val source = pipelineManager.pipelines[index].source - - label.setIcon(when(source) { - PipelineSource.COMPILED_ON_RUNTIME -> gearsIcon - else -> hammerIcon - }) - } - - return label - } - -} +package com.github.serivesmejia.eocvsim.gui.util.icon + +import com.github.serivesmejia.eocvsim.gui.Icons +import com.github.serivesmejia.eocvsim.input.InputSourceManager +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.pipeline.PipelineSource + +import javax.swing.* +import java.awt.* + +class PipelineListIconRenderer( + private val pipelineManager: PipelineManager +) : DefaultListCellRenderer() { + + private val gearsIcon by Icons.lazyGetImageResized("ico_gears", 15, 15) + private val hammerIcon by Icons.lazyGetImageResized("ico_hammer", 15, 15) + + override fun getListCellRendererComponent( + list: JList<*>, + value: Any, + index: Int, + isSelected: Boolean, + cellHasFocus: Boolean + ): Component { + val label = super.getListCellRendererComponent( + list, value, index, isSelected, cellHasFocus + ) as JLabel + + val runtimePipelinesAmount = pipelineManager.getPipelinesFrom( + PipelineSource.COMPILED_ON_RUNTIME + ).size + + if(runtimePipelinesAmount > 0) { + val source = pipelineManager.pipelines[index].source + + label.icon = when(source) { + PipelineSource.COMPILED_ON_RUNTIME -> gearsIcon + else -> hammerIcon + } + } + + return label + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/SourcesListIconRenderer.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/SourcesListIconRenderer.java index 24c3e243..dd582038 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/SourcesListIconRenderer.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/util/icon/SourcesListIconRenderer.java @@ -1,79 +1,84 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.gui.util.icon; - -import com.github.serivesmejia.eocvsim.gui.Icons; -import com.github.serivesmejia.eocvsim.input.InputSourceManager; - -import javax.swing.*; -import java.awt.*; - -public class SourcesListIconRenderer extends DefaultListCellRenderer { - - private ImageIcon imgIcon = null; - private ImageIcon camIcon = null; - private ImageIcon vidIcon = null; - - public static final int ICO_W = 15; - public static final int ICO_H = 15; - - public InputSourceManager sourceManager = null; - - public SourcesListIconRenderer(InputSourceManager sourceManager) { - imgIcon = Icons.INSTANCE.getImageResized("ico_img", 15, 15); - camIcon = Icons.INSTANCE.getImageResized("ico_cam", 15, 15); - vidIcon = Icons.INSTANCE.getImageResized("ico_vid", 15, 15); - - this.sourceManager = sourceManager; - } - - @Override - public Component getListCellRendererComponent( - JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus) { - - // Get the renderer component from parent class - JLabel label = (JLabel) super.getListCellRendererComponent(list, - value, index, isSelected, cellHasFocus); - - switch (sourceManager.getSourceType((String) value)) { - case IMAGE: - label.setIcon(imgIcon); - break; - case CAMERA: - label.setIcon(camIcon); - break; - case VIDEO: - label.setIcon(vidIcon); - break; - } - - return label; - - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.gui.util.icon; + +import com.github.serivesmejia.eocvsim.gui.Icons; +import com.github.serivesmejia.eocvsim.input.InputSourceManager; + +import javax.swing.*; +import java.awt.*; + +public class SourcesListIconRenderer extends DefaultListCellRenderer { + + public static final int ICO_W = 15; + public static final int ICO_H = 15; + + public InputSourceManager sourceManager = null; + + ImageIcon imageIcon = null; + ImageIcon camIcon = null; + ImageIcon vidIcon = null; + + public SourcesListIconRenderer(InputSourceManager sourceManager) { + this.sourceManager = sourceManager; + } + + @Override + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus) { + + // Get the renderer component from parent class + JLabel label = (JLabel) super.getListCellRendererComponent(list, + value, index, isSelected, cellHasFocus); + + switch (sourceManager.getSourceType((String) value)) { + case IMAGE: + if(imageIcon == null) { + imageIcon = Icons.INSTANCE.getImageResized("ico_img", 15, 15); + } + label.setIcon(imageIcon); + break; + case CAMERA: + if(camIcon == null) { + camIcon = Icons.INSTANCE.getImageResized("ico_cam", 15, 15); + } + label.setIcon(camIcon); + break; + case VIDEO: + if(vidIcon == null) { + vidIcon = Icons.INSTANCE.getImageResized("ico_vid", 15, 15); + } + label.setIcon(vidIcon); + break; + } + + return label; + + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSource.java index 9ceec0e3..7fbcb034 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSource.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSource.java @@ -1,88 +1,90 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.input; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import org.opencv.core.Mat; - -import javax.swing.filechooser.FileFilter; - -public abstract class InputSource implements Comparable { - - public transient boolean isDefault = false; - public transient EOCVSim eocvSim = null; - - protected transient String name = ""; - protected transient boolean isPaused = false; - private transient boolean beforeIsPaused = false; - - protected long createdOn = -1L; - - public abstract boolean init(); - public abstract void reset(); - public abstract void close(); - - public abstract void onPause(); - public abstract void onResume(); - - public Mat update() { - return null; - } - - public final InputSource cloneSource() { - InputSource source = internalCloneSource(); - source.createdOn = createdOn; - return source; - } - - protected abstract InputSource internalCloneSource(); - - public final void setPaused(boolean paused) { - - isPaused = paused; - - if (beforeIsPaused != isPaused) { - if (isPaused) { - onPause(); - } else { - onResume(); - } - } - - beforeIsPaused = paused; - - } - - public final String getName() { - return name; - } - - public abstract FileFilter getFileFilters(); - - @Override - public final int compareTo(InputSource source) { - return createdOn > source.createdOn ? 1 : -1; - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.input; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import org.opencv.core.Mat; + +import javax.swing.filechooser.FileFilter; + +public abstract class InputSource implements Comparable { + + public transient boolean isDefault = false; + public transient EOCVSim eocvSim = null; + + protected transient String name = ""; + protected transient boolean isPaused = false; + private transient boolean beforeIsPaused = false; + + protected long createdOn = -1L; + + public abstract boolean init(); + public abstract void reset(); + public abstract void close(); + + public abstract void onPause(); + public abstract void onResume(); + + public Mat update() { + return null; + } + + public final InputSource cloneSource() { + InputSource source = internalCloneSource(); + source.createdOn = createdOn; + return source; + } + + protected abstract InputSource internalCloneSource(); + + public final void setPaused(boolean paused) { + + isPaused = paused; + + if (beforeIsPaused != isPaused) { + if (isPaused) { + onPause(); + } else { + onResume(); + } + } + + beforeIsPaused = paused; + + } + + public final String getName() { + return name; + } + + public abstract FileFilter getFileFilters(); + + public abstract long getCaptureTimeNanos(); + + @Override + public final int compareTo(InputSource source) { + return createdOn > source.createdOn ? 1 : -1; + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceLoader.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceLoader.java index 9f4eccf5..f2be987f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceLoader.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceLoader.java @@ -1,183 +1,183 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.input; - -import com.github.serivesmejia.eocvsim.input.source.CameraSource; -import com.github.serivesmejia.eocvsim.input.source.ImageSource; -import com.github.serivesmejia.eocvsim.input.source.VideoSource; -import com.github.serivesmejia.eocvsim.util.Log; -import com.github.serivesmejia.eocvsim.util.SysUtil; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.annotations.Expose; - -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -public class InputSourceLoader { - - public static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); - - public static final String SOURCES_SAVEFILE_NAME = "eocvsim_sources.json"; - - public static final File SOURCES_SAVEFILE = new File(SysUtil.getEOCVSimFolder() + File.separator + SOURCES_SAVEFILE_NAME); - public static final File SOURCES_SAVEFILE_OLD = new File(SysUtil.getAppData() + File.separator + SOURCES_SAVEFILE_NAME); - - public static final InputSourcesContainer.SourcesFileVersion CURRENT_FILE_VERSION = InputSourcesContainer.SourcesFileVersion.SEIS; - - public HashMap loadedInputSources = new HashMap<>(); - - public InputSourcesContainer.SourcesFileVersion fileVersion = null; - - public void saveInputSource(String name, InputSource source) { - loadedInputSources.put(name, source); - } - - public void deleteInputSource(String name) { - loadedInputSources.remove(name); - } - - public void saveInputSourcesToFile() { - saveInputSourcesToFile(SOURCES_SAVEFILE); - } - - public void saveInputSourcesToFile(File f) { - - InputSourcesContainer sourcesContainer = new InputSourcesContainer(); - - //updates file version to most recent since it will be regenerated at this point - if(fileVersion != null) - sourcesContainer.sourcesFileVersion = fileVersion.ordinal() < CURRENT_FILE_VERSION.ordinal() - ? CURRENT_FILE_VERSION : fileVersion; - - for (Map.Entry entry : loadedInputSources.entrySet()) { - if (!entry.getValue().isDefault) { - InputSource source = entry.getValue().cloneSource(); - sourcesContainer.classifySource(entry.getKey(), source); - } - } - - saveInputSourcesToFile(f, sourcesContainer); - - } - - public void saveInputSourcesToFile(File file, InputSourcesContainer sourcesContainer) { - String jsonInputSources = gson.toJson(sourcesContainer); - SysUtil.saveFileStr(file, jsonInputSources); - } - - public void saveInputSourcesToFile(InputSourcesContainer sourcesContainer) { - saveInputSourcesToFile(SOURCES_SAVEFILE, sourcesContainer); - } - - public void loadInputSourcesFromFile() { - SysUtil.migrateFile(SOURCES_SAVEFILE_OLD, SOURCES_SAVEFILE); - loadInputSourcesFromFile(SOURCES_SAVEFILE); - } - - public void loadInputSourcesFromFile(File f) { - - if (!f.exists()) return; - - String jsonSources = SysUtil.loadFileStr(f); - if (jsonSources.trim().equals("")) return; - - InputSourcesContainer sources; - - try { - sources = gson.fromJson(jsonSources, InputSourcesContainer.class); - } catch (Exception ex) { - Log.error("InputSourceLoader", "Error while parsing sources file, it will be replaced and fixed later on, but the user created sources will be deleted.", ex); - Log.blank(); - return; - } - - sources.updateAllSources(); - fileVersion = sources.sourcesFileVersion; - - saveInputSourcesToFile(sources); //to make sure version gets declared in case it was an older file - - Log.info("InputSourceLoader", "InputSources file version is " + sources.sourcesFileVersion); - - loadedInputSources = sources.allSources; - - } - - static class InputSourcesContainer { - - public transient HashMap allSources = new HashMap<>(); - - public HashMap imageSources = new HashMap<>(); - public HashMap cameraSources = new HashMap<>(); - public HashMap videoSources = new HashMap<>(); - - @Expose - public SourcesFileVersion sourcesFileVersion = null; - - enum SourcesFileVersion { DOS, SEIS, SIETE } - - public void updateAllSources() { - - if(sourcesFileVersion == null) sourcesFileVersion = SourcesFileVersion.DOS; - - allSources.clear(); - - for (Map.Entry entry : imageSources.entrySet()) { - allSources.put(entry.getKey(), entry.getValue()); - } - - for (Map.Entry entry : cameraSources.entrySet()) { - allSources.put(entry.getKey(), entry.getValue()); - } - - //check if file version is bigger than DOS, we should have video sources section - //declared in any file with a version greater than that - if(sourcesFileVersion.ordinal() >= 1) { - for (Map.Entry entry : videoSources.entrySet()) { - allSources.put(entry.getKey(), entry.getValue()); - } - } - - } - - public void classifySource(String sourceName, InputSource source) { - - switch (SourceType.fromClass(source.getClass())) { - case IMAGE: - imageSources.put(sourceName, (ImageSource) source); - break; - case CAMERA: - cameraSources.put(sourceName, (CameraSource) source); - break; - case VIDEO: - videoSources.put(sourceName, (VideoSource) source); - break; - } - - } - - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.input; + +import com.github.serivesmejia.eocvsim.input.source.CameraSource; +import com.github.serivesmejia.eocvsim.input.source.ImageSource; +import com.github.serivesmejia.eocvsim.input.source.VideoSource; +import com.github.serivesmejia.eocvsim.util.Log; +import com.github.serivesmejia.eocvsim.util.SysUtil; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +public class InputSourceLoader { + + public static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + public static final String SOURCES_SAVEFILE_NAME = "eocvsim_sources.json"; + + public static final File SOURCES_SAVEFILE = new File(SysUtil.getEOCVSimFolder() + File.separator + SOURCES_SAVEFILE_NAME); + public static final File SOURCES_SAVEFILE_OLD = new File(SysUtil.getAppData() + File.separator + SOURCES_SAVEFILE_NAME); + + public static final InputSourcesContainer.SourcesFileVersion CURRENT_FILE_VERSION = InputSourcesContainer.SourcesFileVersion.SEIS; + + public HashMap loadedInputSources = new HashMap<>(); + + public InputSourcesContainer.SourcesFileVersion fileVersion = null; + + public void saveInputSource(String name, InputSource source) { + loadedInputSources.put(name, source); + } + + public void deleteInputSource(String name) { + loadedInputSources.remove(name); + } + + public void saveInputSourcesToFile() { + saveInputSourcesToFile(SOURCES_SAVEFILE); + } + + public void saveInputSourcesToFile(File f) { + + InputSourcesContainer sourcesContainer = new InputSourcesContainer(); + + //updates file version to most recent since it will be regenerated at this point + if(fileVersion != null) + sourcesContainer.sourcesFileVersion = fileVersion.ordinal() < CURRENT_FILE_VERSION.ordinal() + ? CURRENT_FILE_VERSION : fileVersion; + + for (Map.Entry entry : loadedInputSources.entrySet()) { + if (!entry.getValue().isDefault) { + InputSource source = entry.getValue().cloneSource(); + sourcesContainer.classifySource(entry.getKey(), source); + } + } + + saveInputSourcesToFile(f, sourcesContainer); + + } + + public void saveInputSourcesToFile(File file, InputSourcesContainer sourcesContainer) { + String jsonInputSources = gson.toJson(sourcesContainer); + SysUtil.saveFileStr(file, jsonInputSources); + } + + public void saveInputSourcesToFile(InputSourcesContainer sourcesContainer) { + saveInputSourcesToFile(SOURCES_SAVEFILE, sourcesContainer); + } + + public void loadInputSourcesFromFile() { + SysUtil.migrateFile(SOURCES_SAVEFILE_OLD, SOURCES_SAVEFILE); + loadInputSourcesFromFile(SOURCES_SAVEFILE); + } + + public void loadInputSourcesFromFile(File f) { + + if (!f.exists()) return; + + String jsonSources = SysUtil.loadFileStr(f); + if (jsonSources.trim().equals("")) return; + + InputSourcesContainer sources; + + try { + sources = gson.fromJson(jsonSources, InputSourcesContainer.class); + } catch (Exception ex) { + Log.error("InputSourceLoader", "Error while parsing sources file, it will be replaced and fixed later on, but the user created sources will be deleted.", ex); + Log.blank(); + return; + } + + sources.updateAllSources(); + fileVersion = sources.sourcesFileVersion; + + saveInputSourcesToFile(sources); //to make sure version gets declared in case it was an older file + + Log.info("InputSourceLoader", "InputSources file version is " + sources.sourcesFileVersion); + + loadedInputSources = sources.allSources; + + } + + static class InputSourcesContainer { + + public transient HashMap allSources = new HashMap<>(); + + public HashMap imageSources = new HashMap<>(); + public HashMap cameraSources = new HashMap<>(); + public HashMap videoSources = new HashMap<>(); + + @Expose + public SourcesFileVersion sourcesFileVersion = null; + + enum SourcesFileVersion { DOS, SEIS, SIETE } + + public void updateAllSources() { + + if(sourcesFileVersion == null) sourcesFileVersion = SourcesFileVersion.DOS; + + allSources.clear(); + + for (Map.Entry entry : imageSources.entrySet()) { + allSources.put(entry.getKey(), entry.getValue()); + } + + for (Map.Entry entry : cameraSources.entrySet()) { + allSources.put(entry.getKey(), entry.getValue()); + } + + //check if file version is bigger than DOS, we should have video sources section + //declared in any file with a version greater than that + if(sourcesFileVersion.ordinal() >= 1) { + for (Map.Entry entry : videoSources.entrySet()) { + allSources.put(entry.getKey(), entry.getValue()); + } + } + + } + + public void classifySource(String sourceName, InputSource source) { + + switch (SourceType.fromClass(source.getClass())) { + case IMAGE: + imageSources.put(sourceName, (ImageSource) source); + break; + case CAMERA: + cameraSources.put(sourceName, (CameraSource) source); + break; + case VIDEO: + videoSources.put(sourceName, (VideoSource) source); + break; + } + + } + + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceManager.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceManager.java index f677e2ec..72eeb263 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceManager.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/InputSourceManager.java @@ -1,265 +1,308 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.input; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.Visualizer; -import com.github.serivesmejia.eocvsim.input.source.ImageSource; -import com.github.serivesmejia.eocvsim.pipeline.PipelineManager; -import com.github.serivesmejia.eocvsim.util.Log; -import com.github.serivesmejia.eocvsim.util.SysUtil; -import org.opencv.core.Mat; -import org.opencv.core.Size; - -import javax.swing.SwingUtilities; -import java.awt.*; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; - -public class InputSourceManager { - - private final EOCVSim eocvSim; - - public volatile Mat lastMatFromSource = null; - public volatile InputSource currentInputSource = null; - - public volatile HashMap sources = new HashMap<>(); - - public InputSourceLoader inputSourceLoader = new InputSourceLoader(); - - public InputSourceManager(EOCVSim eocvSim) { - this.eocvSim = eocvSim; - } - public void init() { - - Log.info("InputSourceManager", "Initializing..."); - - if(lastMatFromSource == null) - lastMatFromSource = new Mat(); - - Size size = new Size(320, 240); - createDefaultImgInputSource("/images/ug_4.jpg", "ug_eocvsim_4.jpg", "Ultimate Goal 4 Ring", size); - createDefaultImgInputSource("/images/ug_1.jpg", "ug_eocvsim_1.jpg", "Ultimate Goal 1 Ring", size); - createDefaultImgInputSource("/images/ug_0.jpg", "ug_eocvsim_0.jpg", "Ultimate Goal 0 Ring", size); - - inputSourceLoader.loadInputSourcesFromFile(); - - for (Map.Entry entry : inputSourceLoader.loadedInputSources.entrySet()) { - addInputSource(entry.getKey(), entry.getValue()); - } - - Log.blank(); - } - - private void createDefaultImgInputSource(String resourcePath, String fileName, String sourceName, Size imgSize) { - try { - - InputStream is = InputSource.class.getResourceAsStream(resourcePath); - File f = SysUtil.copyFileIsTemp(is, fileName, true).file; - - ImageSource src = new ImageSource(f.getAbsolutePath(), imgSize); - src.isDefault = true; - src.createdOn = sources.size(); - - addInputSource(sourceName, src); - - } catch (IOException e) { - e.printStackTrace(); - } - } - - public void update(boolean isPaused) { - if(currentInputSource == null) return; - currentInputSource.setPaused(isPaused); - - try { - Mat m = currentInputSource.update(); - if(m != null && !m.empty()) m.copyTo(lastMatFromSource); - } catch(Exception ex) { - Log.error("InputSourceManager", "Error while processing current source", ex); - } - } - - public void addInputSource(String name, InputSource inputSource) { - if (inputSource == null) { - return; - } - - if (sources.containsKey(name)) return; - - inputSource.name = name; - - sources.put(name, inputSource); - - if(inputSource.createdOn == -1) - inputSource.createdOn = System.currentTimeMillis(); - - if(!inputSource.isDefault) { - inputSourceLoader.saveInputSource(name, inputSource); - inputSourceLoader.saveInputSourcesToFile(); - } - - if(eocvSim.visualizer.sourceSelectorPanel != null) { - eocvSim.visualizer.sourceSelectorPanel.updateSourcesList(); - - SwingUtilities.invokeLater(() -> { - int index = eocvSim.visualizer.sourceSelectorPanel.getIndexOf(name); - - eocvSim.visualizer.sourceSelectorPanel - .getSourceSelector().setSelectedIndex(index); - - eocvSim.onMainUpdate.doOnce(() -> { - eocvSim.pipelineManager.requestSetPaused(false); - pauseIfImageTwoFrames(); - }); - }); - } - - Log.info("InputSourceManager", "Adding InputSource " + inputSource.toString() + " (" + inputSource.getClass().getSimpleName() + ")"); - } - - public void deleteInputSource(String sourceName) { - InputSource src = sources.get(sourceName); - - if (src == null) return; - if (src.isDefault) return; - - sources.remove(sourceName); - - inputSourceLoader.deleteInputSource(sourceName); - inputSourceLoader.saveInputSourcesToFile(); - } - - public boolean setInputSource(String sourceName) { - InputSource src = sources.get(sourceName); - - if (src != null) { - src.reset(); - src.eocvSim = eocvSim; - } - - //check if source type is a camera, and if so, create a please wait dialog - Visualizer.AsyncPleaseWaitDialog apwd = showApwdIfNeeded(sourceName); - - if (src != null) { - if (!src.init()) { - if (apwd != null) { - apwd.destroyDialog(); - } - - eocvSim.visualizer.asyncPleaseWaitDialog("Error while loading requested source", "Falling back to previous source", - "Close", new Dimension(300, 150), true, true); - - Log.error("InputSourceManager", "Error while loading requested source (" + sourceName + ") reported by itself (init method returned false)"); - - return false; - } - } - - //if there's a please wait dialog for a camera source, destroy it. - if (apwd != null) { - apwd.destroyDialog(); - } - - if (currentInputSource != null) { - currentInputSource.reset(); - } - - currentInputSource = src; - - //if pause on images option is turned on by user - if (eocvSim.configManager.getConfig().pauseOnImages) - pauseIfImage(); - - Log.info("InputSourceManager", "Set InputSource to " + currentInputSource.toString() + " (" + src.getClass().getSimpleName() + ")"); - - return true; - - } - - public boolean isNameOnUse(String name) { - return sources.containsKey(name); - } - - public void pauseIfImage() { - //if the new input source is an image, we will pause the next frame - //to execute one shot analysis on images and save resources. - if (SourceType.fromClass(currentInputSource.getClass()) == SourceType.IMAGE) { - eocvSim.onMainUpdate.doOnce(() -> - eocvSim.pipelineManager.setPaused( - true, - PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS - ) - ); - } - } - - public void pauseIfImageTwoFrames() { - //if the new input source is an image, we will pause the next frame - //to execute one shot analysis on images and save resources. - eocvSim.onMainUpdate.doOnce(this::pauseIfImage); - } - - public void requestSetInputSource(String name) { - eocvSim.onMainUpdate.doOnce(() -> setInputSource(name)); - } - - public Visualizer.AsyncPleaseWaitDialog showApwdIfNeeded(String sourceName) { - Visualizer.AsyncPleaseWaitDialog apwd = null; - - if (getSourceType(sourceName) == SourceType.CAMERA || getSourceType(sourceName) == SourceType.VIDEO) { - apwd = eocvSim.visualizer.asyncPleaseWaitDialog( - "Opening source...", null, "Exit", - new Dimension(300, 150), true - ); - - apwd.onCancel(() -> eocvSim.destroy()); - } - - return apwd; - } - - public SourceType getSourceType(String sourceName) { - if(sourceName == null) { - return SourceType.UNKNOWN; - } - - InputSource source = sources.get(sourceName); - - if(source == null) { - return SourceType.UNKNOWN; - } - return SourceType.fromClass(source.getClass()); - } - - public InputSource[] getSortedInputSources() { - ArrayList sources = new ArrayList<>(this.sources.values()); - Collections.sort(sources); - - return sources.toArray(new InputSource[0]); - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.input; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.Visualizer; +import com.github.serivesmejia.eocvsim.gui.component.visualizer.SourceSelectorPanel; +import com.github.serivesmejia.eocvsim.input.source.ImageSource; +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager; +import com.github.serivesmejia.eocvsim.util.Log; +import com.github.serivesmejia.eocvsim.util.SysUtil; +import org.opencv.core.Mat; +import org.opencv.core.Size; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +public class InputSourceManager { + + private final EOCVSim eocvSim; + + public volatile Mat lastMatFromSource = null; + public volatile InputSource currentInputSource = null; + + public volatile HashMap sources = new HashMap<>(); + + public InputSourceLoader inputSourceLoader = new InputSourceLoader(); + public SourceSelectorPanel selectorPanel; + + private String defaultSource = ""; + + public InputSourceManager(EOCVSim eocvSim) { + this.eocvSim = eocvSim; + selectorPanel = eocvSim.visualizer.sourceSelectorPanel; + } + + public void init() { + + Log.info("InputSourceManager", "Initializing..."); + + if(lastMatFromSource == null) + lastMatFromSource = new Mat(); + + Size size = new Size(320, 240); + createDefaultImgInputSource("/images/ug_4.jpg", "ug_eocvsim_4.jpg", "Ultimate Goal 4 Ring", size); + createDefaultImgInputSource("/images/ug_1.jpg", "ug_eocvsim_1.jpg", "Ultimate Goal 1 Ring", size); + createDefaultImgInputSource("/images/ug_0.jpg", "ug_eocvsim_0.jpg", "Ultimate Goal 0 Ring", size); + + setInputSource("Ultimate Goal 4 Ring", true); + + inputSourceLoader.loadInputSourcesFromFile(); + + for (Map.Entry entry : inputSourceLoader.loadedInputSources.entrySet()) { + addInputSource(entry.getKey(), entry.getValue()); + } + + Log.blank(); + } + + private void createDefaultImgInputSource(String resourcePath, String fileName, String sourceName, Size imgSize) { + try { + + InputStream is = InputSource.class.getResourceAsStream(resourcePath); + File f = SysUtil.copyFileIsTemp(is, fileName, true).file; + + ImageSource src = new ImageSource(f.getAbsolutePath(), imgSize); + src.isDefault = true; + src.createdOn = sources.size(); + + addInputSource(sourceName, src); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void update(boolean isPaused) { + if(currentInputSource == null) return; + + try { + currentInputSource.setPaused(isPaused); + + Mat m = currentInputSource.update(); + if(m != null && !m.empty()) m.copyTo(lastMatFromSource); + } catch(Exception ex) { + Log.error("InputSourceManager", "Error while processing current source", ex); + Log.warn("InputSourceManager", "Changing to default source"); + + setInputSource(defaultSource); + } + } + + + public void addInputSource(String name, InputSource inputSource) { + addInputSource(name, inputSource, false); + } + + public void addInputSource(String name, InputSource inputSource, boolean dispatchedByUser) { + if (inputSource == null) { + return; + } + + if (sources.containsKey(name)) return; + + if(eocvSim.visualizer.sourceSelectorPanel != null) { + eocvSim.visualizer.sourceSelectorPanel.setAllowSourceSwitching(false); + } + inputSource.name = name; + + sources.put(name, inputSource); + + if(inputSource.createdOn == -1) + inputSource.createdOn = System.currentTimeMillis(); + + if(!inputSource.isDefault) { + inputSourceLoader.saveInputSource(name, inputSource); + inputSourceLoader.saveInputSourcesToFile(); + } + + if(eocvSim.visualizer.sourceSelectorPanel != null) { + SourceSelectorPanel selectorPanel = eocvSim.visualizer.sourceSelectorPanel; + + selectorPanel.updateSourcesList(); + + SwingUtilities.invokeLater(() -> { + JList sourceSelector = selectorPanel.getSourceSelector(); + + int currentSourceIndex = sourceSelector.getSelectedIndex(); + + if(dispatchedByUser) { + int index = selectorPanel.getIndexOf(name); + + sourceSelector.setSelectedIndex(index); + + requestSetInputSource(name); + + eocvSim.onMainUpdate.doOnce(() -> { + eocvSim.pipelineManager.requestSetPaused(false); + pauseIfImageTwoFrames(); + }); + } else { + sourceSelector.setSelectedIndex(currentSourceIndex); + } + + selectorPanel.setAllowSourceSwitching(true); + }); + } + + Log.info("InputSourceManager", "Adding InputSource " + inputSource + " (" + inputSource.getClass().getSimpleName() + ")"); + } + + public void deleteInputSource(String sourceName) { + InputSource src = sources.get(sourceName); + + if (src == null) return; + if (src.isDefault) return; + + sources.remove(sourceName); + + inputSourceLoader.deleteInputSource(sourceName); + inputSourceLoader.saveInputSourcesToFile(); + } + + public boolean setInputSource(String sourceName, boolean makeDefault) { + boolean result = setInputSource(sourceName); + + if(result && makeDefault) { + defaultSource = sourceName; + } + + return result; + } + + public boolean setInputSource(String sourceName) { + InputSource src = sources.get(sourceName); + + if (src != null) { + src.reset(); + src.eocvSim = eocvSim; + } + + //check if source type is a camera, and if so, create a please wait dialog + Visualizer.AsyncPleaseWaitDialog apwd = showApwdIfNeeded(sourceName); + + if (src != null) { + if (!src.init()) { + if (apwd != null) { + apwd.destroyDialog(); + } + + eocvSim.visualizer.asyncPleaseWaitDialog("Error while loading requested source", "Falling back to previous source", + "Close", new Dimension(300, 150), true, true); + + Log.error("InputSourceManager", "Error while loading requested source (" + sourceName + ") reported by itself (init method returned false)"); + + return false; + } + } + + //if there's a please wait dialog for a camera source, destroy it. + if (apwd != null) { + apwd.destroyDialog(); + } + + if (currentInputSource != null) { + currentInputSource.reset(); + } + + currentInputSource = src; + + //if pause on images option is turned on by user + if (eocvSim.configManager.getConfig().pauseOnImages) + pauseIfImage(); + + Log.info("InputSourceManager", "Set InputSource to " + currentInputSource.toString() + " (" + src.getClass().getSimpleName() + ")"); + + return true; + + } + + public boolean isNameOnUse(String name) { + return sources.containsKey(name); + } + + public void pauseIfImage() { + //if the new input source is an image, we will pause the next frame + //to execute one shot analysis on images and save resources. + if (SourceType.fromClass(currentInputSource.getClass()) == SourceType.IMAGE) { + eocvSim.onMainUpdate.doOnce(() -> + eocvSim.pipelineManager.setPaused( + true, + PipelineManager.PauseReason.IMAGE_ONE_ANALYSIS + ) + ); + } + } + + public void pauseIfImageTwoFrames() { + //if the new input source is an image, we will pause the next frame + //to execute one shot analysis on images and save resources. + eocvSim.onMainUpdate.doOnce(this::pauseIfImage); + } + + public void requestSetInputSource(String name) { + eocvSim.onMainUpdate.doOnce(() -> setInputSource(name)); + } + + public Visualizer.AsyncPleaseWaitDialog showApwdIfNeeded(String sourceName) { + Visualizer.AsyncPleaseWaitDialog apwd = null; + + if (getSourceType(sourceName) == SourceType.CAMERA || getSourceType(sourceName) == SourceType.VIDEO) { + apwd = eocvSim.visualizer.asyncPleaseWaitDialog( + "Opening source...", null, "Exit", + new Dimension(300, 150), true + ); + + apwd.onCancel(eocvSim::destroy); + } + + return apwd; + } + + public SourceType getSourceType(String sourceName) { + if(sourceName == null) { + return SourceType.UNKNOWN; + } + + InputSource source = sources.get(sourceName); + + if(source == null) { + return SourceType.UNKNOWN; + } + return SourceType.fromClass(source.getClass()); + } + + public InputSource[] getSortedInputSources() { + ArrayList sources = new ArrayList<>(this.sources.values()); + Collections.sort(sources); + + return sources.toArray(new InputSource[0]); + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/SourceType.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/SourceType.java index edd1ccad..e64178ec 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/SourceType.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/SourceType.java @@ -1,81 +1,81 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.input; - -import com.github.serivesmejia.eocvsim.input.source.*; - -import javax.swing.filechooser.FileFilter; -import java.io.File; - -public enum SourceType { - - IMAGE(new ImageSource(""), "Image"), - CAMERA(new CameraSource(0, null), "Camera"), - VIDEO(new VideoSource("", null), "Video"), - UNKNOWN(null, "Unknown"); - - public final Class klazz; - public final String coolName; - public final InputSource stubInstance; - - SourceType(InputSource instance, String coolName) { - stubInstance = instance; - - if(instance != null) - this.klazz = instance.getClass(); - else - this.klazz = null; - - this.coolName = coolName; - } - - public static SourceType fromClass(Class clazz) { - for(SourceType sourceType : values()) { - if(sourceType.klazz == clazz) { - return sourceType; - } - } - return UNKNOWN; - } - - public static SourceType fromCoolName(String coolName) { - for(SourceType sourceType : values()) { - if(sourceType.coolName.equalsIgnoreCase(coolName)) { - return sourceType; - } - } - return UNKNOWN; - } - - public static SourceType isFileUsableForSource(File file) { - for(SourceType type : values()) { - if(type.stubInstance != null && type.stubInstance.getFileFilters() != null) - if(type.stubInstance.getFileFilters().accept(file)) - return type; - } - - return UNKNOWN; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.input; + +import com.github.serivesmejia.eocvsim.input.source.*; + +import javax.swing.filechooser.FileFilter; +import java.io.File; + +public enum SourceType { + + IMAGE(new ImageSource(""), "Image"), + CAMERA(new CameraSource(0, null), "Camera"), + VIDEO(new VideoSource("", null), "Video"), + UNKNOWN(null, "Unknown"); + + public final Class klazz; + public final String coolName; + public final InputSource stubInstance; + + SourceType(InputSource instance, String coolName) { + stubInstance = instance; + + if(instance != null) + this.klazz = instance.getClass(); + else + this.klazz = null; + + this.coolName = coolName; + } + + public static SourceType fromClass(Class clazz) { + for(SourceType sourceType : values()) { + if(sourceType.klazz == clazz) { + return sourceType; + } + } + return UNKNOWN; + } + + public static SourceType fromCoolName(String coolName) { + for(SourceType sourceType : values()) { + if(sourceType.coolName.equalsIgnoreCase(coolName)) { + return sourceType; + } + } + return UNKNOWN; + } + + public static SourceType isFileUsableForSource(File file) { + for(SourceType type : values()) { + if(type.stubInstance != null && type.stubInstance.getFileFilters() != null) + if(type.stubInstance.getFileFilters().accept(file)) + return type; + } + + return UNKNOWN; + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSource.java index f7f7d77f..8d098f45 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSource.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/CameraSource.java @@ -1,193 +1,202 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.input.source; - -import com.github.serivesmejia.eocvsim.gui.Visualizer; -import com.github.serivesmejia.eocvsim.input.InputSource; -import com.github.serivesmejia.eocvsim.util.Log; -import com.google.gson.annotations.Expose; -import org.opencv.core.Mat; -import org.opencv.core.Size; -import org.opencv.imgproc.Imgproc; -import org.opencv.videoio.VideoCapture; -import org.openftc.easyopencv.MatRecycler; - -import javax.swing.filechooser.FileFilter; - -public class CameraSource extends InputSource { - - @Expose - private final int webcamIndex; - private transient VideoCapture camera = null; - - private transient MatRecycler.RecyclableMat lastFramePaused = null; - private transient MatRecycler.RecyclableMat lastFrame = null; - - private transient boolean initialized = false; - - @Expose - private volatile Size size; - - private volatile transient MatRecycler matRecycler; - - public CameraSource(int webcamIndex, Size size) { - this.webcamIndex = webcamIndex; - this.size = size; - } - - @Override - public boolean init() { - - if (initialized) return false; - initialized = true; - - camera = new VideoCapture(); - camera.open(webcamIndex); - - if (!camera.isOpened()) { - Log.error("CameraSource", "Unable to open camera " + webcamIndex); - return false; - } - - if (matRecycler == null) matRecycler = new MatRecycler(4); - - MatRecycler.RecyclableMat newFrame = matRecycler.takeMat(); - - camera.read(newFrame); - - if (newFrame.empty()) { - Log.error("CameraSource", "Unable to open camera " + webcamIndex + ", returned Mat was empty."); - newFrame.release(); - return false; - } - - matRecycler.returnMat(newFrame); - - return true; - - } - - @Override - public void reset() { - - if (!initialized) return; - if (camera != null && camera.isOpened()) camera.release(); - - if(lastFrame != null && lastFrame.isCheckedOut()) - lastFrame.returnMat(); - if(lastFramePaused != null && lastFramePaused.isCheckedOut()) - lastFramePaused.returnMat(); - - camera = null; - initialized = false; - - } - - @Override - public void close() { - if (camera != null && camera.isOpened()) camera.release(); - } - - @Override - public Mat update() { - - if (isPaused) { - return lastFramePaused; - } else if (lastFramePaused != null) { - lastFramePaused.release(); - lastFramePaused.returnMat(); - lastFramePaused = null; - } - - if (lastFrame == null) lastFrame = matRecycler.takeMat(); - if (camera == null) return lastFrame; - - MatRecycler.RecyclableMat newFrame = matRecycler.takeMat(); - - camera.read(newFrame); - - if (newFrame.empty()) { - newFrame.returnMat(); - return lastFrame; - } - - if (size == null) size = lastFrame.size(); - - Imgproc.cvtColor(newFrame, lastFrame, Imgproc.COLOR_BGR2RGB); - Imgproc.resize(lastFrame, lastFrame, size, 0.0, 0.0, Imgproc.INTER_AREA); - - newFrame.release(); - newFrame.returnMat(); - - return lastFrame; - - } - - @Override - public void onPause() { - - if (lastFrame != null) lastFrame.release(); - if (lastFramePaused == null) lastFramePaused = matRecycler.takeMat(); - - camera.read(lastFramePaused); - - Imgproc.cvtColor(lastFramePaused, lastFramePaused, Imgproc.COLOR_BGR2RGB); - Imgproc.resize(lastFramePaused, lastFramePaused, size, 0.0, 0.0, Imgproc.INTER_AREA); - - update(); - - camera.release(); - camera = null; - - } - - @Override - public void onResume() { - - Visualizer.AsyncPleaseWaitDialog apwdCam = eocvSim.inputSourceManager.showApwdIfNeeded(name); - - camera = new VideoCapture(); - camera.open(webcamIndex); - - apwdCam.destroyDialog(); - - } - - @Override - protected InputSource internalCloneSource() { - return new CameraSource(webcamIndex, size); - } - - @Override - public FileFilter getFileFilters() { - return null; - } - - @Override - public String toString() { - if (size == null) size = new Size(); - return "CameraSource(" + webcamIndex + ", " + (size != null ? size.toString() : "null") + ")"; - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.input.source; + +import com.github.serivesmejia.eocvsim.gui.Visualizer; +import com.github.serivesmejia.eocvsim.input.InputSource; +import com.github.serivesmejia.eocvsim.util.Log; +import com.google.gson.annotations.Expose; +import org.opencv.core.Mat; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; +import org.opencv.videoio.VideoCapture; +import org.opencv.videoio.Videoio; +import org.openftc.easyopencv.MatRecycler; + +import javax.swing.filechooser.FileFilter; + +public class CameraSource extends InputSource { + + @Expose + private final int webcamIndex; + private transient VideoCapture camera = null; + + private transient MatRecycler.RecyclableMat lastFramePaused = null; + private transient MatRecycler.RecyclableMat lastFrame = null; + + private transient boolean initialized = false; + + @Expose + private volatile Size size; + + private volatile transient MatRecycler matRecycler; + + private transient long capTimeNanos = 0; + + public CameraSource(int webcamIndex, Size size) { + this.webcamIndex = webcamIndex; + this.size = size; + } + + @Override + public boolean init() { + + if (initialized) return false; + initialized = true; + + camera = new VideoCapture(); + camera.open(webcamIndex); + + if (!camera.isOpened()) { + Log.error("CameraSource", "Unable to open camera " + webcamIndex); + return false; + } + + if (matRecycler == null) matRecycler = new MatRecycler(4); + + MatRecycler.RecyclableMat newFrame = matRecycler.takeMat(); + + camera.read(newFrame); + + if (newFrame.empty()) { + Log.error("CameraSource", "Unable to open camera " + webcamIndex + ", returned Mat was empty."); + newFrame.release(); + return false; + } + + matRecycler.returnMat(newFrame); + + return true; + + } + + @Override + public void reset() { + + if (!initialized) return; + if (camera != null && camera.isOpened()) camera.release(); + + if(lastFrame != null && lastFrame.isCheckedOut()) + lastFrame.returnMat(); + if(lastFramePaused != null && lastFramePaused.isCheckedOut()) + lastFramePaused.returnMat(); + + camera = null; + initialized = false; + + } + + @Override + public void close() { + if (camera != null && camera.isOpened()) camera.release(); + } + + @Override + public Mat update() { + + if (isPaused) { + return lastFramePaused; + } else if (lastFramePaused != null) { + lastFramePaused.release(); + lastFramePaused.returnMat(); + lastFramePaused = null; + } + + if (lastFrame == null) lastFrame = matRecycler.takeMat(); + if (camera == null) return lastFrame; + + MatRecycler.RecyclableMat newFrame = matRecycler.takeMat(); + + camera.read(newFrame); + capTimeNanos = System.nanoTime(); + + if (newFrame.empty()) { + newFrame.returnMat(); + return lastFrame; + } + + if (size == null) size = lastFrame.size(); + + Imgproc.cvtColor(newFrame, lastFrame, Imgproc.COLOR_BGR2RGB); + Imgproc.resize(lastFrame, lastFrame, size, 0.0, 0.0, Imgproc.INTER_AREA); + + newFrame.release(); + newFrame.returnMat(); + + return lastFrame; + + } + + @Override + public void onPause() { + + if (lastFrame != null) lastFrame.release(); + if (lastFramePaused == null) lastFramePaused = matRecycler.takeMat(); + + camera.read(lastFramePaused); + + Imgproc.cvtColor(lastFramePaused, lastFramePaused, Imgproc.COLOR_BGR2RGB); + Imgproc.resize(lastFramePaused, lastFramePaused, size, 0.0, 0.0, Imgproc.INTER_AREA); + + update(); + + camera.release(); + camera = null; + + } + + @Override + public void onResume() { + + Visualizer.AsyncPleaseWaitDialog apwdCam = eocvSim.inputSourceManager.showApwdIfNeeded(name); + + camera = new VideoCapture(); + camera.open(webcamIndex); + + apwdCam.destroyDialog(); + + } + + @Override + protected InputSource internalCloneSource() { + return new CameraSource(webcamIndex, size); + } + + @Override + public FileFilter getFileFilters() { + return null; + } + + @Override + public long getCaptureTimeNanos() { + return capTimeNanos; + } + + @Override + public String toString() { + if (size == null) size = new Size(); + return "CameraSource(" + webcamIndex + ", " + (size != null ? size.toString() : "null") + ")"; + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/ImageSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/ImageSource.java index a92a1298..39187cd6 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/ImageSource.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/ImageSource.java @@ -1,173 +1,178 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.input.source; - -import com.github.serivesmejia.eocvsim.input.InputSource; -import com.github.serivesmejia.eocvsim.util.FileFilters; -import com.google.gson.annotations.Expose; -import org.opencv.core.Mat; -import org.opencv.core.Size; -import org.opencv.imgcodecs.Imgcodecs; -import org.opencv.imgproc.Imgproc; -import org.openftc.easyopencv.MatRecycler; - -import javax.swing.filechooser.FileFilter; - -public class ImageSource extends InputSource { - - @Expose - private final String imgPath; - @Expose - private volatile Size size; - - private volatile transient MatRecycler.RecyclableMat img; - private volatile transient MatRecycler.RecyclableMat lastCloneTo; - - private volatile transient boolean initialized = false; - - private volatile transient MatRecycler matRecycler = new MatRecycler(2); - - public ImageSource(String imgPath) { - this(imgPath, null); - } - - public ImageSource(String imgPath, Size size) { - this.imgPath = imgPath; - this.size = size; - } - - @Override - public boolean init() { - - if (initialized) return false; - initialized = true; - - if (matRecycler == null) matRecycler = new MatRecycler(2); - - readImage(); - - return img != null && !img.empty(); - - } - - @Override - public void onPause() { - //if(img != null) img.release(); - } - - @Override - public void onResume() { - } - - @Override - public void reset() { - - if (!initialized) return; - - if (lastCloneTo != null) { - lastCloneTo.returnMat(); - lastCloneTo = null; - } - - if (img != null) { - img.returnMat(); - img = null; - } - - matRecycler.releaseAll(); - - initialized = false; - - } - - public void close() { - - if (img != null) { - matRecycler.returnMat(img); - img = null; - } - - if (lastCloneTo != null) { - lastCloneTo.returnMat(); - lastCloneTo = null; - } - - matRecycler.releaseAll(); - - } - - public void readImage() { - - Mat readMat = Imgcodecs.imread(this.imgPath); - - if (img == null) img = matRecycler.takeMat(); - - if (readMat.empty()) { - return; - } - - readMat.copyTo(img); - readMat.release(); - - if (this.size != null) { - Imgproc.resize(img, img, this.size, 0.0, 0.0, Imgproc.INTER_AREA); - } else { - this.size = img.size(); - } - - Imgproc.cvtColor(img, img, Imgproc.COLOR_BGR2RGB); - - } - - @Override - public Mat update() { - - if (isPaused) return lastCloneTo; - if (lastCloneTo == null) lastCloneTo = matRecycler.takeMat(); - - if (img == null) return null; - - img.copyTo(lastCloneTo); - - return lastCloneTo; - - } - - @Override - protected InputSource internalCloneSource() { - return new ImageSource(imgPath, size); - } - - @Override - public FileFilter getFileFilters() { - return FileFilters.imagesFilter; - } - - @Override - public String toString() { - if (size == null) size = new Size(); - return "ImageSource(\"" + imgPath + "\", " + (size != null ? size.toString() : "null") + ")"; - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.input.source; + +import com.github.serivesmejia.eocvsim.input.InputSource; +import com.github.serivesmejia.eocvsim.util.FileFilters; +import com.google.gson.annotations.Expose; +import org.opencv.core.Mat; +import org.opencv.core.Size; +import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; +import org.openftc.easyopencv.MatRecycler; + +import javax.swing.filechooser.FileFilter; + +public class ImageSource extends InputSource { + + @Expose + private final String imgPath; + @Expose + private volatile Size size; + + private volatile transient MatRecycler.RecyclableMat img; + private volatile transient MatRecycler.RecyclableMat lastCloneTo; + + private volatile transient boolean initialized = false; + + private volatile transient MatRecycler matRecycler = new MatRecycler(2); + + public ImageSource(String imgPath) { + this(imgPath, null); + } + + public ImageSource(String imgPath, Size size) { + this.imgPath = imgPath; + this.size = size; + } + + @Override + public boolean init() { + + if (initialized) return false; + initialized = true; + + if (matRecycler == null) matRecycler = new MatRecycler(2); + + readImage(); + + return img != null && !img.empty(); + + } + + @Override + public void onPause() { + //if(img != null) img.release(); + } + + @Override + public void onResume() { + } + + @Override + public void reset() { + + if (!initialized) return; + + if (lastCloneTo != null) { + lastCloneTo.returnMat(); + lastCloneTo = null; + } + + if (img != null) { + img.returnMat(); + img = null; + } + + matRecycler.releaseAll(); + + initialized = false; + + } + + public void close() { + + if (img != null) { + matRecycler.returnMat(img); + img = null; + } + + if (lastCloneTo != null) { + lastCloneTo.returnMat(); + lastCloneTo = null; + } + + matRecycler.releaseAll(); + + } + + public void readImage() { + + Mat readMat = Imgcodecs.imread(this.imgPath); + + if (img == null) img = matRecycler.takeMat(); + + if (readMat.empty()) { + return; + } + + readMat.copyTo(img); + readMat.release(); + + if (this.size != null) { + Imgproc.resize(img, img, this.size, 0.0, 0.0, Imgproc.INTER_AREA); + } else { + this.size = img.size(); + } + + Imgproc.cvtColor(img, img, Imgproc.COLOR_BGR2RGB); + + } + + @Override + public Mat update() { + + if (isPaused) return lastCloneTo; + if (lastCloneTo == null) lastCloneTo = matRecycler.takeMat(); + + if (img == null) return null; + + img.copyTo(lastCloneTo); + + return lastCloneTo; + + } + + @Override + protected InputSource internalCloneSource() { + return new ImageSource(imgPath, size); + } + + @Override + public FileFilter getFileFilters() { + return FileFilters.imagesFilter; + } + + @Override + public long getCaptureTimeNanos() { + return System.nanoTime(); + } + + @Override + public String toString() { + if (size == null) size = new Size(); + return "ImageSource(\"" + imgPath + "\", " + (size != null ? size.toString() : "null") + ")"; + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/VideoSource.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/VideoSource.java index 00633da4..3c11120b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/VideoSource.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/input/source/VideoSource.java @@ -1,210 +1,218 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.input.source; - -import com.github.serivesmejia.eocvsim.gui.Visualizer; -import com.github.serivesmejia.eocvsim.input.InputSource; -import com.github.serivesmejia.eocvsim.util.FileFilters; -import com.github.serivesmejia.eocvsim.util.Log; -import com.google.gson.annotations.Expose; -import org.opencv.core.Mat; -import org.opencv.core.Size; -import org.opencv.imgproc.Imgproc; -import org.opencv.videoio.VideoCapture; -import org.opencv.videoio.Videoio; -import org.openftc.easyopencv.MatRecycler; - -import javax.swing.filechooser.FileFilter; -import java.util.Objects; - -public class VideoSource extends InputSource { - - @Expose - private final String videoPath; - - private transient VideoCapture video = null; - - private transient MatRecycler.RecyclableMat lastFramePaused = null; - private transient MatRecycler.RecyclableMat lastFrame = null; - - private transient boolean initialized = false; - - @Expose - private volatile Size size; - - private volatile transient MatRecycler matRecycler = null; - - private transient double lastFramePosition = 0; - - public VideoSource(String videoPath, Size size) { - this.videoPath = videoPath; - this.size = size; - } - - @Override - public boolean init() { - - if (initialized) return false; - initialized = true; - - video = new VideoCapture(); - video.open(videoPath); - - if (!video.isOpened()) { - Log.error("VideoSource", "Unable to open video " + videoPath); - return false; - } - - if (matRecycler == null) matRecycler = new MatRecycler(4); - - MatRecycler.RecyclableMat newFrame = matRecycler.takeMat(); - newFrame.release(); - - video.read(newFrame); - - if (newFrame.empty()) { - Log.error("VideoSource", "Unable to open video " + videoPath + ", returned Mat was empty."); - return false; - } - - newFrame.release(); - matRecycler.returnMat(newFrame); - - return true; - - } - - @Override - public void reset() { - - if (!initialized) return; - - if (video != null && video.isOpened()) video.release(); - - if(lastFrame != null && lastFrame.isCheckedOut()) - lastFrame.returnMat(); - if(lastFramePaused != null && lastFramePaused.isCheckedOut()) - lastFramePaused.returnMat(); - - matRecycler.releaseAll(); - - video = null; - initialized = false; - - } - - @Override - public void close() { - - if(video != null && video.isOpened()) video.release(); - if(lastFrame != null) lastFrame.returnMat(); - - if (lastFramePaused != null) { - lastFramePaused.returnMat(); - lastFramePaused = null; - } - - } - - @Override - public Mat update() { - - if (isPaused) { - return lastFramePaused; - } else if (lastFramePaused != null) { - lastFramePaused.returnMat(); - lastFramePaused = null; - } - - if (lastFrame == null) lastFrame = matRecycler.takeMat(); - if (video == null) return lastFrame; - - MatRecycler.RecyclableMat newFrame = matRecycler.takeMat(); - - video.read(newFrame); - - //with videocapture for video files, when an empty mat is returned - //the most likely reason is that the video ended, so we set the - //playback position back to 0 for looping in here and start over - //in next update - if (newFrame.empty()) { - newFrame.returnMat(); - video.set(Videoio.CAP_PROP_POS_FRAMES, 0); - return lastFrame; - } - - if (size == null) size = lastFrame.size(); - - Imgproc.cvtColor(newFrame, lastFrame, Imgproc.COLOR_BGR2RGB); - Imgproc.resize(lastFrame, lastFrame, size, 0.0, 0.0, Imgproc.INTER_AREA); - - matRecycler.returnMat(newFrame); - - return lastFrame; - - } - - @Override - public void onPause() { - - if (lastFrame != null) lastFrame.release(); - if (lastFramePaused == null) lastFramePaused = matRecycler.takeMat(); - - video.read(lastFramePaused); - - Imgproc.cvtColor(lastFramePaused, lastFramePaused, Imgproc.COLOR_BGR2RGB); - Imgproc.resize(lastFramePaused, lastFramePaused, size, 0.0, 0.0, Imgproc.INTER_AREA); - - update(); - - lastFramePosition = video.get(Videoio.CAP_PROP_POS_FRAMES); - - video.release(); - video = null; - - } - - @Override - public void onResume() { - video = new VideoCapture(); - video.open(videoPath); - video.set(Videoio.CAP_PROP_POS_FRAMES, lastFramePosition); - } - - @Override - protected InputSource internalCloneSource() { - return new VideoSource(videoPath, size); - } - - @Override - public FileFilter getFileFilters() { - return FileFilters.videoMediaFilter; - } - - @Override - public String toString() { - return "VideoSource(" + videoPath + ", " + (size != null ? size.toString() : "null") + ")"; - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.input.source; + +import com.github.serivesmejia.eocvsim.gui.Visualizer; +import com.github.serivesmejia.eocvsim.input.InputSource; +import com.github.serivesmejia.eocvsim.util.FileFilters; +import com.github.serivesmejia.eocvsim.util.Log; +import com.google.gson.annotations.Expose; +import org.opencv.core.Mat; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; +import org.opencv.videoio.VideoCapture; +import org.opencv.videoio.Videoio; +import org.openftc.easyopencv.MatRecycler; + +import javax.swing.filechooser.FileFilter; +import java.util.Objects; + +public class VideoSource extends InputSource { + + @Expose + private final String videoPath; + + private transient VideoCapture video = null; + + private transient MatRecycler.RecyclableMat lastFramePaused = null; + private transient MatRecycler.RecyclableMat lastFrame = null; + + private transient boolean initialized = false; + + @Expose + private volatile Size size; + + private volatile transient MatRecycler matRecycler = null; + + private transient double lastFramePosition = 0; + + private transient long capTimeNanos = 0; + + public VideoSource(String videoPath, Size size) { + this.videoPath = videoPath; + this.size = size; + } + + @Override + public boolean init() { + + if (initialized) return false; + initialized = true; + + video = new VideoCapture(); + video.open(videoPath); + + if (!video.isOpened()) { + Log.error("VideoSource", "Unable to open video " + videoPath); + return false; + } + + if (matRecycler == null) matRecycler = new MatRecycler(4); + + MatRecycler.RecyclableMat newFrame = matRecycler.takeMat(); + newFrame.release(); + + video.read(newFrame); + + if (newFrame.empty()) { + Log.error("VideoSource", "Unable to open video " + videoPath + ", returned Mat was empty."); + return false; + } + + newFrame.release(); + matRecycler.returnMat(newFrame); + + return true; + + } + + @Override + public void reset() { + + if (!initialized) return; + + if (video != null && video.isOpened()) video.release(); + + if(lastFrame != null && lastFrame.isCheckedOut()) + lastFrame.returnMat(); + if(lastFramePaused != null && lastFramePaused.isCheckedOut()) + lastFramePaused.returnMat(); + + matRecycler.releaseAll(); + + video = null; + initialized = false; + + } + + @Override + public void close() { + + if(video != null && video.isOpened()) video.release(); + if(lastFrame != null) lastFrame.returnMat(); + + if (lastFramePaused != null) { + lastFramePaused.returnMat(); + lastFramePaused = null; + } + + } + + @Override + public Mat update() { + + if (isPaused) { + return lastFramePaused; + } else if (lastFramePaused != null) { + lastFramePaused.returnMat(); + lastFramePaused = null; + } + + if (lastFrame == null) lastFrame = matRecycler.takeMat(); + if (video == null) return lastFrame; + + MatRecycler.RecyclableMat newFrame = matRecycler.takeMat(); + + video.read(newFrame); + capTimeNanos = System.nanoTime(); + + //with videocapture for video files, when an empty mat is returned + //the most likely reason is that the video ended, so we set the + //playback position back to 0 for looping in here and start over + //in next update + if (newFrame.empty()) { + newFrame.returnMat(); + video.set(Videoio.CAP_PROP_POS_FRAMES, 0); + return lastFrame; + } + + if (size == null) size = lastFrame.size(); + + Imgproc.cvtColor(newFrame, lastFrame, Imgproc.COLOR_BGR2RGB); + Imgproc.resize(lastFrame, lastFrame, size, 0.0, 0.0, Imgproc.INTER_AREA); + + matRecycler.returnMat(newFrame); + + return lastFrame; + + } + + @Override + public void onPause() { + + if (lastFrame != null) lastFrame.release(); + if (lastFramePaused == null) lastFramePaused = matRecycler.takeMat(); + + video.read(lastFramePaused); + + Imgproc.cvtColor(lastFramePaused, lastFramePaused, Imgproc.COLOR_BGR2RGB); + Imgproc.resize(lastFramePaused, lastFramePaused, size, 0.0, 0.0, Imgproc.INTER_AREA); + + update(); + + lastFramePosition = video.get(Videoio.CAP_PROP_POS_FRAMES); + + video.release(); + video = null; + + } + + @Override + public void onResume() { + video = new VideoCapture(); + video.open(videoPath); + video.set(Videoio.CAP_PROP_POS_FRAMES, lastFramePosition); + } + + @Override + protected InputSource internalCloneSource() { + return new VideoSource(videoPath, size); + } + + @Override + public FileFilter getFileFilters() { + return FileFilters.videoMediaFilter; + } + + @Override + public long getCaptureTimeNanos() { + return capTimeNanos; + } + + @Override + public String toString() { + return "VideoSource(" + videoPath + ", " + (size != null ? size.toString() : "null") + ")"; + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/VideoRecordingSession.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/VideoRecordingSession.kt index 2b69495f..81e0d6d9 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/VideoRecordingSession.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/output/VideoRecordingSession.kt @@ -1,160 +1,160 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.output - -import com.github.serivesmejia.eocvsim.gui.util.MatPoster -import com.github.serivesmejia.eocvsim.util.StrUtil -import com.github.serivesmejia.eocvsim.util.extension.aspectRatio -import com.github.serivesmejia.eocvsim.util.extension.clipTo -import com.github.serivesmejia.eocvsim.util.fps.FpsCounter -import org.opencv.core.* -import org.opencv.imgproc.Imgproc -import org.opencv.videoio.VideoWriter -import java.io.File -import java.nio.file.Files -import java.nio.file.StandardCopyOption -import kotlin.math.roundToInt - -class VideoRecordingSession( - val videoFps: Double = 30.0, - val videoSize: Size = Size(320.0, 240.0), - val isFramesRgb: Boolean = true -) { - - private val videoWriter = VideoWriter() - private val tempFile = File.createTempFile(StrUtil.random(), ".avi") - - @Volatile private var videoMat: Mat? = null - - val matPoster = MatPoster("VideoRec", videoFps.toInt()) - - private val fpsCounter = FpsCounter() - - @Volatile var hasStarted = false - private set - @Volatile var hasStopped = false - private set - - val isRecording get() = hasStarted && !hasStopped - - init { - matPoster.addPostable { postMat(it) } - } - - fun startRecordingSession() { - videoWriter.open(tempFile.toString(), VideoWriter.fourcc('M', 'J', 'P', 'G'), videoFps, videoSize) - hasStarted = true; - } - - fun stopRecordingSession() { - videoWriter.release(); videoMat?.release(); matPoster.stop() - hasStopped = true - } - - fun saveTo(file: File) { - if(!hasStopped) return - Files.copy(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING) - Files.delete(tempFile.toPath()) - } - - fun discardVideo() { - if(tempFile.exists()) - Files.delete(tempFile.toPath()) - } - - @Synchronized fun postMatAsync(inputMat: Mat) { - if(!videoWriter.isOpened) return - matPoster.post(inputMat) - } - - @Synchronized fun postMat(inputMat: Mat) { - if(!videoWriter.isOpened) return - - if(videoMat == null) - videoMat = Mat(videoSize, inputMat.type()) - else - videoMat!!.setTo(Scalar(0.0, 0.0, 0.0)) - - //we need BGR frames - if(isFramesRgb) { - Imgproc.cvtColor(inputMat, inputMat, Imgproc.COLOR_RGB2BGR) - } - - if(inputMat.size() == videoSize) { //nice, the mat size is the exact same as the video size - compensateFpsWrite(inputMat, fpsCounter.fps.toDouble(), videoFps) - } else { //uh oh, this might get a bit harder here... - val videoR = videoSize.aspectRatio() - val inputR = inputMat.aspectRatio() - - //ok, we have the same aspect ratio, we can just scale to the required size - if(videoR == inputR) { - Imgproc.resize(inputMat, videoMat, videoSize, 0.0, 0.0, Imgproc.INTER_AREA) - compensateFpsWrite(videoMat!!, fpsCounter.fps.toDouble(), videoFps) - } else { //hmm, not the same aspect ratio, we'll need to do some fancy stuff here... - val inputW = inputMat.size().width - val inputH = inputMat.size().height - - val widthRatio = videoSize.width / inputW - val heightRatio = videoSize.height / inputH - val bestRatio = widthRatio.coerceAtMost(heightRatio) - - val newSize = Size(inputW * bestRatio, inputH * bestRatio).clipTo(videoSize) - - //get offsets so that we center the image instead of leaving it at (0,0) - //(basically the black bars you see) - val xOffset = (videoSize.width - newSize.width) / 2 - val yOffset = (videoSize.height - newSize.height) / 2 - - Imgproc.resize(inputMat, inputMat, newSize, 0.0, 0.0, Imgproc.INTER_AREA) - - //get submat of the exact required size and offset position from the "videoMat", - //which has the user-defined size of the current video. - val submat = videoMat!!.submat(Rect(Point(xOffset, yOffset), newSize)) - - //then we copy our adjusted mat into the gotten submat. since a submat is just - //a reference to the parent mat, when we copy here our data will be actually - //copied to the actual mat, and so our new mat will be of the correct size and - //centered with the required offset - inputMat.copyTo(submat); - - compensateFpsWrite(videoMat!!, fpsCounter.fps.toDouble(), videoFps) - } - - fpsCounter.update() - } - } - - //compensating for variable fps, we write the same mat multiple - //times so that our video stays in sync with the correct speed. - @Synchronized private fun compensateFpsWrite(mat: Mat, currentFps: Double, targetFps: Double) { - if (currentFps < targetFps && currentFps > 0) { - repeat((targetFps / currentFps).roundToInt()) { - videoWriter.write(mat) - } - } else { - videoWriter.write(mat) - } - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.output + +import com.github.serivesmejia.eocvsim.gui.util.MatPoster +import com.github.serivesmejia.eocvsim.util.StrUtil +import com.github.serivesmejia.eocvsim.util.extension.aspectRatio +import com.github.serivesmejia.eocvsim.util.extension.clipTo +import com.github.serivesmejia.eocvsim.util.fps.FpsCounter +import org.opencv.core.* +import org.opencv.imgproc.Imgproc +import org.opencv.videoio.VideoWriter +import java.io.File +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import kotlin.math.roundToInt + +class VideoRecordingSession( + val videoFps: Double = 30.0, + val videoSize: Size = Size(320.0, 240.0), + val isFramesRgb: Boolean = true +) { + + private val videoWriter = VideoWriter() + private val tempFile = File.createTempFile(StrUtil.random(), ".avi") + + @Volatile private var videoMat: Mat? = null + + val matPoster = MatPoster("VideoRec", videoFps.toInt()) + + private val fpsCounter = FpsCounter() + + @Volatile var hasStarted = false + private set + @Volatile var hasStopped = false + private set + + val isRecording get() = hasStarted && !hasStopped + + init { + matPoster.addPostable { postMat(it) } + } + + fun startRecordingSession() { + videoWriter.open(tempFile.toString(), VideoWriter.fourcc('M', 'J', 'P', 'G'), videoFps, videoSize) + hasStarted = true; + } + + fun stopRecordingSession() { + videoWriter.release(); videoMat?.release(); matPoster.stop() + hasStopped = true + } + + fun saveTo(file: File) { + if(!hasStopped) return + Files.copy(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING) + Files.delete(tempFile.toPath()) + } + + fun discardVideo() { + if(tempFile.exists()) + Files.delete(tempFile.toPath()) + } + + @Synchronized fun postMatAsync(inputMat: Mat) { + if(!videoWriter.isOpened) return + matPoster.post(inputMat) + } + + @Synchronized fun postMat(inputMat: Mat) { + if(!videoWriter.isOpened) return + + if(videoMat == null) + videoMat = Mat(videoSize, inputMat.type()) + else + videoMat!!.setTo(Scalar(0.0, 0.0, 0.0)) + + //we need BGR frames + if(isFramesRgb) { + Imgproc.cvtColor(inputMat, inputMat, Imgproc.COLOR_RGB2BGR) + } + + if(inputMat.size() == videoSize) { //nice, the mat size is the exact same as the video size + compensateFpsWrite(inputMat, fpsCounter.fps.toDouble(), videoFps) + } else { //uh oh, this might get a bit harder here... + val videoR = videoSize.aspectRatio() + val inputR = inputMat.aspectRatio() + + //ok, we have the same aspect ratio, we can just scale to the required size + if(videoR == inputR) { + Imgproc.resize(inputMat, videoMat, videoSize, 0.0, 0.0, Imgproc.INTER_AREA) + compensateFpsWrite(videoMat!!, fpsCounter.fps.toDouble(), videoFps) + } else { //hmm, not the same aspect ratio, we'll need to do some fancy stuff here... + val inputW = inputMat.size().width + val inputH = inputMat.size().height + + val widthRatio = videoSize.width / inputW + val heightRatio = videoSize.height / inputH + val bestRatio = widthRatio.coerceAtMost(heightRatio) + + val newSize = Size(inputW * bestRatio, inputH * bestRatio).clipTo(videoSize) + + //get offsets so that we center the image instead of leaving it at (0,0) + //(basically the black bars you see) + val xOffset = (videoSize.width - newSize.width) / 2 + val yOffset = (videoSize.height - newSize.height) / 2 + + Imgproc.resize(inputMat, inputMat, newSize, 0.0, 0.0, Imgproc.INTER_AREA) + + //get submat of the exact required size and offset position from the "videoMat", + //which has the user-defined size of the current video. + val submat = videoMat!!.submat(Rect(Point(xOffset, yOffset), newSize)) + + //then we copy our adjusted mat into the gotten submat. since a submat is just + //a reference to the parent mat, when we copy here our data will be actually + //copied to the actual mat, and so our new mat will be of the correct size and + //centered with the required offset + inputMat.copyTo(submat); + + compensateFpsWrite(videoMat!!, fpsCounter.fps.toDouble(), videoFps) + } + + fpsCounter.update() + } + } + + //compensating for variable fps, we write the same mat multiple + //times so that our video stays in sync with the correct speed. + @Synchronized private fun compensateFpsWrite(mat: Mat, currentFps: Double, targetFps: Double) { + if (currentFps < targetFps && currentFps > 0) { + repeat((targetFps / currentFps).roundToInt()) { + videoWriter.write(mat) + } + } else { + videoWriter.write(mat) + } + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java index 40cc3460..1104f6c0 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java @@ -1,83 +1,83 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline; - -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.opencv.core.*; -import org.opencv.imgproc.Imgproc; -import org.openftc.easyopencv.OpenCvPipeline; - -public class DefaultPipeline extends OpenCvPipeline { - - public int blur = 0; - - private Telemetry telemetry; - - public DefaultPipeline(Telemetry telemetry) { - this.telemetry = telemetry; - } - - @Override - public Mat processFrame(Mat input) { - - double aspectRatio = (double) input.height() / (double) input.width(); - double aspectRatioPercentage = aspectRatio / (580.0 / 480.0); - - telemetry.addData("[>]", "Default pipeline selected."); - telemetry.addData("[Aspect Ratio]", aspectRatio + " (" + String.format("%.2f", aspectRatioPercentage * 100) + "%)"); - telemetry.addData("[Blur]", blur + " (change this value in tuner menu)"); - telemetry.update(); - - if (blur > 0 && blur % 2 == 1) { - Imgproc.GaussianBlur(input, input, new Size(blur, blur), 0); - } - - // Outline - Imgproc.putText( - input, - "Default pipeline selected", - new Point(0, 22 * aspectRatioPercentage), - Imgproc.FONT_HERSHEY_PLAIN, - 2 * aspectRatioPercentage, - new Scalar(255, 255, 255), - (int) Math.round(5 * aspectRatioPercentage) - ); - - - //Text - Imgproc.putText( - input, - "Default pipeline selected", - new Point(0, 22 * aspectRatioPercentage), - Imgproc.FONT_HERSHEY_PLAIN, - 2 * aspectRatioPercentage, - new Scalar(0, 0, 0), - (int) Math.round(2 * aspectRatioPercentage) - ); - - return input; - - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.pipeline; + +import org.firstinspires.ftc.robotcore.external.Telemetry; +import org.opencv.core.*; +import org.opencv.imgproc.Imgproc; +import org.openftc.easyopencv.OpenCvPipeline; + +public class DefaultPipeline extends OpenCvPipeline { + + public int blur = 0; + + private Telemetry telemetry; + + public DefaultPipeline(Telemetry telemetry) { + this.telemetry = telemetry; + } + + @Override + public Mat processFrame(Mat input) { + + double aspectRatio = (double) input.height() / (double) input.width(); + double aspectRatioPercentage = aspectRatio / (580.0 / 480.0); + + telemetry.addData("[>]", "Default pipeline selected."); + telemetry.addData("[Aspect Ratio]", aspectRatio + " (" + String.format("%.2f", aspectRatioPercentage * 100) + "%)"); + telemetry.addData("[Blur]", blur + " (change this value in tuner menu)"); + telemetry.update(); + + if (blur > 0 && blur % 2 == 1) { + Imgproc.GaussianBlur(input, input, new Size(blur, blur), 0); + } + + // Outline + Imgproc.putText( + input, + "Default pipeline selected", + new Point(0, 22 * aspectRatioPercentage), + Imgproc.FONT_HERSHEY_PLAIN, + 2 * aspectRatioPercentage, + new Scalar(255, 255, 255), + (int) Math.round(5 * aspectRatioPercentage) + ); + + + //Text + Imgproc.putText( + input, + "Default pipeline selected", + new Point(0, 22 * aspectRatioPercentage), + Imgproc.FONT_HERSHEY_PLAIN, + 2 * aspectRatioPercentage, + new Scalar(0, 0, 0), + (int) Math.round(2 * aspectRatioPercentage) + ); + + return input; + + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt index e0c3b237..0be58cbd 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt @@ -1,604 +1,664 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.gui.DialogFactory -import com.github.serivesmejia.eocvsim.gui.util.MatPoster -import com.github.serivesmejia.eocvsim.pipeline.compiler.CompiledPipelineManager -import com.github.serivesmejia.eocvsim.pipeline.util.PipelineExceptionTracker -import com.github.serivesmejia.eocvsim.pipeline.util.PipelineSnapshot -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import com.github.serivesmejia.eocvsim.util.exception.MaxActiveContextsException -import com.github.serivesmejia.eocvsim.util.fps.FpsCounter -import kotlinx.coroutines.* -import org.firstinspires.ftc.robotcore.external.Telemetry -import org.opencv.core.Mat -import org.openftc.easyopencv.OpenCvPipeline -import org.openftc.easyopencv.TimestampedPipelineHandler -import java.awt.Dimension -import java.lang.reflect.Constructor -import java.util.* -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.math.roundToLong - -@OptIn(DelicateCoroutinesApi::class) -class PipelineManager(var eocvSim: EOCVSim) { - - companion object { - const val MAX_ALLOWED_ACTIVE_PIPELINE_CONTEXTS = 5 - - var staticSnapshot: PipelineSnapshot? = null - private set - - private const val TAG = "PipelineManager" - } - - @JvmField val onUpdate = EventHandler("OnPipelineUpdate") - @JvmField val onPipelineChange = EventHandler("OnPipelineChange") - @JvmField val onPipelineTimeout = EventHandler("OnPipelineTimeout") - @JvmField val onPause = EventHandler("OnPipelinePause") - @JvmField val onResume = EventHandler("OnPipelineResume") - - val pipelineOutputPosters = ArrayList() - val pipelineFpsCounter = FpsCounter() - - private var hasInitCurrentPipeline = false - var lastPipelineAction = "processFrame" - private set - - val pipelines = ArrayList() - - @Volatile var currentPipeline: OpenCvPipeline? = null - private set - var currentPipelineName = "" - private set - var currentPipelineIndex = -1 - private set - - val activePipelineContexts = ArrayList() - private var currentPipelineContext: ExecutorCoroutineDispatcher? = null - - @Volatile var currentTelemetry: Telemetry? = null - private set - - @Volatile var paused = false - private set - get() { - if (!field) pauseReason = PauseReason.NOT_PAUSED - return field - } - - var pauseReason = PauseReason.NOT_PAUSED - private set - get() { - if (!paused) field = PauseReason.NOT_PAUSED - return field - } - - var latestSnapshot: PipelineSnapshot? = null - private set - - var lastInitialSnapshot: PipelineSnapshot? = null - private set - - //manages and builds pipelines in runtime - @JvmField val compiledPipelineManager = CompiledPipelineManager(this) - //this will be handling the special pipeline "timestamped" type - val timestampedPipelineHandler = TimestampedPipelineHandler() - //counting and tracking exceptions for logging and reporting purposes - val pipelineExceptionTracker = PipelineExceptionTracker(this) - - private var openedPipelineOutputCount = 0 - - enum class PauseReason { - USER_REQUESTED, IMAGE_ONE_ANALYSIS, NOT_PAUSED - } - - fun init() { - Log.info(TAG, "Initializing...") - - //add default pipeline - addPipelineClass(DefaultPipeline::class.java) - - //scan for pipelines - PipelineScanner(eocvSim.params.scanForPipelinesIn).lookForPipelines { - addPipelineClass(it) - } - - Log.info(TAG, "Found " + pipelines.size + " pipeline(s)") - Log.blank() - - compiledPipelineManager.init() - - onUpdate.doOnce { - if(compiledPipelineManager.isBuildRunning) - compiledPipelineManager.onBuildEnd.doOnce(::applyStaticSnapOrDef) - else - applyStaticSnapOrDef() - } - - pipelineExceptionTracker.onNewPipelineException { - if(openedPipelineOutputCount <= 3) { - DialogFactory.createPipelineOutput(eocvSim) - openedPipelineOutputCount++ - } - - currentTelemetry?.errItem?.caption = "[/!\\]" - currentTelemetry?.errItem?.setValue("Uncaught exception thrown in\n pipeline, check Workspace -> Output.") - } - - pipelineExceptionTracker.onPipelineExceptionClear { - currentTelemetry?.errItem?.caption = "" - currentTelemetry?.errItem?.setValue("") - } - - onPipelineChange { - openedPipelineOutputCount = 0 - } - } - - private fun applyStaticSnapOrDef() { - onUpdate.doOnce { - if(!applyStaticSnapshot()) - forceChangePipeline(0) - - eocvSim.visualizer.pipelineSelectorPanel.allowPipelineSwitching = true - } - } - - fun update(inputMat: Mat) { - onUpdate.run() - - if(activePipelineContexts.size > MAX_ALLOWED_ACTIVE_PIPELINE_CONTEXTS) { - throw MaxActiveContextsException("Current amount of active pipeline coroutine contexts (${activePipelineContexts.size}) is more than the maximum allowed. This generally means that there are multiple pipelines stuck in processFrame() running in the background, check for any lengthy operations in your pipelines.") - } - - if(compiledPipelineManager.isBuildRunning) { - currentTelemetry?.infoItem?.caption = "[>]" - currentTelemetry?.infoItem?.setValue("Building java files in workspace...") - } else { - currentTelemetry?.infoItem?.caption = "" - currentTelemetry?.infoItem?.setValue("") - } - - if(paused || currentPipeline == null) { - updateExceptionTracker() - return - } - - timestampedPipelineHandler.update(currentPipeline) - - lastPipelineAction = if(!hasInitCurrentPipeline) { - "init/processFrame" - } else { - "processFrame" - } - - //run our pipeline in the background until it finishes or gets cancelled - val pipelineJob = GlobalScope.launch(currentPipelineContext!!) { - try { - //if we have a pipeline, we run it right here, passing the input mat - //given to us. we'll post the frame the pipeline returns as long - //as we haven't ran out of time (the main loop will not wait it - //forever to finish its job). if we run out of time, and if the - //pipeline ever returns, we will not post the frame, since we - //don't know when it was actually requested, we might even be in - //a different pipeline at this point. we also call init if we - //haven't done so. - - if(!hasInitCurrentPipeline) { - currentPipeline?.init(inputMat) - - Log.info("PipelineManager", "Initialized pipeline $currentPipelineName") - Log.blank() - - hasInitCurrentPipeline = true - } - - //check if we're still active (not timeouted) - //after initialization - currentPipeline?.processFrame(inputMat)?.let { outputMat -> - if (isActive) { - pipelineFpsCounter.update() - - for (poster in pipelineOutputPosters.toTypedArray()) { - try { - poster.post(outputMat) - } catch (ex: Exception) { - Log.error( - TAG, - "Uncaught exception thrown while posting pipeline output Mat to ${poster.name} poster", - ex - ) - } - } - } else { - activePipelineContexts.remove(this.coroutineContext) - } - } - - updateExceptionTracker() - } catch (ex: Exception) { //handling exceptions from pipelines - updateExceptionTracker(ex) - } - } - - runBlocking { - val configTimeout = eocvSim.config.pipelineTimeout - - //allow double timeout if we haven't initialized the pipeline - val timeout = if(hasInitCurrentPipeline) { - configTimeout.ms - } else { - (configTimeout.ms * 1.8).roundToLong() - } - - try { - //ok! this is the part in which we'll wait for the pipeline with a timeout - withTimeout(timeout) { - pipelineJob.join() - } - - activePipelineContexts.remove(currentPipelineContext) - } catch (ex: TimeoutCancellationException) { - //oops, pipeline ran out of time! we'll fall back - //to default pipeline to avoid further issues. - requestForceChangePipeline(0) - //also call the event listeners in case - //someone wants to do something here - onPipelineTimeout.run() - - Log.warn(TAG , "User pipeline $currentPipelineName took too long to $lastPipelineAction (more than $timeout ms), falling back to DefaultPipeline.") - Log.blank() - } finally { - //we cancel our pipeline job so that it - //doesn't post the output mat from the - //pipeline if it ever returns. - pipelineJob.cancel() - } - } - } - - private fun updateExceptionTracker(ex: Throwable? = null) { - if(currentPipelineIndex < pipelines.size && currentPipeline != null) { - pipelineExceptionTracker.update( - pipelines[currentPipelineIndex], ex - ) - } - } - - fun callViewportTapped() = currentPipeline?.let { pipeline -> //run only if our pipeline is not null - if(paused) requestSetPaused(false) - - //similar to pipeline processFrame, call the user function in the background - //and wait for some X timeout for the user to finisih doing what it has to do. - val viewportTappedJob = GlobalScope.launch(currentPipelineContext ?: EmptyCoroutineContext) { - pipeline.onViewportTapped() - } - - val configTimeoutMs = eocvSim.config.pipelineTimeout.ms - - try { - //perform the timeout here (we'll block for a bit - //and if it runs out of time, give up and move on) - runBlocking { - withTimeout(configTimeoutMs) { - viewportTappedJob.join() - } - } - } catch(ex: TimeoutCancellationException) { - //send a warning to the user - Log.warn(TAG , "User pipeline $currentPipelineName took too long to handle onViewportTapped (more than $configTimeoutMs ms).") - } finally { - //cancel the job - viewportTappedJob.cancel() - } - } - - @JvmOverloads - fun requestAddPipelineClass(C: Class<*>, source: PipelineSource = PipelineSource.CLASSPATH) { - onUpdate.doOnce { addPipelineClass(C, source) } - } - - fun requestAddPipelineClasses(classes: List>, - source: PipelineSource = PipelineSource.CLASSPATH, - refreshGui: Boolean = false) { - onUpdate.doOnce { - for(clazz in classes) { - addPipelineClass(clazz, source) - } - if(refreshGui) refreshGuiPipelineList() - } - } - - @Suppress("UNCHECKED_CAST") - @JvmOverloads fun addPipelineClass(C: Class<*>, source: PipelineSource = PipelineSource.CLASSPATH) { - try { - pipelines.add(PipelineData(source, C as Class)) - } catch (ex: Exception) { - Log.warn(TAG, "Error while adding pipeline class", ex) - Log.warn(TAG, "Unable to cast " + C.name + " to OpenCvPipeline class.") - Log.warn(TAG, "Remember that the pipeline class should extend OpenCvPipeline") - } - } - - @JvmOverloads fun removeAllPipelinesFrom(source: PipelineSource, - refreshGuiPipelineList: Boolean = true, - changeToDefaultIfRemoved: Boolean = true) { - for(pipeline in pipelines.toTypedArray()) { - if(pipeline.source == source) { - pipelines.remove(pipeline) - - if(currentPipeline != null && currentPipeline!!::class.java == pipeline.clazz) { - if(changeToDefaultIfRemoved) - requestChangePipeline(0) //change to default pipeline if the current pipeline was deleted - } - } - } - - if(refreshGuiPipelineList) refreshGuiPipelineList() - } - - @JvmOverloads - fun requestRemoveAllPipelinesFrom(source: PipelineSource, - refreshGuiPipelineList: Boolean = true, - changeToDefaultIfRemoved: Boolean = true) { - onUpdate.doOnce { - removeAllPipelinesFrom(source, refreshGuiPipelineList, changeToDefaultIfRemoved) - } - } - - /** - * Changes to the requested pipeline, no matter - * if we're currently on the same pipeline or not - */ - @OptIn(ExperimentalCoroutinesApi::class) - fun forceChangePipeline(index: Int?, - applyLatestSnapshot: Boolean = false, - applyStaticSnapshot: Boolean = false) { - if(index == null) return - - captureSnapshot() - - var nextPipeline: OpenCvPipeline? - var nextTelemetry: Telemetry? - val pipelineClass = pipelines[index].clazz - - Log.info(TAG, "Changing to pipeline " + pipelineClass.name) - - var constructor: Constructor<*> - - try { - nextTelemetry = Telemetry() - - try { //instantiate pipeline if it has a constructor of a telemetry parameter - constructor = pipelineClass.getConstructor(Telemetry::class.java) - nextPipeline = constructor.newInstance(nextTelemetry) as OpenCvPipeline - } catch (ex: NoSuchMethodException) { //instantiating with a constructor of no params - constructor = pipelineClass.getConstructor() - nextPipeline = constructor.newInstance() as OpenCvPipeline - } - - Log.info(TAG, "Instantiated pipeline class " + pipelineClass.name) - } catch (ex: NoSuchMethodException) { - pipelineExceptionTracker.addMessage("Error while instantiating requested pipeline, \"${pipelineClass.simpleName}\". Falling back to previous one.") - pipelineExceptionTracker.addMessage("Make sure your pipeline implements a public constructor with no parameters or a Telemetry parameter.") - - eocvSim.visualizer.pipelineSelectorPanel.selectedIndex = currentPipelineIndex - - Log.error(TAG, "Error while instantiating requested pipeline, ${pipelineClass.simpleName} (usable constructor missing)", ex) - Log.blank() - return - } catch (ex: Exception) { - pipelineExceptionTracker.addMessage("Error while instantiating requested pipeline, \"${pipelineClass.simpleName}\". Falling back to previous one.") - updateExceptionTracker(ex) - - Log.error(TAG, "Error while instantiating requested pipeline, ${pipelineClass.simpleName} (unknown issue)", ex) - Log.blank() - - eocvSim.visualizer.pipelineSelectorPanel.selectedIndex = currentPipelineIndex - - return - } - - currentPipeline = nextPipeline - currentTelemetry = nextTelemetry - currentPipelineIndex = index - currentPipelineName = currentPipeline!!.javaClass.simpleName - - val snap = PipelineSnapshot(currentPipeline!!) - - lastInitialSnapshot = if(applyLatestSnapshot) { - applyLatestSnapshot() - snap - } else snap - - if(applyStaticSnapshot) staticSnapshot?.transferTo(currentPipeline!!) - - hasInitCurrentPipeline = false - - currentPipelineContext?.close() - currentPipelineContext = newSingleThreadContext("Pipeline-$currentPipelineName") - - activePipelineContexts.add(currentPipelineContext!!) - - eocvSim.visualizer.pipelineSelectorPanel.selectedIndex = currentPipelineIndex - - setPaused(false) - - //if pause on images option is turned on by user - if (eocvSim.configManager.config.pauseOnImages) { - //pause next frame if current selected input source is an image - eocvSim.inputSourceManager.pauseIfImageTwoFrames() - } - - onPipelineChange.run() - } - - /** - * Change to the requested pipeline only if we're - * not in the requested pipeline right now. - */ - fun changePipeline(index: Int?) { - if (index == currentPipelineIndex) return - forceChangePipeline(index) - } - - fun requestChangePipeline(index: Int?) { - onUpdate.doOnce { - changePipeline(index) - } - } - - fun requestForceChangePipeline(index: Int) = onUpdate.doOnce { forceChangePipeline(index) } - - fun applyLatestSnapshot() { - if(currentPipeline != null && latestSnapshot != null) { - latestSnapshot!!.transferTo(currentPipeline!!, lastInitialSnapshot) - } - } - - fun captureSnapshot() { - if(currentPipeline != null) { - latestSnapshot = PipelineSnapshot(currentPipeline!!) - } - } - - fun captureStaticSnapshot() { - if(currentPipeline != null) { - staticSnapshot = PipelineSnapshot(currentPipeline!!) - } - } - - fun applyStaticSnapshot(): Boolean { - staticSnapshot?.let { snap -> - onUpdate.doOnce { - val index = getIndexOf(snap.pipelineClass) - - if(index != null) { - forceChangePipeline(index, applyStaticSnapshot = true) - staticSnapshot = null - } - } - return@applyStaticSnapshot true - } - - staticSnapshot = null - return false - } - - fun getIndexOf(pipeline: OpenCvPipeline) = getIndexOf(pipeline::class.java) - - fun getIndexOf(pipelineClass: Class): Int? { - for((i, pipelineData) in pipelines.withIndex()) { - if(pipelineData.clazz.name == pipelineClass.name) { - return i - } - } - - return null - } - - fun getPipelinesFrom(source: PipelineSource): Array { - val pipelinesData = arrayListOf() - - for(pipeline in pipelines) { - if(pipeline.source == source) - pipelinesData.add(pipeline) - } - - return pipelinesData.toTypedArray() - } - - fun runThenPause() { - setPaused(false) - eocvSim.onMainUpdate.doOnce { setPaused(true) } - } - - fun setPaused(paused: Boolean, pauseReason: PauseReason = PauseReason.USER_REQUESTED) { - this.paused = paused - - if (this.paused) { - this.pauseReason = pauseReason - onPause.run() - } else { - this.pauseReason = PauseReason.NOT_PAUSED - onResume.run() - } - - eocvSim.visualizer.pipelineSelectorPanel.buttonsPanel.pipelinePauseBtt.isSelected = paused - } - - fun togglePause() = setPaused(!paused) - - @JvmOverloads - fun requestSetPaused(paused: Boolean, pauseReason: PauseReason = PauseReason.USER_REQUESTED) { - eocvSim.onMainUpdate.doOnce { setPaused(paused, pauseReason) } - } - - fun refreshGuiPipelineList() = eocvSim.visualizer.pipelineSelectorPanel.updatePipelinesList() - -} - -enum class PipelineTimeout(val ms: Long, val coolName: String) { - LOW(1000, "Low (1 sec)"), - MEDIUM(4100, "Medium (4.1 secs)"), - HIGH(8200, "High (8.2 secs)"), - HIGHEST(12400, "Highest (12.4 secs)"); - - companion object { - @JvmStatic - fun fromCoolName(coolName: String): PipelineTimeout? { - for(timeout in values()) { - if(timeout.coolName == coolName) - return timeout - } - return null - } - } -} - -enum class PipelineFps(val fps: Int, val coolName: String) { - LOW(10, "Low (10 FPS)"), - MEDIUM(30, "Medium (30 FPS)"), - HIGH(60, "High (60 FPS)"), - HIGHEST(100, "Highest (100 FPS)"); - - companion object { - @JvmStatic - fun fromCoolName(coolName: String): PipelineFps? { - for(fps in values()) { - if(fps.coolName == coolName) - return fps - } - return null - } - } -} - -data class PipelineData(val source: PipelineSource, val clazz: Class) - -enum class PipelineSource { CLASSPATH, COMPILED_ON_RUNTIME } +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.pipeline + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.gui.util.MatPoster +import com.github.serivesmejia.eocvsim.pipeline.compiler.CompiledPipelineManager +import com.github.serivesmejia.eocvsim.pipeline.util.PipelineExceptionTracker +import com.github.serivesmejia.eocvsim.pipeline.util.PipelineSnapshot +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.StrUtil +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.exception.MaxActiveContextsException +import com.github.serivesmejia.eocvsim.util.fps.FpsCounter +import kotlinx.coroutines.* +import org.firstinspires.ftc.robotcore.external.Telemetry +import org.opencv.core.Mat +import org.openftc.easyopencv.OpenCvPipeline +import org.openftc.easyopencv.TimestampedPipelineHandler +import java.lang.reflect.Constructor +import java.util.* +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.math.roundToLong + +@OptIn(DelicateCoroutinesApi::class) +class PipelineManager(var eocvSim: EOCVSim) { + + companion object { + const val MAX_ALLOWED_ACTIVE_PIPELINE_CONTEXTS = 5 + + var staticSnapshot: PipelineSnapshot? = null + private set + + private const val TAG = "PipelineManager" + } + + @JvmField val onUpdate = EventHandler("OnPipelineUpdate") + @JvmField val onPipelineChange = EventHandler("OnPipelineChange") + @JvmField val onPipelineTimeout = EventHandler("OnPipelineTimeout") + @JvmField val onPause = EventHandler("OnPipelinePause") + @JvmField val onResume = EventHandler("OnPipelineResume") + + val pipelineOutputPosters = ArrayList() + val pipelineFpsCounter = FpsCounter() + + private var hasInitCurrentPipeline = false + var lastPipelineAction = "processFrame" + private set + + val pipelines = ArrayList() + + @Volatile var currentPipeline: OpenCvPipeline? = null + private set + @Volatile var currentPipelineData: PipelineData? = null + private set + var currentPipelineName = "" + private set + var currentPipelineIndex = -1 + private set + var previousPipelineIndex = 0 + + val activePipelineContexts = ArrayList() + private var currentPipelineContext: ExecutorCoroutineDispatcher? = null + + @Volatile var currentTelemetry: Telemetry? = null + private set + + @Volatile var paused = false + private set + get() { + if (!field) pauseReason = PauseReason.NOT_PAUSED + return field + } + + var pauseReason = PauseReason.NOT_PAUSED + private set + get() { + if (!paused) field = PauseReason.NOT_PAUSED + return field + } + + var latestSnapshot: PipelineSnapshot? = null + private set + + var lastInitialSnapshot: PipelineSnapshot? = null + private set + + //manages and builds pipelines in runtime + @JvmField val compiledPipelineManager = CompiledPipelineManager(this) + //this will be handling the special pipeline "timestamped" type + val timestampedPipelineHandler = TimestampedPipelineHandler() + //counting and tracking exceptions for logging and reporting purposes + val pipelineExceptionTracker = PipelineExceptionTracker(this) + + private var openedPipelineOutputCount = 0 + + enum class PauseReason { + USER_REQUESTED, IMAGE_ONE_ANALYSIS, NOT_PAUSED + } + + fun init() { + Log.info(TAG, "Initializing...") + + //add default pipeline + addPipelineClass(DefaultPipeline::class.java) + + compiledPipelineManager.init() + + eocvSim.classpathScan.join() + + //scan for pipelines + for(pipelineClass in eocvSim.classpathScan.scanResult.pipelineClasses) { + addPipelineClass(pipelineClass) + } + + Log.info(TAG, "Found " + pipelines.size + " pipeline(s)") + Log.blank() + + // changing to initial pipeline + onUpdate.doOnce { + if(compiledPipelineManager.isBuildRunning) + compiledPipelineManager.onBuildEnd.doOnce(::applyStaticSnapOrDef) + else + applyStaticSnapOrDef() + } + + pipelineExceptionTracker.onNewPipelineException { + if(openedPipelineOutputCount <= 3) { + DialogFactory.createPipelineOutput(eocvSim) + openedPipelineOutputCount++ + } + + currentTelemetry?.errItem?.caption = "[/!\\]" + currentTelemetry?.errItem?.setValue("Uncaught exception thrown in\n pipeline, check Workspace -> Output.") + } + + pipelineExceptionTracker.onPipelineExceptionClear { + currentTelemetry?.errItem?.caption = "" + currentTelemetry?.errItem?.setValue("") + } + + onPipelineChange { + openedPipelineOutputCount = 0 + } + } + + private fun applyStaticSnapOrDef() { + onUpdate.doOnce { + if(!applyStaticSnapshot()) { + val params = eocvSim.params + + // changing to the initial pipeline, defined by the eocv sim parameters or the default pipeline + if(params.initialPipelineName != null) { + changePipeline(params.initialPipelineName!!, params.initialPipelineSource ?: PipelineSource.CLASSPATH) + } else { + forceChangePipeline(0) + } + } + + eocvSim.visualizer.pipelineSelectorPanel.allowPipelineSwitching = true + } + } + + fun update(inputMat: Mat?) { + onUpdate.run() + + if(activePipelineContexts.size > MAX_ALLOWED_ACTIVE_PIPELINE_CONTEXTS) { + throw MaxActiveContextsException("Current amount of active pipeline coroutine contexts (${activePipelineContexts.size}) is more than the maximum allowed. This generally means that there are multiple pipelines stuck in processFrame() running in the background, check for any lengthy operations in your pipelines.") + } + + if(compiledPipelineManager.isBuildRunning) { + currentTelemetry?.infoItem?.caption = "[>]" + currentTelemetry?.infoItem?.setValue("Building java files in workspace...") + } else { + currentTelemetry?.infoItem?.caption = "" + currentTelemetry?.infoItem?.setValue("") + } + + if(paused || currentPipeline == null) { + updateExceptionTracker() + return + } + + timestampedPipelineHandler.update(currentPipeline, eocvSim.inputSourceManager.currentInputSource) + + lastPipelineAction = if(!hasInitCurrentPipeline) { + "init/processFrame" + } else { + "processFrame" + } + + //run our pipeline in the background until it finishes or gets cancelled + val pipelineJob = GlobalScope.launch(currentPipelineContext!!) { + try { + //if we have a pipeline, we run it right here, passing the input mat + //given to us. we'll post the frame the pipeline returns as long + //as we haven't ran out of time (the main loop will not wait it + //forever to finish its job). if we run out of time, and if the + //pipeline ever returns, we will not post the frame, since we + //don't know when it was actually requested, we might even be in + //a different pipeline at this point. we also call init if we + //haven't done so. + + if(!hasInitCurrentPipeline && inputMat != null) { + currentPipeline?.init(inputMat) + + Log.info("PipelineManager", "Initialized pipeline $currentPipelineName") + Log.blank() + + hasInitCurrentPipeline = true + } + + //check if we're still active (not timeouted) + //after initialization + if(inputMat != null) { + currentPipeline?.processFrame(inputMat)?.let { outputMat -> + if (isActive) { + pipelineFpsCounter.update() + + for (poster in pipelineOutputPosters.toTypedArray()) { + try { + poster.post(outputMat) + } catch (ex: Exception) { + Log.error( + TAG, + "Uncaught exception thrown while posting pipeline output Mat to ${poster.name} poster", + ex + ) + } + } + } + } + } + + if(!isActive) { + activePipelineContexts.remove(this.coroutineContext) + } + + updateExceptionTracker() + } catch (ex: Exception) { //handling exceptions from pipelines + if(!hasInitCurrentPipeline) { + pipelineExceptionTracker.addMessage("Error while initializing requested pipeline, \"$currentPipelineName\". Falling back to previous one.") + pipelineExceptionTracker.addMessage( + StrUtil.cutStringBy( + StrUtil.fromException(ex), "\n", 9 + ).trim() + ) + + eocvSim.visualizer.pipelineSelectorPanel.selectedIndex = previousPipelineIndex + changePipeline(currentPipelineIndex) + + Log.error(TAG, "Error while initializing requested pipeline, $currentPipelineName", ex) + Log.blank() + } else { + updateExceptionTracker(ex) + } + } + } + + runBlocking { + val configTimeout = eocvSim.config.pipelineTimeout + + //allow double timeout if we haven't initialized the pipeline + val timeout = if(hasInitCurrentPipeline) { + configTimeout.ms + } else { + (configTimeout.ms * 1.8).roundToLong() + } + + try { + //ok! this is the part in which we'll wait for the pipeline with a timeout + withTimeout(timeout) { + pipelineJob.join() + } + + activePipelineContexts.remove(currentPipelineContext) + } catch (ex: TimeoutCancellationException) { + //oops, pipeline ran out of time! we'll fall back + //to default pipeline to avoid further issues. + requestForceChangePipeline(0) + //also call the event listeners in case + //someone wants to do something here + onPipelineTimeout.run() + + Log.warn(TAG , "User pipeline $currentPipelineName took too long to $lastPipelineAction (more than $timeout ms), falling back to DefaultPipeline.") + Log.blank() + } finally { + //we cancel our pipeline job so that it + //doesn't post the output mat from the + //pipeline if it ever returns. + pipelineJob.cancel() + } + } + } + + private fun updateExceptionTracker(ex: Throwable? = null) { + if(currentPipelineIndex < pipelines.size && currentPipeline != null) { + pipelineExceptionTracker.update( + pipelines[currentPipelineIndex], ex + ) + } + } + + fun callViewportTapped() = currentPipeline?.let { pipeline -> //run only if our pipeline is not null + if(paused) requestSetPaused(false) + + //similar to pipeline processFrame, call the user function in the background + //and wait for some X timeout for the user to finisih doing what it has to do. + val viewportTappedJob = GlobalScope.launch(currentPipelineContext ?: EmptyCoroutineContext) { + pipeline.onViewportTapped() + } + + val configTimeoutMs = eocvSim.config.pipelineTimeout.ms + + try { + //perform the timeout here (we'll block for a bit + //and if it runs out of time, give up and move on) + runBlocking { + withTimeout(configTimeoutMs) { + viewportTappedJob.join() + } + } + } catch(ex: TimeoutCancellationException) { + //send a warning to the user + Log.warn(TAG , "User pipeline $currentPipelineName took too long to handle onViewportTapped (more than $configTimeoutMs ms).") + } finally { + //cancel the job + viewportTappedJob.cancel() + } + } + + @JvmOverloads + fun requestAddPipelineClass(C: Class<*>, source: PipelineSource = PipelineSource.CLASSPATH) { + onUpdate.doOnce { addPipelineClass(C, source) } + } + + fun requestAddPipelineClasses(classes: List>, + source: PipelineSource = PipelineSource.CLASSPATH, + refreshGui: Boolean = false) { + onUpdate.doOnce { + for(clazz in classes) { + addPipelineClass(clazz, source) + } + if(refreshGui) refreshGuiPipelineList() + } + } + + @Suppress("UNCHECKED_CAST") + @JvmOverloads fun addPipelineClass(C: Class<*>, source: PipelineSource = PipelineSource.CLASSPATH) { + try { + pipelines.add(PipelineData(source, C as Class)) + } catch (ex: Exception) { + Log.warn(TAG, "Error while adding pipeline class", ex) + Log.warn(TAG, "Unable to cast " + C.name + " to OpenCvPipeline class.") + Log.warn(TAG, "Remember that the pipeline class should extend OpenCvPipeline") + updateExceptionTracker(ex) + } + } + + @JvmOverloads fun removeAllPipelinesFrom(source: PipelineSource, + refreshGuiPipelineList: Boolean = true, + changeToDefaultIfRemoved: Boolean = true) { + for(pipeline in pipelines.toTypedArray()) { + if(pipeline.source == source) { + pipelines.remove(pipeline) + + if(currentPipeline != null && currentPipeline!!::class.java == pipeline.clazz) { + if(changeToDefaultIfRemoved) + requestChangePipeline(0) //change to default pipeline if the current pipeline was deleted + } + } + } + + if(refreshGuiPipelineList) refreshGuiPipelineList() + } + + @JvmOverloads + fun requestRemoveAllPipelinesFrom(source: PipelineSource, + refreshGuiPipelineList: Boolean = true, + changeToDefaultIfRemoved: Boolean = true) { + onUpdate.doOnce { + removeAllPipelinesFrom(source, refreshGuiPipelineList, changeToDefaultIfRemoved) + } + } + + fun changePipeline(name: String, source: PipelineSource) { + for((i, data) in pipelines.withIndex()) { + if(data.clazz.simpleName.equals(name, true) && data.source == source) { + changePipeline(i) + return + } + + if(data.clazz.name.equals(name, true) && data.source == source) { + changePipeline(i) + return + } + } + + Log.warn(TAG, "Pipeline class with name $name and source $source couldn't be found") + } + + fun requestChangePipeline(name: String, source: PipelineSource) { + eocvSim.onMainUpdate.doOnce { + changePipeline(name, source) + } + } + + /** + * Changes to the requested pipeline, no matter + * if we're currently on the same pipeline or not + */ + @OptIn(ExperimentalCoroutinesApi::class) + fun forceChangePipeline(index: Int?, + applyLatestSnapshot: Boolean = false, + applyStaticSnapshot: Boolean = false) { + if(index == null) return + + captureSnapshot() + + var nextPipeline: OpenCvPipeline? + var nextTelemetry: Telemetry? + val pipelineClass = pipelines[index].clazz + + Log.info(TAG, "Changing to pipeline " + pipelineClass.name) + + var constructor: Constructor<*> + + try { + nextTelemetry = Telemetry() + + try { //instantiate pipeline if it has a constructor of a telemetry parameter + constructor = pipelineClass.getConstructor(Telemetry::class.java) + nextPipeline = constructor.newInstance(nextTelemetry) as OpenCvPipeline + } catch (ex: NoSuchMethodException) { //instantiating with a constructor of no params + constructor = pipelineClass.getConstructor() + nextPipeline = constructor.newInstance() as OpenCvPipeline + } + + Log.info(TAG, "Instantiated pipeline class " + pipelineClass.name) + } catch (ex: NoSuchMethodException) { + pipelineExceptionTracker.addMessage("Error while instantiating requested pipeline, \"${pipelineClass.simpleName}\". Falling back to previous one.") + pipelineExceptionTracker.addMessage("Make sure your pipeline implements a public constructor with no parameters or a Telemetry parameter.") + + eocvSim.visualizer.pipelineSelectorPanel.selectedIndex = currentPipelineIndex + + Log.error(TAG, "Error while instantiating requested pipeline, ${pipelineClass.simpleName} (usable constructor missing)", ex) + Log.blank() + return + } catch (ex: Exception) { + pipelineExceptionTracker.addMessage("Error while instantiating requested pipeline, \"${pipelineClass.simpleName}\". Falling back to previous one.") + updateExceptionTracker(ex) + + Log.error(TAG, "Error while instantiating requested pipeline, ${pipelineClass.simpleName} (unknown issue)", ex) + Log.blank() + + eocvSim.visualizer.pipelineSelectorPanel.selectedIndex = currentPipelineIndex + + return + } + + previousPipelineIndex = currentPipelineIndex + + currentPipeline = nextPipeline + currentPipelineData = pipelines[index] + currentTelemetry = nextTelemetry + currentPipelineIndex = index + currentPipelineName = currentPipeline!!.javaClass.simpleName + + val snap = PipelineSnapshot(currentPipeline!!) + + lastInitialSnapshot = if(applyLatestSnapshot) { + applyLatestSnapshot() + snap + } else snap + + if(applyStaticSnapshot) staticSnapshot?.transferTo(currentPipeline!!) + + hasInitCurrentPipeline = false + + currentPipelineContext?.close() + currentPipelineContext = newSingleThreadContext("Pipeline-$currentPipelineName") + + activePipelineContexts.add(currentPipelineContext!!) + + eocvSim.visualizer.pipelineSelectorPanel.selectedIndex = currentPipelineIndex + + setPaused(false) + + //if pause on images option is turned on by user + if (eocvSim.configManager.config.pauseOnImages) { + //pause next frame if current selected input source is an image + eocvSim.inputSourceManager.pauseIfImageTwoFrames() + } + + onPipelineChange.run() + } + + /** + * Change to the requested pipeline only if we're + * not in the requested pipeline right now. + */ + fun changePipeline(index: Int?) { + if (index == currentPipelineIndex) return + forceChangePipeline(index) + } + + fun requestChangePipeline(index: Int?) { + onUpdate.doOnce { + changePipeline(index) + } + } + + fun requestForceChangePipeline(index: Int) = onUpdate.doOnce { forceChangePipeline(index) } + + fun applyLatestSnapshot() { + if(currentPipeline != null && latestSnapshot != null) { + latestSnapshot!!.transferTo(currentPipeline!!, lastInitialSnapshot) + } + } + + fun captureSnapshot() { + if(currentPipeline != null) { + latestSnapshot = PipelineSnapshot(currentPipeline!!) + } + } + + fun captureStaticSnapshot() { + if(currentPipeline != null) { + staticSnapshot = PipelineSnapshot(currentPipeline!!) + } + } + + fun applyStaticSnapshot(): Boolean { + staticSnapshot?.let { snap -> + onUpdate.doOnce { + val index = getIndexOf(snap.pipelineClass) + + if(index != null) { + forceChangePipeline(index, applyStaticSnapshot = true) + staticSnapshot = null + } + } + return@applyStaticSnapshot true + } + + staticSnapshot = null + return false + } + + fun getIndexOf(pipeline: OpenCvPipeline, source: PipelineSource = PipelineSource.CLASSPATH) = + getIndexOf(pipeline::class.java, source) + + fun getIndexOf(pipelineClass: Class, source: PipelineSource = PipelineSource.CLASSPATH): Int? { + for((i, pipelineData) in pipelines.withIndex()) { + if(pipelineData.clazz.name == pipelineClass.name && pipelineData.source == source) { + return i + } + } + + return null + } + + fun getPipelinesFrom(source: PipelineSource): Array { + val pipelinesData = arrayListOf() + + for(pipeline in pipelines) { + if(pipeline.source == source) + pipelinesData.add(pipeline) + } + + return pipelinesData.toTypedArray() + } + + fun runThenPause() { + setPaused(false) + eocvSim.onMainUpdate.doOnce { setPaused(true) } + } + + fun setPaused(paused: Boolean, pauseReason: PauseReason = PauseReason.USER_REQUESTED) { + this.paused = paused + + if (this.paused) { + this.pauseReason = pauseReason + onPause.run() + } else { + this.pauseReason = PauseReason.NOT_PAUSED + onResume.run() + } + + eocvSim.visualizer.pipelineSelectorPanel.buttonsPanel.pipelinePauseBtt.isSelected = paused + } + + fun togglePause() = setPaused(!paused) + + @JvmOverloads + fun requestSetPaused(paused: Boolean, pauseReason: PauseReason = PauseReason.USER_REQUESTED) { + eocvSim.onMainUpdate.doOnce { setPaused(paused, pauseReason) } + } + + fun refreshGuiPipelineList() = eocvSim.visualizer.pipelineSelectorPanel.updatePipelinesList() + +} + +enum class PipelineTimeout(val ms: Long, val coolName: String) { + LOW(1000, "Low (1 sec)"), + MEDIUM(4100, "Medium (4.1 secs)"), + HIGH(8200, "High (8.2 secs)"), + HIGHEST(12400, "Highest (12.4 secs)"); + + companion object { + @JvmStatic + fun fromCoolName(coolName: String): PipelineTimeout? { + for(timeout in values()) { + if(timeout.coolName == coolName) + return timeout + } + return null + } + } +} + +enum class PipelineFps(val fps: Int, val coolName: String) { + LOW(10, "Low (10 FPS)"), + MEDIUM(30, "Medium (30 FPS)"), + HIGH(60, "High (60 FPS)"), + HIGHEST(100, "Highest (100 FPS)"); + + companion object { + @JvmStatic + fun fromCoolName(coolName: String): PipelineFps? { + for(fps in values()) { + if(fps.coolName == coolName) + return fps + } + return null + } + } +} + +data class PipelineData(val source: PipelineSource, val clazz: Class) + +enum class PipelineSource { CLASSPATH, COMPILED_ON_RUNTIME } diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineScanner.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineScanner.kt index 8131cde0..8e3292eb 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineScanner.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineScanner.kt @@ -1,63 +1,64 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline - -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.ReflectUtil -import io.github.classgraph.ClassGraph -import io.github.classgraph.ScanResult -import org.openftc.easyopencv.OpenCvPipeline - -@Suppress("UNCHECKED_CAST") -class PipelineScanner(val scanInPackage: String = "org.firstinspires") { - - fun lookForPipelines(callback: (Class) -> Unit) { - Log.info("PipelineScanner", "Scanning for pipelines...") - val scanResult = scanClasspath(scanInPackage) - - //iterate over the results of the scan - for (routeClassInfo in scanResult.allClasses) { - - val foundClass: Class<*> = try { - Class.forName(routeClassInfo.name) - } catch (e1: ClassNotFoundException) { - e1.printStackTrace() - continue //continue because we couldn't get the class... - } - - if(ReflectUtil.hasSuperclass(foundClass, OpenCvPipeline::class.java)) { - Log.info("PipelineScanner", "Found pipeline class ${foundClass.canonicalName}") - callback(foundClass as Class); - } - - } - } - - fun scanClasspath(inPackage: String): ScanResult { - //Scan for all classes in the specified package - val classGraph = ClassGraph().enableAllInfo().acceptPackages(inPackage) - return classGraph.scan() - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.pipeline + +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.ReflectUtil +import io.github.classgraph.ClassGraph +import io.github.classgraph.ScanResult +import org.openftc.easyopencv.OpenCvPipeline +import java.lang.reflect.Modifier + +@Suppress("UNCHECKED_CAST") +class PipelineScanner(val scanInPackage: String = "org.firstinspires") { + + fun lookForPipelines(callback: (Class) -> Unit) { + Log.info("PipelineScanner", "Scanning for pipelines...") + val scanResult = scanClasspath(scanInPackage) + + //iterate over the results of the scan + for (routeClassInfo in scanResult.allClasses) { + + val foundClass: Class<*> = try { + Class.forName(routeClassInfo.name) + } catch (e1: ClassNotFoundException) { + e1.printStackTrace() + continue //continue because we couldn't get the class... + } + + if(ReflectUtil.hasSuperclass(foundClass, OpenCvPipeline::class.java) && Modifier.isPublic(foundClass.modifiers)) { + Log.info("PipelineScanner", "Found pipeline class ${foundClass.canonicalName}") + callback(foundClass as Class); + } + + } + } + + fun scanClasspath(inPackage: String): ScanResult { + //Scan for all classes in the specified package + val classGraph = ClassGraph().enableAllInfo().acceptPackages(inPackage) + return classGraph.scan() + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt index d90bc353..fd00895a 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt @@ -1,264 +1,261 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline.compiler - -import com.github.serivesmejia.eocvsim.gui.DialogFactory -import com.github.serivesmejia.eocvsim.gui.dialog.Output -import com.github.serivesmejia.eocvsim.pipeline.PipelineManager -import com.github.serivesmejia.eocvsim.pipeline.PipelineSource -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.StrUtil -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import com.github.serivesmejia.eocvsim.workspace.util.template.DefaultWorkspaceTemplate -import com.qualcomm.robotcore.util.ElapsedTime -import kotlinx.coroutines.* -import org.openftc.easyopencv.OpenCvPipeline -import java.io.File - -class CompiledPipelineManager(private val pipelineManager: PipelineManager) { - - companion object { - val DEF_WORKSPACE_FOLDER = File(SysUtil.getEOCVSimFolder(), File.separator + "default_workspace").apply { - if(!exists()) { - mkdir() - DefaultWorkspaceTemplate.extractToIfEmpty(this) - } - } - - val COMPILER_FOLDER = File(SysUtil.getEOCVSimFolder(), File.separator + "compiler").mkdirLazy() - - val SOURCES_OUTPUT_FOLDER = File(COMPILER_FOLDER, File.separator + "gen_src").mkdirLazy() - val CLASSES_OUTPUT_FOLDER = File(COMPILER_FOLDER, File.separator + "out_classes").mkdirLazy() - val JARS_OUTPUT_FOLDER = File(COMPILER_FOLDER, File.separator + "out_jars").mkdirLazy() - - val PIPELINES_OUTPUT_JAR = File(JARS_OUTPUT_FOLDER, File.separator + "pipelines.jar") - - const val TAG = "CompiledPipelineManager" - } - - var currentPipelineClassLoader: PipelineClassLoader? = null - private set - - val onBuildStart = EventHandler("CompiledPipelineManager-OnBuildStart") - val onBuildEnd = EventHandler("CompiledPipelineManager-OnBuildEnd") - - var lastBuildResult: PipelineCompileResult? = null - private set - var lastBuildOutputMessage: String? = null - private set - - var isBuildRunning = false - private set - - val workspaceManager get() = pipelineManager.eocvSim.workspaceManager - - private val visualizer get() = pipelineManager.eocvSim.visualizer - - fun init() { - Log.info(TAG, "Initializing...") - asyncCompile(false) - - workspaceManager.onWorkspaceChange { - asyncCompile() - } - } - - @OptIn(DelicateCoroutinesApi::class) - suspend fun uncheckedCompile(fixSelectedPipeline: Boolean = false): PipelineCompileResult { - if(isBuildRunning) return PipelineCompileResult( - PipelineCompileStatus.FAILED, "A build is already running" - ) - - isBuildRunning = true - onBuildStart.run() - - if(!PipelineCompiler.IS_USABLE) { - lastBuildResult = PipelineCompileResult( - PipelineCompileStatus.FAILED, - "Current JVM does not have a javac executable (a JDK is needed)" - ) - lastBuildOutputMessage = null - - onBuildEnd.run() - isBuildRunning = false - - return lastBuildResult!! - } - - workspaceManager.reloadConfig() - - val absoluteSourcesPath = workspaceManager.sourcesAbsolutePath.toFile() - - Log.info(TAG, "Building java files in workspace, at ${absoluteSourcesPath.absolutePath}") - - val runtime = ElapsedTime() - - val compiler = PipelineCompiler( - absoluteSourcesPath, workspaceManager.sourceFiles, - workspaceManager.resourcesAbsolutePath.toFile(), workspaceManager.resourceFiles - ) - - val result = compiler.compile(PIPELINES_OUTPUT_JAR) - lastBuildResult = result - - val timeElapsed = String.format("%.2f", runtime.seconds()) - - currentPipelineClassLoader = null - val messageEnd = "(took $timeElapsed seconds)\n\n${result.message}".trim() - - val pipelineSelectorPanel = pipelineManager.eocvSim.visualizer.pipelineSelectorPanel - val beforeAllowSwitching = pipelineSelectorPanel?.allowPipelineSwitching - - if(fixSelectedPipeline) - pipelineSelectorPanel?.allowPipelineSwitching = false - - pipelineManager.requestRemoveAllPipelinesFrom( - PipelineSource.COMPILED_ON_RUNTIME, - refreshGuiPipelineList = false, - changeToDefaultIfRemoved = false - ) - - lastBuildOutputMessage = when(result.status) { - PipelineCompileStatus.SUCCESS -> { - loadFromPipelinesJar() - "Build successful $messageEnd" - } - PipelineCompileStatus.NO_SOURCE -> { - //delete jar if we had no sources, the most logical outcome in this case - deleteJarFile() - if(pipelineManager.eocvSim.visualizer.hasFinishedInit()) - pipelineManager.refreshGuiPipelineList() - - "Build cancelled, no source files to compile $messageEnd" - } - else -> { - deleteJarFile() - "Build failed $messageEnd" - } - } - - val beforePipeline = pipelineManager.currentPipeline - - pipelineManager.onUpdate.doOnce { - pipelineManager.refreshGuiPipelineList() - - if(fixSelectedPipeline) { - if(beforePipeline != null) { - val pipeline = pipelineManager.getIndexOf(beforePipeline) - - pipelineManager.forceChangePipeline(pipeline, true) - } else { - pipelineManager.changePipeline(0) //default pipeline - } - - pipelineSelectorPanel?.allowPipelineSwitching = beforeAllowSwitching!! - } - } - - if(result.status == PipelineCompileStatus.SUCCESS) { - Log.info(TAG, "$lastBuildOutputMessage\n") - } else { - Log.warn(TAG, "$lastBuildOutputMessage\n") - - if(result.status == PipelineCompileStatus.FAILED && !Output.isAlreadyOpened) - DialogFactory.createBuildOutput(pipelineManager.eocvSim) - } - - onBuildEnd.callRightAway = true - onBuildEnd.run() - - GlobalScope.launch { - delay(1000) - onBuildEnd.callRightAway = false - } - - isBuildRunning = false - - return result - } - - fun compile(fixSelectedPipeline: Boolean = true) = try { - runBlocking { uncheckedCompile(fixSelectedPipeline) } - } catch(e: Exception) { - isBuildRunning = false - onBuildEnd.run() - - val stacktrace = StrUtil.fromException(e) - lastBuildOutputMessage = """ - |Unexpected exception thrown while the build was running - | - |$stacktrace - | - |If this seems like a bug, please open an issue in the EOCV-Sim github repo - """.trimMargin() - - Log.error(TAG, lastBuildOutputMessage) - - lastBuildResult = PipelineCompileResult(PipelineCompileStatus.FAILED, lastBuildOutputMessage!!) - - if(!Output.isAlreadyOpened) - DialogFactory.createBuildOutput(pipelineManager.eocvSim) - - lastBuildResult!! - } - - @JvmOverloads - @OptIn(DelicateCoroutinesApi::class) - fun asyncCompile( - fixSelectedPipeline: Boolean = true, - endCallback: (PipelineCompileResult) -> Unit = {} - ) = GlobalScope.launch(Dispatchers.IO) { - endCallback(compile(fixSelectedPipeline)) - } - - private fun deleteJarFile() { - if(PIPELINES_OUTPUT_JAR.exists()) PIPELINES_OUTPUT_JAR.delete() - currentPipelineClassLoader = null - } - - fun loadFromPipelinesJar() { - if(!PIPELINES_OUTPUT_JAR.exists()) return - - Log.info(TAG, "Looking for pipelines in jar file $PIPELINES_OUTPUT_JAR") - - try { - currentPipelineClassLoader = PipelineClassLoader(PIPELINES_OUTPUT_JAR) - - val pipelines = mutableListOf>() - - for(pipelineClass in currentPipelineClassLoader!!.pipelineClasses) { - pipelines.add(pipelineClass) - Log.info(TAG, "Added ${pipelineClass.simpleName} from jar") - } - - pipelineManager.requestAddPipelineClasses(pipelines, PipelineSource.COMPILED_ON_RUNTIME, false) - } catch(e: Exception) { - Log.error(TAG, "Uncaught exception thrown while loading jar $PIPELINES_OUTPUT_JAR", e) - } - } - -} - -private fun File.mkdirLazy() = apply { mkdir() } +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.pipeline.compiler + +import com.github.serivesmejia.eocvsim.gui.DialogFactory +import com.github.serivesmejia.eocvsim.gui.dialog.Output +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.pipeline.PipelineSource +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.StrUtil +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.workspace.util.template.DefaultWorkspaceTemplate +import com.qualcomm.robotcore.util.ElapsedTime +import kotlinx.coroutines.* +import org.openftc.easyopencv.OpenCvPipeline +import java.io.File + +class CompiledPipelineManager(private val pipelineManager: PipelineManager) { + + companion object { + val DEF_WORKSPACE_FOLDER = File(SysUtil.getEOCVSimFolder(), File.separator + "default_workspace").apply { + if(!exists()) { + mkdir() + DefaultWorkspaceTemplate.extractToIfEmpty(this) + } + } + + val COMPILER_FOLDER = File(SysUtil.getEOCVSimFolder(), File.separator + "compiler").mkdirLazy() + + val SOURCES_OUTPUT_FOLDER = File(COMPILER_FOLDER, File.separator + "gen_src").mkdirLazy() + val CLASSES_OUTPUT_FOLDER = File(COMPILER_FOLDER, File.separator + "out_classes").mkdirLazy() + val JARS_OUTPUT_FOLDER = File(COMPILER_FOLDER, File.separator + "out_jars").mkdirLazy() + + val PIPELINES_OUTPUT_JAR = File(JARS_OUTPUT_FOLDER, File.separator + "pipelines.jar") + + const val TAG = "CompiledPipelineManager" + } + + var currentPipelineClassLoader: PipelineClassLoader? = null + private set + + val onBuildStart = EventHandler("CompiledPipelineManager-OnBuildStart") + val onBuildEnd = EventHandler("CompiledPipelineManager-OnBuildEnd") + + var lastBuildResult: PipelineCompileResult? = null + private set + var lastBuildOutputMessage: String? = null + private set + + var isBuildRunning = false + private set + + val workspaceManager get() = pipelineManager.eocvSim.workspaceManager + + fun init() { + Log.info(TAG, "Initializing...") + asyncCompile(false) + + workspaceManager.onWorkspaceChange { + asyncCompile() + } + } + + @OptIn(DelicateCoroutinesApi::class) + suspend fun uncheckedCompile(fixSelectedPipeline: Boolean = false): PipelineCompileResult { + if(isBuildRunning) return PipelineCompileResult( + PipelineCompileStatus.FAILED, "A build is already running" + ) + + isBuildRunning = true + onBuildStart.run() + + if(!PipelineCompiler.IS_USABLE) { + lastBuildResult = PipelineCompileResult( + PipelineCompileStatus.FAILED, + "Current JVM does not have a javac executable (a JDK is needed)" + ) + lastBuildOutputMessage = null + + onBuildEnd.run() + isBuildRunning = false + + return lastBuildResult!! + } + + workspaceManager.reloadConfig() + + val absoluteSourcesPath = workspaceManager.sourcesAbsolutePath.toFile() + Log.info(TAG, "Building java files in workspace, at ${absoluteSourcesPath.absolutePath}") + + val runtime = ElapsedTime() + + val compiler = PipelineCompiler( + absoluteSourcesPath, workspaceManager.sourceFiles, + workspaceManager.resourcesAbsolutePath.toFile(), workspaceManager.resourceFiles + ) + + val result = compiler.compile(PIPELINES_OUTPUT_JAR) + lastBuildResult = result + + val timeElapsed = String.format("%.2f", runtime.seconds()) + + currentPipelineClassLoader = null + val messageEnd = "(took $timeElapsed seconds)\n\n${result.message}".trim() + + val pipelineSelectorPanel = pipelineManager.eocvSim.visualizer.pipelineSelectorPanel + val beforeAllowSwitching = pipelineSelectorPanel?.allowPipelineSwitching + + if(fixSelectedPipeline) + pipelineSelectorPanel?.allowPipelineSwitching = false + + pipelineManager.requestRemoveAllPipelinesFrom( + PipelineSource.COMPILED_ON_RUNTIME, + refreshGuiPipelineList = false, + changeToDefaultIfRemoved = false + ) + + lastBuildOutputMessage = when(result.status) { + PipelineCompileStatus.SUCCESS -> { + loadFromPipelinesJar() + "Build successful $messageEnd" + } + PipelineCompileStatus.NO_SOURCE -> { + //delete jar if we had no sources, the most logical outcome in this case + deleteJarFile() + if(pipelineManager.eocvSim.visualizer.hasFinishedInit()) + pipelineManager.refreshGuiPipelineList() + + "Build cancelled, no source files to compile $messageEnd" + } + else -> { + deleteJarFile() + "Build failed $messageEnd" + } + } + + val beforePipeline = pipelineManager.currentPipelineData + + pipelineManager.onUpdate.doOnce { + pipelineManager.refreshGuiPipelineList() + + if(fixSelectedPipeline) { + if(beforePipeline != null) { + val pipeline = pipelineManager.getIndexOf(beforePipeline.clazz, beforePipeline.source) + + pipelineManager.forceChangePipeline(pipeline, true) + } else { + pipelineManager.changePipeline(0) //default pipeline + } + + pipelineSelectorPanel?.allowPipelineSwitching = beforeAllowSwitching!! + } + } + + if(result.status == PipelineCompileStatus.SUCCESS) { + Log.info(TAG, "$lastBuildOutputMessage\n") + } else { + Log.warn(TAG, "$lastBuildOutputMessage\n") + + if(result.status == PipelineCompileStatus.FAILED && !Output.isAlreadyOpened) + DialogFactory.createBuildOutput(pipelineManager.eocvSim) + } + + onBuildEnd.callRightAway = true + onBuildEnd.run() + + GlobalScope.launch { + delay(1000) + onBuildEnd.callRightAway = false + } + + isBuildRunning = false + + return result + } + + fun compile(fixSelectedPipeline: Boolean = true) = try { + runBlocking { uncheckedCompile(fixSelectedPipeline) } + } catch(e: Throwable) { + isBuildRunning = false + onBuildEnd.run() + + val stacktrace = StrUtil.fromException(e) + lastBuildOutputMessage = """ + |Unexpected exception thrown while the build was running + | + |$stacktrace + | + |If this seems like a bug, please open an issue in the EOCV-Sim github repo + """.trimMargin() + + Log.error(TAG, lastBuildOutputMessage) + + lastBuildResult = PipelineCompileResult(PipelineCompileStatus.FAILED, lastBuildOutputMessage!!) + + if(!Output.isAlreadyOpened) + DialogFactory.createBuildOutput(pipelineManager.eocvSim) + + lastBuildResult!! + } + + @JvmOverloads + @OptIn(DelicateCoroutinesApi::class) + fun asyncCompile( + fixSelectedPipeline: Boolean = true, + endCallback: (PipelineCompileResult) -> Unit = {} + ) = GlobalScope.launch(Dispatchers.IO) { + endCallback(compile(fixSelectedPipeline)) + } + + private fun deleteJarFile() { + if(PIPELINES_OUTPUT_JAR.exists()) PIPELINES_OUTPUT_JAR.delete() + currentPipelineClassLoader = null + } + + fun loadFromPipelinesJar() { + if(!PIPELINES_OUTPUT_JAR.exists()) return + + Log.info(TAG, "Looking for pipelines in jar file $PIPELINES_OUTPUT_JAR") + + try { + currentPipelineClassLoader = PipelineClassLoader(PIPELINES_OUTPUT_JAR) + + val pipelines = mutableListOf>() + + for(pipelineClass in currentPipelineClassLoader!!.pipelineClasses) { + pipelines.add(pipelineClass) + Log.info(TAG, "Added ${pipelineClass.simpleName} from jar") + } + + pipelineManager.requestAddPipelineClasses(pipelines, PipelineSource.COMPILED_ON_RUNTIME, false) + } catch(e: Exception) { + Log.error(TAG, "Uncaught exception thrown while loading jar $PIPELINES_OUTPUT_JAR", e) + } + } + +} + +private fun File.mkdirLazy() = apply { mkdir() } diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineClassLoader.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineClassLoader.kt index 327da7bf..567990ba 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineClassLoader.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineClassLoader.kt @@ -1,101 +1,103 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline.compiler - -import com.github.serivesmejia.eocvsim.util.ReflectUtil -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.util.extension.removeFromEnd -import org.openftc.easyopencv.OpenCvPipeline -import java.io.* -import java.util.zip.ZipEntry -import java.util.zip.ZipFile - -@Suppress("UNCHECKED_CAST") -class PipelineClassLoader(pipelinesJar: File) : ClassLoader() { - - private val zipFile = ZipFile(pipelinesJar) - - var pipelineClasses: List> - private set - - init { - val pipelineClasses = mutableListOf>() - - for(entry in zipFile.entries()) { - if(!entry.name.endsWith(".class")) continue - - val clazz = loadClass(entry) - - if(ReflectUtil.hasSuperclass(clazz, OpenCvPipeline::class.java)) { - pipelineClasses.add(clazz as Class) - } - } - - this.pipelineClasses = pipelineClasses.toList() - } - - private fun loadClass(entry: ZipEntry): Class<*> { - val name = entry.name.removeFromEnd(".class").replace(File.pathSeparatorChar, '.') - - zipFile.getInputStream(entry).use { inStream -> - ByteArrayOutputStream().use { outStream -> - SysUtil.copyStream(inStream, outStream) - val bytes = outStream.toByteArray() - - return defineClass(name, bytes, 0, bytes.size) - } - } - } - - override fun loadClass(name: String, resolve: Boolean): Class<*> { - var clazz = findLoadedClass(name) - - if(clazz == null) { - try { - clazz = loadClass(zipFile.getEntry(name.replace('.', File.pathSeparatorChar) + ".class")) - if(resolve) resolveClass(clazz) - } catch(e: Exception) { - clazz = super.loadClass(name, resolve) - } - } - - return clazz - } - - override fun getResourceAsStream(name: String): InputStream? { - val entry = zipFile.getEntry(name) - - if(entry != null) { - try { - return zipFile.getInputStream(entry) - } catch (e: IOException) { } - } - - return super.getResourceAsStream(name) - } - -} - -val OpenCvPipeline.isFromRuntimeCompilation +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.pipeline.compiler + +import com.github.serivesmejia.eocvsim.util.ReflectUtil +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.extension.removeFromEnd +import org.openftc.easyopencv.OpenCvPipeline +import java.io.* +import java.util.zip.ZipEntry +import java.util.zip.ZipFile + +@Suppress("UNCHECKED_CAST") +class PipelineClassLoader(pipelinesJar: File) : ClassLoader() { + + private val zipFile = ZipFile(pipelinesJar) + + var pipelineClasses: List> + private set + + init { + val pipelineClasses = mutableListOf>() + + for(entry in zipFile.entries()) { + if(!entry.name.endsWith(".class")) continue + + val clazz = loadClass(entry) + + if(ReflectUtil.hasSuperclass(clazz, OpenCvPipeline::class.java)) { + pipelineClasses.add(clazz as Class) + } + } + + this.pipelineClasses = pipelineClasses.toList() + } + + private fun loadClass(entry: ZipEntry): Class<*> { + val name = entry.name.removeFromEnd(".class").replace(File.separatorChar, '.') + + zipFile.getInputStream(entry).use { inStream -> + ByteArrayOutputStream().use { outStream -> + SysUtil.copyStream(inStream, outStream) + val bytes = outStream.toByteArray() + + return defineClass(name, bytes, 0, bytes.size) + } + } + } + + override fun loadClass(name: String, resolve: Boolean): Class<*> { + var clazz = findLoadedClass(name) + + if(clazz == null) { + try { + clazz = loadClass(zipFile.getEntry(name.replace('.', File.separatorChar) + ".class")) + if(resolve) resolveClass(clazz) + } catch(e: Exception) { + clazz = super.loadClass(name, resolve) + } + } + + return clazz + } + + override fun getResourceAsStream(name: String): InputStream? { + println("trying to load $name") + + val entry = zipFile.getEntry(name) + + if(entry != null) { + try { + return zipFile.getInputStream(entry) + } catch (e: IOException) { } + } + + return super.getResourceAsStream(name) + } + +} + +val OpenCvPipeline.isFromRuntimeCompilation get() = this::class.java.classLoader is PipelineClassLoader \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineCompiler.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineCompiler.kt index 13b2bd7d..bae0fe06 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineCompiler.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineCompiler.kt @@ -1,149 +1,149 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline.compiler - -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.util.compiler.JarPacker -import java.io.File -import java.io.PrintWriter -import java.util.* -import javax.tools.* - -class PipelineCompiler( - private val sourcesInputPath: File, private val sourceFiles: List, - private val resInputPath: File? = null, private val resFiles: List? = null -): DiagnosticListener { - - companion object { - val IS_USABLE by lazy { - val usable = COMPILER != null - - // Send a warning message to console - // will only be sent once (that's why it's done here) - if(!usable) { - Log.warn(TAG, "Unable to compile Java source code in this JVM (the ToolProvider wasn't able to provide a compiler)") - Log.warn(TAG, "For the user, this probably means that the sim is running in a JRE which doesn't include the javac compiler executable") - Log.warn(TAG, "To be able to compile pipelines on runtime, make sure the sim is running on a JDK that includes the javac executable (any JDK probably does)") - } - - usable - } - - val COMPILER = ToolProvider.getSystemJavaCompiler() - - val INDENT = " " - val TAG = "PipelineCompiler" - } - - private var diagnosticBuilders = mutableMapOf() - - val latestDiagnostic: String - get() { - val diagnostic = StringBuilder() - for((_, builder) in diagnosticBuilders) { - diagnostic.appendLine(builder) - diagnostic.appendLine("") - } - - return diagnostic.toString().trim() - } - - val args = arrayListOf( - "-source", "1.8", - "-target", "1.8", - "-g", - "-encoding", "UTF-8", - "-Xlint:unchecked", - "-Xlint:deprecation", - "-XDuseUnsharedTable=true" - ) - - constructor(inputPath: File) : this(inputPath, SysUtil.filesUnder(inputPath, ".java")) - - fun compile(outputJar: File): PipelineCompileResult { - val javac = COMPILER - - val fileManager = PipelineStandardFileManager(javac.getStandardFileManager(this, null, null)) - fileManager.sourcePath = Collections.singleton(sourcesInputPath) - - val javaFileObjects = fileManager.getJavaFileObjects(*sourceFiles.toTypedArray()) - - if(javaFileObjects.iterator().hasNext()) { - SysUtil.deleteFilesUnder(CompiledPipelineManager.CLASSES_OUTPUT_FOLDER) - - val task = javac.getTask( - PrintWriter(System.out), - fileManager, - this, - args, - null, - javaFileObjects - ) - - if(task.call()) { - val outputClasses = fileManager.getLocation(StandardLocation.CLASS_OUTPUT).iterator().next() - - if(resInputPath == null || resFiles == null) { - JarPacker.packClassesUnder(outputJar, outputClasses) - } else { - JarPacker.packResAndClassesUnder(outputJar, outputClasses, resInputPath, resFiles) - } - - return PipelineCompileResult(PipelineCompileStatus.SUCCESS, latestDiagnostic) - } - - return PipelineCompileResult(PipelineCompileStatus.FAILED, latestDiagnostic) - } else { - return PipelineCompileResult(PipelineCompileStatus.NO_SOURCE, "No source files") - } - } - - override fun report(diagnostic: Diagnostic) { - val locale = Locale.getDefault() - val relativeFile = SysUtil.getRelativePath(sourcesInputPath, File(diagnostic.source.name)) - - val builder = diagnosticBuilders[relativeFile.path] ?: StringBuilder() - - if(!diagnosticBuilders.containsKey(relativeFile.path)) { - builder.appendLine("> ${relativeFile.path}") - diagnosticBuilders[relativeFile.path] = builder - } - - val formattedMessage = diagnostic.getMessage(locale).replace("\n", "\n$INDENT") - - builder.appendLine(String.format(locale, "$INDENT(%d:%d): %s: %s", - diagnostic.lineNumber, diagnostic.columnNumber, diagnostic.kind, formattedMessage - )) - } - -} - -enum class PipelineCompileStatus { - SUCCESS, - FAILED, - NO_SOURCE -} - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.pipeline.compiler + +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.compiler.JarPacker +import java.io.File +import java.io.PrintWriter +import java.util.* +import javax.tools.* + +class PipelineCompiler( + private val sourcesInputPath: File, private val sourceFiles: List, + private val resInputPath: File? = null, private val resFiles: List? = null +): DiagnosticListener { + + companion object { + val IS_USABLE by lazy { + val usable = COMPILER != null + + // Send a warning message to console + // will only be sent once (that's why it's done here) + if(!usable) { + Log.warn(TAG, "Unable to compile Java source code in this JVM (the ToolProvider wasn't able to provide a compiler)") + Log.warn(TAG, "For the user, this probably means that the sim is running in a JRE which doesn't include the javac compiler executable") + Log.warn(TAG, "To be able to compile pipelines on runtime, make sure the sim is running on a JDK that includes the javac executable (any JDK probably does)") + } + + usable + } + + val COMPILER = ToolProvider.getSystemJavaCompiler() + + val INDENT = " " + val TAG = "PipelineCompiler" + } + + private var diagnosticBuilders = mutableMapOf() + + val latestDiagnostic: String + get() { + val diagnostic = StringBuilder() + for((_, builder) in diagnosticBuilders) { + diagnostic.appendLine(builder) + diagnostic.appendLine("") + } + + return diagnostic.toString().trim() + } + + val args = arrayListOf( + "-source", "1.8", + "-target", "1.8", + "-g", + "-encoding", "UTF-8", + "-Xlint:unchecked", + "-Xlint:deprecation", + "-XDuseUnsharedTable=true" + ) + + constructor(inputPath: File) : this(inputPath, SysUtil.filesUnder(inputPath, ".java")) + + fun compile(outputJar: File): PipelineCompileResult { + val javac = COMPILER + + val fileManager = PipelineStandardFileManager(javac.getStandardFileManager(this, null, null)) + fileManager.sourcePath = Collections.singleton(sourcesInputPath) + + val javaFileObjects = fileManager.getJavaFileObjects(*sourceFiles.toTypedArray()) + + if(javaFileObjects.iterator().hasNext()) { + SysUtil.deleteFilesUnder(CompiledPipelineManager.CLASSES_OUTPUT_FOLDER) + + val task = javac.getTask( + PrintWriter(System.out), + fileManager, + this, + args, + null, + javaFileObjects + ) + + if(task.call()) { + val outputClasses = fileManager.getLocation(StandardLocation.CLASS_OUTPUT).iterator().next() + + if(resInputPath == null || resFiles == null) { + JarPacker.packClassesUnder(outputJar, outputClasses) + } else { + JarPacker.packResAndClassesUnder(outputJar, outputClasses, resInputPath, resFiles) + } + + return PipelineCompileResult(PipelineCompileStatus.SUCCESS, latestDiagnostic) + } + + return PipelineCompileResult(PipelineCompileStatus.FAILED, latestDiagnostic) + } else { + return PipelineCompileResult(PipelineCompileStatus.NO_SOURCE, "No source files") + } + } + + override fun report(diagnostic: Diagnostic) { + val locale = Locale.getDefault() + val relativeFile = SysUtil.getRelativePath(sourcesInputPath, File(diagnostic.source.name)) + + val builder = diagnosticBuilders[relativeFile.path] ?: StringBuilder() + + if(!diagnosticBuilders.containsKey(relativeFile.path)) { + builder.appendLine("> ${relativeFile.path}") + diagnosticBuilders[relativeFile.path] = builder + } + + val formattedMessage = diagnostic.getMessage(locale).replace("\n", "\n$INDENT") + + builder.appendLine(String.format(locale, "$INDENT(%d:%d): %s: %s", + diagnostic.lineNumber, diagnostic.columnNumber, diagnostic.kind, formattedMessage + )) + } + +} + +enum class PipelineCompileStatus { + SUCCESS, + FAILED, + NO_SOURCE +} + data class PipelineCompileResult(val status: PipelineCompileStatus, val message: String) \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineStandardFileManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineStandardFileManager.kt index 8b1efc94..868d372b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineStandardFileManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/PipelineStandardFileManager.kt @@ -1,67 +1,67 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline.compiler - -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.util.compiler.DelegatingStandardFileManager -import java.io.File -import java.util.* -import javax.tools.StandardJavaFileManager -import javax.tools.StandardLocation - -class PipelineStandardFileManager(delegate: StandardJavaFileManager) : DelegatingStandardFileManager(delegate) { - - var sourcePath: Iterable - set(value) = delegate.setLocation(StandardLocation.SOURCE_PATH, value) - get() = delegate.getLocation(StandardLocation.SOURCE_PATH) - - companion object { - val classpath by lazy { - val classpathList = arrayListOf() - - Log.info(TAG, "Scanning classpath files...") - - for(file in SysUtil.getClasspathFiles()) { - val files = SysUtil.filesUnder(file, ".jar") - files.forEach { Log.info(TAG, "Found classpath file ${it.absolutePath} in classpath") } - - classpathList.addAll(files) - } - - Log.blank() - - classpathList.toList() - } - - private const val TAG = "PipelineStandardFileManager" - } - - init { - delegate.setLocation(StandardLocation.CLASS_PATH, classpath) - delegate.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singletonList(CompiledPipelineManager.CLASSES_OUTPUT_FOLDER)) - delegate.setLocation(StandardLocation.SOURCE_OUTPUT, Collections.singletonList(CompiledPipelineManager.SOURCES_OUTPUT_FOLDER)) - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.pipeline.compiler + +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.compiler.DelegatingStandardFileManager +import java.io.File +import java.util.* +import javax.tools.StandardJavaFileManager +import javax.tools.StandardLocation + +class PipelineStandardFileManager(delegate: StandardJavaFileManager) : DelegatingStandardFileManager(delegate) { + + var sourcePath: Iterable + set(value) = delegate.setLocation(StandardLocation.SOURCE_PATH, value) + get() = delegate.getLocation(StandardLocation.SOURCE_PATH) + + companion object { + val classpath by lazy { + val classpathList = arrayListOf() + + Log.info(TAG, "Scanning classpath files...") + + for(file in SysUtil.getClasspathFiles()) { + val files = SysUtil.filesUnder(file, ".jar") + files.forEach { Log.info(TAG, "Found classpath file ${it.absolutePath} in classpath") } + + classpathList.addAll(files) + } + + Log.blank() + + classpathList.toList() + } + + private const val TAG = "PipelineStandardFileManager" + } + + init { + delegate.setLocation(StandardLocation.CLASS_PATH, classpath) + delegate.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singletonList(CompiledPipelineManager.CLASSES_OUTPUT_FOLDER)) + delegate.setLocation(StandardLocation.SOURCE_OUTPUT, Collections.singletonList(CompiledPipelineManager.SOURCES_OUTPUT_FOLDER)) + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt index c763c03f..b844e203 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt @@ -1,171 +1,171 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -package com.github.serivesmejia.eocvsim.pipeline.util; - -import com.github.serivesmejia.eocvsim.pipeline.PipelineData -import com.github.serivesmejia.eocvsim.pipeline.PipelineManager -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import com.github.serivesmejia.eocvsim.util.StrUtil -import com.github.serivesmejia.eocvsim.util.Log - -class PipelineExceptionTracker(private val pipelineManager: PipelineManager) { - - companion object { - private const val TAG = "PipelineExceptionTracker" - - const val millisExceptionExpire = 25000L - const val cutStacktraceLines = 9 - } - - var currentPipeline: PipelineData? = null - private set - - val exceptionsThrown = mutableMapOf() - val messages = mutableMapOf() - - val onPipelineException = EventHandler("OnPipelineException") - val onNewPipelineException = EventHandler("OnNewPipelineException") - val onPipelineExceptionClear = EventHandler("OnPipelineExceptionClear") - - val onUpdate = EventHandler("OnPipelineExceptionTrackerUpdate") - - fun update(data: PipelineData, ex: Throwable?) { - if(currentPipeline != data) { - exceptionsThrown.clear() - currentPipeline = data - } - - val exStr = if(ex != null) StrUtil.fromException(ex) else "" - - if(ex != null) { - onPipelineException.run() - - val exception = exceptionsThrown.values.stream().filter { - it.stacktrace == exStr - }.findFirst() - - if(!exception.isPresent) { - Log.blank() - Log.warn( - TAG, "Uncaught exception thrown while processing pipeline ${data.clazz.simpleName}", - ex - ) - - Log.warn(TAG, "Note that to avoid spam, continuously equal thrown exceptions are only logged once.") - Log.warn(TAG, "It will be reported once the pipeline stops throwing the exception after $millisExceptionExpire ms") - Log.blank() - - exceptionsThrown[ex] = PipelineException( - 0, exStr, System.currentTimeMillis() - ) - - onNewPipelineException.run() - } - } - - for((e, d) in exceptionsThrown.entries.toTypedArray()) { - if(ex != null && d.stacktrace == exStr) { - d.count++ - d.millisThrown = System.currentTimeMillis() - } - - val timeElapsed = System.currentTimeMillis() - d.millisThrown - if(timeElapsed >= millisExceptionExpire) { - exceptionsThrown.remove(e) - Log.info( - TAG, - "Pipeline ${currentPipeline!!.clazz.simpleName} stopped throwing $e" - ) - - if(exceptionsThrown.isEmpty()) - onPipelineExceptionClear.run() - } - } - - for((message, millisAdded) in messages.entries.toTypedArray()) { - val timeElapsed = System.currentTimeMillis() - millisAdded - if(timeElapsed >= millisExceptionExpire) { - messages.remove(message) - } - } - - onUpdate.run() - } - - val message: String get() { - if(currentPipeline == null) - return "**No pipeline selected**" - - val messageBuilder = StringBuilder() - val pipelineName = currentPipeline!!.clazz.simpleName - - if(exceptionsThrown.isNotEmpty()) { - messageBuilder - .append("**Pipeline $pipelineName is throwing ${exceptionsThrown.size} exception(s)**") - .appendLine("\n") - } else { - messageBuilder.append("**Pipeline $pipelineName ") - - if(pipelineManager.paused) { - messageBuilder.append("is paused (last time was running at ${pipelineManager.pipelineFpsCounter.fps} FPS)") - } else { - messageBuilder.append("running OK at ${pipelineManager.pipelineFpsCounter.fps} FPS") - } - - messageBuilder.append("**").appendLine("\n") - } - - for((_, data) in exceptionsThrown) { - val expiresIn = millisExceptionExpire - (System.currentTimeMillis() - data.millisThrown) - val expiresInSecs = String.format("%.1f", expiresIn.toDouble() / 1000.0) - - val shortStacktrace = StrUtil.cutStringBy( - data.stacktrace, "\n", cutStacktraceLines - ).trim() - - messageBuilder - .appendLine("> $shortStacktrace") - .appendLine() - .appendLine("! It has been thrown ${data.count} times, and will expire in $expiresInSecs seconds !") - .appendLine() - } - - for((message, _) in messages) { - messageBuilder.appendLine(message) - } - - return messageBuilder.toString().trim() - } - - fun clear() = exceptionsThrown.clear() - - fun addMessage(s: String) { - messages[s] = System.currentTimeMillis() - onNewPipelineException.run() - } - - data class PipelineException(var count: Int, - val stacktrace: String, - var millisThrown: Long) - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +package com.github.serivesmejia.eocvsim.pipeline.util; + +import com.github.serivesmejia.eocvsim.pipeline.PipelineData +import com.github.serivesmejia.eocvsim.pipeline.PipelineManager +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.StrUtil +import com.github.serivesmejia.eocvsim.util.Log + +class PipelineExceptionTracker(private val pipelineManager: PipelineManager) { + + companion object { + private const val TAG = "PipelineExceptionTracker" + + const val millisExceptionExpire = 25000L + const val cutStacktraceLines = 9 + } + + var currentPipeline: PipelineData? = null + private set + + val exceptionsThrown = mutableMapOf() + val messages = mutableMapOf() + + val onPipelineException = EventHandler("OnPipelineException") + val onNewPipelineException = EventHandler("OnNewPipelineException") + val onPipelineExceptionClear = EventHandler("OnPipelineExceptionClear") + + val onUpdate = EventHandler("OnPipelineExceptionTrackerUpdate") + + fun update(data: PipelineData, ex: Throwable?) { + if(currentPipeline != data) { + exceptionsThrown.clear() + currentPipeline = data + } + + val exStr = if(ex != null) StrUtil.fromException(ex) else "" + + if(ex != null) { + onPipelineException.run() + + val exception = exceptionsThrown.values.stream().filter { + it.stacktrace == exStr + }.findFirst() + + if(!exception.isPresent) { + Log.blank() + Log.warn( + TAG, "Uncaught exception thrown while processing pipeline ${data.clazz.simpleName}", + ex + ) + + Log.warn(TAG, "Note that to avoid spam, continuously equal thrown exceptions are only logged once.") + Log.warn(TAG, "It will be reported once the pipeline stops throwing the exception after $millisExceptionExpire ms") + Log.blank() + + exceptionsThrown[ex] = PipelineException( + 0, exStr, System.currentTimeMillis() + ) + + onNewPipelineException.run() + } + } + + for((e, d) in exceptionsThrown.entries.toTypedArray()) { + if(ex != null && d.stacktrace == exStr) { + d.count++ + d.millisThrown = System.currentTimeMillis() + } + + val timeElapsed = System.currentTimeMillis() - d.millisThrown + if(timeElapsed >= millisExceptionExpire) { + exceptionsThrown.remove(e) + Log.info( + TAG, + "Pipeline ${currentPipeline!!.clazz.simpleName} stopped throwing $e" + ) + + if(exceptionsThrown.isEmpty()) + onPipelineExceptionClear.run() + } + } + + for((message, millisAdded) in messages.entries.toTypedArray()) { + val timeElapsed = System.currentTimeMillis() - millisAdded + if(timeElapsed >= millisExceptionExpire) { + messages.remove(message) + } + } + + onUpdate.run() + } + + val message: String get() { + if(currentPipeline == null) + return "**No pipeline selected**" + + val messageBuilder = StringBuilder() + val pipelineName = currentPipeline!!.clazz.simpleName + + if(exceptionsThrown.isNotEmpty()) { + messageBuilder + .append("**Pipeline $pipelineName is throwing ${exceptionsThrown.size} exception(s)**") + .appendLine("\n") + } else { + messageBuilder.append("**Pipeline $pipelineName ") + + if(pipelineManager.paused) { + messageBuilder.append("is paused (last time was running at ${pipelineManager.pipelineFpsCounter.fps} FPS)") + } else { + messageBuilder.append("running OK at ${pipelineManager.pipelineFpsCounter.fps} FPS") + } + + messageBuilder.append("**").appendLine("\n") + } + + for((_, data) in exceptionsThrown) { + val expiresIn = millisExceptionExpire - (System.currentTimeMillis() - data.millisThrown) + val expiresInSecs = String.format("%.1f", expiresIn.toDouble() / 1000.0) + + val shortStacktrace = StrUtil.cutStringBy( + data.stacktrace, "\n", cutStacktraceLines + ).trim() + + messageBuilder + .appendLine("> $shortStacktrace") + .appendLine() + .appendLine("! It has been thrown ${data.count} times, and will expire in $expiresInSecs seconds !") + .appendLine() + } + + for((message, _) in messages) { + messageBuilder.appendLine(message) + } + + return messageBuilder.toString().trim() + } + + fun clear() = exceptionsThrown.clear() + + fun addMessage(s: String) { + messages[s] = System.currentTimeMillis() + onNewPipelineException.run() + } + + data class PipelineException(var count: Int, + val stacktrace: String, + var millisThrown: Long) + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineSnapshot.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineSnapshot.kt index c37810fe..1df10189 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineSnapshot.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineSnapshot.kt @@ -1,132 +1,132 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.pipeline.util - -import com.github.serivesmejia.eocvsim.util.Log -import org.openftc.easyopencv.OpenCvPipeline -import java.lang.reflect.Field -import java.lang.reflect.Modifier -import java.util.* - -class PipelineSnapshot(holdingPipeline: OpenCvPipeline) { - - companion object { - private val TAG = "PipelineSnapshot" - } - - val holdingPipelineName = holdingPipeline::class.simpleName - - val pipelineFieldValues: Map - val pipelineClass = holdingPipeline::class.java - - init { - val fieldValues = mutableMapOf() - - for(field in pipelineClass.declaredFields) { - if(Modifier.isFinal(field.modifiers) || !Modifier.isPublic(field.modifiers)) - continue - - fieldValues[field] = field.get(holdingPipeline) - } - - pipelineFieldValues = fieldValues.toMap() - - Log.info(TAG, "Taken snapshot of pipeline ${pipelineClass.name}") - } - - fun transferTo(otherPipeline: OpenCvPipeline, - lastInitialPipelineSnapshot: PipelineSnapshot? = null) { - if(pipelineClass.name != otherPipeline::class.java.name) return - - val changedList = if(lastInitialPipelineSnapshot != null) - getChangedFieldsComparedTo(PipelineSnapshot(otherPipeline), lastInitialPipelineSnapshot) - else Collections.emptyList() - - fieldValuesLoop@ - for((field, value) in pipelineFieldValues) { - for(changedField in changedList) { - if(changedField.name == field.name && changedField.type == field.type) { - Log.info( - TAG, - "Skipping field ${field.name} since its value was changed in code, compared to the initial state of the pipeline" - ) - - continue@fieldValuesLoop - } - } - - try { - field.set(otherPipeline, value) - } catch(e: Exception) { - Log.warn( - TAG, - "Failed to set field ${field.name} from snapshot of ${pipelineClass.name}. " + - "Retrying with by name lookup logic..." - ) - - try { - val byNameField = otherPipeline::class.java.getDeclaredField(field.name) - byNameField.set(otherPipeline, value) - } catch(e: Exception) { - Log.warn( - TAG, "Definitely failed to set field ${field.name} from snapshot of ${pipelineClass.name}. " + - "Did the source code change?", e - ) - } - } - } - } - - fun getField(name: String): Pair? { - for((field, value) in pipelineFieldValues) { - if(field.name == name) { - return Pair(field, value) - } - } - - return null - } - - private fun getChangedFieldsComparedTo( - pipelineSnapshotA: PipelineSnapshot, - pipelineSnapshotB: PipelineSnapshot - ): List = pipelineSnapshotA.run { - if(holdingPipelineName != pipelineSnapshotB.holdingPipelineName && pipelineClass != pipelineSnapshotB.pipelineClass) - return Collections.emptyList() - - val changedList = mutableListOf() - - for((field, value) in pipelineFieldValues) { - val (otherField, otherValue) = pipelineSnapshotB.getField(field.name) ?: continue - if (field.type != otherField.type) continue - - if(otherValue != value) { - changedList.add(field) - } - } - - return changedList - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.pipeline.util + +import com.github.serivesmejia.eocvsim.util.Log +import org.openftc.easyopencv.OpenCvPipeline +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import java.util.* + +class PipelineSnapshot(holdingPipeline: OpenCvPipeline) { + + companion object { + private val TAG = "PipelineSnapshot" + } + + val holdingPipelineName = holdingPipeline::class.simpleName + + val pipelineFieldValues: Map + val pipelineClass = holdingPipeline::class.java + + init { + val fieldValues = mutableMapOf() + + for(field in pipelineClass.declaredFields) { + if(Modifier.isFinal(field.modifiers) || !Modifier.isPublic(field.modifiers)) + continue + + fieldValues[field] = field.get(holdingPipeline) + } + + pipelineFieldValues = fieldValues.toMap() + + Log.info(TAG, "Taken snapshot of pipeline ${pipelineClass.name}") + } + + fun transferTo(otherPipeline: OpenCvPipeline, + lastInitialPipelineSnapshot: PipelineSnapshot? = null) { + if(pipelineClass.name != otherPipeline::class.java.name) return + + val changedList = if(lastInitialPipelineSnapshot != null) + getChangedFieldsComparedTo(PipelineSnapshot(otherPipeline), lastInitialPipelineSnapshot) + else Collections.emptyList() + + fieldValuesLoop@ + for((field, value) in pipelineFieldValues) { + for(changedField in changedList) { + if(changedField.name == field.name && changedField.type == field.type) { + Log.info( + TAG, + "Skipping field ${field.name} since its value was changed in code, compared to the initial state of the pipeline" + ) + + continue@fieldValuesLoop + } + } + + try { + field.set(otherPipeline, value) + } catch(e: Exception) { + Log.warn( + TAG, + "Failed to set field ${field.name} from snapshot of ${pipelineClass.name}. " + + "Retrying with by name lookup logic..." + ) + + try { + val byNameField = otherPipeline::class.java.getDeclaredField(field.name) + byNameField.set(otherPipeline, value) + } catch(e: Exception) { + Log.warn( + TAG, "Definitely failed to set field ${field.name} from snapshot of ${pipelineClass.name}. " + + "Did the source code change?", e + ) + } + } + } + } + + fun getField(name: String): Pair? { + for((field, value) in pipelineFieldValues) { + if(field.name == name) { + return Pair(field, value) + } + } + + return null + } + + private fun getChangedFieldsComparedTo( + pipelineSnapshotA: PipelineSnapshot, + pipelineSnapshotB: PipelineSnapshot + ): List = pipelineSnapshotA.run { + if(holdingPipelineName != pipelineSnapshotB.holdingPipelineName && pipelineClass != pipelineSnapshotB.pipelineClass) + return Collections.emptyList() + + val changedList = mutableListOf() + + for((field, value) in pipelineFieldValues) { + val (otherField, otherValue) = pipelineSnapshotB.getField(field.name) ?: continue + if (field.type != otherField.type) continue + + if(otherValue != value) { + changedList.add(field) + } + } + + return changedList + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableField.java index 19a7c7f3..dfecbe76 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableField.java @@ -1,147 +1,147 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanelConfig; -import com.github.serivesmejia.eocvsim.util.event.EventHandler; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; - -public abstract class TunableField { - - protected Field reflectionField; - protected TunableFieldPanel fieldPanel; - - protected OpenCvPipeline pipeline; - protected AllowMode allowMode; - protected EOCVSim eocvSim; - - protected Object initialFieldValue; - - private int guiFieldAmount = 1; - private int guiComboBoxAmount = 0; - - public final EventHandler onValueChange = new EventHandler("TunableField-ValueChange"); - - private TunableFieldPanel.Mode recommendedMode = null; - - public TunableField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim, AllowMode allowMode) throws IllegalAccessException { - this.reflectionField = reflectionField; - this.pipeline = instance; - this.allowMode = allowMode; - this.eocvSim = eocvSim; - - initialFieldValue = reflectionField.get(instance); - } - - public TunableField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - this(instance, reflectionField, eocvSim, AllowMode.TEXT); - } - - public abstract void init(); - - public abstract void update(); - - public abstract void updateGuiFieldValues(); - - public void setPipelineFieldValue(T newValue) throws IllegalAccessException { - if (hasChanged()) { //execute if value is not the same to save resources - reflectionField.set(pipeline, newValue); - onValueChange.run(); - } - } - - public abstract void setGuiFieldValue(int index, String newValue) throws IllegalAccessException; - - public void setGuiComboBoxValue(int index, String newValue) throws IllegalAccessException { } - - public final void setTunableFieldPanel(TunableFieldPanel fieldPanel) { - this.fieldPanel = fieldPanel; - } - - protected final void setRecommendedPanelMode(TunableFieldPanel.Mode mode) { - recommendedMode = mode; - } - - public final void evalRecommendedPanelMode() { - TunableFieldPanelConfig configPanel = fieldPanel.panelOptions.getConfigPanel(); - TunableFieldPanelConfig.ConfigSource configSource = configPanel.getLocalConfig().getSource(); - //only apply the recommendation if user hasn't - //configured a global or specific field config - if(recommendedMode != null && fieldPanel != null && configSource == TunableFieldPanelConfig.ConfigSource.GLOBAL_DEFAULT) { - fieldPanel.setMode(recommendedMode); - } - } - - public abstract T getValue(); - - public abstract Object getGuiFieldValue(int index); - - public Object[] getGuiComboBoxValues(int index) { - return new Object[0]; - } - - public final int getGuiFieldAmount() { - return guiFieldAmount; - } - - public final void setGuiFieldAmount(int amount) { - this.guiFieldAmount = amount; - } - - public final int getGuiComboBoxAmount() { - return guiComboBoxAmount; - } - - public final void setGuiComboBoxAmount(int amount) { - this.guiComboBoxAmount = amount; - } - - public final String getFieldName() { - return reflectionField.getName(); - } - - public final AllowMode getAllowMode() { - return allowMode; - } - - public final boolean isOnlyNumbers() { - return getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS || - getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL; - } - - public abstract boolean hasChanged(); - - public final EOCVSim getEOCVSim() { - return eocvSim; - } - - public enum AllowMode {ONLY_NUMBERS, ONLY_NUMBERS_DECIMAL, TEXT} - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanelConfig; +import com.github.serivesmejia.eocvsim.util.event.EventHandler; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public abstract class TunableField { + + protected Field reflectionField; + protected TunableFieldPanel fieldPanel; + + protected OpenCvPipeline pipeline; + protected AllowMode allowMode; + protected EOCVSim eocvSim; + + protected Object initialFieldValue; + + private int guiFieldAmount = 1; + private int guiComboBoxAmount = 0; + + public final EventHandler onValueChange = new EventHandler("TunableField-ValueChange"); + + private TunableFieldPanel.Mode recommendedMode = null; + + public TunableField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim, AllowMode allowMode) throws IllegalAccessException { + this.reflectionField = reflectionField; + this.pipeline = instance; + this.allowMode = allowMode; + this.eocvSim = eocvSim; + + initialFieldValue = reflectionField.get(instance); + } + + public TunableField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { + this(instance, reflectionField, eocvSim, AllowMode.TEXT); + } + + public abstract void init(); + + public abstract void update(); + + public abstract void updateGuiFieldValues(); + + public void setPipelineFieldValue(T newValue) throws IllegalAccessException { + if (hasChanged()) { //execute if value is not the same to save resources + reflectionField.set(pipeline, newValue); + onValueChange.run(); + } + } + + public abstract void setGuiFieldValue(int index, String newValue) throws IllegalAccessException; + + public void setGuiComboBoxValue(int index, String newValue) throws IllegalAccessException { } + + public final void setTunableFieldPanel(TunableFieldPanel fieldPanel) { + this.fieldPanel = fieldPanel; + } + + protected final void setRecommendedPanelMode(TunableFieldPanel.Mode mode) { + recommendedMode = mode; + } + + public final void evalRecommendedPanelMode() { + TunableFieldPanelConfig configPanel = fieldPanel.panelOptions.getConfigPanel(); + TunableFieldPanelConfig.ConfigSource configSource = configPanel.getLocalConfig().getSource(); + //only apply the recommendation if user hasn't + //configured a global or specific field config + if(recommendedMode != null && fieldPanel != null && configSource == TunableFieldPanelConfig.ConfigSource.GLOBAL_DEFAULT) { + fieldPanel.setMode(recommendedMode); + } + } + + public abstract T getValue(); + + public abstract Object getGuiFieldValue(int index); + + public Object[] getGuiComboBoxValues(int index) { + return new Object[0]; + } + + public final int getGuiFieldAmount() { + return guiFieldAmount; + } + + public final void setGuiFieldAmount(int amount) { + this.guiFieldAmount = amount; + } + + public final int getGuiComboBoxAmount() { + return guiComboBoxAmount; + } + + public final void setGuiComboBoxAmount(int amount) { + this.guiComboBoxAmount = amount; + } + + public final String getFieldName() { + return reflectionField.getName(); + } + + public final AllowMode getAllowMode() { + return allowMode; + } + + public final boolean isOnlyNumbers() { + return getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS || + getAllowMode() == TunableField.AllowMode.ONLY_NUMBERS_DECIMAL; + } + + public abstract boolean hasChanged(); + + public final EOCVSim getEOCVSim() { + return eocvSim; + } + + public enum AllowMode {ONLY_NUMBERS, ONLY_NUMBERS_DECIMAL, TEXT} + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptor.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptor.kt index dcfa4a43..787e8b8f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptor.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptor.kt @@ -1,28 +1,28 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner - -interface TunableFieldAcceptor { - fun accept(clazz: Class<*>): Boolean +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner + +interface TunableFieldAcceptor { + fun accept(clazz: Class<*>): Boolean } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptorManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptorManager.kt index 19f47e03..03b9fe97 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptorManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunableFieldAcceptorManager.kt @@ -1,28 +1,28 @@ -package com.github.serivesmejia.eocvsim.tuner - -import com.github.serivesmejia.eocvsim.util.Log -import java.util.HashMap - -class TunableFieldAcceptorManager(private val acceptors: HashMap>, Class>) { - - fun accept(clazz: Class<*>): Class>? { - for((fieldClass, acceptorClass) in acceptors) { - //try getting a constructor for this acceptor - val acceptorConstructor = try { - acceptorClass.getConstructor() //get constructor with no params - } catch(ex: NoSuchMethodException) { - Log.warn("TunableFieldAcceptorManager", "TunableFieldAcceptor ${acceptorClass.typeName} doesn't implement a constructor with zero parameters", ex) - continue - } - - val acceptor = acceptorConstructor.newInstance() //create an instance of this acceptor - - if(acceptor.accept(clazz)) { //try accepting the given clazz type - return fieldClass //wooo someone accepted our type! return to tell who did. - } - } - - return null //no one accepted our type... poor clazz :( - } - +package com.github.serivesmejia.eocvsim.tuner + +import com.github.serivesmejia.eocvsim.util.Log +import java.util.HashMap + +class TunableFieldAcceptorManager(private val acceptors: HashMap>, Class>) { + + fun accept(clazz: Class<*>): Class>? { + for((fieldClass, acceptorClass) in acceptors) { + //try getting a constructor for this acceptor + val acceptorConstructor = try { + acceptorClass.getConstructor() //get constructor with no params + } catch(ex: NoSuchMethodException) { + Log.warn("TunableFieldAcceptorManager", "TunableFieldAcceptor ${acceptorClass.typeName} doesn't implement a constructor with zero parameters", ex) + continue + } + + val acceptor = acceptorConstructor.newInstance() //create an instance of this acceptor + + if(acceptor.accept(clazz)) { //try accepting the given clazz type + return fieldClass //wooo someone accepted our type! return to tell who did. + } + } + + return null //no one accepted our type... poor clazz :( + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunerManager.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunerManager.java index d97f235b..2df9c07d 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunerManager.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/TunerManager.java @@ -1,171 +1,177 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.tuner.scanner.AnnotatedTunableFieldScanner; -import com.github.serivesmejia.eocvsim.util.Log; -import com.github.serivesmejia.eocvsim.util.ReflectUtil; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -@SuppressWarnings("rawtypes") -public class TunerManager { - - private final EOCVSim eocvSim; - - private final List fields = new ArrayList<>(); - - private TunableFieldAcceptorManager acceptorManager = null; - - private static HashMap>> tunableFieldsTypes = null; - private static HashMap>, Class> tunableFieldAcceptors = null; - - private boolean firstInit = true; - - public TunerManager(EOCVSim eocvSim) { - this.eocvSim = eocvSim; - } - - public void init() { - if(tunableFieldsTypes == null) { - AnnotatedTunableFieldScanner.ScanResult result = new AnnotatedTunableFieldScanner( - eocvSim.getParams().getScanForTunableFieldsIn() - ).scan(); - - tunableFieldsTypes = result.getTunableFields(); - tunableFieldAcceptors = result.getAcceptors(); - } - - // for some reason, acceptorManager becomes null after a certain time passes - // (maybe garbage collected? i don't know for sure...), but we can simply recover - // from this by creating a new one with the found acceptors by the scanner, no problem. - if(acceptorManager == null) - acceptorManager = new TunableFieldAcceptorManager(tunableFieldAcceptors); - - if (firstInit) { - eocvSim.pipelineManager.onPipelineChange.doPersistent(this::reset); - firstInit = false; - } - - if (eocvSim.pipelineManager.getCurrentPipeline() != null) { - addFieldsFrom(eocvSim.pipelineManager.getCurrentPipeline()); - eocvSim.visualizer.updateTunerFields(createTunableFieldPanels()); - - for(TunableField field : fields) { - field.init(); - } - } - } - - public void update() { - //update all fields - for(TunableField field : fields.toArray(new TunableField[0])) { - try { - field.update(); - } catch(Exception ex) { - Log.error("Error while updating field " + field.getFieldName(), ex); - } - - //check if this field has requested to reevaluate config for all panels - if(field.fieldPanel.hasRequestedAllConfigReeval()) { - //if so, iterate through all fields to reevaluate - for(TunableField f : fields.toArray(new TunableField[0])) { - f.fieldPanel.panelOptions.reevaluateConfig(); - } - } - } - } - - public void reset() { - fields.clear(); - init(); - } - - public void addFieldsFrom(OpenCvPipeline pipeline) { - - if (pipeline == null) return; - - Field[] fields = pipeline.getClass().getFields(); - - for (Field field : fields) { - - //we only accept non-final fields - if (Modifier.isFinal(field.getModifiers())) continue; - - Class type = field.getType(); - if (field.getType().isPrimitive()) { //wrap to java object equivalent if field type is primitive - type = ReflectUtil.wrap(type); - } - - Class tunableFieldClass = null; - - if(tunableFieldsTypes.containsKey(type)) { - tunableFieldClass = tunableFieldsTypes.get(type); - } else { - //if we don't have a class yet, use our acceptors - if(acceptorManager != null) tunableFieldClass = acceptorManager.accept(type); - //still haven't got anything, give up here. - if(tunableFieldClass == null) continue; - } - - //yay we have a registered TunableField which handles this - //now, lets do some more reflection to instantiate this TunableField - //and add it to the list... - try { - Constructor constructor = tunableFieldClass.getConstructor(OpenCvPipeline.class, Field.class, EOCVSim.class); - this.fields.add(constructor.newInstance(pipeline, field, eocvSim)); - } catch (Exception ex) { - //oops rip - Log.error("TunerManager", "Reflection error while processing field: " + field.getName(), ex); - } - - } - } - - public void reevaluateConfigs() { - for(TunableField field : fields) { - field.fieldPanel.panelOptions.reevaluateConfig(); - } - } - - private List createTunableFieldPanels() { - List panels = new ArrayList<>(); - - for (TunableField field : fields) { - panels.add(new TunableFieldPanel(field, eocvSim)); - } - - return panels; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; +import com.github.serivesmejia.eocvsim.tuner.scanner.AnnotatedTunableFieldScanner; +import com.github.serivesmejia.eocvsim.util.Log; +import com.github.serivesmejia.eocvsim.util.ReflectUtil; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("rawtypes") +public class TunerManager { + + private final EOCVSim eocvSim; + + private final List fields = new ArrayList<>(); + + private TunableFieldAcceptorManager acceptorManager = null; + + private static HashMap>> tunableFieldsTypes = null; + private static HashMap>, Class> tunableFieldAcceptors = null; + + private boolean firstInit = true; + + public TunerManager(EOCVSim eocvSim) { + this.eocvSim = eocvSim; + } + + public void init() { + if(tunableFieldsTypes == null) { + tunableFieldsTypes = new HashMap<>(); + // ... + for(Class> clazz : eocvSim.getClasspathScan().getScanResult().getTunableFieldClasses()) { + tunableFieldsTypes.put(ReflectUtil.getTypeArgumentsFrom(clazz)[0], clazz); + } + } + + if(tunableFieldAcceptors == null) { + tunableFieldAcceptors = new HashMap<>(); + // oh god... + eocvSim.getClasspathScan().getScanResult().getTunableFieldAcceptorClasses().forEach(tunableFieldAcceptors::put); + } + + // for some reason, acceptorManager becomes null after a certain time passes + // (maybe garbage collected? i don't know for sure...), but we can simply recover + // from this by creating a new one with the found acceptors by the scanner, no problem. + if(acceptorManager == null) + acceptorManager = new TunableFieldAcceptorManager(tunableFieldAcceptors); + + if (firstInit) { + eocvSim.pipelineManager.onPipelineChange.doPersistent(this::reset); + firstInit = false; + } + + if (eocvSim.pipelineManager.getCurrentPipeline() != null) { + addFieldsFrom(eocvSim.pipelineManager.getCurrentPipeline()); + eocvSim.visualizer.updateTunerFields(createTunableFieldPanels()); + + for(TunableField field : fields) { + field.init(); + } + } + } + + public void update() { + //update all fields + for(TunableField field : fields.toArray(new TunableField[0])) { + try { + field.update(); + } catch(Exception ex) { + Log.error("Error while updating field " + field.getFieldName(), ex); + } + + //check if this field has requested to reevaluate config for all panels + if(field.fieldPanel.hasRequestedAllConfigReeval()) { + //if so, iterate through all fields to reevaluate + for(TunableField f : fields.toArray(new TunableField[0])) { + f.fieldPanel.panelOptions.reevaluateConfig(); + } + } + } + } + + public void reset() { + fields.clear(); + init(); + } + + public void addFieldsFrom(OpenCvPipeline pipeline) { + + if (pipeline == null) return; + + Field[] fields = pipeline.getClass().getFields(); + + for (Field field : fields) { + + //we only accept non-final fields + if (Modifier.isFinal(field.getModifiers())) continue; + + Class type = field.getType(); + if (field.getType().isPrimitive()) { //wrap to java object equivalent if field type is primitive + type = ReflectUtil.wrap(type); + } + + Class tunableFieldClass = null; + + if(tunableFieldsTypes.containsKey(type)) { + tunableFieldClass = tunableFieldsTypes.get(type); + } else { + //if we don't have a class yet, use our acceptors + if(acceptorManager != null) tunableFieldClass = acceptorManager.accept(type); + //still haven't got anything, give up here. + if(tunableFieldClass == null) continue; + } + + //yay we have a registered TunableField which handles this + //now, lets do some more reflection to instantiate this TunableField + //and add it to the list... + try { + Constructor constructor = tunableFieldClass.getConstructor(OpenCvPipeline.class, Field.class, EOCVSim.class); + this.fields.add(constructor.newInstance(pipeline, field, eocvSim)); + } catch (Exception ex) { + //oops rip + Log.error("TunerManager", "Reflection error while processing field: " + field.getName(), ex); + } + + } + } + + public void reevaluateConfigs() { + for(TunableField field : fields) { + field.fieldPanel.panelOptions.reevaluateConfig(); + } + } + + private List createTunableFieldPanels() { + List panels = new ArrayList<>(); + + for (TunableField field : fields) { + panels.add(new TunableFieldPanel(field, eocvSim)); + } + + return panels; + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/BooleanField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/BooleanField.java index 687cfba4..289e49a3 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/BooleanField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/BooleanField.java @@ -1,106 +1,106 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.TunableField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Field; - -@RegisterTunableField -public class BooleanField extends TunableField { - - boolean value; - - boolean lastVal; - volatile boolean hasChanged = false; - - public BooleanField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - - super(instance, reflectionField, eocvSim, AllowMode.TEXT); - - setGuiFieldAmount(0); - setGuiComboBoxAmount(1); - - value = (boolean) initialFieldValue; - - } - - @Override - public void init() {} - - @Override - public void update() { - - hasChanged = value != lastVal; - - if (hasChanged) { //update values in GUI if they changed since last check - updateGuiFieldValues(); - } - - lastVal = value; - - } - - @Override - public void updateGuiFieldValues() { - fieldPanel.setComboBoxSelection(0, value); - } - - @Override - public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { - setGuiComboBoxValue(index, newValue); - } - - @Override - public void setGuiComboBoxValue(int index, String newValue) throws IllegalAccessException { - value = Boolean.parseBoolean(newValue); - setPipelineFieldValue(value); - lastVal = value; - } - - @Override - public Boolean getValue() { - return value; - } - - @Override - public Object getGuiFieldValue(int index) { - return value; - } - - @Override - public Object[] getGuiComboBoxValues(int index) { - return new Boolean[]{value, !value}; - } - - @Override - public boolean hasChanged() { - hasChanged = value != lastVal; - return hasChanged; - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.field; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.tuner.TunableField; +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Field; + +@RegisterTunableField +public class BooleanField extends TunableField { + + boolean value; + + boolean lastVal; + volatile boolean hasChanged = false; + + public BooleanField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { + + super(instance, reflectionField, eocvSim, AllowMode.TEXT); + + setGuiFieldAmount(0); + setGuiComboBoxAmount(1); + + value = (boolean) initialFieldValue; + + } + + @Override + public void init() {} + + @Override + public void update() { + + hasChanged = value != lastVal; + + if (hasChanged) { //update values in GUI if they changed since last check + updateGuiFieldValues(); + } + + lastVal = value; + + } + + @Override + public void updateGuiFieldValues() { + fieldPanel.setComboBoxSelection(0, value); + } + + @Override + public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { + setGuiComboBoxValue(index, newValue); + } + + @Override + public void setGuiComboBoxValue(int index, String newValue) throws IllegalAccessException { + value = Boolean.parseBoolean(newValue); + setPipelineFieldValue(value); + lastVal = value; + } + + @Override + public Boolean getValue() { + return value; + } + + @Override + public Object getGuiFieldValue(int index) { + return value; + } + + @Override + public Object[] getGuiComboBoxValues(int index) { + return new Boolean[]{value, !value}; + } + + @Override + public boolean hasChanged() { + hasChanged = value != lastVal; + return hasChanged; + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/EnumField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/EnumField.kt index 30aa7405..42f60e99 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/EnumField.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/EnumField.kt @@ -1,65 +1,65 @@ -package com.github.serivesmejia.eocvsim.tuner.field - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.tuner.TunableField -import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableFieldAcceptor -import org.openftc.easyopencv.OpenCvPipeline -import java.lang.reflect.Field - -@RegisterTunableField -class EnumField(private val instance: OpenCvPipeline, - reflectionField: Field, - eocvSim: EOCVSim) : TunableField>(instance, reflectionField, eocvSim, AllowMode.TEXT) { - - val values = reflectionField.type.enumConstants - - private val initialValue = initialFieldValue as Enum<*> - - private var currentValue = initialValue - private var beforeValue: Any? = null - - init { - guiComboBoxAmount = 1 - guiFieldAmount = 0 - } - - override fun init() { - fieldPanel.setComboBoxSelection(0, currentValue) - } - - override fun update() { - if(hasChanged()) { - currentValue = value - updateGuiFieldValues() - } - beforeValue = currentValue - } - - override fun updateGuiFieldValues() { - fieldPanel.setComboBoxSelection(0, currentValue) - } - - override fun setGuiComboBoxValue(index: Int, newValue: String) = setGuiFieldValue(index, newValue) - - override fun setGuiFieldValue(index: Int, newValue: String) { - currentValue = java.lang.Enum.valueOf(initialValue::class.java, newValue) - reflectionField.set(instance, currentValue) - } - - override fun getValue() = currentValue - - override fun getGuiFieldValue(index: Int) = currentValue.name - - override fun getGuiComboBoxValues(index: Int): Array { - return values - } - - override fun hasChanged() = reflectionField.get(instance) == beforeValue - - class EnumFieldAcceptor: TunableFieldAcceptor { - override fun accept(clazz: Class<*>) = clazz.isEnum - } - +package com.github.serivesmejia.eocvsim.tuner.field + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.tuner.TunableField +import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableFieldAcceptor +import org.openftc.easyopencv.OpenCvPipeline +import java.lang.reflect.Field + +@RegisterTunableField +class EnumField(private val instance: OpenCvPipeline, + reflectionField: Field, + eocvSim: EOCVSim) : TunableField>(instance, reflectionField, eocvSim, AllowMode.TEXT) { + + val values = reflectionField.type.enumConstants + + private val initialValue = initialFieldValue as Enum<*> + + private var currentValue = initialValue + private var beforeValue: Any? = null + + init { + guiComboBoxAmount = 1 + guiFieldAmount = 0 + } + + override fun init() { + fieldPanel.setComboBoxSelection(0, currentValue) + } + + override fun update() { + if(hasChanged()) { + currentValue = value + updateGuiFieldValues() + } + beforeValue = currentValue + } + + override fun updateGuiFieldValues() { + fieldPanel.setComboBoxSelection(0, currentValue) + } + + override fun setGuiComboBoxValue(index: Int, newValue: String) = setGuiFieldValue(index, newValue) + + override fun setGuiFieldValue(index: Int, newValue: String) { + currentValue = java.lang.Enum.valueOf(initialValue::class.java, newValue) + reflectionField.set(instance, currentValue) + } + + override fun getValue() = currentValue + + override fun getGuiFieldValue(index: Int) = currentValue.name + + override fun getGuiComboBoxValues(index: Int): Array { + return values + } + + override fun hasChanged() = reflectionField.get(instance) != beforeValue + + class EnumFieldAcceptor : TunableFieldAcceptor { + override fun accept(clazz: Class<*>) = clazz.isEnum + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/NumericField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/NumericField.java index 29a19690..cdca560a 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/NumericField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/NumericField.java @@ -1,89 +1,89 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.tuner.TunableField; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Field; - -public class NumericField extends TunableField { - - protected T value; - - protected volatile boolean hasChanged = false; - - public NumericField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim, AllowMode allowMode) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, allowMode); - } - - @Override - public void init() { - setRecommendedPanelMode(TunableFieldPanel.Mode.TEXTBOXES); - } - - @Override - public void update() { - if (value == null) return; - - try { - value = (T) reflectionField.get(pipeline); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - - hasChanged = hasChanged(); - - if (hasChanged) { - updateGuiFieldValues(); - } - } - - @Override - public void updateGuiFieldValues() { - fieldPanel.setFieldValue(0, value); - } - - @Override - public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { - } - - @Override - public T getValue() { - return value; - } - - @Override - public Object getGuiFieldValue(int index) { - return value; - } - - @Override - public boolean hasChanged() { - return false; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.field; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; +import com.github.serivesmejia.eocvsim.tuner.TunableField; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Field; + +public class NumericField extends TunableField { + + protected T value; + + protected volatile boolean hasChanged = false; + + public NumericField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim, AllowMode allowMode) throws IllegalAccessException { + super(instance, reflectionField, eocvSim, allowMode); + } + + @Override + public void init() { + setRecommendedPanelMode(TunableFieldPanel.Mode.TEXTBOXES); + } + + @Override + public void update() { + if (value == null) return; + + try { + value = (T) reflectionField.get(pipeline); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + hasChanged = hasChanged(); + + if (hasChanged) { + updateGuiFieldValues(); + } + } + + @Override + public void updateGuiFieldValues() { + fieldPanel.setFieldValue(0, value); + } + + @Override + public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { + } + + @Override + public T getValue() { + return value; + } + + @Override + public Object getGuiFieldValue(int index) { + return value; + } + + @Override + public boolean hasChanged() { + return false; + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/StringField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/StringField.java index af69baad..f1cd85a3 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/StringField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/StringField.java @@ -1,98 +1,98 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.tuner.TunableField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Field; - -@RegisterTunableField -public class StringField extends TunableField { - - String value; - - String lastVal = ""; - - volatile boolean hasChanged = false; - - public StringField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - - super(instance, reflectionField, eocvSim, AllowMode.TEXT); - value = (String) initialFieldValue; - - } - - @Override - public void init() { - setRecommendedPanelMode(TunableFieldPanel.Mode.TEXTBOXES); - } - - @Override - public void update() { - hasChanged = !value.equals(lastVal); - - if (hasChanged) { //update values in GUI if they changed since last check - updateGuiFieldValues(); - } - - lastVal = value; - } - - @Override - public void updateGuiFieldValues() { - fieldPanel.setFieldValue(0, value); - } - - @Override - public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { - - value = newValue; - - setPipelineFieldValue(value); - - lastVal = value; - - } - - @Override - public String getValue() { - return value; - } - - @Override - public Object getGuiFieldValue(int index) { - return value; - } - - @Override - public boolean hasChanged() { - hasChanged = !value.equals(lastVal); - return hasChanged; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.field; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; +import com.github.serivesmejia.eocvsim.tuner.TunableField; +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Field; + +@RegisterTunableField +public class StringField extends TunableField { + + String value; + + String lastVal = ""; + + volatile boolean hasChanged = false; + + public StringField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { + + super(instance, reflectionField, eocvSim, AllowMode.TEXT); + value = (String) initialFieldValue; + + } + + @Override + public void init() { + setRecommendedPanelMode(TunableFieldPanel.Mode.TEXTBOXES); + } + + @Override + public void update() { + hasChanged = !value.equals(lastVal); + + if (hasChanged) { //update values in GUI if they changed since last check + updateGuiFieldValues(); + } + + lastVal = value; + } + + @Override + public void updateGuiFieldValues() { + fieldPanel.setFieldValue(0, value); + } + + @Override + public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { + + value = newValue; + + setPipelineFieldValue(value); + + lastVal = value; + + } + + @Override + public String getValue() { + return value; + } + + @Override + public Object getGuiFieldValue(int index) { + return value; + } + + @Override + public boolean hasChanged() { + hasChanged = !value.equals(lastVal); + return hasChanged; + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/PointField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/PointField.java index 65b4c185..0f545153 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/PointField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/PointField.java @@ -1,113 +1,113 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.cv; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.TunableField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import org.opencv.core.Point; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Field; - -@RegisterTunableField -public class PointField extends TunableField { - - Point point; - - double[] lastXY = {0, 0}; - - volatile boolean hasChanged = false; - - public PointField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); - - Point p = (Point) initialFieldValue; - - point = new Point(p.x, p.y); - - setGuiFieldAmount(2); - - } - - @Override - public void init() { } - - @Override - public void update() { - - hasChanged = point.x != lastXY[0] || point.y != lastXY[1]; - - if (hasChanged) { //update values in GUI if they changed since last check - updateGuiFieldValues(); - } - - lastXY = new double[]{point.x, point.y}; - - } - - @Override - public void updateGuiFieldValues() { - fieldPanel.setFieldValue(0, point.x); - fieldPanel.setFieldValue(1, point.y); - } - - @Override - public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { - - try { - double value = Double.parseDouble(newValue); - if (index == 0) { - point.x = value; - } else { - point.y = value; - } - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid numeric String"); - } - - setPipelineFieldValue(point); - - lastXY = new double[]{point.x, point.y}; - - } - - @Override - public Point getValue() { - return point; - } - - @Override - public Object getGuiFieldValue(int index) { - return index == 0 ? point.x : point.y; - } - - @Override - public boolean hasChanged() { - hasChanged = point.x != lastXY[0] || point.y != lastXY[1]; - return hasChanged; - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.field.cv; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.tuner.TunableField; +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; +import org.opencv.core.Point; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Field; + +@RegisterTunableField +public class PointField extends TunableField { + + Point point; + + double[] lastXY = {0, 0}; + + volatile boolean hasChanged = false; + + public PointField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { + + super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); + + Point p = (Point) initialFieldValue; + + point = new Point(p.x, p.y); + + setGuiFieldAmount(2); + + } + + @Override + public void init() { } + + @Override + public void update() { + + hasChanged = point.x != lastXY[0] || point.y != lastXY[1]; + + if (hasChanged) { //update values in GUI if they changed since last check + updateGuiFieldValues(); + } + + lastXY = new double[]{point.x, point.y}; + + } + + @Override + public void updateGuiFieldValues() { + fieldPanel.setFieldValue(0, point.x); + fieldPanel.setFieldValue(1, point.y); + } + + @Override + public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { + + try { + double value = Double.parseDouble(newValue); + if (index == 0) { + point.x = value; + } else { + point.y = value; + } + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Parameter should be a valid numeric String"); + } + + setPipelineFieldValue(point); + + lastXY = new double[]{point.x, point.y}; + + } + + @Override + public Point getValue() { + return point; + } + + @Override + public Object getGuiFieldValue(int index) { + return index == 0 ? point.x : point.y; + } + + @Override + public boolean hasChanged() { + hasChanged = point.x != lastXY[0] || point.y != lastXY[1]; + return hasChanged; + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/RectField.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/RectField.kt index ed612476..648ea148 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/RectField.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/RectField.kt @@ -1,101 +1,101 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.cv - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.tuner.TunableField -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField -import org.opencv.core.Rect -import org.openftc.easyopencv.OpenCvPipeline -import java.lang.reflect.Field - -@RegisterTunableField -class RectField(instance: OpenCvPipeline, reflectionField: Field, eocvSim: EOCVSim) : - TunableField(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL) { - - private var rect = arrayOf(0.0, 0.0, 0.0, 0.0) - private var lastRect = arrayOf(0.0, 0.0, 0.0, 0.0) - - @Volatile private var hasChanged = false - - private var initialRect = initialFieldValue as Rect - - init { - rect[0] = initialRect.x.toDouble() - rect[1] = initialRect.y.toDouble() - rect[2] = initialRect.width.toDouble() - rect[3] = initialRect.height.toDouble() - - guiFieldAmount = 4 - } - - override fun init() {} - - override fun update() { - if(hasChanged()){ - initialRect = reflectionField.get(pipeline) as Rect - - rect[0] = initialRect.x.toDouble() - rect[1] = initialRect.y.toDouble() - rect[2] = initialRect.width.toDouble() - rect[3] = initialRect.height.toDouble() - - updateGuiFieldValues() - } - } - - override fun updateGuiFieldValues() { - for((i, value) in rect.withIndex()) { - fieldPanel.setFieldValue(i, value) - } - } - - override fun setGuiFieldValue(index: Int, newValue: String) { - try { - val value = newValue.toDouble() - rect[index] = value - } catch (ex: NumberFormatException) { - throw IllegalArgumentException("Parameter should be a valid numeric String") - } - - initialRect.set(rect.toDoubleArray()); - setPipelineFieldValue(initialRect) - - lastRect[0] = initialRect.x.toDouble() - lastRect[1] = initialRect.y.toDouble() - lastRect[2] = initialRect.width.toDouble() - lastRect[3] = initialRect.height.toDouble() - } - - override fun getValue(): Rect = Rect(rect.toDoubleArray()) - - override fun getGuiFieldValue(index: Int): Any = rect[index] - - override fun hasChanged(): Boolean { - hasChanged = rect[0] != lastRect[0] || rect[1] != lastRect[1] - || rect[2] != lastRect[2] || rect[3] != lastRect[3] - return hasChanged - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.field.cv + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.tuner.TunableField +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField +import org.opencv.core.Rect +import org.openftc.easyopencv.OpenCvPipeline +import java.lang.reflect.Field + +@RegisterTunableField +class RectField(instance: OpenCvPipeline, reflectionField: Field, eocvSim: EOCVSim) : + TunableField(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL) { + + private var rect = arrayOf(0.0, 0.0, 0.0, 0.0) + private var lastRect = arrayOf(0.0, 0.0, 0.0, 0.0) + + @Volatile private var hasChanged = false + + private var initialRect = initialFieldValue as Rect + + init { + rect[0] = initialRect.x.toDouble() + rect[1] = initialRect.y.toDouble() + rect[2] = initialRect.width.toDouble() + rect[3] = initialRect.height.toDouble() + + guiFieldAmount = 4 + } + + override fun init() {} + + override fun update() { + if(hasChanged()){ + initialRect = reflectionField.get(pipeline) as Rect + + rect[0] = initialRect.x.toDouble() + rect[1] = initialRect.y.toDouble() + rect[2] = initialRect.width.toDouble() + rect[3] = initialRect.height.toDouble() + + updateGuiFieldValues() + } + } + + override fun updateGuiFieldValues() { + for((i, value) in rect.withIndex()) { + fieldPanel.setFieldValue(i, value) + } + } + + override fun setGuiFieldValue(index: Int, newValue: String) { + try { + val value = newValue.toDouble() + rect[index] = value + } catch (ex: NumberFormatException) { + throw IllegalArgumentException("Parameter should be a valid numeric String") + } + + initialRect.set(rect.toDoubleArray()); + setPipelineFieldValue(initialRect) + + lastRect[0] = initialRect.x.toDouble() + lastRect[1] = initialRect.y.toDouble() + lastRect[2] = initialRect.width.toDouble() + lastRect[3] = initialRect.height.toDouble() + } + + override fun getValue(): Rect = Rect(rect.toDoubleArray()) + + override fun getGuiFieldValue(index: Int): Any = rect[index] + + override fun hasChanged(): Boolean { + hasChanged = rect[0] != lastRect[0] || rect[1] != lastRect[1] + || rect[2] != lastRect[2] || rect[3] != lastRect[3] + return hasChanged + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/ScalarField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/ScalarField.java index 7906c7d5..d6d103f2 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/ScalarField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/cv/ScalarField.java @@ -1,112 +1,112 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.cv; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; -import com.github.serivesmejia.eocvsim.tuner.TunableField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import org.opencv.core.Scalar; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Field; -import java.util.Arrays; - -@RegisterTunableField -public class ScalarField extends TunableField { - - int scalarSize; - Scalar scalar; - - double[] lastVal = {}; - - volatile boolean hasChanged = false; - - public ScalarField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); - - scalar = (Scalar) initialFieldValue; - scalarSize = scalar.val.length; - - setGuiFieldAmount(scalarSize); - setRecommendedPanelMode(TunableFieldPanel.Mode.SLIDERS); - } - - @Override - public void init() { } - - @Override - public void update() { - try { - scalar = (Scalar) reflectionField.get(pipeline); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - - hasChanged = !Arrays.equals(scalar.val, lastVal); - - if (hasChanged) { //update values in GUI if they changed since last check - updateGuiFieldValues(); - } - - lastVal = scalar.val.clone(); - } - - @Override - public void updateGuiFieldValues() { - for (int i = 0; i < scalar.val.length; i++) { - fieldPanel.setFieldValue(i, scalar.val[i]); - } - } - - @Override - public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { - try { - scalar.val[index] = Double.parseDouble(newValue); - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid numeric String"); - } - - setPipelineFieldValue(scalar); - - lastVal = scalar.val.clone(); - } - - @Override - public Scalar getValue() { - return scalar; - } - - @Override - public Object getGuiFieldValue(int index) { - return scalar.val[index]; - } - - @Override - public boolean hasChanged() { - hasChanged = !Arrays.equals(scalar.val, lastVal); - return hasChanged; - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.field.cv; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.gui.component.tuner.TunableFieldPanel; +import com.github.serivesmejia.eocvsim.tuner.TunableField; +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; +import org.opencv.core.Scalar; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Field; +import java.util.Arrays; + +@RegisterTunableField +public class ScalarField extends TunableField { + + int scalarSize; + Scalar scalar; + + double[] lastVal = {}; + + volatile boolean hasChanged = false; + + public ScalarField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { + super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); + + scalar = (Scalar) initialFieldValue; + scalarSize = scalar.val.length; + + setGuiFieldAmount(scalarSize); + setRecommendedPanelMode(TunableFieldPanel.Mode.SLIDERS); + } + + @Override + public void init() { } + + @Override + public void update() { + try { + scalar = (Scalar) reflectionField.get(pipeline); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + hasChanged = !Arrays.equals(scalar.val, lastVal); + + if (hasChanged) { //update values in GUI if they changed since last check + updateGuiFieldValues(); + } + + lastVal = scalar.val.clone(); + } + + @Override + public void updateGuiFieldValues() { + for (int i = 0; i < scalar.val.length; i++) { + fieldPanel.setFieldValue(i, scalar.val[i]); + } + } + + @Override + public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { + try { + scalar.val[index] = Double.parseDouble(newValue); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Parameter should be a valid numeric String"); + } + + setPipelineFieldValue(scalar); + + lastVal = scalar.val.clone(); + } + + @Override + public Scalar getValue() { + return scalar; + } + + @Override + public Object getGuiFieldValue(int index) { + return scalar.val[index]; + } + + @Override + public boolean hasChanged() { + hasChanged = !Arrays.equals(scalar.val, lastVal); + return hasChanged; + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/DoubleField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/DoubleField.java index 75998ee7..32dd0ef2 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/DoubleField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/DoubleField.java @@ -1,66 +1,66 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.numeric; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.field.NumericField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Field; - -@RegisterTunableField -public class DoubleField extends NumericField { - - private double beforeValue; - - public DoubleField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); - value = (double) initialFieldValue; - } - - @Override - public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { - - try { - value = Double.valueOf(newValue); - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid numeric String"); - } - - setPipelineFieldValue(value); - - beforeValue = value; - - } - - @Override - public boolean hasChanged() { - boolean hasChanged = value != beforeValue; - beforeValue = value; - return hasChanged; - } - - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.field.numeric; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.tuner.field.NumericField; +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Field; + +@RegisterTunableField +public class DoubleField extends NumericField { + + private double beforeValue; + + public DoubleField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { + super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); + value = (double) initialFieldValue; + } + + @Override + public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { + + try { + value = Double.valueOf(newValue); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Parameter should be a valid numeric String"); + } + + setPipelineFieldValue(value); + + beforeValue = value; + + } + + @Override + public boolean hasChanged() { + boolean hasChanged = value != beforeValue; + beforeValue = value; + return hasChanged; + } + + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/FloatField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/FloatField.java index 84e4a829..ec788b74 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/FloatField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/FloatField.java @@ -1,65 +1,65 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.numeric; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.field.NumericField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Field; - -@RegisterTunableField -public class FloatField extends NumericField { - - protected float beforeValue; - - public FloatField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); - value = (float) initialFieldValue; - } - - @Override - public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { - - try { - value = Float.parseFloat(newValue); - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid numeric String"); - } - - setPipelineFieldValue(value); - - beforeValue = value; - - } - - @Override - public boolean hasChanged() { - boolean hasChanged = value != beforeValue; - beforeValue = value; - return hasChanged; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.field.numeric; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.tuner.field.NumericField; +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Field; + +@RegisterTunableField +public class FloatField extends NumericField { + + protected float beforeValue; + + public FloatField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { + super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS_DECIMAL); + value = (float) initialFieldValue; + } + + @Override + public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { + + try { + value = Float.parseFloat(newValue); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Parameter should be a valid numeric String"); + } + + setPipelineFieldValue(value); + + beforeValue = value; + + } + + @Override + public boolean hasChanged() { + boolean hasChanged = value != beforeValue; + beforeValue = value; + return hasChanged; + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/IntegerField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/IntegerField.java index 60fa12d0..99b8aa01 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/IntegerField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/IntegerField.java @@ -1,63 +1,63 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.numeric; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.field.NumericField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Field; - -@RegisterTunableField -public class IntegerField extends NumericField { - - protected int beforeValue; - - public IntegerField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS); - value = (int) initialFieldValue; - } - - @Override - public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { - try { - value = (int) Math.round(Double.parseDouble(newValue)); - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid numeric String"); - } - - setPipelineFieldValue(value); - - beforeValue = value; - } - - @Override - public boolean hasChanged() { - boolean hasChanged = value != beforeValue; - beforeValue = value; - return hasChanged; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.field.numeric; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.tuner.field.NumericField; +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Field; + +@RegisterTunableField +public class IntegerField extends NumericField { + + protected int beforeValue; + + public IntegerField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { + super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS); + value = (int) initialFieldValue; + } + + @Override + public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { + try { + value = (int) Math.round(Double.parseDouble(newValue)); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Parameter should be a valid numeric String"); + } + + setPipelineFieldValue(value); + + beforeValue = value; + } + + @Override + public boolean hasChanged() { + boolean hasChanged = value != beforeValue; + beforeValue = value; + return hasChanged; + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/LongField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/LongField.java index aea7e18d..45fdff7f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/LongField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/field/numeric/LongField.java @@ -1,63 +1,63 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.field.numeric; - -import com.github.serivesmejia.eocvsim.EOCVSim; -import com.github.serivesmejia.eocvsim.tuner.field.NumericField; -import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.lang.reflect.Field; - -@RegisterTunableField -public class LongField extends NumericField { - - private long beforeValue; - - public LongField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { - super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS); - value = (long) initialFieldValue; - } - - @Override - public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { - try { - value = Math.round(Double.parseDouble(newValue)); - } catch (NumberFormatException ex) { - throw new IllegalArgumentException("Parameter should be a valid numeric String"); - } - - setPipelineFieldValue(value); - - beforeValue = value; - } - - @Override - public boolean hasChanged() { - boolean hasChanged = value != beforeValue; - beforeValue = value; - return hasChanged; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.field.numeric; + +import com.github.serivesmejia.eocvsim.EOCVSim; +import com.github.serivesmejia.eocvsim.tuner.field.NumericField; +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.lang.reflect.Field; + +@RegisterTunableField +public class LongField extends NumericField { + + private long beforeValue; + + public LongField(OpenCvPipeline instance, Field reflectionField, EOCVSim eocvSim) throws IllegalAccessException { + super(instance, reflectionField, eocvSim, AllowMode.ONLY_NUMBERS); + value = (long) initialFieldValue; + } + + @Override + public void setGuiFieldValue(int index, String newValue) throws IllegalAccessException { + try { + value = Math.round(Double.parseDouble(newValue)); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Parameter should be a valid numeric String"); + } + + setPipelineFieldValue(value); + + beforeValue = value; + } + + @Override + public boolean hasChanged() { + boolean hasChanged = value != beforeValue; + beforeValue = value; + return hasChanged; + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/AnnotatedTunableFieldScanner.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/AnnotatedTunableFieldScanner.kt index 2460a398..bd872203 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/AnnotatedTunableFieldScanner.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/AnnotatedTunableFieldScanner.kt @@ -1,95 +1,95 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.scanner - -import com.github.serivesmejia.eocvsim.tuner.TunableField -import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.ReflectUtil -import io.github.classgraph.ClassGraph -import java.lang.reflect.Type -import java.util.* - -@Suppress("UNCHECKED_CAST") -class AnnotatedTunableFieldScanner(private val lookInPackage: String) { - - data class ScanResult(val tunableFields: HashMap>>, - val acceptors: HashMap>, Class>) - - fun scan(): ScanResult { - val tunableFields = HashMap>>() - val acceptors = HashMap>, Class>() - - Log.info("AnnotatedTunableFieldScanner", "Scanning in $lookInPackage...") - Log.blank() - - //Scan for all classes in the specified package - val classGraph = ClassGraph().enableAnnotationInfo().acceptPackages(lookInPackage) - val result = classGraph.scan() - - //SCANNING FOR TUNABLE FIELDS - - for (classInfo in result.getClassesWithAnnotation(RegisterTunableField::class.java.name)) { - try { - val foundClass: Class<*> = try { - Class.forName(classInfo.name) - } catch (ex: ClassNotFoundException) { - Log.error("AnnotatedTunableFieldScanner", "Unable to find class ${classInfo.name}", ex) - continue //continue because we couldn't get the class... - } - - if (!ReflectUtil.hasSuperclass(foundClass, TunableField::class.java)) continue - - val foundClassTunableField = foundClass as Class> - val type = ReflectUtil.getTypeArgumentsFrom(foundClassTunableField)[0] - - Log.info( - "AnnotatedTunableFieldScanner", - "Found TunableField for " + type.typeName + " (" + foundClass.name + ")" - ) - - tunableFields[type] = foundClassTunableField - - for(innerClass in foundClass.declaredClasses) { - if (!ReflectUtil.hasSuperclass(innerClass, TunableFieldAcceptor::class.java)) continue - - acceptors[foundClass] = innerClass as Class - Log.info( - "AnnotatedTunableFieldScanner", - "Found TunableFieldAcceptor for ${foundClass.typeName} (${innerClass.name})" - ) - } - } catch (ex: Exception) { - Log.warn("AnnotatedTunableFieldScanner", "Error while processing " + classInfo.name, ex) - } - } - - Log.info("AnnotatedTunableFieldScanner", "Found " + tunableFields.size + " TunableField(s)") - Log.info("AnnotatedTunableFieldScanner", "Found " + acceptors.size + " TunableFieldAcceptors(s)") - Log.blank() - - return ScanResult(tunableFields, acceptors) - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.scanner + +import com.github.serivesmejia.eocvsim.tuner.TunableField +import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.ReflectUtil +import io.github.classgraph.ClassGraph +import java.lang.reflect.Type +import java.util.* + +@Suppress("UNCHECKED_CAST") +class AnnotatedTunableFieldScanner(private val lookInPackage: String) { + + data class ScanResult(val tunableFields: HashMap>>, + val acceptors: HashMap>, Class>) + + fun scan(): ScanResult { + val tunableFields = HashMap>>() + val acceptors = HashMap>, Class>() + + Log.info("AnnotatedTunableFieldScanner", "Scanning in $lookInPackage...") + Log.blank() + + //Scan for all classes in the specified package + val classGraph = ClassGraph().enableAnnotationInfo().acceptPackages(lookInPackage) + val result = classGraph.scan() + + //SCANNING FOR TUNABLE FIELDS + + for (classInfo in result.getClassesWithAnnotation(RegisterTunableField::class.java.name)) { + try { + val foundClass: Class<*> = try { + Class.forName(classInfo.name) + } catch (ex: ClassNotFoundException) { + Log.error("AnnotatedTunableFieldScanner", "Unable to find class ${classInfo.name}", ex) + continue //continue because we couldn't get the class... + } + + if (!ReflectUtil.hasSuperclass(foundClass, TunableField::class.java)) continue + + val foundClassTunableField = foundClass as Class> + val type = ReflectUtil.getTypeArgumentsFrom(foundClassTunableField)[0] + + Log.info( + "AnnotatedTunableFieldScanner", + "Found TunableField for " + type.typeName + " (" + foundClass.name + ")" + ) + + tunableFields[type] = foundClassTunableField + + for(innerClass in foundClass.declaredClasses) { + if (!ReflectUtil.hasSuperclass(innerClass, TunableFieldAcceptor::class.java)) continue + + acceptors[foundClass] = innerClass as Class + Log.info( + "AnnotatedTunableFieldScanner", + "Found TunableFieldAcceptor for ${foundClass.typeName} (${innerClass.name})" + ) + } + } catch (ex: Exception) { + Log.warn("AnnotatedTunableFieldScanner", "Error while processing " + classInfo.name, ex) + } + } + + Log.info("AnnotatedTunableFieldScanner", "Found " + tunableFields.size + " TunableField(s)") + Log.info("AnnotatedTunableFieldScanner", "Found " + acceptors.size + " TunableFieldAcceptors(s)") + Log.blank() + + return ScanResult(tunableFields, acceptors) + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableField.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableField.java index 3212c70f..48e2bfd0 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableField.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableField.java @@ -1,34 +1,34 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.scanner; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.TYPE; - -@Target({TYPE}) -@Retention(RetentionPolicy.RUNTIME) +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.scanner; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; + +@Target({TYPE}) +@Retention(RetentionPolicy.RUNTIME) public @interface RegisterTunableField { } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableFieldAcceptor.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableFieldAcceptor.java index c02ec58d..d692c53e 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableFieldAcceptor.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/tuner/scanner/RegisterTunableFieldAcceptor.java @@ -1,38 +1,38 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.tuner.scanner; - -import com.github.serivesmejia.eocvsim.tuner.TunableField; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.TYPE; - -@Target({TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface RegisterTunableFieldAcceptor { - Class> tunableFieldType(); +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.tuner.scanner; + +import com.github.serivesmejia.eocvsim.tuner.TunableField; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; + +@Target({TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RegisterTunableFieldAcceptor { + Class> tunableFieldType(); } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt new file mode 100644 index 00000000..550ce527 --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt @@ -0,0 +1,117 @@ +package com.github.serivesmejia.eocvsim.util + +import com.github.serivesmejia.eocvsim.tuner.TunableField +import com.github.serivesmejia.eocvsim.tuner.TunableFieldAcceptor +import com.github.serivesmejia.eocvsim.tuner.scanner.RegisterTunableField +import com.qualcomm.robotcore.util.ElapsedTime +import io.github.classgraph.ClassGraph +import kotlinx.coroutines.* +import org.openftc.easyopencv.OpenCvPipeline + +class ClasspathScan { + + companion object { + val TAG = "ClasspathScan" + } + + val ignoredPackages = arrayOf( + "java", + "org.opencv", + "imgui", + "io.github.classgraph", + "io.github.deltacv", + "com.github.serivesmejia.eocvsim.pipeline", + "org.openftc", + "org.lwjgl" + ) + + lateinit var scanResult: ScanResult + private set + + private lateinit var scanResultJob: Job + + @Suppress("UNCHECKED_CAST") + fun scan() { + val timer = ElapsedTime() + val classGraph = ClassGraph() + .enableClassInfo() + .enableAnnotationInfo() + .rejectPackages(*ignoredPackages) + + Log.info(TAG, "Starting to scan classpath...") + + val scanResult = classGraph.scan() + + Log.info(TAG, "ClassGraph finished scanning (took ${timer.seconds()}s)") + + val tunableFieldClassesInfo = scanResult.getClassesWithAnnotation(RegisterTunableField::class.java.name) + val pipelineClassesInfo = scanResult.getSubclasses(OpenCvPipeline::class.java.name) + + val pipelineClasses = mutableListOf>() + + for(pipelineClassInfo in pipelineClassesInfo) { + val clazz = Class.forName(pipelineClassInfo.name) + + if(ReflectUtil.hasSuperclass(clazz, OpenCvPipeline::class.java)) { + Log.info(TAG, "Found pipeline ${clazz.typeName}") + pipelineClasses.add(clazz as Class) + } + } + + Log.blank() + Log.info(TAG, "Found ${pipelineClasses.size} pipelines") + Log.blank() + + val tunableFieldClasses = mutableListOf>>() + val tunableFieldAcceptorClasses = mutableMapOf>, Class>() + + for(tunableFieldClassInfo in tunableFieldClassesInfo) { + val clazz = Class.forName(tunableFieldClassInfo.name) + + if(ReflectUtil.hasSuperclass(clazz, TunableField::class.java)) { + val tunableFieldClass = clazz as Class> + + tunableFieldClasses.add(tunableFieldClass) + Log.info(TAG, "Found tunable field ${clazz.typeName}") + + for(subclass in clazz.declaredClasses) { + if(ReflectUtil.hasSuperclass(subclass, TunableFieldAcceptor::class.java)) { + tunableFieldAcceptorClasses[tunableFieldClass] = subclass as Class + Log.info(TAG, "Found acceptor for this tunable field, ${clazz.typeName}") + break + } + } + } + } + + Log.blank() + Log.info(TAG, "Found ${tunableFieldClasses.size} tunable fields and ${tunableFieldAcceptorClasses.size} acceptors") + Log.blank() + + Log.info(TAG, "Finished scanning (took ${timer.seconds()}s)") + + this.scanResult = ScanResult( + pipelineClasses.toTypedArray(), + tunableFieldClasses.toTypedArray(), + tunableFieldAcceptorClasses.toMap() + ) + } + + @OptIn(DelicateCoroutinesApi::class) + fun asyncScan() { + scanResultJob = GlobalScope.launch(Dispatchers.IO) { + scan() + } + } + + fun join() = runBlocking { + scanResultJob.join() + } + +} + +data class ScanResult( + val pipelineClasses: Array>, + val tunableFieldClasses: Array>>, + val tunableFieldAcceptorClasses: Map>, Class> +) \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/CvUtil.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/CvUtil.java index bb44d490..b77d17ec 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/CvUtil.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/CvUtil.java @@ -1,181 +1,181 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util; - -import com.github.serivesmejia.eocvsim.util.extension.CvExt; -import org.opencv.core.Mat; -import org.opencv.core.MatOfByte; -import org.opencv.core.Size; -import org.opencv.imgcodecs.Imgcodecs; -import org.opencv.imgproc.Imgproc; -import org.opencv.videoio.VideoCapture; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; -import java.io.ByteArrayInputStream; -import java.io.IOException; - -public class CvUtil { - - public static void matToBufferedImage(Mat m, BufferedImage buffImg) { - // Get the BufferedImage's backing array and copy the pixels directly into it - byte[] data = ((DataBufferByte) buffImg.getRaster().getDataBuffer()).getData(); - m.get(0, 0, data); - } - - public static BufferedImage matToBufferedImage(Mat m) { - // Fastest code - // output can be assigned either to a BufferedImage or to an Image - int type = BufferedImage.TYPE_BYTE_GRAY; - if (m.channels() > 1) { - type = BufferedImage.TYPE_3BYTE_BGR; - } - - // Create an empty image in matching format - BufferedImage buffImg = new BufferedImage(m.width(), m.height(), type); - matToBufferedImage(m, buffImg); - - return buffImg; - } - - - public static boolean checkImageValid(String imagePath) { - try { - - //test if image is valid - Mat img = Imgcodecs.imread(imagePath); - - if (img != null && !img.empty()) { //image is valid - img.release(); - return true; - } else { //image is not valid - return false; - } - - } catch (Throwable ex) { - return false; - } - } - - public static boolean checkVideoValid(String videoPath) { - try { - - VideoCapture capture = new VideoCapture(); - - Mat img = new Mat(); - - capture.open(videoPath); - capture.read(img); - capture.release(); - - if (!img.empty()) { //image is valid - img.release(); - return true; - } else { //image is not valid - img.release(); - return false; - } - - } catch (Exception ex) { - return false; - } - } - - public static Size getImageSize(String imagePath) { - try { - - //test if image is valid - Mat img = Imgcodecs.imread(imagePath); - - if (img != null && !img.empty()) { //image is valid - Size size = img.size(); - img.release(); - return size; - } else { //image is not valid - return new Size(0, 0); - } - - } catch (Exception ex) { - return new Size(0, 0); - } - } - - public static Size getVideoSize(String videoPath) { - try { - - VideoCapture capture = new VideoCapture(); - - Mat img = new Mat(); - - capture.open(videoPath); - capture.read(img); - capture.release(); - - Size size = img.size(); - img.release(); - - return size; - - } catch (Exception ex) { - return new Size(); - } - } - - public static Mat readOnceFromVideo(String videoPath) { - VideoCapture capture = new VideoCapture(); - - Mat img = new Mat(); - - try { - capture.open(videoPath); - capture.read(img); - capture.release(); - - return img; - } catch (Exception ex) { - return img; - } - } - - public static Size scaleToFit(Size currentSize, Size targetSize) { - double targetAspectRatio = CvExt.aspectRatio(targetSize); - double currentAspectRatio = CvExt.aspectRatio(currentSize); - - if(currentAspectRatio == targetAspectRatio) { - return targetSize.clone(); - } else { - - double currentW = currentSize.width; - double currentH = currentSize.height; - - double widthRatio = targetSize.width / currentW; - double heightRatio = targetSize.height / currentH; - double bestRatio = Math.min(widthRatio, heightRatio); - - return new Size(currentW * bestRatio, currentH * bestRatio); - } - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util; + +import com.github.serivesmejia.eocvsim.util.extension.CvExt; +import org.opencv.core.Mat; +import org.opencv.core.MatOfByte; +import org.opencv.core.Size; +import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; +import org.opencv.videoio.VideoCapture; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class CvUtil { + + public static void matToBufferedImage(Mat m, BufferedImage buffImg) { + // Get the BufferedImage's backing array and copy the pixels directly into it + byte[] data = ((DataBufferByte) buffImg.getRaster().getDataBuffer()).getData(); + m.get(0, 0, data); + } + + public static BufferedImage matToBufferedImage(Mat m) { + // Fastest code + // output can be assigned either to a BufferedImage or to an Image + int type = BufferedImage.TYPE_BYTE_GRAY; + if (m.channels() > 1) { + type = BufferedImage.TYPE_3BYTE_BGR; + } + + // Create an empty image in matching format + BufferedImage buffImg = new BufferedImage(m.width(), m.height(), type); + matToBufferedImage(m, buffImg); + + return buffImg; + } + + + public static boolean checkImageValid(String imagePath) { + try { + + //test if image is valid + Mat img = Imgcodecs.imread(imagePath); + + if (img != null && !img.empty()) { //image is valid + img.release(); + return true; + } else { //image is not valid + return false; + } + + } catch (Throwable ex) { + return false; + } + } + + public static boolean checkVideoValid(String videoPath) { + try { + + VideoCapture capture = new VideoCapture(); + + Mat img = new Mat(); + + capture.open(videoPath); + capture.read(img); + capture.release(); + + if (!img.empty()) { //image is valid + img.release(); + return true; + } else { //image is not valid + img.release(); + return false; + } + + } catch (Exception ex) { + return false; + } + } + + public static Size getImageSize(String imagePath) { + try { + + //test if image is valid + Mat img = Imgcodecs.imread(imagePath); + + if (img != null && !img.empty()) { //image is valid + Size size = img.size(); + img.release(); + return size; + } else { //image is not valid + return new Size(0, 0); + } + + } catch (Exception ex) { + return new Size(0, 0); + } + } + + public static Size getVideoSize(String videoPath) { + try { + + VideoCapture capture = new VideoCapture(); + + Mat img = new Mat(); + + capture.open(videoPath); + capture.read(img); + capture.release(); + + Size size = img.size(); + img.release(); + + return size; + + } catch (Exception ex) { + return new Size(); + } + } + + public static Mat readOnceFromVideo(String videoPath) { + VideoCapture capture = new VideoCapture(); + + Mat img = new Mat(); + + try { + capture.open(videoPath); + capture.read(img); + capture.release(); + + return img; + } catch (Exception ex) { + return img; + } + } + + public static Size scaleToFit(Size currentSize, Size targetSize) { + double targetAspectRatio = CvExt.aspectRatio(targetSize); + double currentAspectRatio = CvExt.aspectRatio(currentSize); + + if(currentAspectRatio == targetAspectRatio) { + return targetSize.clone(); + } else { + + double currentW = currentSize.width; + double currentH = currentSize.height; + + double widthRatio = targetSize.width / currentW; + double heightRatio = targetSize.height / currentH; + double bestRatio = Math.min(widthRatio, heightRatio); + + return new Size(currentW * bestRatio, currentH * bestRatio); + } + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/FileFilters.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/FileFilters.kt index fa06f71a..bfed253c 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/FileFilters.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/FileFilters.kt @@ -1,38 +1,38 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util - -import javax.swing.filechooser.FileNameExtensionFilter - -object FileFilters { - - @JvmField val imagesFilter = FileNameExtensionFilter("Images", - "jpg", "jpeg", "jpe", "jp2", "bmp", "png", "tiff", "tif") - - @JvmField var videoMediaFilter = FileNameExtensionFilter("Video Media", - "avi", "mkv", "mov", "mp4") - - @JvmField var recordedVideoFilter = FileNameExtensionFilter("AVI (*.avi)", "avi") - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util + +import javax.swing.filechooser.FileNameExtensionFilter + +object FileFilters { + + @JvmField val imagesFilter = FileNameExtensionFilter("Images", + "jpg", "jpeg", "jpe", "jp2", "bmp", "png", "tiff", "tif") + + @JvmField var videoMediaFilter = FileNameExtensionFilter("Video Media", + "avi", "mkv", "mov", "mp4") + + @JvmField var recordedVideoFilter = FileNameExtensionFilter("AVI (*.avi)", "avi") + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/Log.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/Log.java index 226ec490..89703ea3 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/Log.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/Log.java @@ -1,277 +1,277 @@ -package com.github.serivesmejia.eocvsim.util; - -import java.io.PrintWriter; -import java.io.StringWriter; - -/** - * A low overhead, lightweight logging system. - * - * @author Nathan Sweet - */ -public class Log { - /** - * No logging at all. - */ - static public final int LEVEL_NONE = 6; - /** - * Critical errors. The application may no longer work correctly. - */ - static public final int LEVEL_ERROR = 5; - /** - * Important warnings. The application will continue to work correctly. - */ - static public final int LEVEL_WARN = 4; - /** - * Informative messages. Typically used for deployment. - */ - static public final int LEVEL_INFO = 3; - /** - * Debug messages. This level is useful during development. - */ - static public final int LEVEL_DEBUG = 2; - /** - * Trace messages. A lot of information is logged, so this level is usually only needed when debugging a problem. - */ - static public final int LEVEL_TRACE = 1; - /** - * White lines - **/ - static public final int LEVEL_BLANK = 0; - - /** - * The level of messages that will be logged. Compiling this and the booleans below as "final" will cause the compiler to - * remove all "if (Log.info) ..." type statements below the set level. - */ - static private int level = LEVEL_INFO; - - /** - * True when the ERROR level will be logged. - */ - static public boolean ERROR = level <= LEVEL_ERROR; - /** - * True when the WARN level will be logged. - */ - static public boolean WARN = level <= LEVEL_WARN; - /** - * True when the INFO level will be logged. - */ - static public boolean INFO = level <= LEVEL_INFO; - /** - * True when the DEBUG level will be logged. - */ - static public boolean DEBUG = level <= LEVEL_DEBUG; - /** - * True when the TRACE level will be logged. - */ - static public boolean TRACE = level <= LEVEL_TRACE; - static private Logger logger = new Logger(); - - public static final StringBuilder fullLogs = new StringBuilder(); - - private Log() { - } - - static public int getLevel() { - return level; - } - - /** - * Sets the level to log. If a version of this class is being used that has a final log level, this has no affect. - */ - static public void set(int level) { - // Comment out method contents when compiling fixed level JARs. - Log.level = level; - ERROR = level <= LEVEL_ERROR; - WARN = level <= LEVEL_WARN; - INFO = level <= LEVEL_INFO; - DEBUG = level <= LEVEL_DEBUG; - TRACE = level <= LEVEL_TRACE; - } - - static public void NONE() { - set(LEVEL_NONE); - } - - static public void ERROR() { - set(LEVEL_ERROR); - } - - static public void WARN() { - set(LEVEL_WARN); - } - - static public void INFO() { - set(LEVEL_INFO); - } - - static public void DEBUG() { - set(LEVEL_DEBUG); - } - - static public void TRACE() { - set(LEVEL_TRACE); - } - - /** - * Sets the logger that will write the log messages. - */ - static public void setLogger(Logger logger) { - Log.logger = logger; - } - - static public void error(String message, Throwable ex) { - if (ERROR) logger.log(LEVEL_ERROR, null, message, ex); - } - - static public void error(String category, String message, Throwable ex) { - if (ERROR) logger.log(LEVEL_ERROR, category, message, ex); - } - - static public void error(String message) { - if (ERROR) logger.log(LEVEL_ERROR, null, message, null); - } - - static public void error(String category, String message) { - if (ERROR) logger.log(LEVEL_ERROR, category, message, null); - } - - static public void warn(String message, Throwable ex) { - if (WARN) logger.log(LEVEL_WARN, null, message, ex); - } - - static public void warn(String category, String message, Throwable ex) { - if (WARN) logger.log(LEVEL_WARN, category, message, ex); - } - - static public void warn(String message) { - if (WARN) logger.log(LEVEL_WARN, null, message, null); - } - - static public void warn(String category, String message) { - if (WARN) logger.log(LEVEL_WARN, category, message, null); - } - - static public void info(String message, Throwable ex) { - if (INFO) logger.log(LEVEL_INFO, null, message, ex); - } - - static public void info(String category, String message, Throwable ex) { - if (INFO) logger.log(LEVEL_INFO, category, message, ex); - } - - static public void info(String message) { - if (INFO) logger.log(LEVEL_INFO, null, message, null); - } - - static public void info(String category, String message) { - if (INFO) logger.log(LEVEL_INFO, category, message, null); - } - - static public void debug(String message, Throwable ex) { - if (DEBUG) logger.log(LEVEL_DEBUG, null, message, ex); - } - - static public void debug(String category, String message, Throwable ex) { - if (DEBUG) logger.log(LEVEL_DEBUG, category, message, ex); - } - - static public void debug(String message) { - if (DEBUG) logger.log(LEVEL_DEBUG, null, message, null); - } - - static public void debug(String category, String message) { - if (DEBUG) logger.log(LEVEL_DEBUG, category, message, null); - } - - static public void trace(String message, Throwable ex) { - if (TRACE) logger.log(LEVEL_TRACE, null, message, ex); - } - - static public void trace(String category, String message, Throwable ex) { - if (TRACE) logger.log(LEVEL_TRACE, category, message, ex); - } - - static public void trace(String message) { - if (TRACE) logger.log(LEVEL_TRACE, null, message, null); - } - - static public void trace(String category, String message) { - if (TRACE) logger.log(LEVEL_TRACE, category, message, null); - } - - static public void blank(int lines) { - for (int i = 0; i < lines; i++) { - logger.log(LEVEL_BLANK, null, "", null); - } - } - - static public void blank() { - blank(1); - } - - /** - * Performs the actual logging. Default implementation logs to System.out. Extended and use {@link Log#logger} set to handle - * logging differently. - */ - static public class Logger { - private final long firstLogTime = System.currentTimeMillis(); - - public void log(int level, String category, String message, Throwable ex) { - StringBuilder builder = new StringBuilder(256); - - if (level != LEVEL_BLANK) { - long time = System.currentTimeMillis() - firstLogTime; - long minutes = time / (1000 * 60); - long seconds = time / (1000) % 60; - if (minutes <= 9) builder.append('0'); - builder.append(minutes); - builder.append(':'); - if (seconds <= 9) builder.append('0'); - builder.append(seconds); - } - - switch (level) { - case LEVEL_ERROR: - builder.append(" ERROR: "); - break; - case LEVEL_WARN: - builder.append(" WARN: "); - break; - case LEVEL_INFO: - builder.append(" INFO: "); - break; - case LEVEL_DEBUG: - builder.append(" DEBUG: "); - break; - case LEVEL_TRACE: - builder.append(" TRACE: "); - break; - } - - if (category != null) { - builder.append('['); - builder.append(category); - builder.append("] "); - } - - builder.append(message); - - if (ex != null) { - StringWriter writer = new StringWriter(256); - ex.printStackTrace(new PrintWriter(writer)); - builder.append('\n'); - builder.append(writer.toString().trim()); - } - - print(builder.toString()); - } - - /** - * Prints the message to System.out. Called by the default implementation of {@link #log(int, String, String, Throwable)}. - */ - protected void print(String message) { - fullLogs.append(message + "\n"); - System.out.println(message); - } - } +package com.github.serivesmejia.eocvsim.util; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * A low overhead, lightweight logging system. + * + * @author Nathan Sweet + */ +public class Log { + /** + * No logging at all. + */ + static public final int LEVEL_NONE = 6; + /** + * Critical errors. The application may no longer work correctly. + */ + static public final int LEVEL_ERROR = 5; + /** + * Important warnings. The application will continue to work correctly. + */ + static public final int LEVEL_WARN = 4; + /** + * Informative messages. Typically used for deployment. + */ + static public final int LEVEL_INFO = 3; + /** + * Debug messages. This level is useful during development. + */ + static public final int LEVEL_DEBUG = 2; + /** + * Trace messages. A lot of information is logged, so this level is usually only needed when debugging a problem. + */ + static public final int LEVEL_TRACE = 1; + /** + * White lines + **/ + static public final int LEVEL_BLANK = 0; + + /** + * The level of messages that will be logged. Compiling this and the booleans below as "final" will cause the compiler to + * remove all "if (Log.info) ..." type statements below the set level. + */ + static private int level = LEVEL_INFO; + + /** + * True when the ERROR level will be logged. + */ + static public boolean ERROR = level <= LEVEL_ERROR; + /** + * True when the WARN level will be logged. + */ + static public boolean WARN = level <= LEVEL_WARN; + /** + * True when the INFO level will be logged. + */ + static public boolean INFO = level <= LEVEL_INFO; + /** + * True when the DEBUG level will be logged. + */ + static public boolean DEBUG = level <= LEVEL_DEBUG; + /** + * True when the TRACE level will be logged. + */ + static public boolean TRACE = level <= LEVEL_TRACE; + static private Logger logger = new Logger(); + + public static final StringBuilder fullLogs = new StringBuilder(); + + private Log() { + } + + static public int getLevel() { + return level; + } + + /** + * Sets the level to log. If a version of this class is being used that has a final log level, this has no affect. + */ + static public void set(int level) { + // Comment out method contents when compiling fixed level JARs. + Log.level = level; + ERROR = level <= LEVEL_ERROR; + WARN = level <= LEVEL_WARN; + INFO = level <= LEVEL_INFO; + DEBUG = level <= LEVEL_DEBUG; + TRACE = level <= LEVEL_TRACE; + } + + static public void NONE() { + set(LEVEL_NONE); + } + + static public void ERROR() { + set(LEVEL_ERROR); + } + + static public void WARN() { + set(LEVEL_WARN); + } + + static public void INFO() { + set(LEVEL_INFO); + } + + static public void DEBUG() { + set(LEVEL_DEBUG); + } + + static public void TRACE() { + set(LEVEL_TRACE); + } + + /** + * Sets the logger that will write the log messages. + */ + static public void setLogger(Logger logger) { + Log.logger = logger; + } + + static public void error(String message, Throwable ex) { + if (ERROR) logger.log(LEVEL_ERROR, null, message, ex); + } + + static public void error(String category, String message, Throwable ex) { + if (ERROR) logger.log(LEVEL_ERROR, category, message, ex); + } + + static public void error(String message) { + if (ERROR) logger.log(LEVEL_ERROR, null, message, null); + } + + static public void error(String category, String message) { + if (ERROR) logger.log(LEVEL_ERROR, category, message, null); + } + + static public void warn(String message, Throwable ex) { + if (WARN) logger.log(LEVEL_WARN, null, message, ex); + } + + static public void warn(String category, String message, Throwable ex) { + if (WARN) logger.log(LEVEL_WARN, category, message, ex); + } + + static public void warn(String message) { + if (WARN) logger.log(LEVEL_WARN, null, message, null); + } + + static public void warn(String category, String message) { + if (WARN) logger.log(LEVEL_WARN, category, message, null); + } + + static public void info(String message, Throwable ex) { + if (INFO) logger.log(LEVEL_INFO, null, message, ex); + } + + static public void info(String category, String message, Throwable ex) { + if (INFO) logger.log(LEVEL_INFO, category, message, ex); + } + + static public void info(String message) { + if (INFO) logger.log(LEVEL_INFO, null, message, null); + } + + static public void info(String category, String message) { + if (INFO) logger.log(LEVEL_INFO, category, message, null); + } + + static public void debug(String message, Throwable ex) { + if (DEBUG) logger.log(LEVEL_DEBUG, null, message, ex); + } + + static public void debug(String category, String message, Throwable ex) { + if (DEBUG) logger.log(LEVEL_DEBUG, category, message, ex); + } + + static public void debug(String message) { + if (DEBUG) logger.log(LEVEL_DEBUG, null, message, null); + } + + static public void debug(String category, String message) { + if (DEBUG) logger.log(LEVEL_DEBUG, category, message, null); + } + + static public void trace(String message, Throwable ex) { + if (TRACE) logger.log(LEVEL_TRACE, null, message, ex); + } + + static public void trace(String category, String message, Throwable ex) { + if (TRACE) logger.log(LEVEL_TRACE, category, message, ex); + } + + static public void trace(String message) { + if (TRACE) logger.log(LEVEL_TRACE, null, message, null); + } + + static public void trace(String category, String message) { + if (TRACE) logger.log(LEVEL_TRACE, category, message, null); + } + + static public void blank(int lines) { + for (int i = 0; i < lines; i++) { + logger.log(LEVEL_BLANK, null, "", null); + } + } + + static public void blank() { + blank(1); + } + + /** + * Performs the actual logging. Default implementation logs to System.out. Extended and use {@link Log#logger} set to handle + * logging differently. + */ + static public class Logger { + private final long firstLogTime = System.currentTimeMillis(); + + public void log(int level, String category, String message, Throwable ex) { + StringBuilder builder = new StringBuilder(256); + + if (level != LEVEL_BLANK) { + long time = System.currentTimeMillis() - firstLogTime; + long minutes = time / (1000 * 60); + long seconds = time / (1000) % 60; + if (minutes <= 9) builder.append('0'); + builder.append(minutes); + builder.append(':'); + if (seconds <= 9) builder.append('0'); + builder.append(seconds); + } + + switch (level) { + case LEVEL_ERROR: + builder.append(" ERROR: "); + break; + case LEVEL_WARN: + builder.append(" WARN: "); + break; + case LEVEL_INFO: + builder.append(" INFO: "); + break; + case LEVEL_DEBUG: + builder.append(" DEBUG: "); + break; + case LEVEL_TRACE: + builder.append(" TRACE: "); + break; + } + + if (category != null) { + builder.append('['); + builder.append(category); + builder.append("] "); + } + + builder.append(message); + + if (ex != null) { + StringWriter writer = new StringWriter(256); + ex.printStackTrace(new PrintWriter(writer)); + builder.append('\n'); + builder.append(writer.toString().trim()); + } + + print(builder.toString()); + } + + /** + * Prints the message to System.out. Called by the default implementation of {@link #log(int, String, String, Throwable)}. + */ + protected void print(String message) { + fullLogs.append(message + "\n"); + System.out.println(message); + } + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ReflectUtil.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ReflectUtil.java index e58f926b..f4820729 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ReflectUtil.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ReflectUtil.java @@ -1,51 +1,51 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util; - -import java.lang.invoke.MethodType; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; - -public class ReflectUtil { - - public static boolean hasSuperclass(Class clazz, Class superClass) { - try { - clazz.asSubclass(superClass); - return true; - } catch (ClassCastException ex) { - return false; - } - } - - public static Type[] getTypeArgumentsFrom(Class clazz) { - //get type argument - Type sooper = clazz.getGenericSuperclass(); - return ((ParameterizedType)sooper).getActualTypeArguments(); - } - - public static Class wrap(Class c) { - return (Class) MethodType.methodType(c).wrap().returnType(); - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util; + +import java.lang.invoke.MethodType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public class ReflectUtil { + + public static boolean hasSuperclass(Class clazz, Class superClass) { + try { + clazz.asSubclass(superClass); + return true; + } catch (ClassCastException ex) { + return false; + } + } + + public static Type[] getTypeArgumentsFrom(Class clazz) { + //get type argument + Type sooper = clazz.getGenericSuperclass(); + return ((ParameterizedType)sooper).getActualTypeArguments(); + } + + public static Class wrap(Class c) { + return (Class) MethodType.methodType(c).wrap().returnType(); + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/StrUtil.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/StrUtil.java index 60ea0d77..257f645e 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/StrUtil.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/StrUtil.java @@ -1,85 +1,85 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util; - -import java.awt.*; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.net.URI; -import java.util.ArrayList; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class StrUtil { - - public static final Pattern URL_PATTERN = Pattern.compile( - "((https?|ftp|gopher|telnet|file):((//)|(\\\\))+[\\w\\d:#@%/;$()~_?\\+-=\\\\\\.&]*)", - Pattern.CASE_INSENSITIVE); - - public static String[] findUrlsInString(String str) { - - Matcher urlMatcher = URL_PATTERN.matcher(str); - - ArrayList matches = new ArrayList<>(); - - while(urlMatcher.find()) { - String url = str.substring(urlMatcher.start(0), - urlMatcher.end(0)); - matches.add(url); - } - - return matches.toArray(new String[0]); - - } - - public static String getFileBaseName(String fileName) { - int index = fileName.lastIndexOf('.'); - if(index == -1) - return fileName; - else - return fileName.substring(0, index); - } - - public static String random() { - return UUID.randomUUID().toString().replace("-", ""); - } - - public static String fromException(Throwable ex) { - StringWriter writer = new StringWriter(256); - ex.printStackTrace(new PrintWriter(writer)); - return writer.toString().trim(); - } - - public static String cutStringBy(String str, String by, int amount) { - int truncateIndex = str.length(); - - for(int i = 0 ; i < amount ; i++) { - truncateIndex = str.lastIndexOf(by, truncateIndex - 1); - } - - return str.substring(0, truncateIndex); - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util; + +import java.awt.*; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URI; +import java.util.ArrayList; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class StrUtil { + + public static final Pattern URL_PATTERN = Pattern.compile( + "((https?|ftp|gopher|telnet|file):((//)|(\\\\))+[\\w\\d:#@%/;$()~_?\\+-=\\\\\\.&]*)", + Pattern.CASE_INSENSITIVE); + + public static String[] findUrlsInString(String str) { + + Matcher urlMatcher = URL_PATTERN.matcher(str); + + ArrayList matches = new ArrayList<>(); + + while(urlMatcher.find()) { + String url = str.substring(urlMatcher.start(0), + urlMatcher.end(0)); + matches.add(url); + } + + return matches.toArray(new String[0]); + + } + + public static String getFileBaseName(String fileName) { + int index = fileName.lastIndexOf('.'); + if(index == -1) + return fileName; + else + return fileName.substring(0, index); + } + + public static String random() { + return UUID.randomUUID().toString().replace("-", ""); + } + + public static String fromException(Throwable ex) { + StringWriter writer = new StringWriter(256); + ex.printStackTrace(new PrintWriter(writer)); + return writer.toString().trim(); + } + + public static String cutStringBy(String str, String by, int amount) { + int truncateIndex = str.length(); + + for(int i = 0 ; i < amount ; i++) { + truncateIndex = str.lastIndexOf(by, truncateIndex - 1); + } + + return str.substring(0, truncateIndex); + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/SysUtil.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/SysUtil.java index 0aeeabda..35917838 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/SysUtil.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/SysUtil.java @@ -1,385 +1,385 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util; - -import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder; -import org.opencv.core.Core; - -import java.io.*; -import java.net.URI; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -public class SysUtil { - - public static OperatingSystem OS = SysUtil.getOS(); - public static int MB = 1024 * 1024; - public static String GH_NATIVE_LIBS_URL = "https://github.com/serivesmejia/OpenCVNativeLibs/raw/master/"; - - public static OperatingSystem getOS() { - String osName = System.getProperty("os.name").toLowerCase(); - - if (osName.contains("win")) { - return OperatingSystem.WINDOWS; - } else if (osName.contains("nux")) { - return OperatingSystem.LINUX; - } else if (osName.contains("mac") || osName.contains("darwin")) { - return OperatingSystem.MACOS; - } - - return OperatingSystem.UNKNOWN; - } - - public static boolean loadCvNativeLib() { - String os = null; - String fileExt = null; - - switch (OS) { //getting os prefix - case WINDOWS: - os = "win"; - fileExt = "dll"; - break; - case LINUX: - os = "linux"; - fileExt = "so"; - break; - case MACOS: - os = "mac"; - fileExt = "dylib"; - break; - } - - boolean is64bit = System.getProperty("sun.arch.data.model").contains("64"); //Checking if JVM is 64 bits or not - - return loadLib(os, fileExt, is64bit, Core.NATIVE_LIBRARY_NAME, 0); - } - - public static boolean loadLib(String os, String fileExt, boolean is64bit, String name, int attempts) { - String arch = is64bit ? "64" : "32"; //getting os arch - - String libName = os + arch + "_" + name; //resultant lib name from those two - String libNameExt = libName + "." + fileExt; //resultant lib name from those two - - File nativeLibFile = new File(getAppData() + File.separator + libNameExt); - - if (!nativeLibFile.exists()) { - Log.info("SysUtil", "Downloading native lib from " + GH_NATIVE_LIBS_URL + libNameExt); - try { - download(GH_NATIVE_LIBS_URL + libNameExt, nativeLibFile.getAbsolutePath()); - } catch (Throwable ex) { - ex.printStackTrace(); - } - Log.blank(); - } - - Log.info("SysUtil", "Loading native lib \"" + libNameExt + "\""); - - try { - - System.load(nativeLibFile.getAbsolutePath()); //Loading OpenCV native library - Log.info("SysUtil", "Successfully loaded native lib \"" + libName + "\""); - - } catch (UnsatisfiedLinkError ex) { - ex.printStackTrace(); - - if (attempts < 4) { - ex.printStackTrace(); - Log.error("SysUtil", "Failure loading lib \"" + libName + "\", retrying with different architecture... (" + attempts + " attempts)"); - loadLib(os, fileExt, !is64bit, Core.NATIVE_LIBRARY_NAME, attempts + 1); - } else { - ex.printStackTrace(); - Log.error("SysUtil", "Failure loading lib \"" + libName + "\" 4 times, giving up."); - return false; - } - } - - return true; - } - - public static void copyStream(File inFile, OutputStream out) throws IOException { - try (InputStream in = new FileInputStream(inFile)) { - copyStream(in, out); - } - } - - public static void copyStream(InputStream in, OutputStream out) throws IOException { - int cbBuffer = Math.min(4096, in.available()); - byte[] buffer = new byte[cbBuffer]; - - while(true) { - int cbRead = in.read(buffer); - if(cbRead <= 0) break; - - out.write(buffer, 0, cbRead); - } - } - - public static CopyFileIsData copyFileIs(InputStream is, File toPath, boolean replaceIfExisting) throws IOException { - - boolean alreadyExists = true; - - if (toPath.exists()) { - if (replaceIfExisting) { - Files.copy(is, toPath.toPath(), StandardCopyOption.REPLACE_EXISTING); - } else { - alreadyExists = false; - } - } else { - Files.copy(is, toPath.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - - is.close(); - - CopyFileIsData data = new CopyFileIsData(); - data.alreadyExists = alreadyExists; - data.file = toPath; - - return data; - - } - - public static CopyFileIsData copyFileIsTemp(InputStream is, String fileName, boolean replaceIfExisting) throws IOException { - String tmpDir = System.getProperty("java.io.tmpdir"); - File tempFile = new File(tmpDir + File.separator + fileName); - - return copyFileIs(is, tempFile, replaceIfExisting); - } - - public static long getMemoryUsageMB() { - return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / MB; - } - - public static String loadIsStr(InputStream is, Charset charset) throws UnsupportedEncodingException { - return new BufferedReader(new InputStreamReader(is, String.valueOf(charset))) - .lines().collect(Collectors.joining("\n")); - } - - public static String loadFileStr(File f) { - String content = ""; - - try { - content = new String(Files.readAllBytes(f.toPath())); - } catch (IOException e) { - e.printStackTrace(); - } - - return content; - } - - public static void replaceStrInFile(File f, String target, String replacement) { - String fileContents = loadFileStr(f); - saveFileStr(f, fileContents.replace(target, replacement)); - } - - public static boolean saveFileStr(File f, String contents) { - try { - FileWriter fw = new FileWriter(f); - fw.append(contents); - fw.close(); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - - public static void download(String url, String fileName) throws Exception { - try (InputStream in = URI.create(url).toURL().openStream()) { - Files.copy(in, Paths.get(fileName)); - } - } - - public static File getAppData() { - return new File(System.getProperty("user.home") + File.separator); - } - - public static File getEOCVSimFolder() { - return EOCVSimFolder.INSTANCE; - } - - public static Optional getExtensionByStringHandling(String filename) { - return Optional.ofNullable(filename) - .filter(f -> f.contains(".")) - .map(f -> f.substring(filename.lastIndexOf(".") + 1)); - } - - public static List filesUnder(File parent, Predicate predicate) { - ArrayList result = new ArrayList<>(); - - if(parent.isDirectory()) { - for(File child : parent.listFiles()) { - result.addAll(filesUnder(child, predicate)); - } - } else if(parent.exists() && (predicate != null && predicate.test(parent))) { - result.add(parent.getAbsoluteFile()); - } - - return result; - } - - public static List filesUnder(File parent, String extension) { - return filesUnder(parent, (f) -> f.getName().endsWith(extension)); - } - - public static List filesUnder(File parent) { - return filesUnder(parent, (f) -> true); - } - - public static List filesIn(File parent, Predicate predicate) { - ArrayList result = new ArrayList<>(); - - if(!parent.exists()) return result; - - if(parent.isDirectory()) { - for(File f : parent.listFiles()) { - if(predicate != null && predicate.test(f)) - result.add(f); - } - } else { - if(predicate != null && predicate.test(parent)) - result.add(parent); - } - - return result; - } - - public static List filesIn(File parent, String extension) { - return filesIn(parent, (f) -> f.getName().endsWith(extension)); - } - - public static void deleteFilesUnder(File parent, Predicate predicate) { - for(File file : parent.listFiles()) { - if(file.isDirectory()) - deleteFilesUnder(file, predicate); - - if(predicate != null) { - if(predicate.test(file)) file.delete(); - } else { - file.delete(); - } - } - } - - public static void deleteFilesUnder(File parent) { - deleteFilesUnder(parent, null); - } - - public static boolean migrateFile(File oldFile, File newFile) { - if(newFile.exists() || !oldFile.exists()) return false; - - Log.info("SysUtil", "Migrating old file " + oldFile.getAbsolutePath() + " to " + newFile.getAbsolutePath()); - - try { - Files.move(oldFile.toPath(), newFile.toPath()); - } catch (IOException e) { - Log.warn("SysUtil", "Failed to migrate old file " + oldFile.getAbsolutePath()); - return false; - } - - return true; - } - - public static File getRelativePath(File root, File child) { - File result = new File(""); - - while(!root.equals(child)) { - File parent = child.getParentFile(); - result = new File(new File(child.getName()), result.getPath()); - - if(parent == null) break; - - child = parent; - } - - return result; - } - - public static List getClasspathFiles() { - String[] classpaths = System.getProperty("java.class.path").split(File.pathSeparator); - ArrayList files = new ArrayList<>(); - - for(String path : classpaths) { - files.add(new File(path)); - } - - return files; - } - - public static CommandResult runShellCommand(String command) { - CommandResult result = new CommandResult(); - - ProcessBuilder processBuilder = new ProcessBuilder(); - if (OS == OperatingSystem.WINDOWS) { - processBuilder.command("cmd.exe", "/c", command); - } else { - processBuilder.command("sh", "-c", command); - } - - try { - Process process = processBuilder.start(); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - - String line = ""; - StringBuilder message = new StringBuilder(); - - while((line = reader.readLine()) != null) { - message.append(line); - } - - result.exitCode = process.waitFor(); - - result.output = message.toString(); - } catch (IOException | InterruptedException e) { - result.output = StrUtil.fromException(e); - result.exitCode = -1; - } - - return result; - } - - public enum OperatingSystem { - WINDOWS, - LINUX, - MACOS, - UNKNOWN - } - - public static class CopyFileIsData { - public File file = null; - public boolean alreadyExists = false; - } - - public static class CommandResult { - public String output = ""; - public int exitCode = 0; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util; + +import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder; +import org.opencv.core.Core; + +import java.io.*; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class SysUtil { + + public static OperatingSystem OS = SysUtil.getOS(); + public static int MB = 1024 * 1024; + public static String GH_NATIVE_LIBS_URL = "https://github.com/serivesmejia/OpenCVNativeLibs/raw/master/"; + + public static OperatingSystem getOS() { + String osName = System.getProperty("os.name").toLowerCase(); + + if (osName.contains("win")) { + return OperatingSystem.WINDOWS; + } else if (osName.contains("nux")) { + return OperatingSystem.LINUX; + } else if (osName.contains("mac") || osName.contains("darwin")) { + return OperatingSystem.MACOS; + } + + return OperatingSystem.UNKNOWN; + } + + public static boolean loadCvNativeLib() { + String os = null; + String fileExt = null; + + switch (OS) { //getting os prefix + case WINDOWS: + os = "win"; + fileExt = "dll"; + break; + case LINUX: + os = "linux"; + fileExt = "so"; + break; + case MACOS: + os = "mac"; + fileExt = "dylib"; + break; + } + + boolean is64bit = System.getProperty("sun.arch.data.model").contains("64"); //Checking if JVM is 64 bits or not + + return loadLib(os, fileExt, is64bit, Core.NATIVE_LIBRARY_NAME, 0); + } + + public static boolean loadLib(String os, String fileExt, boolean is64bit, String name, int attempts) { + String arch = is64bit ? "64" : "32"; //getting os arch + + String libName = os + arch + "_" + name; //resultant lib name from those two + String libNameExt = libName + "." + fileExt; //resultant lib name from those two + + File nativeLibFile = new File(getAppData() + File.separator + libNameExt); + + if (!nativeLibFile.exists()) { + Log.info("SysUtil", "Downloading native lib from " + GH_NATIVE_LIBS_URL + libNameExt); + try { + download(GH_NATIVE_LIBS_URL + libNameExt, nativeLibFile.getAbsolutePath()); + } catch (Throwable ex) { + ex.printStackTrace(); + } + Log.blank(); + } + + Log.info("SysUtil", "Loading native lib \"" + libNameExt + "\""); + + try { + + System.load(nativeLibFile.getAbsolutePath()); //Loading OpenCV native library + Log.info("SysUtil", "Successfully loaded native lib \"" + libName + "\""); + + } catch (UnsatisfiedLinkError ex) { + ex.printStackTrace(); + + if (attempts < 4) { + ex.printStackTrace(); + Log.error("SysUtil", "Failure loading lib \"" + libName + "\", retrying with different architecture... (" + attempts + " attempts)"); + loadLib(os, fileExt, !is64bit, Core.NATIVE_LIBRARY_NAME, attempts + 1); + } else { + ex.printStackTrace(); + Log.error("SysUtil", "Failure loading lib \"" + libName + "\" 4 times, giving up."); + return false; + } + } + + return true; + } + + public static void copyStream(File inFile, OutputStream out) throws IOException { + try (InputStream in = new FileInputStream(inFile)) { + copyStream(in, out); + } + } + + public static void copyStream(InputStream in, OutputStream out) throws IOException { + int cbBuffer = Math.min(4096, in.available()); + byte[] buffer = new byte[cbBuffer]; + + while(true) { + int cbRead = in.read(buffer); + if(cbRead <= 0) break; + + out.write(buffer, 0, cbRead); + } + } + + public static CopyFileIsData copyFileIs(InputStream is, File toPath, boolean replaceIfExisting) throws IOException { + + boolean alreadyExists = true; + + if (toPath.exists()) { + if (replaceIfExisting) { + Files.copy(is, toPath.toPath(), StandardCopyOption.REPLACE_EXISTING); + } else { + alreadyExists = false; + } + } else { + Files.copy(is, toPath.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + + is.close(); + + CopyFileIsData data = new CopyFileIsData(); + data.alreadyExists = alreadyExists; + data.file = toPath; + + return data; + + } + + public static CopyFileIsData copyFileIsTemp(InputStream is, String fileName, boolean replaceIfExisting) throws IOException { + String tmpDir = System.getProperty("java.io.tmpdir"); + File tempFile = new File(tmpDir + File.separator + fileName); + + return copyFileIs(is, tempFile, replaceIfExisting); + } + + public static long getMemoryUsageMB() { + return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / MB; + } + + public static String loadIsStr(InputStream is, Charset charset) throws UnsupportedEncodingException { + return new BufferedReader(new InputStreamReader(is, String.valueOf(charset))) + .lines().collect(Collectors.joining("\n")); + } + + public static String loadFileStr(File f) { + String content = ""; + + try { + content = new String(Files.readAllBytes(f.toPath())); + } catch (IOException e) { + e.printStackTrace(); + } + + return content; + } + + public static void replaceStrInFile(File f, String target, String replacement) { + String fileContents = loadFileStr(f); + saveFileStr(f, fileContents.replace(target, replacement)); + } + + public static boolean saveFileStr(File f, String contents) { + try { + FileWriter fw = new FileWriter(f); + fw.append(contents); + fw.close(); + return true; + } catch (IOException e) { + Log.error("Exception while trying to save file " + f.getAbsolutePath(), e); + return false; + } + } + + public static void download(String url, String fileName) throws Exception { + try (InputStream in = URI.create(url).toURL().openStream()) { + Files.copy(in, Paths.get(fileName)); + } + } + + public static File getAppData() { + return new File(System.getProperty("user.home") + File.separator); + } + + public static File getEOCVSimFolder() { + return EOCVSimFolder.INSTANCE; + } + + public static Optional getExtensionByStringHandling(String filename) { + return Optional.ofNullable(filename) + .filter(f -> f.contains(".")) + .map(f -> f.substring(filename.lastIndexOf(".") + 1)); + } + + public static List filesUnder(File parent, Predicate predicate) { + ArrayList result = new ArrayList<>(); + + if(parent.isDirectory()) { + for(File child : parent.listFiles()) { + result.addAll(filesUnder(child, predicate)); + } + } else if(parent.exists() && (predicate != null && predicate.test(parent))) { + result.add(parent.getAbsoluteFile()); + } + + return result; + } + + public static List filesUnder(File parent, String extension) { + return filesUnder(parent, (f) -> f.getName().endsWith(extension)); + } + + public static List filesUnder(File parent) { + return filesUnder(parent, (f) -> true); + } + + public static List filesIn(File parent, Predicate predicate) { + ArrayList result = new ArrayList<>(); + + if(!parent.exists()) return result; + + if(parent.isDirectory()) { + for(File f : parent.listFiles()) { + if(predicate != null && predicate.test(f)) + result.add(f); + } + } else { + if(predicate != null && predicate.test(parent)) + result.add(parent); + } + + return result; + } + + public static List filesIn(File parent, String extension) { + return filesIn(parent, (f) -> f.getName().endsWith(extension)); + } + + public static void deleteFilesUnder(File parent, Predicate predicate) { + for(File file : parent.listFiles()) { + if(file.isDirectory()) + deleteFilesUnder(file, predicate); + + if(predicate != null) { + if(predicate.test(file)) file.delete(); + } else { + file.delete(); + } + } + } + + public static void deleteFilesUnder(File parent) { + deleteFilesUnder(parent, null); + } + + public static boolean migrateFile(File oldFile, File newFile) { + if(newFile.exists() || !oldFile.exists()) return false; + + Log.info("SysUtil", "Migrating old file " + oldFile.getAbsolutePath() + " to " + newFile.getAbsolutePath()); + + try { + Files.move(oldFile.toPath(), newFile.toPath()); + } catch (IOException e) { + Log.warn("SysUtil", "Failed to migrate old file " + oldFile.getAbsolutePath()); + return false; + } + + return true; + } + + public static File getRelativePath(File root, File child) { + File result = new File(""); + + while(!root.equals(child)) { + File parent = child.getParentFile(); + result = new File(new File(child.getName()), result.getPath()); + + if(parent == null) break; + + child = parent; + } + + return result; + } + + public static List getClasspathFiles() { + String[] classpaths = System.getProperty("java.class.path").split(File.pathSeparator); + ArrayList files = new ArrayList<>(); + + for(String path : classpaths) { + files.add(new File(path)); + } + + return files; + } + + public static CommandResult runShellCommand(String command) { + CommandResult result = new CommandResult(); + + ProcessBuilder processBuilder = new ProcessBuilder(); + if (OS == OperatingSystem.WINDOWS) { + processBuilder.command("cmd.exe", "/c", command); + } else { + processBuilder.command("sh", "-c", command); + } + + try { + Process process = processBuilder.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + + String line = ""; + StringBuilder message = new StringBuilder(); + + while((line = reader.readLine()) != null) { + message.append(line); + } + + result.exitCode = process.waitFor(); + + result.output = message.toString(); + } catch (IOException | InterruptedException e) { + result.output = StrUtil.fromException(e); + result.exitCode = -1; + } + + return result; + } + + public enum OperatingSystem { + WINDOWS, + LINUX, + MACOS, + UNKNOWN + } + + public static class CopyFileIsData { + public File file = null; + public boolean alreadyExists = false; + } + + public static class CommandResult { + public String output = ""; + public int exitCode = 0; + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/DelegatingStandardFileManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/DelegatingStandardFileManager.kt index 3e4dccd4..59c120b6 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/DelegatingStandardFileManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/DelegatingStandardFileManager.kt @@ -1,56 +1,56 @@ -/* - * Copyright (c) 2021 Sebastian Erives & (c) 2017 Robert Atkinson - * - * Based from the FTC SDK's org.firstinspires.ftc.onbotjava.OnBotJavaDelegatingStandardFileManager - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.compiler - -import java.io.File -import javax.tools.ForwardingJavaFileManager -import javax.tools.JavaFileManager -import javax.tools.JavaFileObject -import javax.tools.StandardJavaFileManager - -open class DelegatingStandardFileManager( - val delegate: StandardJavaFileManager -) : ForwardingJavaFileManager(delegate), StandardJavaFileManager { - - override fun getJavaFileObjectsFromFiles(files: MutableIterable): MutableIterable = - delegate.getJavaFileObjectsFromFiles(files) - - override fun getJavaFileObjects(vararg files: File): MutableIterable = - delegate.getJavaFileObjects(*files) - - override fun getJavaFileObjects(vararg names: String): MutableIterable = - delegate.getJavaFileObjects(*names) - - override fun getJavaFileObjectsFromStrings(names: MutableIterable): MutableIterable = - delegate.getJavaFileObjectsFromStrings(names) - - override fun setLocation(location: JavaFileManager.Location, files: MutableIterable) = - delegate.setLocation(location, files) - - override fun getLocation(location: JavaFileManager.Location): MutableIterable = - delegate.getLocation(location) - +/* + * Copyright (c) 2021 Sebastian Erives & (c) 2017 Robert Atkinson + * + * Based from the FTC SDK's org.firstinspires.ftc.onbotjava.OnBotJavaDelegatingStandardFileManager + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.compiler + +import java.io.File +import javax.tools.ForwardingJavaFileManager +import javax.tools.JavaFileManager +import javax.tools.JavaFileObject +import javax.tools.StandardJavaFileManager + +open class DelegatingStandardFileManager( + val delegate: StandardJavaFileManager +) : ForwardingJavaFileManager(delegate), StandardJavaFileManager { + + override fun getJavaFileObjectsFromFiles(files: MutableIterable): MutableIterable = + delegate.getJavaFileObjectsFromFiles(files) + + override fun getJavaFileObjects(vararg files: File): MutableIterable = + delegate.getJavaFileObjects(*files) + + override fun getJavaFileObjects(vararg names: String): MutableIterable = + delegate.getJavaFileObjects(*names) + + override fun getJavaFileObjectsFromStrings(names: MutableIterable): MutableIterable = + delegate.getJavaFileObjectsFromStrings(names) + + override fun setLocation(location: JavaFileManager.Location, files: MutableIterable) = + delegate.setLocation(location, files) + + override fun getLocation(location: JavaFileManager.Location): MutableIterable = + delegate.getLocation(location) + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/JarPacker.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/JarPacker.kt index c99899dc..39224508 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/JarPacker.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/compiler/JarPacker.kt @@ -1,78 +1,78 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.compiler - -import com.github.serivesmejia.eocvsim.util.SysUtil -import java.io.File -import java.io.FileOutputStream -import java.util.jar.JarOutputStream -import java.util.jar.Manifest -import java.util.zip.ZipEntry - -object JarPacker { - - private fun pack(outputJar: File, inputClasses: File, - resourceFilesRoot: File? = null, - resourceFiles: List? = null, - manifest: Manifest = Manifest()) { - - FileOutputStream(outputJar).use { outStream -> - JarOutputStream(outStream, manifest).use { jarOutStream -> - for (classFile in SysUtil.filesUnder(inputClasses, ".class")) { - putFileInJar(jarOutStream, inputClasses, classFile) - } - - if(resourceFiles != null && resourceFilesRoot != null) { - for(resFile in resourceFiles) { - putFileInJar(jarOutStream, resourceFilesRoot, resFile) - } - } - } - } - - } - - fun packClassesUnder(outputJar: File, - inputClasses: File, - manifest: Manifest = Manifest()) = pack(outputJar, inputClasses, manifest = manifest) - - fun packResAndClassesUnder(outputJar: File, - inputClasses: File, - resourceFilesRoot: File, - resourceFiles: List, - manifest: Manifest = Manifest()) = - pack(outputJar, inputClasses, resourceFilesRoot, resourceFiles, manifest) - - private fun putFileInJar(jar: JarOutputStream, rootFile: File, file: File) { - if(!file.exists()) return - - val ze = ZipEntry(SysUtil.getRelativePath(rootFile, file).path) - ze.time = file.lastModified() - - jar.putNextEntry(ze) - SysUtil.copyStream(file, jar) - jar.closeEntry() - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.compiler + +import com.github.serivesmejia.eocvsim.util.SysUtil +import java.io.File +import java.io.FileOutputStream +import java.util.jar.JarOutputStream +import java.util.jar.Manifest +import java.util.zip.ZipEntry + +object JarPacker { + + private fun pack(outputJar: File, inputClasses: File, + resourceFilesRoot: File? = null, + resourceFiles: List? = null, + manifest: Manifest = Manifest()) { + + FileOutputStream(outputJar).use { outStream -> + JarOutputStream(outStream, manifest).use { jarOutStream -> + for (classFile in SysUtil.filesUnder(inputClasses, ".class")) { + putFileInJar(jarOutStream, inputClasses, classFile) + } + + if(resourceFiles != null && resourceFilesRoot != null) { + for(resFile in resourceFiles) { + putFileInJar(jarOutStream, resourceFilesRoot, resFile) + } + } + } + } + + } + + fun packClassesUnder(outputJar: File, + inputClasses: File, + manifest: Manifest = Manifest()) = pack(outputJar, inputClasses, manifest = manifest) + + fun packResAndClassesUnder(outputJar: File, + inputClasses: File, + resourceFilesRoot: File, + resourceFiles: List, + manifest: Manifest = Manifest()) = + pack(outputJar, inputClasses, resourceFilesRoot, resourceFiles, manifest) + + private fun putFileInJar(jar: JarOutputStream, rootFile: File, file: File) { + if(!file.exists()) return + + val ze = ZipEntry(SysUtil.getRelativePath(rootFile, file).path) + ze.time = file.lastModified() + + jar.putNextEntry(ze) + SysUtil.copyStream(file, jar) + jar.closeEntry() + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventHandler.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventHandler.kt index 99b68bb8..ed9b2e0d 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventHandler.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventHandler.kt @@ -1,132 +1,142 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.event - -import com.github.serivesmejia.eocvsim.util.Log - -class EventHandler(val name: String) : Runnable { - - private val lock = Any() - private val onceLock = Any() - - val listeners: Array - get() { - synchronized(lock) { - return internalListeners.toTypedArray() - } - } - - val onceListeners: Array - get() { - synchronized(onceLock) { - return internalOnceListeners.toTypedArray() - } - } - - var callRightAway = false - - private val internalListeners = ArrayList() - private val internalOnceListeners = ArrayList() - - override fun run() { - for(listener in listeners) { - try { - runListener(listener, false) - } catch (ex: Exception) { - Log.warn("${name}-EventHandler", "Error while running listener ${listener.javaClass.name}", ex) - } - } - - val toRemoveOnceListeners = mutableListOf() - - //executing "doOnce" listeners - for(listener in onceListeners) { - try { - runListener(listener, true) - } catch (ex: Exception) { - Log.warn("${name}-EventHandler", "Error while running \"once\" ${listener.javaClass.name}", ex) - } - - toRemoveOnceListeners.add(listener) - } - - synchronized(onceLock) { - for(listener in toRemoveOnceListeners) { - internalOnceListeners.remove(listener) - } - } - } - - fun doOnce(listener: EventListener) { - if(callRightAway) - runListener(listener, true) - else synchronized(onceLock) { - internalOnceListeners.add(listener) - } - } - - fun doOnce(runnable: Runnable) = doOnce { runnable.run() } - - - fun doPersistent(listener: EventListener) { - synchronized(lock) { - internalListeners.add(listener) - } - - if(callRightAway) runListener(listener, false) - } - - fun doPersistent(runnable: Runnable) = doPersistent { runnable.run() } - - fun removePersistentListener(listener: EventListener) { - if(internalListeners.contains(listener)) { - synchronized(lock) { internalListeners.remove(listener) } - } - } - - fun removeOnceListener(listener: EventListener) { - if(internalOnceListeners.contains(listener)) { - synchronized(onceLock) { internalOnceListeners.remove(listener) } - } - } - - fun removeAllListeners() { - removeAllPersistentListeners() - removeAllOnceListeners() - } - - fun removeAllPersistentListeners() = synchronized(lock) { - internalListeners.clear() - } - - fun removeAllOnceListeners() = synchronized(onceLock) { - internalOnceListeners.clear() - } - - operator fun invoke(listener: EventListener) = doPersistent(listener) - - private fun runListener(listener: EventListener, isOnce: Boolean) = - listener.run(EventListenerRemover(this, listener, isOnce)) - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.event + +import com.github.serivesmejia.eocvsim.util.Log + +class EventHandler(val name: String) : Runnable { + + private val lock = Any() + private val onceLock = Any() + + val listeners: Array + get() { + synchronized(lock) { + return internalListeners.toTypedArray() + } + } + + val onceListeners: Array + get() { + synchronized(onceLock) { + return internalOnceListeners.toTypedArray() + } + } + + var callRightAway = false + + private val internalListeners = ArrayList() + private val internalOnceListeners = ArrayList() + + override fun run() { + for(listener in listeners) { + try { + runListener(listener, false) + } catch (ex: Exception) { + if(ex is InterruptedException) { + Log.warn("${name}-EventHandler", "Rethrowing InterruptedException...") + throw ex + } else { + Log.warn("${name}-EventHandler", "Error while running listener ${listener.javaClass.name}", ex) + } + } + } + + val toRemoveOnceListeners = mutableListOf() + + //executing "doOnce" listeners + for(listener in onceListeners) { + try { + runListener(listener, true) + } catch (ex: Exception) { + if(ex is InterruptedException) { + Log.warn("${name}-EventHandler", "Rethrowing InterruptedException...") + throw ex + } else { + Log.warn("${name}-EventHandler", "Error while running \"once\" ${listener.javaClass.name}", ex) + } + } + + toRemoveOnceListeners.add(listener) + } + + synchronized(onceLock) { + for(listener in toRemoveOnceListeners) { + internalOnceListeners.remove(listener) + } + } + } + + fun doOnce(listener: EventListener) { + if(callRightAway) + runListener(listener, true) + else synchronized(onceLock) { + internalOnceListeners.add(listener) + } + } + + fun doOnce(runnable: Runnable) = doOnce { runnable.run() } + + + fun doPersistent(listener: EventListener) { + synchronized(lock) { + internalListeners.add(listener) + } + + if(callRightAway) runListener(listener, false) + } + + fun doPersistent(runnable: Runnable) = doPersistent { runnable.run() } + + fun removePersistentListener(listener: EventListener) { + if(internalListeners.contains(listener)) { + synchronized(lock) { internalListeners.remove(listener) } + } + } + + fun removeOnceListener(listener: EventListener) { + if(internalOnceListeners.contains(listener)) { + synchronized(onceLock) { internalOnceListeners.remove(listener) } + } + } + + fun removeAllListeners() { + removeAllPersistentListeners() + removeAllOnceListeners() + } + + fun removeAllPersistentListeners() = synchronized(lock) { + internalListeners.clear() + } + + fun removeAllOnceListeners() = synchronized(onceLock) { + internalOnceListeners.clear() + } + + operator fun invoke(listener: EventListener) = doPersistent(listener) + + private fun runListener(listener: EventListener, isOnce: Boolean) = + listener.run(EventListenerRemover(this, listener, isOnce)) + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventListener.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventListener.kt index 3285909f..532802f9 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventListener.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/event/EventListener.kt @@ -1,42 +1,42 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.event - -fun interface EventListener { - fun run(remover: EventListenerRemover) -} - -class EventListenerRemover( - val handler: EventHandler, - val listener: EventListener, - val isOnceListener: Boolean -) { - fun removeThis() { - if(isOnceListener) - handler.removeOnceListener(listener) - else - handler.removePersistentListener(listener) - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.event + +fun interface EventListener { + fun run(remover: EventListenerRemover) +} + +class EventListenerRemover( + val handler: EventHandler, + val listener: EventListener, + val isOnceListener: Boolean +) { + fun removeThis() { + if(isOnceListener) + handler.removeOnceListener(listener) + else + handler.removePersistentListener(listener) + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/MaxActiveContextsException.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/MaxActiveContextsException.kt index a5f45e26..306db052 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/MaxActiveContextsException.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/MaxActiveContextsException.kt @@ -1,3 +1,3 @@ -package com.github.serivesmejia.eocvsim.util.exception - +package com.github.serivesmejia.eocvsim.util.exception + class MaxActiveContextsException(message: String = "") : Exception(message) \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReport.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReport.kt index fcfe00a4..919973b7 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReport.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/CrashReport.kt @@ -1,111 +1,118 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.exception.handling - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.Build -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.StrUtil -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.util.extension.plus -import java.io.File -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter - -class CrashReport(causedByException: Throwable) { - - private companion object { - val OS_ARCH = System.getProperty("os.arch") - val OS_VERSION = System.getProperty("os.version") - val OS_NAME = System.getProperty("os.name") - val SYSUTIL_DETECTED_OS = SysUtil.getOS() - val JAVA_VERSION = System.getProperty("java.version") - val JAVA_VENDOR = System.getProperty("java.vendor") - - val dtFormatter = DateTimeFormatter.ofPattern("yyyy_MM_dd-HH.mm.ss") - } - - private val sb = StringBuilder() - - init { - sb.appendLine("/--------------------------------\\").appendLine() - sb.appendLine(" EOCV-Sim v${EOCVSim.VERSION} crash report").appendLine() - sb.appendLine("\\--------------------------------/").appendLine() - - sb.appendLine(": Crash stacktrace").appendLine() - sb.appendLine(StrUtil.fromException(causedByException)).appendLine() - - sb.appendLine("==========================================").appendLine() - - sb.appendLine(": EOCV-Sim info") - sb.appendLine(" Version: ${EOCVSim.VERSION}") - sb.appendLine(" Built on: ${Build.buildDate}").appendLine() - - sb.appendLine(": System specs") - sb.appendLine(" OS name: $OS_NAME") - sb.appendLine(" OS version: $OS_VERSION") - sb.appendLine(" Detected OS: $SYSUTIL_DETECTED_OS") - sb.appendLine(" Architecture: $OS_ARCH") - sb.appendLine(" Java version: $JAVA_VERSION") - sb.appendLine(" Java vendor: $JAVA_VENDOR") - sb.appendLine(" Last memory usage: ${SysUtil.getMemoryUsageMB()} MB").appendLine() - - sb.appendLine("==========================================").appendLine() - - sb.appendLine(": Full thread dump").appendLine() - - for((thread, stacktrace) in Thread.getAllStackTraces()) { - sb.appendLine(" > Thread \"${thread.name}\"") - - for(element in stacktrace) { - sb.appendLine(" $element") - } - } - sb.appendLine() - - sb.appendLine("==================================").appendLine() - - sb.appendLine(": Full logs").appendLine() - sb.appendLine(Log.fullLogs.toString()).appendLine() - - sb.appendLine(";") - } - - fun saveCrashReport(f: File) { - SysUtil.saveFileStr(f, toString()) - Log.info("CrashReport", "Saved crash report to ${f.absolutePath}") - } - - fun saveCrashReport() { - val workingDir = File(System.getProperty("user.dir")) - val dateTimeStr = dtFormatter.format(LocalDateTime.now()) - - val crashLogFile = workingDir + "/crashreport-eocvsim-$dateTimeStr.log" - - saveCrashReport(crashLogFile) - } - - override fun toString() = sb.toString() - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.exception.handling + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.Build +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.StrUtil +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.extension.plus +import java.io.File +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +class CrashReport(causedByException: Throwable) { + + private companion object { + val OS_ARCH = System.getProperty("os.arch") + val OS_VERSION = System.getProperty("os.version") + val OS_NAME = System.getProperty("os.name") + val SYSUTIL_DETECTED_OS = SysUtil.getOS() + val JAVA_VERSION = System.getProperty("java.version") + val JAVA_VENDOR = System.getProperty("java.vendor") + + val dtFormatter = DateTimeFormatter.ofPattern("yyyy_MM_dd-HH.mm.ss") + } + + private val sb = StringBuilder() + + init { + sb.appendLine("/--------------------------------\\").appendLine() + sb.appendLine(" EOCV-Sim v${EOCVSim.VERSION} crash report").appendLine() + sb.appendLine("\\--------------------------------/").appendLine() + + sb.appendLine(": Crash stacktrace").appendLine() + sb.appendLine(StrUtil.fromException(causedByException)).appendLine() + + sb.appendLine("==========================================").appendLine() + + sb.appendLine(": EOCV-Sim info") + sb.appendLine(" Version: ${EOCVSim.VERSION}") + sb.appendLine(" Built on: ${Build.buildDate}").appendLine() + + sb.appendLine(": System specs") + sb.appendLine(" OS name: $OS_NAME") + sb.appendLine(" OS version: $OS_VERSION") + sb.appendLine(" Detected OS: $SYSUTIL_DETECTED_OS") + sb.appendLine(" Architecture: $OS_ARCH") + sb.appendLine(" Java version: $JAVA_VERSION") + sb.appendLine(" Java vendor: $JAVA_VENDOR") + sb.appendLine(" Last memory usage: ${SysUtil.getMemoryUsageMB()} MB").appendLine() + + sb.appendLine("==========================================").appendLine() + + sb.appendLine(": Full thread dump").appendLine() + + for((thread, stacktrace) in Thread.getAllStackTraces()) { + sb.appendLine(" > Thread \"${thread.name}\"") + + for(element in stacktrace) { + sb.appendLine(" $element") + } + } + sb.appendLine() + + sb.appendLine("==================================").appendLine() + + sb.appendLine(": Full logs").appendLine() + sb.appendLine(Log.fullLogs.toString()).appendLine() + + sb.appendLine(";") + } + + fun saveCrashReport(f: File) { + SysUtil.saveFileStr(f, toString()) + Log.info("CrashReport", "Saved crash report to ${f.absolutePath}") + } + + fun saveCrashReport() { + val workingDir = File(System.getProperty("user.dir")) + val dateTimeStr = dtFormatter.format(LocalDateTime.now()) + + val crashLogFile = workingDir + "/crashreport-eocvsim-$dateTimeStr.log" + + saveCrashReport(crashLogFile) + } + + fun saveCrashReport(filename: String) { + val workingDir = File(System.getProperty("user.dir")) + val crashLogFile = workingDir + "/$filename.log" + + saveCrashReport(crashLogFile) + } + + override fun toString() = sb.toString() + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/EOCVSimUncaughtExceptionHandler.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/EOCVSimUncaughtExceptionHandler.kt index 54112afa..e82869d2 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/EOCVSimUncaughtExceptionHandler.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/exception/handling/EOCVSimUncaughtExceptionHandler.kt @@ -1,54 +1,57 @@ -package com.github.serivesmejia.eocvsim.util.exception.handling - -import com.github.serivesmejia.eocvsim.util.Log -import kotlin.system.exitProcess - -class EOCVSimUncaughtExceptionHandler private constructor() : Thread.UncaughtExceptionHandler { - - companion object { - const val MAX_UNCAUGHT_EXCEPTIONS_BEFORE_CRASH = 3 - - @JvmStatic fun register() { - Thread.setDefaultUncaughtExceptionHandler(EOCVSimUncaughtExceptionHandler()) - } - - private const val TAG = "EOCVSimUncaughtExceptionHandler" - } - - private var uncaughtExceptionsCount = 0 - - override fun uncaughtException(t: Thread, e: Throwable) { - //we don't want the whole app to crash on a simple interrupted exception right? - if(e is InterruptedException) { - Log.warn(TAG, "Uncaught InterruptedException thrown in Thread ${t.name}, it will be interrupted", e) - t.interrupt() - return - } - - uncaughtExceptionsCount++ - - Log.error(TAG,"Uncaught exception thrown in \"${t.name}\" thread", e) - Log.blank() - - //Exit if uncaught exception happened in the main thread - //since we would be basically in a deadlock state if that happened - //or if we have a lotta uncaught exceptions. - if(t.name.equals("main", true) || uncaughtExceptionsCount > MAX_UNCAUGHT_EXCEPTIONS_BEFORE_CRASH) { - CrashReport(e).saveCrashReport() - - Log.warn(TAG, "If this error persists, open an issue on EOCV-Sim's GitHub attaching the crash report file.") - Log.blank() - Log.warn(TAG, "The application will exit now (exit code 1)") - - exitProcess(1) - } else { - //if not, eocv sim might still be working (i.e a crash from a MatPoster thread) - //so we might not need to exit in this point, but we'll need to send a warning - //to the user - Log.warn(TAG, "If this error persists, open an issue on EOCV-Sim's GitHub.") - Log.blank() - Log.warn(TAG, "The application might not work as expected from this point") - } - } - +package com.github.serivesmejia.eocvsim.util.exception.handling + +import com.github.serivesmejia.eocvsim.currentMainThread +import com.github.serivesmejia.eocvsim.util.Log +import kotlin.system.exitProcess + +class EOCVSimUncaughtExceptionHandler private constructor() : Thread.UncaughtExceptionHandler { + + companion object { + const val MAX_UNCAUGHT_EXCEPTIONS_BEFORE_CRASH = 3 + + @JvmStatic fun register() { + Thread.setDefaultUncaughtExceptionHandler(EOCVSimUncaughtExceptionHandler()) + } + + private const val TAG = "EOCVSimUncaughtExceptionHandler" + } + + private var uncaughtExceptionsCount = 0 + + override fun uncaughtException(t: Thread, e: Throwable) { + //we don't want the whole app to crash on a simple interrupted exception right? + if(e is InterruptedException) { + Log.warn(TAG, "Uncaught InterruptedException thrown in Thread ${t.name}, it will be interrupted", e) + t.interrupt() + return + } + + uncaughtExceptionsCount++ + + Log.error(TAG,"Uncaught exception thrown in \"${t.name}\" thread", e) + Log.blank() + + //Exit if uncaught exception happened in the main thread + //since we would be basically in a deadlock state if that happened + //or if we have a lotta uncaught exceptions. + if(t == currentMainThread || e !is Exception || uncaughtExceptionsCount > MAX_UNCAUGHT_EXCEPTIONS_BEFORE_CRASH) { + CrashReport(e).saveCrashReport() + + Log.warn(TAG, "If this error persists, open an issue on EOCV-Sim's GitHub attaching the crash report file.") + Log.blank() + Log.warn(TAG, "The application will exit now (exit code 1)") + + exitProcess(1) + } else { + CrashReport(e).saveCrashReport("lasterror-eocvsim") + + //if not, eocv sim might still be working (i.e a crash from a MatPoster thread) + //so we might not need to exit in this point, but we'll need to send a warning + //to the user + Log.warn(TAG, "If this error persists, open an issue on EOCV-Sim's GitHub.") + Log.blank() + Log.warn(TAG, "The application might not work as expected from this point") + } + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/CvExt.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/CvExt.kt index 7c4cc2b0..c192ca07 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/CvExt.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/CvExt.kt @@ -1,52 +1,52 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -@file:JvmName("CvExt") - -package com.github.serivesmejia.eocvsim.util.extension - -import com.qualcomm.robotcore.util.Range -import org.opencv.core.CvType -import org.opencv.core.Mat -import org.opencv.core.Scalar -import org.opencv.core.Size -import org.opencv.imgproc.Imgproc - -fun Scalar.cvtColor(code: Int): Scalar { - val mat = Mat(5, 5, CvType.CV_8UC3); - mat.setTo(this) - Imgproc.cvtColor(mat, mat, code); - - val newScalar = Scalar(mat.get(1, 1)) - mat.release() - - return newScalar -} - -fun Size.aspectRatio() = height / width -fun Mat.aspectRatio() = size().aspectRatio() - -fun Size.clipTo(size: Size): Size { - width = Range.clip(width, 0.0, size.width) - height = Range.clip(height, 0.0, size.height) - return this +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +@file:JvmName("CvExt") + +package com.github.serivesmejia.eocvsim.util.extension + +import com.qualcomm.robotcore.util.Range +import org.opencv.core.CvType +import org.opencv.core.Mat +import org.opencv.core.Scalar +import org.opencv.core.Size +import org.opencv.imgproc.Imgproc + +fun Scalar.cvtColor(code: Int): Scalar { + val mat = Mat(5, 5, CvType.CV_8UC3); + mat.setTo(this) + Imgproc.cvtColor(mat, mat, code); + + val newScalar = Scalar(mat.get(1, 1)) + mat.release() + + return newScalar +} + +fun Size.aspectRatio() = height / width +fun Mat.aspectRatio() = size().aspectRatio() + +fun Size.clipTo(size: Size): Size { + width = Range.clip(width, 0.0, size.width) + height = Range.clip(height, 0.0, size.height) + return this } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/FileExt.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/FileExt.kt index 318bb7b7..c4f8e8a5 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/FileExt.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/FileExt.kt @@ -1,30 +1,30 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.extension - -import java.io.File - -operator fun File.plus(str: String): File { - return File(this.absolutePath + str) +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.extension + +import java.io.File + +operator fun File.plus(str: String): File { + return File(this.absolutePath + str) } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/NumberExt.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/NumberExt.kt index 8194aa84..0b83ed3f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/NumberExt.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/NumberExt.kt @@ -1,34 +1,34 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.extension - -fun Int.clipUpperZero(): Int { - return if(this > 0) { - this - } else { - 0 - } -} - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.extension + +fun Int.clipUpperZero(): Int { + return if(this > 0) { + this + } else { + 0 + } +} + val Int.zeroBased get() = (this - 1).clipUpperZero() \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/StrExt.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/StrExt.kt index 10552992..b256c4f7 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/StrExt.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/extension/StrExt.kt @@ -1,31 +1,31 @@ -package com.github.serivesmejia.eocvsim.util.extension - -fun String.removeFromEnd(rem: String): String { - if(endsWith(rem)) { - return substring(0, length - rem.length).trim() - } - return trim() -} - -fun String.tabIndent() = replace("(?m)^", "\t") - -fun String.removeIndentFromFirstLine(): String { - val lines = split("\n") - - if(lines.size == 1) { - return lines[0].replace("\t", "") - } else { - var str = "" - for(i in 0..lines.size.zeroBased) { - val line = if(i == 0) { - lines[i].replace("\t", "") - } else { - lines[i] - } - - str += line + "\n" - } - - return str.trim() - } +package com.github.serivesmejia.eocvsim.util.extension + +fun String.removeFromEnd(rem: String): String { + if(endsWith(rem)) { + return substring(0, length - rem.length).trim() + } + return trim() +} + +fun String.tabIndent() = replace("(?m)^", "\t") + +fun String.removeIndentFromFirstLine(): String { + val lines = split("\n") + + if(lines.size == 1) { + return lines[0].replace("\t", "") + } else { + var str = "" + for(i in 0..lines.size.zeroBased) { + val line = if(i == 0) { + lines[i].replace("\t", "") + } else { + lines[i] + } + + str += line + "\n" + } + + return str.trim() + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsCounter.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsCounter.kt index 511e8c97..0f422e99 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsCounter.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsCounter.kt @@ -1,58 +1,58 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.fps - -import com.qualcomm.robotcore.util.ElapsedTime -import com.qualcomm.robotcore.util.MovingStatistics - -class FpsCounter { - - private val elapsedTime = ElapsedTime() - - private val avgFpsStatistics = MovingStatistics(100) - - val avgFps: Double - get() { - return avgFpsStatistics.mean - } - - @Volatile - private var fpsCount = 0 - - @get:Synchronized - @Volatile - var fps = 0 - private set - - @Synchronized - fun update() { - fpsCount++ - if (elapsedTime.seconds() >= 1) { - fps = fpsCount; fpsCount = 0 - avgFpsStatistics.add(fps.toDouble()) - elapsedTime.reset() - } - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.fps + +import com.qualcomm.robotcore.util.ElapsedTime +import com.qualcomm.robotcore.util.MovingStatistics + +class FpsCounter { + + private val elapsedTime = ElapsedTime() + + private val avgFpsStatistics = MovingStatistics(100) + + val avgFps: Double + get() { + return avgFpsStatistics.mean + } + + @Volatile + private var fpsCount = 0 + + @get:Synchronized + @Volatile + var fps = 0 + private set + + @Synchronized + fun update() { + fpsCount++ + if (elapsedTime.seconds() >= 1) { + fps = fpsCount; fpsCount = 0 + avgFpsStatistics.add(fps.toDouble()) + elapsedTime.reset() + } + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsLimiter.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsLimiter.kt index 10fb7e25..1c36b284 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsLimiter.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/fps/FpsLimiter.kt @@ -1,42 +1,42 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.fps - -class FpsLimiter(var maxFPS: Double = 30.0) { - - @Volatile private var start = 0.0 - @Volatile private var diff = 0.0 - @Volatile private var wait = 0.0 - - @Throws(InterruptedException::class) - fun sync() { - wait = 1.0 / (maxFPS / 1000.0) - diff = System.currentTimeMillis() - start - if (diff < wait) { - Thread.sleep((wait - diff).toLong()) - } - start = System.currentTimeMillis().toDouble() - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.fps + +class FpsLimiter(var maxFPS: Double = 30.0) { + + @Volatile private var start = 0.0 + @Volatile private var diff = 0.0 + @Volatile private var wait = 0.0 + + @Throws(InterruptedException::class) + fun sync() { + wait = 1.0 / (maxFPS / 1000.0) + diff = System.currentTimeMillis() - start + if (diff < wait) { + Thread.sleep((wait - diff).toLong()) + } + start = System.currentTimeMillis().toDouble() + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/image/BufferedImageRecycler.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/image/BufferedImageRecycler.java index 77e41e8e..d2b21b3f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/image/BufferedImageRecycler.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/image/BufferedImageRecycler.java @@ -1,107 +1,107 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.image; - -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.concurrent.ArrayBlockingQueue; - -public class BufferedImageRecycler { - - private final RecyclableBufferedImage[] allBufferedImages; - private final ArrayBlockingQueue availableBufferedImages; - - public BufferedImageRecycler(int num, int allImgWidth, int allImgHeight, int allImgType) { - allBufferedImages = new RecyclableBufferedImage[num]; - availableBufferedImages = new ArrayBlockingQueue<>(num); - - for (int i = 0; i < allBufferedImages.length; i++) { - allBufferedImages[i] = new RecyclableBufferedImage(i, allImgWidth, allImgHeight, allImgType); - availableBufferedImages.add(allBufferedImages[i]); - } - } - - public BufferedImageRecycler(int num, Dimension allImgSize, int allImgType) { - this(num, (int)allImgSize.getWidth(), (int)allImgSize.getHeight(), allImgType); - } - - public BufferedImageRecycler(int num, int allImgWidth, int allImgHeight) { - this(num, allImgWidth, allImgHeight, BufferedImage.TYPE_3BYTE_BGR); - } - - public BufferedImageRecycler(int num, Dimension allImgSize) { - this(num, (int)allImgSize.getWidth(), (int)allImgSize.getHeight(), BufferedImage.TYPE_3BYTE_BGR); - } - - public boolean isOnUse() { return allBufferedImages.length != availableBufferedImages.size(); } - - public synchronized RecyclableBufferedImage takeBufferedImage() { - - if (availableBufferedImages.size() == 0) { - throw new RuntimeException("All buffered images have been checked out!"); - } - - RecyclableBufferedImage buffImg = null; - try { - buffImg = availableBufferedImages.take(); - buffImg.checkedOut = true; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - return buffImg; - - } - - public synchronized void returnBufferedImage(RecyclableBufferedImage buffImg) { - if (buffImg != allBufferedImages[buffImg.idx]) { - throw new IllegalArgumentException("This BufferedImage does not belong to this recycler!"); - } - - if (buffImg.checkedOut) { - buffImg.checkedOut = false; - buffImg.flush(); - availableBufferedImages.add(buffImg); - } else { - throw new IllegalArgumentException("This BufferedImage has already been returned!"); - } - } - - public synchronized void flushAll() { - for(BufferedImage img : allBufferedImages) { - img.flush(); - } - } - - public static class RecyclableBufferedImage extends BufferedImage { - private int idx = -1; - private volatile boolean checkedOut = false; - - private RecyclableBufferedImage(int idx, int width, int height, int imageType) { - super(width, height, imageType); - this.idx = idx; - } - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.image; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.concurrent.ArrayBlockingQueue; + +public class BufferedImageRecycler { + + private final RecyclableBufferedImage[] allBufferedImages; + private final ArrayBlockingQueue availableBufferedImages; + + public BufferedImageRecycler(int num, int allImgWidth, int allImgHeight, int allImgType) { + allBufferedImages = new RecyclableBufferedImage[num]; + availableBufferedImages = new ArrayBlockingQueue<>(num); + + for (int i = 0; i < allBufferedImages.length; i++) { + allBufferedImages[i] = new RecyclableBufferedImage(i, allImgWidth, allImgHeight, allImgType); + availableBufferedImages.add(allBufferedImages[i]); + } + } + + public BufferedImageRecycler(int num, Dimension allImgSize, int allImgType) { + this(num, (int)allImgSize.getWidth(), (int)allImgSize.getHeight(), allImgType); + } + + public BufferedImageRecycler(int num, int allImgWidth, int allImgHeight) { + this(num, allImgWidth, allImgHeight, BufferedImage.TYPE_3BYTE_BGR); + } + + public BufferedImageRecycler(int num, Dimension allImgSize) { + this(num, (int)allImgSize.getWidth(), (int)allImgSize.getHeight(), BufferedImage.TYPE_3BYTE_BGR); + } + + public boolean isOnUse() { return allBufferedImages.length != availableBufferedImages.size(); } + + public synchronized RecyclableBufferedImage takeBufferedImage() { + + if (availableBufferedImages.size() == 0) { + throw new RuntimeException("All buffered images have been checked out!"); + } + + RecyclableBufferedImage buffImg = null; + try { + buffImg = availableBufferedImages.take(); + buffImg.checkedOut = true; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + return buffImg; + + } + + public synchronized void returnBufferedImage(RecyclableBufferedImage buffImg) { + if (buffImg != allBufferedImages[buffImg.idx]) { + throw new IllegalArgumentException("This BufferedImage does not belong to this recycler!"); + } + + if (buffImg.checkedOut) { + buffImg.checkedOut = false; + buffImg.flush(); + availableBufferedImages.add(buffImg); + } else { + throw new IllegalArgumentException("This BufferedImage has already been returned!"); + } + } + + public synchronized void flushAll() { + for(BufferedImage img : allBufferedImages) { + img.flush(); + } + } + + public static class RecyclableBufferedImage extends BufferedImage { + private int idx = -1; + private volatile boolean checkedOut = false; + + private RecyclableBufferedImage(int idx, int width, int height, int imageType) { + super(width, height, imageType); + this.idx = idx; + } + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/image/DynamicBufferedImageRecycler.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/image/DynamicBufferedImageRecycler.java index 941345d3..b4b50f3f 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/image/DynamicBufferedImageRecycler.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/image/DynamicBufferedImageRecycler.java @@ -1,78 +1,78 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.util.image; - -import java.awt.*; -import java.awt.image.BufferedImage; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -public class DynamicBufferedImageRecycler { - - private final HashMap recyclers = new HashMap<>(); - - public synchronized BufferedImage giveBufferedImage(Dimension size, int recyclerSize) { - - //look for existing buff image recycler with desired dimensions - for(Map.Entry entry : recyclers.entrySet()) { - Dimension dimension = entry.getKey(); - BufferedImageRecycler recycler = entry.getValue(); - - if(dimension.equals(size)) { - BufferedImage buffImg = recycler.takeBufferedImage(); - buffImg.flush(); - return buffImg; - } else if(!recycler.isOnUse()) { - recycler.flushAll(); - recyclers.remove(dimension); - } - } - - //create new one if didn't found an existing recycler - BufferedImageRecycler recycler = new BufferedImageRecycler(recyclerSize, size); - recyclers.put(size, recycler); - - BufferedImage buffImg = recycler.takeBufferedImage(); - - return buffImg; - } - - public synchronized void returnBufferedImage(BufferedImage buffImg) { - Dimension dimension = new Dimension(buffImg.getWidth(), buffImg.getHeight()); - - BufferedImageRecycler recycler = recyclers.get(dimension); - - if(recycler != null) - recycler.returnBufferedImage((BufferedImageRecycler.RecyclableBufferedImage) buffImg); - } - - public synchronized void flushAll() { - for(BufferedImageRecycler recycler : recyclers.values()) { - recycler.flushAll(); - } - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.util.image; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class DynamicBufferedImageRecycler { + + private final HashMap recyclers = new HashMap<>(); + + public synchronized BufferedImage giveBufferedImage(Dimension size, int recyclerSize) { + + //look for existing buff image recycler with desired dimensions + for(Map.Entry entry : recyclers.entrySet()) { + Dimension dimension = entry.getKey(); + BufferedImageRecycler recycler = entry.getValue(); + + if(dimension.equals(size)) { + BufferedImage buffImg = recycler.takeBufferedImage(); + buffImg.flush(); + return buffImg; + } else if(!recycler.isOnUse()) { + recycler.flushAll(); + recyclers.remove(dimension); + } + } + + //create new one if didn't found an existing recycler + BufferedImageRecycler recycler = new BufferedImageRecycler(recyclerSize, size); + recyclers.put(size, recycler); + + BufferedImage buffImg = recycler.takeBufferedImage(); + + return buffImg; + } + + public synchronized void returnBufferedImage(BufferedImage buffImg) { + Dimension dimension = new Dimension(buffImg.getWidth(), buffImg.getHeight()); + + BufferedImageRecycler recycler = recyclers.get(dimension); + + if(recycler != null) + recycler.returnBufferedImage((BufferedImageRecycler.RecyclableBufferedImage) buffImg); + } + + public synchronized void flushAll() { + for(BufferedImageRecycler recycler : recyclers.values()) { + recycler.flushAll(); + } + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/EOCVSimFolder.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/EOCVSimFolder.kt index 2088e384..b9d42bba 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/EOCVSimFolder.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/EOCVSimFolder.kt @@ -1,16 +1,16 @@ -package com.github.serivesmejia.eocvsim.util.io - -import com.github.serivesmejia.eocvsim.util.SysUtil -import java.io.File - -object EOCVSimFolder : File(SysUtil.getAppData().absolutePath + separator + ".eocvsim") { - - val lock by lazy { lockDirectory() } - - val couldLock get() = lock != null && lock!!.isLocked - - init { - mkdir() - } - +package com.github.serivesmejia.eocvsim.util.io + +import com.github.serivesmejia.eocvsim.util.SysUtil +import java.io.File + +object EOCVSimFolder : File(SysUtil.getAppData().absolutePath + separator + ".eocvsim") { + + val lock by lazy { lockDirectory() } + + val couldLock get() = lock != null && lock!!.isLocked + + init { + mkdir() + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/FileWatcher.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/FileWatcher.kt index c5bdba5c..232bb424 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/FileWatcher.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/FileWatcher.kt @@ -1,76 +1,76 @@ -package com.github.serivesmejia.eocvsim.util.io - -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.util.Log -import java.io.File -import java.util.* - -class FileWatcher(private val watchingDirectories: List, - watchingFileExtensions: List?, - name: String) { - - private val TAG = "FileWatcher-$name" - - val onChange = EventHandler("OnChange-$TAG") - - private val watcherThread = Thread( - Runner(watchingDirectories, watchingFileExtensions, onChange), - TAG - ) - - fun init() { - watcherThread.start() - } - - fun stop() { - watcherThread.interrupt() - } - - private class Runner(val watchingDirectories: List, - val fileExts: List?, - val onChange: EventHandler) : Runnable { - - private val lastModifyDates = mutableMapOf() - - override fun run() { - val TAG = Thread.currentThread().name!! - - val directoriesList = StringBuilder() - for(directory in watchingDirectories) { - directoriesList.appendLine(directory.absolutePath) - } - - Log.info(TAG, "Starting to watch directories in:\n$directoriesList") - - while(!Thread.currentThread().isInterrupted) { - var changeDetected = false - - for(directory in watchingDirectories) { - for(file in SysUtil.filesUnder(directory)) { - if(fileExts != null && !fileExts.stream().anyMatch { file.name.endsWith(".$it") }) - continue - - val path = file.absolutePath - val lastModified = file.lastModified() - - if(lastModifyDates.containsKey(path) && lastModified > lastModifyDates[path]!! && !changeDetected) { - Log.info(TAG, "Change detected on ${directory.absolutePath}") - - onChange.run() - changeDetected = true - } - - lastModifyDates[path] = lastModified - } - } - - Thread.sleep(800) //check every 800 ms - } - - Log.info(TAG, "Stopping watching directories:\n$directoriesList") - } - - } - -} +package com.github.serivesmejia.eocvsim.util.io + +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.util.Log +import java.io.File +import java.util.* + +class FileWatcher(private val watchingDirectories: List, + watchingFileExtensions: List?, + name: String) { + + private val TAG = "FileWatcher-$name" + + val onChange = EventHandler("OnChange-$TAG") + + private val watcherThread = Thread( + Runner(watchingDirectories, watchingFileExtensions, onChange), + TAG + ) + + fun init() { + watcherThread.start() + } + + fun stop() { + watcherThread.interrupt() + } + + private class Runner(val watchingDirectories: List, + val fileExts: List?, + val onChange: EventHandler) : Runnable { + + private val lastModifyDates = mutableMapOf() + + override fun run() { + val TAG = Thread.currentThread().name!! + + val directoriesList = StringBuilder() + for(directory in watchingDirectories) { + directoriesList.appendLine(directory.absolutePath) + } + + Log.info(TAG, "Starting to watch directories in:\n$directoriesList") + + while(!Thread.currentThread().isInterrupted) { + var changeDetected = false + + for(directory in watchingDirectories) { + for(file in SysUtil.filesUnder(directory)) { + if(fileExts != null && !fileExts.stream().anyMatch { file.name.endsWith(".$it") }) + continue + + val path = file.absolutePath + val lastModified = file.lastModified() + + if(lastModifyDates.containsKey(path) && lastModified > lastModifyDates[path]!! && !changeDetected) { + Log.info(TAG, "Change detected on ${directory.absolutePath}") + + onChange.run() + changeDetected = true + } + + lastModifyDates[path] = lastModified + } + } + + Thread.sleep(1200) //check every 800 ms + } + + Log.info(TAG, "Stopping watching directories:\n$directoriesList") + } + + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/Lock.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/Lock.kt index 2b1c5ee4..fbd419a4 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/Lock.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/io/Lock.kt @@ -1,76 +1,76 @@ -package com.github.serivesmejia.eocvsim.util.io - -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.SysUtil -import java.io.File -import java.io.RandomAccessFile -import java.nio.channels.FileLock - -class LockFile(pathname: String) : File(pathname) { - - private val raf by lazy { RandomAccessFile(this, "rw") } - - var lock: FileLock? = null - private set - - val isLocked get() = try { - raf - if(lock != null) !tryLock(false) else false - } catch(ex: Exception) { - Log.warn(TAG, "Can't open lock file $absolutePath") - true - } - - companion object { - const val TAG = "LockFile" - } - - init { - if(isDirectory) - throw IllegalArgumentException("Lock file cannot be a directory") - - if(!exists()) - SysUtil.saveFileStr(this, "") - } - - fun tryLock(log: Boolean = true): Boolean { - return try { - lock = raf.channel.tryLock() - if(log) Log.info(TAG, "Probably locked file $absolutePath") - true - } catch(ex: Exception) { - if(log) Log.warn(TAG, "Couldn't lock file $absolutePath", ex); - false - } - } - - fun unlock() { - lock?.release() - raf.close() - - lock = null - } - -} - -val File.directoryLockFile get() = LockFile(absolutePath + File.separator + ".lock") - -val File.isDirectoryLocked: Boolean get() { - val lock = directoryLockFile - val isLocked = lock.isLocked - - lock.unlock() - return isLocked -} - -fun File.lockDirectory(): LockFile? { - if(!isDirectory) - return null - - val lockFile = directoryLockFile - - if(isDirectoryLocked || !lockFile.tryLock()) - return null - - return lockFile +package com.github.serivesmejia.eocvsim.util.io + +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.SysUtil +import java.io.File +import java.io.RandomAccessFile +import java.nio.channels.FileLock + +class LockFile(pathname: String) : File(pathname) { + + private val raf by lazy { RandomAccessFile(this, "rw") } + + var lock: FileLock? = null + private set + + val isLocked get() = try { + raf + if(lock != null) !tryLock(false) else false + } catch(ex: Exception) { + Log.warn(TAG, "Can't open lock file $absolutePath") + true + } + + companion object { + const val TAG = "LockFile" + } + + init { + if(isDirectory) + throw IllegalArgumentException("Lock file cannot be a directory") + + if(!exists()) + SysUtil.saveFileStr(this, "") + } + + fun tryLock(log: Boolean = true): Boolean { + return try { + lock = raf.channel.tryLock() + if(log) Log.info(TAG, "Probably locked file $absolutePath") + true + } catch(ex: Exception) { + if(log) Log.warn(TAG, "Couldn't lock file $absolutePath", ex); + false + } + } + + fun unlock() { + lock?.release() + raf.close() + + lock = null + } + +} + +val File.directoryLockFile get() = LockFile(absolutePath + File.separator + ".lock") + +val File.isDirectoryLocked: Boolean get() { + val lock = directoryLockFile + val isLocked = lock.isLocked + + lock.unlock() + return isLocked +} + +fun File.lockDirectory(): LockFile? { + if(!isDirectory) + return null + + val lockFile = directoryLockFile + + if(isDirectoryLocked || !lockFile.tryLock()) + return null + + return lockFile } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/WorkspaceManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/WorkspaceManager.kt index 386c2a5b..4fb6041b 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/WorkspaceManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/WorkspaceManager.kt @@ -1,190 +1,199 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.workspace - -import com.github.serivesmejia.eocvsim.EOCVSim -import com.github.serivesmejia.eocvsim.pipeline.compiler.CompiledPipelineManager -import com.github.serivesmejia.eocvsim.util.event.EventHandler -import com.github.serivesmejia.eocvsim.util.io.FileWatcher -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.workspace.config.WorkspaceConfig -import com.github.serivesmejia.eocvsim.workspace.config.WorkspaceConfigLoader -import com.github.serivesmejia.eocvsim.workspace.util.WorkspaceTemplate -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import java.io.File -import java.nio.file.Paths - -class WorkspaceManager(val eocvSim: EOCVSim) { - - companion object { - private const val TAG = "WorkspaceManager" - } - - val workspaceConfigLoader by lazy { WorkspaceConfigLoader(workspaceFile) } - - var workspaceFile = File(".") - set(value) { - if(value != workspaceFile) { - workspaceConfigLoader.workspaceFile = value - - eocvSim.config.workspacePath = value.absolutePath - eocvSim.configManager.saveToFile() - - field = value - - Log.info(TAG, "Set current workspace to ${value.absolutePath}") - - if(::fileWatcher.isInitialized) - fileWatcher.stop() - - fileWatcher = FileWatcher( - arrayListOf( - sourcesAbsolutePath.toFile(), - resourcesAbsolutePath.toFile() - ), null, "Workspace" - ) - fileWatcher.init() - - onWorkspaceChange.run() - } - - cachedWorkspConfig = workspaceConfigLoader.loadWorkspaceConfig() - - if(cachedWorkspConfig == null) { - cachedWorkspConfig = WorkspaceConfig() - - if(workspaceConfigLoader.workspaceConfigFile.exists()) - Log.warn(TAG, "Recreating workspace config file, old one failed to parse") - else - Log.info(TAG, "Creating workspace config file...") - - workspaceConfigLoader.saveWorkspaceConfig(workspaceConfig) - } else { - Log.info(TAG, "Loaded workspace config successfully") - } - } - - private var cachedWorkspConfig: WorkspaceConfig? = null - - var workspaceConfig: WorkspaceConfig - set(value) { - Log.info(TAG, "Saving workspace config file of ${workspaceFile.absolutePath}") - workspaceConfigLoader.saveWorkspaceConfig(value) - cachedWorkspConfig = value - } - get() { - if(cachedWorkspConfig == null) - ::workspaceFile.set(workspaceFile) - - return cachedWorkspConfig!! - } - - val sourcesRelativePath get() = workspaceConfig.sourcesPath!! - val sourcesAbsolutePath get() = Paths.get(workspaceFile.absolutePath, sourcesRelativePath).normalize()!! - - val resourcesRelativePath get() = workspaceConfig.resourcesPath!! - val resourcesAbsolutePath get() = Paths.get(workspaceFile.absolutePath, resourcesRelativePath).normalize()!! - - val excludedRelativePaths get() = workspaceConfig.excludedPaths - val excludedAbsolutePaths get() = excludedRelativePaths.map { - Paths.get(workspaceFile.absolutePath, it).normalize()!! - } - - val excludedFileExtensions get() = workspaceConfig.excludedFileExtensions - - // TODO: Excluding ignored paths - val sourceFiles get() = SysUtil.filesUnder(sourcesAbsolutePath.toFile()) { file -> - file.name.endsWith(".java") && excludedAbsolutePaths.stream().noneMatch { - file.startsWith(it.toFile().absolutePath) - } - } - - val resourceFiles get() = SysUtil.filesUnder(resourcesAbsolutePath.toFile()) { file -> - file.name.run { - !endsWith(".java") && !endsWith(".class") && this != "eocvsim_workspace.json" - } && excludedAbsolutePaths.stream().noneMatch { - file.startsWith(it.toFile().absolutePath) - } && excludedFileExtensions.stream().noneMatch { - file.name.endsWith(".$it") - } - } - - val onWorkspaceChange = EventHandler("WorkspaceManager-OnChange") - - lateinit var fileWatcher: FileWatcher - private set - - fun stopFileWatcher() { - if(::fileWatcher.isInitialized) { - fileWatcher.stop() - } - } - - fun createWorkspaceWithTemplate(folder: File, template: WorkspaceTemplate): Boolean { - if(!folder.isDirectory) return false - if(!template.extractToIfEmpty(folder)) return false - - workspaceFile = folder - return true - } - - fun createWorkspaceWithTemplateAsync(folder: File, template: WorkspaceTemplate) = GlobalScope.launch(Dispatchers.IO) { - if(!folder.isDirectory) return@launch - if(!template.extractToIfEmpty(folder)) return@launch - - eocvSim.onMainUpdate.doOnce { - workspaceFile = folder - eocvSim.visualizer.asyncCompilePipelines() - } - } - - fun init() { - onWorkspaceChange { - fileWatcher.onChange { - eocvSim.pipelineManager.compiledPipelineManager.asyncCompile() - } - } - - val file = File(eocvSim.config.workspacePath) - workspaceFile = if(file.exists()) - file - else - CompiledPipelineManager.DEF_WORKSPACE_FOLDER - - Log.blank() - } - - fun saveCurrentConfig() { - ::workspaceConfig.set(workspaceConfig) - } - - fun reloadConfig(): WorkspaceConfig { - cachedWorkspConfig = null - return workspaceConfig - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.workspace + +import com.github.serivesmejia.eocvsim.EOCVSim +import com.github.serivesmejia.eocvsim.pipeline.compiler.CompiledPipelineManager +import com.github.serivesmejia.eocvsim.util.event.EventHandler +import com.github.serivesmejia.eocvsim.util.io.FileWatcher +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.workspace.config.WorkspaceConfig +import com.github.serivesmejia.eocvsim.workspace.config.WorkspaceConfigLoader +import com.github.serivesmejia.eocvsim.workspace.util.WorkspaceTemplate +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import java.io.File +import java.nio.file.Paths + +@OptIn(DelicateCoroutinesApi::class) +class WorkspaceManager(val eocvSim: EOCVSim) { + + companion object { + private const val TAG = "WorkspaceManager" + } + + val workspaceConfigLoader by lazy { WorkspaceConfigLoader(workspaceFile) } + + var workspaceFile = File(".") + set(value) { + if(value != workspaceFile) { + workspaceConfigLoader.workspaceFile = value + + eocvSim.config.workspacePath = value.absolutePath + eocvSim.configManager.saveToFile() + + field = value + + Log.info(TAG, "Set current workspace to ${value.absolutePath}") + + if(::fileWatcher.isInitialized) + fileWatcher.stop() + + fileWatcher = FileWatcher( + arrayListOf( + sourcesAbsolutePath.toFile(), + resourcesAbsolutePath.toFile() + ), null, "Workspace" + ) + fileWatcher.init() + + onWorkspaceChange.run() + } + + cachedWorkspConfig = workspaceConfigLoader.loadWorkspaceConfig() + + if(cachedWorkspConfig == null) { + cachedWorkspConfig = WorkspaceConfig() + + if(workspaceConfigLoader.workspaceConfigFile.exists()) + Log.warn(TAG, "Recreating workspace config file, old one failed to parse") + else + Log.info(TAG, "Creating workspace config file...") + + workspaceConfigLoader.saveWorkspaceConfig(workspaceConfig) + } else { + Log.info(TAG, "Loaded workspace config successfully") + } + } + + private var cachedWorkspConfig: WorkspaceConfig? = null + + var workspaceConfig: WorkspaceConfig + set(value) { + Log.info(TAG, "Saving workspace config file of ${workspaceFile.absolutePath}") + workspaceConfigLoader.saveWorkspaceConfig(value) + cachedWorkspConfig = value + } + get() { + if(cachedWorkspConfig == null) + ::workspaceFile.set(workspaceFile) + + return cachedWorkspConfig!! + } + + val sourcesRelativePath get() = workspaceConfig.sourcesPath!! + val sourcesAbsolutePath get() = Paths.get(workspaceFile.absolutePath, sourcesRelativePath).normalize()!! + + val resourcesRelativePath get() = workspaceConfig.resourcesPath!! + val resourcesAbsolutePath get() = Paths.get(workspaceFile.absolutePath, resourcesRelativePath).normalize()!! + + val excludedRelativePaths get() = workspaceConfig.excludedPaths + val excludedAbsolutePaths get() = excludedRelativePaths.map { + Paths.get(workspaceFile.absolutePath, it).normalize()!! + } + + val excludedFileExtensions get() = workspaceConfig.excludedFileExtensions + + // TODO: Excluding ignored paths + val sourceFiles get() = SysUtil.filesUnder(sourcesAbsolutePath.toFile()) { file -> + file.name.endsWith(".java") && excludedAbsolutePaths.stream().noneMatch { + file.startsWith(it.toFile().absolutePath) + } + } + + val resourceFiles get() = SysUtil.filesUnder(resourcesAbsolutePath.toFile()) { file -> + file.name.run { + !endsWith(".java") && !endsWith(".class") && this != "eocvsim_workspace.json" + } && excludedAbsolutePaths.stream().noneMatch { + file.startsWith(it.toFile().absolutePath) + } && excludedFileExtensions.stream().noneMatch { + file.name.endsWith(".$it") + } + } + + val onWorkspaceChange = EventHandler("WorkspaceManager-OnChange") + + lateinit var fileWatcher: FileWatcher + private set + + fun stopFileWatcher() { + if(::fileWatcher.isInitialized) { + fileWatcher.stop() + } + } + + fun createWorkspaceWithTemplate(folder: File, template: WorkspaceTemplate): Boolean { + if(!folder.isDirectory) return false + if(!template.extractToIfEmpty(folder)) return false + + workspaceFile = folder + return true + } + + @JvmOverloads fun createWorkspaceWithTemplateAsync( + folder: File, + template: WorkspaceTemplate, + finishCallback: (() -> Unit)? = null + ) = GlobalScope.launch(Dispatchers.IO) { + if(!folder.isDirectory) return@launch + if(!template.extractToIfEmpty(folder)) return@launch + + eocvSim.onMainUpdate.doOnce { + workspaceFile = folder + if(finishCallback != null) finishCallback() + + eocvSim.visualizer.asyncCompilePipelines() + } + } + + fun init() { + onWorkspaceChange { + fileWatcher.onChange { + eocvSim.pipelineManager.compiledPipelineManager.asyncCompile() + } + } + + val file = eocvSim.params.initialWorkspace ?: File(eocvSim.config.workspacePath) + + workspaceFile = if(file.exists()) + file + else + CompiledPipelineManager.DEF_WORKSPACE_FOLDER + + Log.blank() + } + + fun saveCurrentConfig() { + ::workspaceConfig.set(workspaceConfig) + } + + fun reloadConfig(): WorkspaceConfig { + cachedWorkspConfig = null + return workspaceConfig + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfig.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfig.java index 061abb8f..1423855e 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfig.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfig.java @@ -1,36 +1,36 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.workspace.config; - -import java.util.ArrayList; - -public class WorkspaceConfig { - - public String sourcesPath = "."; - public String resourcesPath = "."; - - public ArrayList excludedPaths = new ArrayList<>(); - public ArrayList excludedFileExtensions = new ArrayList<>(); - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.workspace.config; + +import java.util.ArrayList; + +public class WorkspaceConfig { + + public String sourcesPath = "."; + public String resourcesPath = "."; + + public ArrayList excludedPaths = new ArrayList<>(); + public ArrayList excludedFileExtensions = new ArrayList<>(); + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfigLoader.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfigLoader.kt index 3a78de50..05ac32a7 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfigLoader.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/config/WorkspaceConfigLoader.kt @@ -1,32 +1,32 @@ -package com.github.serivesmejia.eocvsim.workspace.config - -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.google.gson.GsonBuilder -import java.io.File - -class WorkspaceConfigLoader(var workspaceFile: File) { - - companion object { - private val gson = GsonBuilder().setPrettyPrinting().create() - } - - val workspaceConfigFile get() = File(workspaceFile, File.separator + "eocvsim_workspace.json") - - fun loadWorkspaceConfig(): WorkspaceConfig? { - if(!workspaceConfigFile.exists()) return null - - val configStr = SysUtil.loadFileStr(workspaceConfigFile) - - return try { - gson.fromJson(configStr, WorkspaceConfig::class.java) - } catch(e: Exception) { - null - } - } - - fun saveWorkspaceConfig(config: WorkspaceConfig) { - val configStr = gson.toJson(config) - SysUtil.saveFileStr(workspaceConfigFile, configStr) - } - +package com.github.serivesmejia.eocvsim.workspace.config + +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.google.gson.GsonBuilder +import java.io.File + +class WorkspaceConfigLoader(var workspaceFile: File) { + + companion object { + private val gson = GsonBuilder().setPrettyPrinting().create() + } + + val workspaceConfigFile get() = File(workspaceFile, File.separator + "eocvsim_workspace.json") + + fun loadWorkspaceConfig(): WorkspaceConfig? { + if(!workspaceConfigFile.exists()) return null + + val configStr = SysUtil.loadFileStr(workspaceConfigFile) + + return try { + gson.fromJson(configStr, WorkspaceConfig::class.java) + } catch(e: Exception) { + null + } + } + + fun saveWorkspaceConfig(config: WorkspaceConfig) { + val configStr = gson.toJson(config) + SysUtil.saveFileStr(workspaceConfigFile, configStr) + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/VSCodeLauncher.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/VSCodeLauncher.kt index 9ff44061..d184f9c1 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/VSCodeLauncher.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/VSCodeLauncher.kt @@ -1,53 +1,55 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.workspace.util - -import com.github.serivesmejia.eocvsim.util.Log -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch - -import com.github.serivesmejia.eocvsim.util.SysUtil -import java.io.File - -object VSCodeLauncher { - - private val TAG = "VSCodeLauncher" - - fun launch(workspace: File) { - Log.info(TAG, "Opening VS Code...") - - val result = SysUtil.runShellCommand("code \"${workspace.absolutePath}\"") - - if(result.output.isNotEmpty()) Log.info(TAG, result.output) - - if(result.exitCode == 0) - Log.info(TAG, "VS Code opened") - else - Log.info(TAG, "VS Code failed to open") - } - - fun asyncLaunch(workspace: File) = GlobalScope.launch(Dispatchers.IO) { launch(workspace) } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.workspace.util + +import com.github.serivesmejia.eocvsim.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +import com.github.serivesmejia.eocvsim.util.SysUtil +import kotlinx.coroutines.DelicateCoroutinesApi +import java.io.File + +object VSCodeLauncher { + + private val TAG = "VSCodeLauncher" + + fun launch(workspace: File) { + Log.info(TAG, "Opening VS Code...") + + val result = SysUtil.runShellCommand("code \"${workspace.absolutePath}\"") + + if(result.output.isNotEmpty()) Log.info(TAG, result.output) + + if(result.exitCode == 0) + Log.info(TAG, "VS Code opened") + else + Log.info(TAG, "VS Code failed to open") + } + + @OptIn(DelicateCoroutinesApi::class) + fun asyncLaunch(workspace: File) = GlobalScope.launch(Dispatchers.IO) { launch(workspace) } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/WorkspaceTemplate.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/WorkspaceTemplate.kt index a0f60d83..d8fe3cb6 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/WorkspaceTemplate.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/WorkspaceTemplate.kt @@ -1,40 +1,40 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.workspace.util - -import java.io.File - -abstract class WorkspaceTemplate { - - fun extractToIfEmpty(folder: File): Boolean { - if(folder.isDirectory && folder.listFiles()!!.isEmpty()) { - return extractTo(folder) - } - - return false - } - - abstract fun extractTo(folder: File): Boolean - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.workspace.util + +import java.io.File + +abstract class WorkspaceTemplate { + + fun extractToIfEmpty(folder: File): Boolean { + if(folder.isDirectory && folder.listFiles()!!.isEmpty()) { + return extractTo(folder) + } + + return false + } + + abstract fun extractTo(folder: File): Boolean + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt index dd7de8ad..9626d7ca 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt @@ -1,60 +1,60 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.workspace.util.template - -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.workspace.util.VSCodeLauncher -import com.github.serivesmejia.eocvsim.workspace.util.WorkspaceTemplate -import net.lingala.zip4j.ZipFile -import java.io.File -import java.io.IOException - -object DefaultWorkspaceTemplate : WorkspaceTemplate() { - - private val TAG = "DefaultWorkspaceTemplate" - - val templateZipResource = javaClass.getResourceAsStream("/templates/default_workspace.zip") - - override fun extractTo(folder: File): Boolean { - if(!folder.isDirectory) return false - - val templateZipFile = SysUtil.copyFileIsTemp( - templateZipResource, "default_workspace.zip", false - ).file - - return try { - Log.info(TAG, "Extracting template to ${folder.absolutePath}") - - ZipFile(templateZipFile).extractAll(folder.absolutePath) - - Log.info(TAG, "Successfully extracted template") - true - } catch(ex: IOException) { - Log.warn(TAG, "Failed to extract workspace template to ${folder.absolutePath}", ex) - false - } - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.workspace.util.template + +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.workspace.util.VSCodeLauncher +import com.github.serivesmejia.eocvsim.workspace.util.WorkspaceTemplate +import net.lingala.zip4j.ZipFile +import java.io.File +import java.io.IOException + +object DefaultWorkspaceTemplate : WorkspaceTemplate() { + + private val TAG = "DefaultWorkspaceTemplate" + + val templateZipResource = javaClass.getResourceAsStream("/templates/default_workspace.zip") + + override fun extractTo(folder: File): Boolean { + if(!folder.isDirectory) return false + + val templateZipFile = SysUtil.copyFileIsTemp( + templateZipResource, "default_workspace.zip", false + ).file + + return try { + Log.info(TAG, "Extracting template to ${folder.absolutePath}") + + ZipFile(templateZipFile).extractAll(folder.absolutePath) + + Log.info(TAG, "Successfully extracted template") + true + } catch(ex: IOException) { + Log.warn(TAG, "Failed to extract workspace template to ${folder.absolutePath}", ex) + false + } + } + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt index 72c16827..a049e1f8 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt @@ -1,76 +1,78 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package com.github.serivesmejia.eocvsim.workspace.util.template - -import com.github.serivesmejia.eocvsim.util.Log -import com.github.serivesmejia.eocvsim.Build -import com.github.serivesmejia.eocvsim.util.SysUtil -import com.github.serivesmejia.eocvsim.workspace.util.VSCodeLauncher -import com.github.serivesmejia.eocvsim.workspace.util.WorkspaceTemplate -import com.github.serivesmejia.eocvsim.EOCVSim -import net.lingala.zip4j.ZipFile -import java.io.File -import java.io.IOException - -object GradleWorkspaceTemplate : WorkspaceTemplate() { - - private val TAG = "GradleWorkspaceTemplate" - - val templateZipResource = javaClass.getResourceAsStream("/templates/gradle_workspace.zip") - - override fun extractTo(folder: File): Boolean { - if(!folder.isDirectory) return false - - val templateZipFile = SysUtil.copyFileIsTemp( - templateZipResource, "gradle_workspace.zip", false - ).file - - return try { - Log.info(TAG, "Extracting template to ${folder.absolutePath}") - - ZipFile(templateZipFile).extractAll(folder.absolutePath) - - Log.info(TAG, "Successfully extracted template") - reformatTemplate(folder) //format necessary template files in the folder - - VSCodeLauncher.asyncLaunch(folder) // launch vs code - true - } catch(ex: IOException) { - Log.warn(TAG, "Failed to extract workspace template to ${folder.absolutePath}", ex) - false - } - } - - private fun reformatTemplate(folder: File) { - val settingsGradleFile = File(folder, File.separator + "settings.gradle") - val buildGradleFile = File(folder, File.separator + "build.gradle") - - //replace the root project name variable in the file to the root folder name - SysUtil.replaceStrInFile(settingsGradleFile, "\$workspace_name", folder.name) - - //replace the version of the eocvsim dependency in build.gradle to the current one - SysUtil.replaceStrInFile(buildGradleFile, "\$version", Build.standardVersionString) - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package com.github.serivesmejia.eocvsim.workspace.util.template + +import com.github.serivesmejia.eocvsim.Build +import com.github.serivesmejia.eocvsim.util.Log +import com.github.serivesmejia.eocvsim.util.SysUtil +import com.github.serivesmejia.eocvsim.workspace.util.WorkspaceTemplate +import net.lingala.zip4j.ZipFile +import java.io.File +import java.io.IOException + +object GradleWorkspaceTemplate : WorkspaceTemplate() { + + private val TAG = "GradleWorkspaceTemplate" + + val templateZipResource = javaClass.getResourceAsStream("/templates/gradle_workspace.zip") + + override fun extractTo(folder: File): Boolean { + if(!folder.isDirectory) return false + + val templateZipFile = SysUtil.copyFileIsTemp( + templateZipResource, "gradle_workspace.zip", false + ).file + + return try { + Log.info(TAG, "Extracting template to ${folder.absolutePath}") + + ZipFile(templateZipFile).extractAll(folder.absolutePath) + + Log.info(TAG, "Successfully extracted template") + reformatTemplate(folder) //format necessary template files in the folder + true + } catch(ex: IOException) { + Log.warn(TAG, "Failed to extract workspace template to ${folder.absolutePath}", ex) + false + } + } + + private fun reformatTemplate(folder: File) { + val settingsGradleFile = File(folder, File.separator + "settings.gradle") + val buildGradleFile = File(folder, File.separator + "build.gradle") + + //replace the root project name variable in the file to the root folder name + SysUtil.replaceStrInFile(settingsGradleFile, "\$workspace_name", folder.name) + + // replace the versions of the eocvsim dependencies in build.gradle to the current ones + // based on the autogenerated Build.java file + val fileContents = SysUtil.loadFileStr(buildGradleFile) + .replace("\$eocvsim_version", Build.standardVersionString) + .replace("\$opencv_version", Build.opencvVersion) + .replace("\$apriltag_version", Build.apriltagPluginVersion) + + SysUtil.saveFileStr(buildGradleFile, fileContents) + } + +} diff --git a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/ElapsedTime.java b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/ElapsedTime.java index bd216dcd..a508fd5c 100644 --- a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/ElapsedTime.java +++ b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/ElapsedTime.java @@ -1,266 +1,266 @@ -/* Copyright (c) 2014, 2015 Qualcomm Technologies Inc -All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. -Neither the name of Qualcomm Technologies Inc nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package com.qualcomm.robotcore.util; - -import com.github.serivesmejia.eocvsim.util.Log; - -import java.util.concurrent.TimeUnit; - -/** - * The {@link ElapsedTime} class provides a simple handy timer to measure elapsed time intervals. - * The timer does not provide events or callbacks, as some other timers do. Rather, at an application- - * determined juncture, one can {@link #reset()} the timer. Thereafter, one can query the interval - * of wall-clock time that has subsequently elapsed by calling the {@link #time()}, {@link #seconds()}, - * or {@link #milliseconds()} methods. The timer has nanosecond internal accuracy. The precision - * reported by the {@link #time()} method is either seconds or milliseconds, depending on how the - * timer is initially constructed. - *

- * This class is thread-safe. - */ -@SuppressWarnings("WeakerAccess") -public class ElapsedTime { - - //------------------------------------------------------------------------------------------------ - // Types and constants - //------------------------------------------------------------------------------------------------ - - /** - * the number of nanoseconds in a second - */ - public static final long SECOND_IN_NANO = 1000000000; - /** - * the number of nanoseconds in a millisecond - */ - public static final long MILLIS_IN_NANO = 1000000; - protected final double resolution; - - //------------------------------------------------------------------------------------------------ - // State - //------------------------------------------------------------------------------------------------ - protected volatile long nsStartTime; - /** - * Creates a timer with resolution {@link com.qualcomm.robotcore.util.ElapsedTime.Resolution#SECONDS Resolution.Seconds} - * that is initialized with the now-current time. - * - * @see #ElapsedTime(long) - * @see #ElapsedTime(Resolution) - */ - public ElapsedTime() { - reset(); - this.resolution = SECOND_IN_NANO; - } - - //------------------------------------------------------------------------------------------------ - // Construction - //------------------------------------------------------------------------------------------------ - - /** - * Creates a timer with resolution {@link com.qualcomm.robotcore.util.ElapsedTime.Resolution#SECONDS Resolution.Seconds}. - * The timer is initialized with the provided start time. Zero is often a useful value to provide - * here: in common usage such timers will often be processed by application logic virtually immediately. - * - * @param startTime the initial value of the timer - * @see #ElapsedTime() - */ - public ElapsedTime(long startTime) { - this.nsStartTime = startTime; - this.resolution = SECOND_IN_NANO; - } - - /** - * Creates a timer with a resolution of seconds or milliseconds. The resolution - * affects the units in which the {@link #time()} method reports. The timer is initialized - * with the current time. - * - * @param resolution the resolution of the new timer - * @see #ElapsedTime() - */ - public ElapsedTime(Resolution resolution) { - reset(); - switch (resolution) { - case SECONDS: - default: - this.resolution = SECOND_IN_NANO; - break; - case MILLISECONDS: - this.resolution = MILLIS_IN_NANO; - break; - } - } - - protected long nsNow() { - return System.nanoTime(); - } - - //------------------------------------------------------------------------------------------------ - // Operations - //------------------------------------------------------------------------------------------------ - - /** - * Returns the current time on the clock used by the timer - * - * @param unit the time unit in which the current time should be returned - * @return the current time on the clock used by the timer - */ - public long now(TimeUnit unit) { - return unit.convert(nsNow(), TimeUnit.NANOSECONDS); - } - - /** - * Resets the internal state of the timer to reflect the current time. Instantaneously following - * this reset, {@link #time()} will report as zero. - * - * @see #time() - */ - public void reset() { - nsStartTime = nsNow(); - } - - /** - * Returns, in resolution-dependent units, the time at which this timer was last reset. - * - * @return the reset time of the timer - */ - public double startTime() { - return nsStartTime / resolution; - } - - /** - * Returns the time at which the timer was last reset, in units of nanoseconds - * - * @return the time at which the timer was last reset, in units of nanoseconds - */ - public long startTimeNanoseconds() { - return this.nsStartTime; - } - - /** - * Returns the duration that has elapsed since the last reset of this timer. - * Units used are either seconds or milliseconds, depending on the resolution with - * which the timer was instantiated. - * - * @return time duration since last timer reset - * @see #ElapsedTime() - * @see #ElapsedTime(Resolution) - * @see #seconds() - * @see #milliseconds() - */ - public double time() { - return (nsNow() - nsStartTime) / resolution; - } - - /** - * Returns the duration that has elapsed since the last reset of this timer - * as an integer in the units requested. - * - * @param unit the units in which to return the answer - * @return time duration since last timer reset - */ - public long time(TimeUnit unit) { - return unit.convert(nanoseconds(), TimeUnit.NANOSECONDS); - } - - /** - * Returns the duration that has elapsed since the last reset of this timer in seconds - * - * @return time duration since last timer reset - * @see #time() - */ - public double seconds() { - return nanoseconds() / ((double) (SECOND_IN_NANO)); - } - - /** - * Returns the duration that has elapsed since the last reset of this timer in milliseconds - * - * @return time duration since last timer reset - * @see #time() - */ - public double milliseconds() { - return seconds() * 1000; - } - - /** - * Returns the duration that has elapsed since the last reset of this timer in nanoseconds - * - * @return time duration since last timer reset - * @see #time() - */ - public long nanoseconds() { - return (nsNow() - nsStartTime); - } - - /** - * Returns the resolution with which the timer was instantiated. - * - * @return the resolution of the timer - */ - public Resolution getResolution() { - if (this.resolution == MILLIS_IN_NANO) - return Resolution.MILLISECONDS; - else - return Resolution.SECONDS; - } - - private String resolutionStr() { - if (resolution == SECOND_IN_NANO) { - return "seconds"; - } else if (resolution == MILLIS_IN_NANO) { - return "milliseconds"; - } else { - return "unknown units"; - } - } - - //------------------------------------------------------------------------------------------------ - // Utility - //------------------------------------------------------------------------------------------------ - - /** - * Log a message stating how long the timer has been running - */ - public void log(String label) { - Log.info("ElapsedTime", String.format("TIMER: %20s - %1.3f %s", label, time(), resolutionStr())); - } - - /** - * Returns a string indicating the current elapsed time of the timer. - */ - @Override - public String toString() { - return String.format("%1.4f %s", time(), resolutionStr()); - } - - /** - * An indicator of the resolution of a timer. - * - * @see ElapsedTime#ElapsedTime(Resolution) - */ - public enum Resolution { - SECONDS, - MILLISECONDS - } +/* Copyright (c) 2014, 2015 Qualcomm Technologies Inc +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. +Neither the name of Qualcomm Technologies Inc nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.qualcomm.robotcore.util; + +import com.github.serivesmejia.eocvsim.util.Log; + +import java.util.concurrent.TimeUnit; + +/** + * The {@link ElapsedTime} class provides a simple handy timer to measure elapsed time intervals. + * The timer does not provide events or callbacks, as some other timers do. Rather, at an application- + * determined juncture, one can {@link #reset()} the timer. Thereafter, one can query the interval + * of wall-clock time that has subsequently elapsed by calling the {@link #time()}, {@link #seconds()}, + * or {@link #milliseconds()} methods. The timer has nanosecond internal accuracy. The precision + * reported by the {@link #time()} method is either seconds or milliseconds, depending on how the + * timer is initially constructed. + *

+ * This class is thread-safe. + */ +@SuppressWarnings("WeakerAccess") +public class ElapsedTime { + + //------------------------------------------------------------------------------------------------ + // Types and constants + //------------------------------------------------------------------------------------------------ + + /** + * the number of nanoseconds in a second + */ + public static final long SECOND_IN_NANO = 1000000000; + /** + * the number of nanoseconds in a millisecond + */ + public static final long MILLIS_IN_NANO = 1000000; + protected final double resolution; + + //------------------------------------------------------------------------------------------------ + // State + //------------------------------------------------------------------------------------------------ + protected volatile long nsStartTime; + /** + * Creates a timer with resolution {@link com.qualcomm.robotcore.util.ElapsedTime.Resolution#SECONDS Resolution.Seconds} + * that is initialized with the now-current time. + * + * @see #ElapsedTime(long) + * @see #ElapsedTime(Resolution) + */ + public ElapsedTime() { + reset(); + this.resolution = SECOND_IN_NANO; + } + + //------------------------------------------------------------------------------------------------ + // Construction + //------------------------------------------------------------------------------------------------ + + /** + * Creates a timer with resolution {@link com.qualcomm.robotcore.util.ElapsedTime.Resolution#SECONDS Resolution.Seconds}. + * The timer is initialized with the provided start time. Zero is often a useful value to provide + * here: in common usage such timers will often be processed by application logic virtually immediately. + * + * @param startTime the initial value of the timer + * @see #ElapsedTime() + */ + public ElapsedTime(long startTime) { + this.nsStartTime = startTime; + this.resolution = SECOND_IN_NANO; + } + + /** + * Creates a timer with a resolution of seconds or milliseconds. The resolution + * affects the units in which the {@link #time()} method reports. The timer is initialized + * with the current time. + * + * @param resolution the resolution of the new timer + * @see #ElapsedTime() + */ + public ElapsedTime(Resolution resolution) { + reset(); + switch (resolution) { + case SECONDS: + default: + this.resolution = SECOND_IN_NANO; + break; + case MILLISECONDS: + this.resolution = MILLIS_IN_NANO; + break; + } + } + + protected long nsNow() { + return System.nanoTime(); + } + + //------------------------------------------------------------------------------------------------ + // Operations + //------------------------------------------------------------------------------------------------ + + /** + * Returns the current time on the clock used by the timer + * + * @param unit the time unit in which the current time should be returned + * @return the current time on the clock used by the timer + */ + public long now(TimeUnit unit) { + return unit.convert(nsNow(), TimeUnit.NANOSECONDS); + } + + /** + * Resets the internal state of the timer to reflect the current time. Instantaneously following + * this reset, {@link #time()} will report as zero. + * + * @see #time() + */ + public void reset() { + nsStartTime = nsNow(); + } + + /** + * Returns, in resolution-dependent units, the time at which this timer was last reset. + * + * @return the reset time of the timer + */ + public double startTime() { + return nsStartTime / resolution; + } + + /** + * Returns the time at which the timer was last reset, in units of nanoseconds + * + * @return the time at which the timer was last reset, in units of nanoseconds + */ + public long startTimeNanoseconds() { + return this.nsStartTime; + } + + /** + * Returns the duration that has elapsed since the last reset of this timer. + * Units used are either seconds or milliseconds, depending on the resolution with + * which the timer was instantiated. + * + * @return time duration since last timer reset + * @see #ElapsedTime() + * @see #ElapsedTime(Resolution) + * @see #seconds() + * @see #milliseconds() + */ + public double time() { + return (nsNow() - nsStartTime) / resolution; + } + + /** + * Returns the duration that has elapsed since the last reset of this timer + * as an integer in the units requested. + * + * @param unit the units in which to return the answer + * @return time duration since last timer reset + */ + public long time(TimeUnit unit) { + return unit.convert(nanoseconds(), TimeUnit.NANOSECONDS); + } + + /** + * Returns the duration that has elapsed since the last reset of this timer in seconds + * + * @return time duration since last timer reset + * @see #time() + */ + public double seconds() { + return nanoseconds() / ((double) (SECOND_IN_NANO)); + } + + /** + * Returns the duration that has elapsed since the last reset of this timer in milliseconds + * + * @return time duration since last timer reset + * @see #time() + */ + public double milliseconds() { + return seconds() * 1000; + } + + /** + * Returns the duration that has elapsed since the last reset of this timer in nanoseconds + * + * @return time duration since last timer reset + * @see #time() + */ + public long nanoseconds() { + return (nsNow() - nsStartTime); + } + + /** + * Returns the resolution with which the timer was instantiated. + * + * @return the resolution of the timer + */ + public Resolution getResolution() { + if (this.resolution == MILLIS_IN_NANO) + return Resolution.MILLISECONDS; + else + return Resolution.SECONDS; + } + + private String resolutionStr() { + if (resolution == SECOND_IN_NANO) { + return "seconds"; + } else if (resolution == MILLIS_IN_NANO) { + return "milliseconds"; + } else { + return "unknown units"; + } + } + + //------------------------------------------------------------------------------------------------ + // Utility + //------------------------------------------------------------------------------------------------ + + /** + * Log a message stating how long the timer has been running + */ + public void log(String label) { + Log.info("ElapsedTime", String.format("TIMER: %20s - %1.3f %s", label, time(), resolutionStr())); + } + + /** + * Returns a string indicating the current elapsed time of the timer. + */ + @Override + public String toString() { + return String.format("%1.4f %s", time(), resolutionStr()); + } + + /** + * An indicator of the resolution of a timer. + * + * @see ElapsedTime#ElapsedTime(Resolution) + */ + public enum Resolution { + SECONDS, + MILLISECONDS + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/MovingStatistics.java b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/MovingStatistics.java index dc730bbd..5b2ec6f6 100644 --- a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/MovingStatistics.java +++ b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/MovingStatistics.java @@ -1,125 +1,125 @@ -/* -Copyright (c) 2016 Robert Atkinson -All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. -Neither the name of Robert Atkinson nor the names of his contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package com.qualcomm.robotcore.util; - -import java.util.LinkedList; -import java.util.Queue; - -/** - * MovingStatistics keeps statistics on the most recent samples in a data set, automatically - * removing old samples as the size of the data exceeds a fixed capacity. This class is *not* - * thread-safe. - */ -public class MovingStatistics -{ - //---------------------------------------------------------------------------------------------- - // State - //---------------------------------------------------------------------------------------------- - - final Statistics statistics; - final int capacity; - final Queue samples; - - //---------------------------------------------------------------------------------------------- - // Construction - //---------------------------------------------------------------------------------------------- - - public MovingStatistics(int capacity) - { - if (capacity <= 0) throw new IllegalArgumentException("MovingStatistics capacity must be positive"); - this.statistics = new Statistics(); - this.capacity = capacity; - this.samples = new LinkedList(); - } - - //---------------------------------------------------------------------------------------------- - // Accessing - //---------------------------------------------------------------------------------------------- - - /** - * Returns the current number of samples - * @return the number of samples - */ - public int getCount() - { - return this.statistics.getCount(); - } - - /** - * Returns the mean of the current set of samples - * @return the mean of the samples - */ - public double getMean() - { - return this.statistics.getMean(); - } - - /** - * Returns the sample variance of the current set of samples - * @return the variance of the samples - */ - public double getVariance() - { - return this.statistics.getVariance(); - } - - /** - * Returns the sample standard deviation of the current set of samples - * @return the standard deviation of the samples - */ - public double getStandardDeviation() - { - return this.statistics.getStandardDeviation(); - } - - //---------------------------------------------------------------------------------------------- - // Modifying - //---------------------------------------------------------------------------------------------- - - /** - * Resets the statistics to an empty state - */ - public void clear() - { - this.statistics.clear(); - this.samples.clear(); - } - - /** - * Adds a new sample to the statistics, possibly also removing the oldest. - * @param x the sample to add - */ - public void add(double x) - { - this.statistics.add(x); - this.samples.add(x); - if (this.samples.size() > capacity) - { - this.statistics.remove(this.samples.remove()); - } - } +/* +Copyright (c) 2016 Robert Atkinson +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package com.qualcomm.robotcore.util; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * MovingStatistics keeps statistics on the most recent samples in a data set, automatically + * removing old samples as the size of the data exceeds a fixed capacity. This class is *not* + * thread-safe. + */ +public class MovingStatistics +{ + //---------------------------------------------------------------------------------------------- + // State + //---------------------------------------------------------------------------------------------- + + final Statistics statistics; + final int capacity; + final Queue samples; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + public MovingStatistics(int capacity) + { + if (capacity <= 0) throw new IllegalArgumentException("MovingStatistics capacity must be positive"); + this.statistics = new Statistics(); + this.capacity = capacity; + this.samples = new LinkedList(); + } + + //---------------------------------------------------------------------------------------------- + // Accessing + //---------------------------------------------------------------------------------------------- + + /** + * Returns the current number of samples + * @return the number of samples + */ + public int getCount() + { + return this.statistics.getCount(); + } + + /** + * Returns the mean of the current set of samples + * @return the mean of the samples + */ + public double getMean() + { + return this.statistics.getMean(); + } + + /** + * Returns the sample variance of the current set of samples + * @return the variance of the samples + */ + public double getVariance() + { + return this.statistics.getVariance(); + } + + /** + * Returns the sample standard deviation of the current set of samples + * @return the standard deviation of the samples + */ + public double getStandardDeviation() + { + return this.statistics.getStandardDeviation(); + } + + //---------------------------------------------------------------------------------------------- + // Modifying + //---------------------------------------------------------------------------------------------- + + /** + * Resets the statistics to an empty state + */ + public void clear() + { + this.statistics.clear(); + this.samples.clear(); + } + + /** + * Adds a new sample to the statistics, possibly also removing the oldest. + * @param x the sample to add + */ + public void add(double x) + { + this.statistics.add(x); + this.samples.add(x); + if (this.samples.size() > capacity) + { + this.statistics.remove(this.samples.remove()); + } + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/Range.java b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/Range.java index 378251a1..16f5eb28 100644 --- a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/Range.java +++ b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/Range.java @@ -1,158 +1,158 @@ - -/* - * Copyright (c) 2014, 2015 Qualcomm Technologies Inc - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * (subject to the limitations in the disclaimer below) provided that the following conditions are - * met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following disclaimer in the documentation and/or other materials provided with the - * distribution. - * - * Neither the name of Qualcomm Technologies Inc nor the names of its contributors may be used to - * endorse or promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS - * SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.qualcomm.robotcore.util; - -/** - * Utility class for performing range operations - */ -public class Range { - - /* - * This class contains only static utility methods - */ - private Range() {} - - //------------------------------------------------------------------------------------------------ - // Scaling - //------------------------------------------------------------------------------------------------ - - /** - * Scale a number in the range of x1 to x2, to the range of y1 to y2 - * @param n number to scale - * @param x1 lower bound range of n - * @param x2 upper bound range of n - * @param y1 lower bound of scale - * @param y2 upper bound of scale - * @return a double scaled to a value between y1 and y2, inclusive - */ - public static double scale(double n, double x1, double x2, double y1, double y2) { - double a = (y1-y2)/(x1-x2); - double b = y1 - x1*(y1-y2)/(x1-x2); - return a*n+b; - } - - //------------------------------------------------------------------------------------------------ - // Clipping - //------------------------------------------------------------------------------------------------ - - /** - * clip 'number' if 'number' is less than 'min' or greater than 'max' - * @param number number to test - * @param min minimum value allowed - * @param max maximum value allowed - */ - public static double clip(double number, double min, double max) { - if (number < min) return min; - if (number > max) return max; - return number; - } - - /** - * clip 'number' if 'number' is less than 'min' or greater than 'max' - * @param number number to test - * @param min minimum value allowed - * @param max maximum value allowed - */ - public static float clip(float number, float min, float max) { - if (number < min) return min; - if (number > max) return max; - return number; - } - - /** - * clip 'number' if 'number' is less than 'min' or greater than 'max' - * @param number number to test - * @param min minimum value allowed - * @param max maximum value allowed - */ - public static int clip(int number, int min, int max) { - if (number < min) return min; - if (number > max) return max; - return number; - } - - /** - * clip 'number' if 'number' is less than 'min' or greater than 'max' - * @param number number to test - * @param min minimum value allowed - * @param max maximum value allowed - */ - public static short clip(short number, short min, short max) { - if (number < min) return min; - if (number > max) return max; - return number; - } - - /** - * clip 'number' if 'number' is less than 'min' or greater than 'max' - * @param number number to test - * @param min minimum value allowed - * @param max maximum value allowed - */ - public static byte clip(byte number, byte min, byte max) { - if (number < min) return min; - if (number > max) return max; - return number; - } - - //------------------------------------------------------------------------------------------------ - // Validation - //------------------------------------------------------------------------------------------------ - - /** - * Throw an IllegalArgumentException if 'number' is less than 'min' or greater than 'max' - * @param number number to test - * @param min minimum value allowed - * @param max maximum value allowed - * @throws IllegalArgumentException if number is outside of range - */ - public static void throwIfRangeIsInvalid(double number, double min, double max) throws IllegalArgumentException { - if (number < min || number > max) { - throw new IllegalArgumentException( - String.format("number %f is invalid; valid ranges are %f..%f", number, min, max)); - } - } - - /** - * Throw an IllegalArgumentException if 'number' is less than 'min' or greater than 'max' - * @param number number to test - * @param min minimum value allowed - * @param max maximum value allowed - * @throws IllegalArgumentException if number is outside of range - */ - public static void throwIfRangeIsInvalid(int number, int min, int max) throws IllegalArgumentException { - if (number < min || number > max) { - throw new IllegalArgumentException( - String.format("number %d is invalid; valid ranges are %d..%d", number, min, max)); - } - } + +/* + * Copyright (c) 2014, 2015 Qualcomm Technologies Inc + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * (subject to the limitations in the disclaimer below) provided that the following conditions are + * met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Qualcomm Technologies Inc nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS + * SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.qualcomm.robotcore.util; + +/** + * Utility class for performing range operations + */ +public class Range { + + /* + * This class contains only static utility methods + */ + private Range() {} + + //------------------------------------------------------------------------------------------------ + // Scaling + //------------------------------------------------------------------------------------------------ + + /** + * Scale a number in the range of x1 to x2, to the range of y1 to y2 + * @param n number to scale + * @param x1 lower bound range of n + * @param x2 upper bound range of n + * @param y1 lower bound of scale + * @param y2 upper bound of scale + * @return a double scaled to a value between y1 and y2, inclusive + */ + public static double scale(double n, double x1, double x2, double y1, double y2) { + double a = (y1-y2)/(x1-x2); + double b = y1 - x1*(y1-y2)/(x1-x2); + return a*n+b; + } + + //------------------------------------------------------------------------------------------------ + // Clipping + //------------------------------------------------------------------------------------------------ + + /** + * clip 'number' if 'number' is less than 'min' or greater than 'max' + * @param number number to test + * @param min minimum value allowed + * @param max maximum value allowed + */ + public static double clip(double number, double min, double max) { + if (number < min) return min; + if (number > max) return max; + return number; + } + + /** + * clip 'number' if 'number' is less than 'min' or greater than 'max' + * @param number number to test + * @param min minimum value allowed + * @param max maximum value allowed + */ + public static float clip(float number, float min, float max) { + if (number < min) return min; + if (number > max) return max; + return number; + } + + /** + * clip 'number' if 'number' is less than 'min' or greater than 'max' + * @param number number to test + * @param min minimum value allowed + * @param max maximum value allowed + */ + public static int clip(int number, int min, int max) { + if (number < min) return min; + if (number > max) return max; + return number; + } + + /** + * clip 'number' if 'number' is less than 'min' or greater than 'max' + * @param number number to test + * @param min minimum value allowed + * @param max maximum value allowed + */ + public static short clip(short number, short min, short max) { + if (number < min) return min; + if (number > max) return max; + return number; + } + + /** + * clip 'number' if 'number' is less than 'min' or greater than 'max' + * @param number number to test + * @param min minimum value allowed + * @param max maximum value allowed + */ + public static byte clip(byte number, byte min, byte max) { + if (number < min) return min; + if (number > max) return max; + return number; + } + + //------------------------------------------------------------------------------------------------ + // Validation + //------------------------------------------------------------------------------------------------ + + /** + * Throw an IllegalArgumentException if 'number' is less than 'min' or greater than 'max' + * @param number number to test + * @param min minimum value allowed + * @param max maximum value allowed + * @throws IllegalArgumentException if number is outside of range + */ + public static void throwIfRangeIsInvalid(double number, double min, double max) throws IllegalArgumentException { + if (number < min || number > max) { + throw new IllegalArgumentException( + String.format("number %f is invalid; valid ranges are %f..%f", number, min, max)); + } + } + + /** + * Throw an IllegalArgumentException if 'number' is less than 'min' or greater than 'max' + * @param number number to test + * @param min minimum value allowed + * @param max maximum value allowed + * @throws IllegalArgumentException if number is outside of range + */ + public static void throwIfRangeIsInvalid(int number, int min, int max) throws IllegalArgumentException { + if (number < min || number > max) { + throw new IllegalArgumentException( + String.format("number %d is invalid; valid ranges are %d..%d", number, min, max)); + } + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/Statistics.java b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/Statistics.java index 0dc0e20c..520b64f6 100644 --- a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/Statistics.java +++ b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/util/Statistics.java @@ -1,140 +1,140 @@ -/* -Copyright (c) 2016 Robert Atkinson -All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. -Neither the name of Robert Atkinson nor the names of his contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package com.qualcomm.robotcore.util; - -/** - * This handy utility class supports the ongoing calculation of mean and variance of a - * series of numbers. This class is *not* thread-safe. - * - * @see Wikipedia - */ -public class Statistics -{ - //---------------------------------------------------------------------------------------------- - // State - //---------------------------------------------------------------------------------------------- - - int n; - double mean; - double m2; - - //---------------------------------------------------------------------------------------------- - // Construction - //---------------------------------------------------------------------------------------------- - - public Statistics() - { - this.clear(); - } - - //---------------------------------------------------------------------------------------------- - // Accessing - //---------------------------------------------------------------------------------------------- - - /** - * Returns the current number of samples - * @return the number of samples - */ - public int getCount() - { - return n; - } - - /** - * Returns the mean of the current set of samples - * @return the mean of the samples - */ - public double getMean() - { - return mean; - } - - /** - * Returns the sample variance of the current set of samples - * @return the variance of the samples - */ - public double getVariance() - { - return m2 / (n - 1); - } - - /** - * Returns the sample standard deviation of the current set of samples - * @return the standard deviation of the samples - */ - public double getStandardDeviation() - { - return Math.sqrt(this.getVariance()); - } - - //---------------------------------------------------------------------------------------------- - // Modifying - //---------------------------------------------------------------------------------------------- - - /** - * Resets the statistics to an empty state - */ - public void clear() - { - n = 0; - mean = 0; - m2 = 0; - } - - /** - * Adds a new sample to the statistics - * @param x the sample to add - */ - public void add(double x) - { - n = n + 1; - double delta = x - mean; - mean = mean + delta / n; - m2 = m2 + delta*(x - mean); - } - - /** - * Removes a sample from the statistics - * @param x the sample to remove - */ - public void remove(double x) - { - int nPrev = n-1; - if (nPrev==0) - { - clear(); - } - else - { - double delta = x - mean; - double deltaPrev = n * delta / nPrev; - m2 = m2 - deltaPrev * delta; - mean = (mean * n - x) / nPrev; - n = nPrev; - } - } +/* +Copyright (c) 2016 Robert Atkinson +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package com.qualcomm.robotcore.util; + +/** + * This handy utility class supports the ongoing calculation of mean and variance of a + * series of numbers. This class is *not* thread-safe. + * + * @see Wikipedia + */ +public class Statistics +{ + //---------------------------------------------------------------------------------------------- + // State + //---------------------------------------------------------------------------------------------- + + int n; + double mean; + double m2; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + public Statistics() + { + this.clear(); + } + + //---------------------------------------------------------------------------------------------- + // Accessing + //---------------------------------------------------------------------------------------------- + + /** + * Returns the current number of samples + * @return the number of samples + */ + public int getCount() + { + return n; + } + + /** + * Returns the mean of the current set of samples + * @return the mean of the samples + */ + public double getMean() + { + return mean; + } + + /** + * Returns the sample variance of the current set of samples + * @return the variance of the samples + */ + public double getVariance() + { + return m2 / (n - 1); + } + + /** + * Returns the sample standard deviation of the current set of samples + * @return the standard deviation of the samples + */ + public double getStandardDeviation() + { + return Math.sqrt(this.getVariance()); + } + + //---------------------------------------------------------------------------------------------- + // Modifying + //---------------------------------------------------------------------------------------------- + + /** + * Resets the statistics to an empty state + */ + public void clear() + { + n = 0; + mean = 0; + m2 = 0; + } + + /** + * Adds a new sample to the statistics + * @param x the sample to add + */ + public void add(double x) + { + n = n + 1; + double delta = x - mean; + mean = mean + delta / n; + m2 = m2 + delta*(x - mean); + } + + /** + * Removes a sample from the statistics + * @param x the sample to remove + */ + public void remove(double x) + { + int nPrev = n-1; + if (nPrev==0) + { + clear(); + } + else + { + double delta = x - mean; + double deltaPrev = n * delta / nPrev; + m2 = m2 - deltaPrev * delta; + mean = (mean * n - x) / nPrev; + n = nPrev; + } + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/Func.java b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/Func.java index 78847cef..3737d2fe 100644 --- a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/Func.java +++ b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/Func.java @@ -1,7 +1,7 @@ -package org.firstinspires.ftc.robotcore.external; - -public interface Func { - - T value(); - -} +package org.firstinspires.ftc.robotcore.external; + +public interface Func { + + T value(); + +} diff --git a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java index 0d2c2559..2efd7d6a 100644 --- a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java +++ b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java @@ -1,266 +1,266 @@ -package org.firstinspires.ftc.robotcore.external; - -import java.util.ArrayList; - -public class Telemetry { - - private final ArrayList telem = new ArrayList<>(); - private ArrayList lastTelem = new ArrayList<>(); - - public Item infoItem = new Item( "", ""); - public Item errItem = new Item("", ""); - - private String captionValueSeparator = " : "; - - private volatile String lastTelemUpdate = ""; - private volatile String beforeTelemUpdate = "mai"; - - private boolean autoClear = true; - - public synchronized Item addData(String caption, String value) { - - Item item = new Item(caption, value); - item.valueSeparator = captionValueSeparator; - - telem.add(item); - - return item; - - } - - public synchronized Item addData(String caption, Func valueProducer) { - Item item = new Item(caption, valueProducer); - item.valueSeparator = captionValueSeparator; - - telem.add(item); - - return item; - } - - public synchronized Item addData(String caption, Object value) { - Item item = new Item(caption, ""); - item.valueSeparator = captionValueSeparator; - - item.setValue(value); - - telem.add(item); - - return item; - } - - public synchronized Item addData(String caption, String value, Object... args) { - Item item = new Item(caption, ""); - item.valueSeparator = captionValueSeparator; - - item.setValue(value, args); - - telem.add(item); - - return item; - } - - public synchronized Item addData(String caption, Func valueProducer, Object... args) { - - Item item = new Item(caption, ""); - item.valueSeparator = captionValueSeparator; - - item.setValue(valueProducer, args); - - telem.add(item); - - return item; - - } - - public synchronized Line addLine() { - return addLine(""); - } - - public synchronized Line addLine(String caption) { - Line line = new Line(caption); - telem.add(line); - return line; - } - - @SuppressWarnings("unchecked") - public synchronized void update() { - lastTelemUpdate = ""; - lastTelem = (ArrayList) telem.clone(); - - evalLastTelem(); - - if(autoClear) clear(); - } - - private synchronized void evalLastTelem() { - StringBuilder inTelemUpdate = new StringBuilder(); - - if (infoItem != null && !infoItem.caption.trim().equals("")) { - inTelemUpdate.append(infoItem.toString()).append("\n"); - } - - if(lastTelem != null) { - int i = 0; - for (ItemOrLine iol : lastTelem) { - if (iol instanceof Item) { - Item item = (Item) iol; - item.valueSeparator = captionValueSeparator; - inTelemUpdate.append(item.toString()); //to avoid volatile issues we write into a stringbuilder - } else if (iol instanceof Line) { - Line line = (Line) iol; - inTelemUpdate.append(line.toString()); //to avoid volatile issues we write into a stringbuilder - } - - if (i < lastTelem.size() - 1) - inTelemUpdate.append("\n"); //append new line if this is not the lastest item - - i++; - } - } - - if(errItem != null && !errItem.caption.trim().equals("")) { - inTelemUpdate.append("\n").append(errItem.toString()); - } - - inTelemUpdate.append("\n"); - - lastTelemUpdate = inTelemUpdate.toString().trim(); //and then we write to the volatile, public one - } - - public synchronized boolean removeItem(Item item) { - if (telem.contains(item)) { - telem.remove(item); - return true; - } - - return false; - } - - public synchronized void clear() { - for (ItemOrLine i : telem.toArray(new ItemOrLine[0])) { - if (i instanceof Item) { - if (!((Item) i).isRetained) telem.remove(i); - } else { - telem.remove(i); - } - } - } - - public synchronized boolean hasChanged() { - boolean hasChanged = !lastTelemUpdate.equals(beforeTelemUpdate); - beforeTelemUpdate = lastTelemUpdate; - - return hasChanged; - } - - public synchronized String getCaptionValueSeparator() { - return captionValueSeparator; - } - - public synchronized void setCaptionValueSeparator(String captionValueSeparator) { - this.captionValueSeparator = captionValueSeparator; - } - - public synchronized void setAutoClear(boolean autoClear) { - this.autoClear = autoClear; - } - - @Override - public String toString() { - evalLastTelem(); - return lastTelemUpdate; - } - - private interface ItemOrLine { - String getCaption(); - - void setCaption(String caption); - } - - public static class Item implements ItemOrLine { - - protected String caption = ""; - - protected Func valueProducer = null; - - protected String valueSeparator = " : "; - - protected boolean isRetained = false; - - public Item(String caption, String value) { - setCaption(caption); - setValue(value); - } - - public Item(String caption, Func valueProducer) { - this.caption = caption; - this.valueProducer = valueProducer; - } - - public synchronized void setValue(String value) { - setValue((Func) () -> value); - } - - public synchronized void setValue(Func func) { - this.valueProducer = func; - } - - public synchronized void setValue(Object value) { - setValue(value.toString()); - } - - public synchronized void setValue(String value, Object... args) { - setValue(String.format(value, args)); - } - - public synchronized void setValue(Func func, Object... args) { - setValue((Func) () -> String.format(func.value().toString(), args)); - } - - public synchronized String getCaption() { - return caption; - } - - public synchronized void setCaption(String caption) { - this.caption = caption; - } - - public synchronized boolean isRetained() { - return isRetained; - } - - public synchronized void setRetained(boolean retained) { - this.isRetained = retained; - } - - @Override - public String toString() { - return caption + " " + valueSeparator + " " + valueProducer.value().toString(); - } - - } - - public static class Line implements ItemOrLine { - - protected String caption; - - public Line(String caption) { - this.caption = caption; - } - - public synchronized String getCaption() { - return caption; - } - - public synchronized void setCaption(String caption) { - this.caption = caption; - } - - @Override - public synchronized String toString() { - return caption; - } - - } - -} +package org.firstinspires.ftc.robotcore.external; + +import java.util.ArrayList; + +public class Telemetry { + + private final ArrayList telem = new ArrayList<>(); + private ArrayList lastTelem = new ArrayList<>(); + + public Item infoItem = new Item( "", ""); + public Item errItem = new Item("", ""); + + private String captionValueSeparator = " : "; + + private volatile String lastTelemUpdate = ""; + private volatile String beforeTelemUpdate = "mai"; + + private boolean autoClear = true; + + public synchronized Item addData(String caption, String value) { + + Item item = new Item(caption, value); + item.valueSeparator = captionValueSeparator; + + telem.add(item); + + return item; + + } + + public synchronized Item addData(String caption, Func valueProducer) { + Item item = new Item(caption, valueProducer); + item.valueSeparator = captionValueSeparator; + + telem.add(item); + + return item; + } + + public synchronized Item addData(String caption, Object value) { + Item item = new Item(caption, ""); + item.valueSeparator = captionValueSeparator; + + item.setValue(value); + + telem.add(item); + + return item; + } + + public synchronized Item addData(String caption, String value, Object... args) { + Item item = new Item(caption, ""); + item.valueSeparator = captionValueSeparator; + + item.setValue(value, args); + + telem.add(item); + + return item; + } + + public synchronized Item addData(String caption, Func valueProducer, Object... args) { + + Item item = new Item(caption, ""); + item.valueSeparator = captionValueSeparator; + + item.setValue(valueProducer, args); + + telem.add(item); + + return item; + + } + + public synchronized Line addLine() { + return addLine(""); + } + + public synchronized Line addLine(String caption) { + Line line = new Line(caption); + telem.add(line); + return line; + } + + @SuppressWarnings("unchecked") + public synchronized void update() { + lastTelemUpdate = ""; + lastTelem = (ArrayList) telem.clone(); + + evalLastTelem(); + + if(autoClear) clear(); + } + + private synchronized void evalLastTelem() { + StringBuilder inTelemUpdate = new StringBuilder(); + + if (infoItem != null && !infoItem.caption.trim().equals("")) { + inTelemUpdate.append(infoItem.toString()).append("\n"); + } + + if(lastTelem != null) { + int i = 0; + for (ItemOrLine iol : lastTelem) { + if (iol instanceof Item) { + Item item = (Item) iol; + item.valueSeparator = captionValueSeparator; + inTelemUpdate.append(item.toString()); //to avoid volatile issues we write into a stringbuilder + } else if (iol instanceof Line) { + Line line = (Line) iol; + inTelemUpdate.append(line.toString()); //to avoid volatile issues we write into a stringbuilder + } + + if (i < lastTelem.size() - 1) + inTelemUpdate.append("\n"); //append new line if this is not the lastest item + + i++; + } + } + + if(errItem != null && !errItem.caption.trim().equals("")) { + inTelemUpdate.append("\n").append(errItem.toString()); + } + + inTelemUpdate.append("\n"); + + lastTelemUpdate = inTelemUpdate.toString().trim(); //and then we write to the volatile, public one + } + + public synchronized boolean removeItem(Item item) { + if (telem.contains(item)) { + telem.remove(item); + return true; + } + + return false; + } + + public synchronized void clear() { + for (ItemOrLine i : telem.toArray(new ItemOrLine[0])) { + if (i instanceof Item) { + if (!((Item) i).isRetained) telem.remove(i); + } else { + telem.remove(i); + } + } + } + + public synchronized boolean hasChanged() { + boolean hasChanged = !lastTelemUpdate.equals(beforeTelemUpdate); + beforeTelemUpdate = lastTelemUpdate; + + return hasChanged; + } + + public synchronized String getCaptionValueSeparator() { + return captionValueSeparator; + } + + public synchronized void setCaptionValueSeparator(String captionValueSeparator) { + this.captionValueSeparator = captionValueSeparator; + } + + public synchronized void setAutoClear(boolean autoClear) { + this.autoClear = autoClear; + } + + @Override + public String toString() { + evalLastTelem(); + return lastTelemUpdate; + } + + private interface ItemOrLine { + String getCaption(); + + void setCaption(String caption); + } + + public static class Item implements ItemOrLine { + + protected String caption = ""; + + protected Func valueProducer = null; + + protected String valueSeparator = " : "; + + protected boolean isRetained = false; + + public Item(String caption, String value) { + setCaption(caption); + setValue(value); + } + + public Item(String caption, Func valueProducer) { + this.caption = caption; + this.valueProducer = valueProducer; + } + + public synchronized void setValue(String value) { + setValue((Func) () -> value); + } + + public synchronized void setValue(Func func) { + this.valueProducer = func; + } + + public synchronized void setValue(Object value) { + setValue(value.toString()); + } + + public synchronized void setValue(String value, Object... args) { + setValue(String.format(value, args)); + } + + public synchronized void setValue(Func func, Object... args) { + setValue((Func) () -> String.format(func.value().toString(), args)); + } + + public synchronized String getCaption() { + return caption; + } + + public synchronized void setCaption(String caption) { + this.caption = caption; + } + + public synchronized boolean isRetained() { + return isRetained; + } + + public synchronized void setRetained(boolean retained) { + this.isRetained = retained; + } + + @Override + public String toString() { + return caption + " " + valueSeparator + " " + valueProducer.value().toString(); + } + + } + + public static class Line implements ItemOrLine { + + protected String caption; + + public Line(String caption) { + this.caption = caption; + } + + public synchronized String getCaption() { + return caption; + } + + public synchronized void setCaption(String caption) { + this.caption = caption; + } + + @Override + public synchronized String toString() { + return caption; + } + + } + +} diff --git a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/function/Consumer.java b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/function/Consumer.java index e3bf77a3..640cf070 100644 --- a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/function/Consumer.java +++ b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/external/function/Consumer.java @@ -1,39 +1,39 @@ -/* -Copyright (c) 2016 Robert Atkinson -All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. -Neither the name of Robert Atkinson nor the names of his contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcore.external.function; - -/** - * Instances of {@link Consumer} are functions that act on an instance of a indicated type - */ -public interface Consumer { - /** - * Performs this operation on the given argument. - * - * @param value the input argument - */ - void accept(T value); +/* +Copyright (c) 2016 Robert Atkinson +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.function; + +/** + * Instances of {@link Consumer} are functions that act on an instance of a indicated type + */ +public interface Consumer { + /** + * Performs this operation on the given argument. + * + * @param value the input argument + */ + void accept(T value); } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/EvictingBlockingQueue.java b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/EvictingBlockingQueue.java index f68bc53a..e64f66a6 100644 --- a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/EvictingBlockingQueue.java +++ b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/collections/EvictingBlockingQueue.java @@ -1,204 +1,204 @@ -/* -Copyright (c) 2016 Robert Atkinson -All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. -Neither the name of Robert Atkinson nor the names of his contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcore.internal.collections; - -import com.qualcomm.robotcore.util.ElapsedTime; -import org.firstinspires.ftc.robotcore.external.function.Consumer; -import org.firstinspires.ftc.robotcore.internal.system.Assert; - -import java.util.AbstractQueue; -import java.util.Collection; -import java.util.Iterator; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; - -/** - * {@link EvictingBlockingQueue} is a {@link BlockingQueue} that evicts old elements - * rather than failing when new data is added to the queue. - */ -@SuppressWarnings("WeakerAccess") -public class EvictingBlockingQueue extends AbstractQueue implements BlockingQueue { - //---------------------------------------------------------------------------------------------- - // State - // - // The central implementation idea is that we must hold theLock to make any additions, and - // must then with lock held ensure capacity by evicting if necessary before doing any addition. - // Removals also take the lock so we don't evict data unncessarily. - //---------------------------------------------------------------------------------------------- - - protected final Object theLock = new Object(); - protected BlockingQueue targetQueue; - protected Consumer evictAction = null; - - //---------------------------------------------------------------------------------------------- - // Construction - //---------------------------------------------------------------------------------------------- - - /** - * Constructs an EvictingBlockingQueue using the target queue as an implementation. The - * target queue must have a capacity of at least one. - * - * @param targetQueue the underlying implementation queue from which we will auto-evict as needed - */ - public EvictingBlockingQueue(BlockingQueue targetQueue) { - this.targetQueue = targetQueue; - } - - public void setEvictAction(Consumer evictAction) { - synchronized (theLock) { - this.evictAction = evictAction; - } - } - - //---------------------------------------------------------------------------------------------- - // AbstractCollection - //---------------------------------------------------------------------------------------------- - - @Override - public Iterator iterator() { - return targetQueue.iterator(); - } - - @Override - public int size() { - return targetQueue.size(); - } - - //---------------------------------------------------------------------------------------------- - // Core: the hard parts - //---------------------------------------------------------------------------------------------- - - @Override - public boolean offer(E e) { - synchronized (theLock) { - if (targetQueue.remainingCapacity() == 0) { - E evicted = targetQueue.poll(); - Assert.assertNotNull(evicted); - if (evictAction != null) { - evictAction.accept(evicted); - } - } - boolean result = targetQueue.offer(e); - Assert.assertTrue(result); - theLock.notifyAll(); // pending polls/takes are worth trying again - return result; - } - } - - @Override - public E take() throws InterruptedException { - synchronized (theLock) { - for (; ; ) { - // Can we get something? Return if we can. - E result = poll(); - if (result != null) - return result; - - // Punt if we've been asked to - if (Thread.currentThread().isInterrupted()) - throw new InterruptedException(); - - // Wait and then try again - theLock.wait(); - } - } - } - - @Override - public E poll(long timeout, TimeUnit unit) throws InterruptedException { - synchronized (theLock) { - final long deadline = System.nanoTime() + unit.toNanos(timeout); - for (; ; ) { - // Can we get something? Return if we can. - E result = poll(); - if (result != null) - return result; - - // Punt if we've been asked to - if (Thread.currentThread().isInterrupted()) - throw new InterruptedException(); - - // How much longer can we wait? - long remaining = deadline - System.nanoTime(); - if (remaining > 0) { - // Wait up to that much and then try again - long ms = remaining / ElapsedTime.MILLIS_IN_NANO; - long ns = remaining - ms * ElapsedTime.MILLIS_IN_NANO; - theLock.wait(ms, (int) ns); - } else - return null; - } - } - } - - //---------------------------------------------------------------------------------------------- - // Remaining parts - //---------------------------------------------------------------------------------------------- - - @Override - public E poll() { - synchronized (theLock) { - return targetQueue.poll(); - } - } - - @Override - public E peek() { - return targetQueue.peek(); - } - - @Override - public void put(E e) throws InterruptedException { - offer(e); - } - - @Override - public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { - // We will never block because we're full, so the timeouts are unnecessary - return offer(e); - } - - @Override - public int remainingCapacity() { - // We *always* have capacity - return Math.max(targetQueue.remainingCapacity(), 1); - } - - @Override - public int drainTo(Collection c) { - synchronized (theLock) { - return targetQueue.drainTo(c); - } - } - - @Override - public int drainTo(Collection c, int maxElements) { - synchronized (theLock) { - return targetQueue.drainTo(c, maxElements); - } - } +/* +Copyright (c) 2016 Robert Atkinson +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.internal.collections; + +import com.qualcomm.robotcore.util.ElapsedTime; +import org.firstinspires.ftc.robotcore.external.function.Consumer; +import org.firstinspires.ftc.robotcore.internal.system.Assert; + +import java.util.AbstractQueue; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * {@link EvictingBlockingQueue} is a {@link BlockingQueue} that evicts old elements + * rather than failing when new data is added to the queue. + */ +@SuppressWarnings("WeakerAccess") +public class EvictingBlockingQueue extends AbstractQueue implements BlockingQueue { + //---------------------------------------------------------------------------------------------- + // State + // + // The central implementation idea is that we must hold theLock to make any additions, and + // must then with lock held ensure capacity by evicting if necessary before doing any addition. + // Removals also take the lock so we don't evict data unncessarily. + //---------------------------------------------------------------------------------------------- + + protected final Object theLock = new Object(); + protected BlockingQueue targetQueue; + protected Consumer evictAction = null; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + /** + * Constructs an EvictingBlockingQueue using the target queue as an implementation. The + * target queue must have a capacity of at least one. + * + * @param targetQueue the underlying implementation queue from which we will auto-evict as needed + */ + public EvictingBlockingQueue(BlockingQueue targetQueue) { + this.targetQueue = targetQueue; + } + + public void setEvictAction(Consumer evictAction) { + synchronized (theLock) { + this.evictAction = evictAction; + } + } + + //---------------------------------------------------------------------------------------------- + // AbstractCollection + //---------------------------------------------------------------------------------------------- + + @Override + public Iterator iterator() { + return targetQueue.iterator(); + } + + @Override + public int size() { + return targetQueue.size(); + } + + //---------------------------------------------------------------------------------------------- + // Core: the hard parts + //---------------------------------------------------------------------------------------------- + + @Override + public boolean offer(E e) { + synchronized (theLock) { + if (targetQueue.remainingCapacity() == 0) { + E evicted = targetQueue.poll(); + Assert.assertNotNull(evicted); + if (evictAction != null) { + evictAction.accept(evicted); + } + } + boolean result = targetQueue.offer(e); + Assert.assertTrue(result); + theLock.notifyAll(); // pending polls/takes are worth trying again + return result; + } + } + + @Override + public E take() throws InterruptedException { + synchronized (theLock) { + for (; ; ) { + // Can we get something? Return if we can. + E result = poll(); + if (result != null) + return result; + + // Punt if we've been asked to + if (Thread.currentThread().isInterrupted()) + throw new InterruptedException(); + + // Wait and then try again + theLock.wait(); + } + } + } + + @Override + public E poll(long timeout, TimeUnit unit) throws InterruptedException { + synchronized (theLock) { + final long deadline = System.nanoTime() + unit.toNanos(timeout); + for (; ; ) { + // Can we get something? Return if we can. + E result = poll(); + if (result != null) + return result; + + // Punt if we've been asked to + if (Thread.currentThread().isInterrupted()) + throw new InterruptedException(); + + // How much longer can we wait? + long remaining = deadline - System.nanoTime(); + if (remaining > 0) { + // Wait up to that much and then try again + long ms = remaining / ElapsedTime.MILLIS_IN_NANO; + long ns = remaining - ms * ElapsedTime.MILLIS_IN_NANO; + theLock.wait(ms, (int) ns); + } else + return null; + } + } + } + + //---------------------------------------------------------------------------------------------- + // Remaining parts + //---------------------------------------------------------------------------------------------- + + @Override + public E poll() { + synchronized (theLock) { + return targetQueue.poll(); + } + } + + @Override + public E peek() { + return targetQueue.peek(); + } + + @Override + public void put(E e) throws InterruptedException { + offer(e); + } + + @Override + public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { + // We will never block because we're full, so the timeouts are unnecessary + return offer(e); + } + + @Override + public int remainingCapacity() { + // We *always* have capacity + return Math.max(targetQueue.remainingCapacity(), 1); + } + + @Override + public int drainTo(Collection c) { + synchronized (theLock) { + return targetQueue.drainTo(c); + } + } + + @Override + public int drainTo(Collection c, int maxElements) { + synchronized (theLock) { + return targetQueue.drainTo(c, maxElements); + } + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Assert.java b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Assert.java index cb5e676b..391749f3 100644 --- a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Assert.java +++ b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/system/Assert.java @@ -1,116 +1,116 @@ -/* -Copyright (c) 2016 Robert Atkinson -All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. -Neither the name of Robert Atkinson nor the names of his contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcore.internal.system; - -import com.github.serivesmejia.eocvsim.util.Log; - -/** - * {@link Assert} is a utility class for assertions that generates an exception which - * gets written to the log, but then continues on with the application. The write to the - * log gives us notices that something is amiss that needs addressing, but continuing with - * the app might, for example, allow a robot to continue on in a match rather than aborting - * in the middle, depending on the nature of the failure. - */ -public class Assert { - public static final String TAG = "Assert"; - - public static void assertTrue(boolean value) { - if (!value) { - assertFailed(); - } - } - - public static void assertFalse(boolean value) { - if (value) { - assertFailed(); - } - } - - public static void assertNull(Object value) { - if (value != null) { - assertFailed(); - } - } - - public static void assertNotNull(Object value) { - if (value == null) { - assertFailed(); - } - } - - public static void assertEquals(int expected, int actual) { - if (expected != actual) { - assertFailed(); - } - } - - //---------------------------------------------------------------------------------------------- - - public static void assertTrue(boolean value, String format, Object... args) { - if (!value) { - assertFailed(format, args); - } - } - - public static void assertFalse(boolean value, String format, Object... args) { - if (value) { - assertFailed(format, args); - } - } - - public static void assertNull(Object value, String format, Object... args) { - if (value != null) { - assertFailed(format, args); - } - } - - public static void assertNotNull(Object value, String format, Object... args) { - if (value == null) { - assertFailed(format, args); - } - } - - //---------------------------------------------------------------------------------------------- - - public static void assertFailed() { - try { - throw new RuntimeException("assertion failed"); - } catch (Exception e) { - Log.error(TAG, "assertion failed", e); - } - } - - public static void assertFailed(String format, Object[] args) { - String message = String.format(format, args); - String banner = "assertion failed: " + message; - try { - throw new RuntimeException(banner); - } catch (Exception e) { - Log.error(TAG, banner, e); - } - } +/* +Copyright (c) 2016 Robert Atkinson +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.internal.system; + +import com.github.serivesmejia.eocvsim.util.Log; + +/** + * {@link Assert} is a utility class for assertions that generates an exception which + * gets written to the log, but then continues on with the application. The write to the + * log gives us notices that something is amiss that needs addressing, but continuing with + * the app might, for example, allow a robot to continue on in a match rather than aborting + * in the middle, depending on the nature of the failure. + */ +public class Assert { + public static final String TAG = "Assert"; + + public static void assertTrue(boolean value) { + if (!value) { + assertFailed(); + } + } + + public static void assertFalse(boolean value) { + if (value) { + assertFailed(); + } + } + + public static void assertNull(Object value) { + if (value != null) { + assertFailed(); + } + } + + public static void assertNotNull(Object value) { + if (value == null) { + assertFailed(); + } + } + + public static void assertEquals(int expected, int actual) { + if (expected != actual) { + assertFailed(); + } + } + + //---------------------------------------------------------------------------------------------- + + public static void assertTrue(boolean value, String format, Object... args) { + if (!value) { + assertFailed(format, args); + } + } + + public static void assertFalse(boolean value, String format, Object... args) { + if (value) { + assertFailed(format, args); + } + } + + public static void assertNull(Object value, String format, Object... args) { + if (value != null) { + assertFailed(format, args); + } + } + + public static void assertNotNull(Object value, String format, Object... args) { + if (value == null) { + assertFailed(format, args); + } + } + + //---------------------------------------------------------------------------------------------- + + public static void assertFailed() { + try { + throw new RuntimeException("assertion failed"); + } catch (Exception e) { + Log.error(TAG, "assertion failed", e); + } + } + + public static void assertFailed(String format, Object[] args) { + String message = String.format(format, args); + String banner = "assertion failed: " + message; + try { + throw new RuntimeException(banner); + } catch (Exception e) { + Log.error(TAG, banner, e); + } + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/org/openftc/easyopencv/MatRecycler.java b/EOCV-Sim/src/main/java/org/openftc/easyopencv/MatRecycler.java index 98d4578a..1f11b825 100644 --- a/EOCV-Sim/src/main/java/org/openftc/easyopencv/MatRecycler.java +++ b/EOCV-Sim/src/main/java/org/openftc/easyopencv/MatRecycler.java @@ -1,113 +1,113 @@ -/* - * Copyright (c) 2019 OpenFTC Team - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.openftc.easyopencv; - -import com.github.serivesmejia.eocvsim.util.Log; -import org.opencv.core.Mat; - -import java.util.concurrent.ArrayBlockingQueue; - -/* - * A utility class for managing the re-use of Mats - * so as to re-use already allocated memory instead - * of constantly allocating new Mats and then freeing - * them after use. - */ -public class MatRecycler { - private final RecyclableMat[] mats; - private final ArrayBlockingQueue availableMats; - - public MatRecycler(int num) { - mats = new RecyclableMat[num]; - availableMats = new ArrayBlockingQueue<>(num); - - for (int i = 0; i < mats.length; i++) { - mats[i] = new RecyclableMat(i); - availableMats.add(mats[i]); - } - } - - public synchronized RecyclableMat takeMat() { - if (availableMats.size() == 0) { - throw new RuntimeException("All mats have been checked out!"); - } - - RecyclableMat mat = null; - try { - mat = availableMats.take(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - mat.checkedOut = true; - return mat; - - } - - public synchronized void returnMat(RecyclableMat mat) { - if (mat != mats[mat.idx]) { - throw new IllegalArgumentException("This mat does not belong to this recycler!"); - } - - if (mat.checkedOut) { - mat.checkedOut = false; - availableMats.add(mat); - } else { - throw new IllegalArgumentException("This mat has already been returned!"); - } - } - - public void releaseAll() { - for (Mat mat : mats) { - mat.release(); - } - } - - public int getSize() { - return mats.length; - } - - public int getAvailableMatsAmount() { return availableMats.size(); } - - public final class RecyclableMat extends Mat { - - private int idx = -1; - private volatile boolean checkedOut = false; - - private RecyclableMat(int idx) { - this.idx = idx; - } - - public void returnMat() { - synchronized(MatRecycler.this) { - try { - MatRecycler.this.returnMat(this); - } catch (IllegalArgumentException ex) { - Log.warn("RecyclableMat", "Tried to return a Mat which was already returned", ex); - } - } - } - - public boolean isCheckedOut() { return checkedOut; } - - } -} +/* + * Copyright (c) 2019 OpenFTC Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.openftc.easyopencv; + +import com.github.serivesmejia.eocvsim.util.Log; +import org.opencv.core.Mat; + +import java.util.concurrent.ArrayBlockingQueue; + +/* + * A utility class for managing the re-use of Mats + * so as to re-use already allocated memory instead + * of constantly allocating new Mats and then freeing + * them after use. + */ +public class MatRecycler { + private final RecyclableMat[] mats; + private final ArrayBlockingQueue availableMats; + + public MatRecycler(int num) { + mats = new RecyclableMat[num]; + availableMats = new ArrayBlockingQueue<>(num); + + for (int i = 0; i < mats.length; i++) { + mats[i] = new RecyclableMat(i); + availableMats.add(mats[i]); + } + } + + public synchronized RecyclableMat takeMat() { + if (availableMats.size() == 0) { + throw new RuntimeException("All mats have been checked out!"); + } + + RecyclableMat mat = null; + try { + mat = availableMats.take(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + mat.checkedOut = true; + return mat; + + } + + public synchronized void returnMat(RecyclableMat mat) { + if (mat != mats[mat.idx]) { + throw new IllegalArgumentException("This mat does not belong to this recycler!"); + } + + if (mat.checkedOut) { + mat.checkedOut = false; + availableMats.add(mat); + } else { + throw new IllegalArgumentException("This mat has already been returned!"); + } + } + + public void releaseAll() { + for (Mat mat : mats) { + mat.release(); + } + } + + public int getSize() { + return mats.length; + } + + public int getAvailableMatsAmount() { return availableMats.size(); } + + public final class RecyclableMat extends Mat { + + private int idx = -1; + private volatile boolean checkedOut = false; + + private RecyclableMat(int idx) { + this.idx = idx; + } + + public void returnMat() { + synchronized(MatRecycler.this) { + try { + MatRecycler.this.returnMat(this); + } catch (IllegalArgumentException ex) { + Log.warn("RecyclableMat", "Tried to return a Mat which was already returned", ex); + } + } + } + + public boolean isCheckedOut() { return checkedOut; } + + } +} diff --git a/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java b/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java index 5093888c..c108e94b 100644 --- a/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java +++ b/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java @@ -1,13 +1,13 @@ -package org.openftc.easyopencv; - -import org.opencv.core.Mat; - -public abstract class OpenCvPipeline { - - public abstract Mat processFrame(Mat input); - - public void onViewportTapped() { } - - public void init(Mat mat) { } - +package org.openftc.easyopencv; + +import org.opencv.core.Mat; + +public abstract class OpenCvPipeline { + + public abstract Mat processFrame(Mat input); + + public void onViewportTapped() { } + + public void init(Mat mat) { } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvTracker.java b/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvTracker.java index a0042a68..a1422e4f 100644 --- a/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvTracker.java +++ b/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvTracker.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2019 OpenFTC Team - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.openftc.easyopencv; - -import org.opencv.core.Mat; - -public abstract class OpenCvTracker { - private final Mat mat = new Mat(); - - public abstract Mat processFrame(Mat input); - - protected final Mat processFrameInternal(Mat input) { - input.copyTo(mat); - return processFrame(mat); - } +/* + * Copyright (c) 2019 OpenFTC Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.openftc.easyopencv; + +import org.opencv.core.Mat; + +public abstract class OpenCvTracker { + private final Mat mat = new Mat(); + + public abstract Mat processFrame(Mat input); + + protected final Mat processFrameInternal(Mat input) { + input.copyTo(mat); + return processFrame(mat); + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvTrackerApiPipeline.java b/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvTrackerApiPipeline.java index 406c2a0b..1f4c4504 100644 --- a/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvTrackerApiPipeline.java +++ b/EOCV-Sim/src/main/java/org/openftc/easyopencv/OpenCvTrackerApiPipeline.java @@ -1,71 +1,71 @@ -/* - * Copyright (c) 2019 OpenFTC Team - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.openftc.easyopencv; - -import org.opencv.core.Mat; - -import java.util.ArrayList; - -public class OpenCvTrackerApiPipeline extends OpenCvPipeline { - private final ArrayList trackers = new ArrayList<>(); - private int trackerDisplayIdx = 0; - - public synchronized void addTracker(OpenCvTracker tracker) { - trackers.add(tracker); - } - - public synchronized void removeTracker(OpenCvTracker tracker) { - trackers.remove(tracker); - - if (trackerDisplayIdx >= trackers.size()) { - trackerDisplayIdx--; - - if (trackerDisplayIdx < 0) { - trackerDisplayIdx = 0; - } - } - } - - @Override - public synchronized Mat processFrame(Mat input) { - if (trackers.size() == 0) { - return input; - } - - ArrayList returnMats = new ArrayList<>(trackers.size()); - - for (OpenCvTracker tracker : trackers) { - returnMats.add(tracker.processFrameInternal(input)); - } - - return returnMats.get(trackerDisplayIdx); - } - - @Override - public synchronized void onViewportTapped() { - trackerDisplayIdx++; - - if (trackerDisplayIdx >= trackers.size()) { - trackerDisplayIdx = 0; - } - } +/* + * Copyright (c) 2019 OpenFTC Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.openftc.easyopencv; + +import org.opencv.core.Mat; + +import java.util.ArrayList; + +public class OpenCvTrackerApiPipeline extends OpenCvPipeline { + private final ArrayList trackers = new ArrayList<>(); + private int trackerDisplayIdx = 0; + + public synchronized void addTracker(OpenCvTracker tracker) { + trackers.add(tracker); + } + + public synchronized void removeTracker(OpenCvTracker tracker) { + trackers.remove(tracker); + + if (trackerDisplayIdx >= trackers.size()) { + trackerDisplayIdx--; + + if (trackerDisplayIdx < 0) { + trackerDisplayIdx = 0; + } + } + } + + @Override + public synchronized Mat processFrame(Mat input) { + if (trackers.size() == 0) { + return input; + } + + ArrayList returnMats = new ArrayList<>(trackers.size()); + + for (OpenCvTracker tracker : trackers) { + returnMats.add(tracker.processFrameInternal(input)); + } + + return returnMats.get(trackerDisplayIdx); + } + + @Override + public synchronized void onViewportTapped() { + trackerDisplayIdx++; + + if (trackerDisplayIdx >= trackers.size()) { + trackerDisplayIdx = 0; + } + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedOpenCvPipeline.java b/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedOpenCvPipeline.java index 7281d245..e2453bae 100644 --- a/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedOpenCvPipeline.java +++ b/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedOpenCvPipeline.java @@ -1,42 +1,42 @@ -/* - * Copyright (c) 2020 OpenFTC Team - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.openftc.easyopencv; - -import org.opencv.core.Mat; - -public abstract class TimestampedOpenCvPipeline extends OpenCvPipeline -{ - private long timestamp; - - @Override - public final Mat processFrame(Mat input) - { - return processFrame(input, timestamp); - } - - public abstract Mat processFrame(Mat input, long captureTimeNanos); - - protected void setTimestamp(long timestamp) - { - this.timestamp = timestamp; - } +/* + * Copyright (c) 2020 OpenFTC Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.openftc.easyopencv; + +import org.opencv.core.Mat; + +public abstract class TimestampedOpenCvPipeline extends OpenCvPipeline +{ + private long timestamp; + + @Override + public final Mat processFrame(Mat input) + { + return processFrame(input, timestamp); + } + + public abstract Mat processFrame(Mat input, long captureTimeNanos); + + protected void setTimestamp(long timestamp) + { + this.timestamp = timestamp; + } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedPipelineHandler.kt b/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedPipelineHandler.kt index 0524d8ed..74248561 100644 --- a/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedPipelineHandler.kt +++ b/EOCV-Sim/src/main/java/org/openftc/easyopencv/TimestampedPipelineHandler.kt @@ -1,40 +1,36 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package org.openftc.easyopencv - -import com.qualcomm.robotcore.util.ElapsedTime - -class TimestampedPipelineHandler { - - private val elapsedTime = ElapsedTime() - - fun update(currentPipeline: OpenCvPipeline?) { - if(currentPipeline is TimestampedOpenCvPipeline) { - currentPipeline.setTimestamp(elapsedTime.nanoseconds()) - } - - elapsedTime.reset() - } - +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package org.openftc.easyopencv + +import com.github.serivesmejia.eocvsim.input.InputSource + +class TimestampedPipelineHandler { + + fun update(currentPipeline: OpenCvPipeline?, currentInputSource: InputSource?) { + if(currentPipeline is TimestampedOpenCvPipeline) { + currentPipeline.setTimestamp(currentInputSource?.captureTimeNanos ?: 0L) + } + } + } \ No newline at end of file diff --git a/EOCV-Sim/src/main/resources/contributors.txt b/EOCV-Sim/src/main/resources/contributors.txt index a93f3620..52c2729e 100644 --- a/EOCV-Sim/src/main/resources/contributors.txt +++ b/EOCV-Sim/src/main/resources/contributors.txt @@ -1,6 +1,6 @@ -NPE & the OpenFTC Team - EOCV Developers & Advisors -serivesmejia - Main Dev -Purav - Contributor & Mac Tester -Jaran - Kotlin & Coroutines Advisor -Shaurya - Guide Contributor & Mac Tester +NPE (Windwoes) - EasyOpenCV and AprilTag Plugin +serivesmejia - Main Dev +Purav - Contributor & Mac Tester +Jaran - Kotlin & Coroutines Advisor +Shaurya - Guide Contributor & Mac Tester Henopied - JVM Crash Bugfix \ No newline at end of file diff --git a/EOCV-Sim/src/main/resources/opensourcelibs.txt b/EOCV-Sim/src/main/resources/opensourcelibs.txt index 414e5665..c02ffecc 100644 --- a/EOCV-Sim/src/main/resources/opensourcelibs.txt +++ b/EOCV-Sim/src/main/resources/opensourcelibs.txt @@ -1,8 +1,16 @@ -OpenCV - Under Apache 2.0 License -FTC SDK - Some source code under the BSD License -EasyOpenCV - Some source code under MIT License -Gson - Under Apache 2.0 License -ClassGraph - Under MIT License -FlatLaf - Under Apache 2.0 License - -EOCV-Sim and its source code is distributed under the MIT License \ No newline at end of file +EOCV-Sim and its source code is distributed under the MIT License + +OpenCV - Under Apache 2.0 License +OpenCV for Desktop Java - Under Apache 2.0 License +FTC SDK - Some source code under the BSD License +EasyOpenCV - Some source code under MIT License +EOCV-AprilTag-Plugin - Source code under MIT License +webcam-capture - Under MIT License +Gson - Under Apache 2.0 License +ClassGraph - Under MIT License +FlatLaf - Under Apache 2.0 License +Kotlin stdlib & coroutines - Under Apache 2.0 License +picocli - Under Apache 2.0 License +dear imgui - Under MIT License +imgui-java - Under Apache 2.0 License +LWJGL - Under BSD 3-Clause License \ No newline at end of file diff --git a/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip b/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip index 0050ab1a..207a505c 100644 Binary files a/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip and b/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip differ diff --git a/EOCV-Sim/src/test/kotlin/com/github/serivesmejia/eocvsim/test/CoreTests.kt b/EOCV-Sim/src/test/kotlin/com/github/serivesmejia/eocvsim/test/CoreTests.kt index 655475e3..d1916747 100644 --- a/EOCV-Sim/src/test/kotlin/com/github/serivesmejia/eocvsim/test/CoreTests.kt +++ b/EOCV-Sim/src/test/kotlin/com/github/serivesmejia/eocvsim/test/CoreTests.kt @@ -1,39 +1,39 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -@file:Suppress("UNUSED") - -package com.github.serivesmejia.eocvsim.test - -import com.github.serivesmejia.eocvsim.EOCVSim -import io.kotest.core.spec.style.StringSpec -import org.opencv.core.Mat - -class OpenCvTest : StringSpec({ - "Loading native library" { - EOCVSim.loadOpenCvLib() - } - - "Creating a Mat" { - Mat() - } +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +@file:Suppress("UNUSED") + +package com.github.serivesmejia.eocvsim.test + +import com.github.serivesmejia.eocvsim.EOCVSim +import io.kotest.core.spec.style.StringSpec +import org.opencv.core.Mat + +class OpenCvTest : StringSpec({ + "Loading native library" { + EOCVSim.loadOpenCvLib() + } + + "Creating a Mat" { + Mat() + } }) \ No newline at end of file diff --git a/LICENSE b/LICENSE index c3097512..bcb08de3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2020 Sebastian Erives - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2020 Sebastian Erives + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 36bc046b..03220394 100644 --- a/README.md +++ b/README.md @@ -1,308 +1,229 @@ - - -![Java CI with Gradle](https://github.com/deltacv/EOCV-Sim/workflows/Build%20and%20test%20with%20Gradle/badge.svg) -[![](https://jitpack.io/v/deltacvvesmejia/EOCV-Sim.svg)](https://jitpack.io/#deltacv/EOCV-Sim) -[![Run on Repl.it](https://repl.it/badge/github/deltacv/EOCV-Sim)](https://repl.it/github/deltacv/EOCV-Sim) - - -# Welcome! - -EOCV-Sim (EasyOpenCV Simulator) is a straightforward way to test your pipelines in a -simple user interface directly in your computer, simulating the EasyOpenCV library & a bit of -FTC SDK structure, allowing you to simply copy paste directly your pipeline code once you want to -transfer it onto your robot! - - - -### If you'd like to learn how to use the simulator, you can find a complete usage explaination [here](https://github.com/serivesmejia/EOCV-Sim/blob/master/USAGE.md) - -# Compatibility - -Since OpenCV in Java uses a native library, which is platform specific, the simulator is currently limited to the following platforms: - -* Windows x64 (tested) -* Windows x32 (untested) -* MacOS x64 (tested) -* Linux x64 (tested for Ubuntu 20.04)
- -# Installation - -1) **Download & install the Java Development Kit if you haven't already:**

- JDK 8 is the minimum required one, any JDK above that version will probably work fine.
- You can download it from [the Oracle webpage](https://www.oracle.com/java/technologies/javase-downloads.html), - and here is a [step by step video](https://www.youtube.com/watch?v=IJ-PJbvJBGs) of the installation process
- -## Recommended method - -1) **Make sure you have downloaded a JDK as mentioned above** - -2) **Go to the releases page on this repo and find the latest version ([or click here](https://github.com/deltacv/EOCV-Sim/releases/latest))** - -3) **Download the jar file, named `EOCV-Sim-X.X.X-all.jar`, available at the bottom on the "assets" section** - -4) **Choose and install an IDE/text editor**

- The recommended text editor is VS Code, with the Java Extension Pack. EOCV-Sim provides direct support for it, for creating a "VS Code Workspace" from a template, although it can also be imported into IntelliJ IDEA since it's just a normal Gradle project. - - This installation method provides the benefit of "runtime compiling", which means that the user pipelines are compiled and loaded on the fly and therefore the changes made in code can be reflected immediately, as opposed to the [old IntelliJ IDEA method](#altenative-installation-method-intellij-idea) in which the simulator had to be closed, compiled and then opened again to apply the smallest change made in a pipeline. Plus, VS Code is a lightweight editor which provides Java syntax highlighting and IntelliSense with the Java Extension Pack, making development of pipelines easy with tools like code completion. - - You can download and install VS Code from the [Visual Studio page](https://code.visualstudio.com/). The [Java Extension Pack](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack) can be installed from the [VS Code extension marketplace](https://code.visualstudio.com/docs/introvideos/extend). - - Here's a [tutorial video](https://www.youtube.com/watch?v=KwnavHTOBiA) explaining how to download and install VS Code & the Java Extension Pack - -5) **Running EOCV-Sim**

- For running the sim, simply double click the jar file downloaded from the releases page, or it can also be executed from the command line: - ```python - java -jar "EOCV-Sim-X.X.X-all.jar" - ``` - - When running on Linux (distros such as Ubuntu, Linux Mint, etc) or Unix-like secure operating systems, it might prohibit you to run it by double clicking the file from a file explorer. This can be fixed by giving execute permissions to the jar file with the following command - ```bash - chmod +x EOCV-Sim-X.X.X-all.jar - ``` - -**Now the sim should be running without any issues! If you find any problem feel free to open an issue, and check the [usage explanation](https://github.com/deltacv/EOCV-Sim/blob/master/USAGE.md) for more details about how to use the simulator (and VS Code).** - -## Altenative installation method (IntelliJ IDEA) - -No complicated setup is required for this method either, it's straight up importing the EOCV-Sim project into IntelliJ IDEA: - -\**The downside of this method is that this repo has grown to a considerable amount of space, due to a bloated history, and takes some time to clone, and also builds can be slower depending on your device.* - -1) **Make sure you have downloaded a JDK as mentioned [here](#installation)** - -2) **Download & install IntelliJ IDEA Community IDE if you haven't already:**

- You can download it from the [JetBrains webpage](https://www.jetbrains.com/idea/download/)
- Here is another great [step by step video](https://www.youtube.com/watch?v=E2okEJIbUYs) for IntelliJ installation. - -3) **Clone and import the project:**
- - 1) Open IntelliJ IDEA and in the main screen click on "Get from Version Control"
- -

- Alternatively, if you already had another project opened, go to File > New > Project from Version Control...

- - - 2) Another window will show up for cloning and importing a repository into IntelliJ
- - 1) In the "URL" field, enter: ```https://github.com/deltacv/EOCV-Sim.git```
- 2) The directory can be changed, but it will be automatically filled so it's not necessary. - 3) Make sure the "Version control" is set to "Git".

-
- 4) After that, click on the "Clone" button, located at the bottom right and the cloning process will begin...
-
- 5) After the cloning finishes, the project should automatically import and you'll have something like this:

-
- -### And you're ready to go! Refer to the [usage explanation](https://github.com/deltacv/EOCV-Sim/blob/master/USAGE.md) for further details on how to utilize the simulator.
- -## From the command-line - - 1) Clone EOCV-Sim repo and cd to the cloned folder - - git clone https://github.com/deltacv/EOCV-Sim.git - cd EOCV-Sim - \**Or it can also be manually downloaded as a ZIP file from GitHub*
- - 2) Run EOCV-Sim through gradle: - - gradlew runSim - - \**On some command lines (such as Windows PowerShell and macOS) you might need to execute "./gradlew" instead*
- -#### And that's it! You might need to wait a bit for gradle to download all the dependencies but EOCV-Sim will open eventually. - -## From repl.it - - 1) Click [here](https://repl.it/github/deltacv/EOCV-Sim) to go to repl.it, you might require to create an account if you haven't already. Once you do that, it will automatically create a new project and start cloning the EOCV-Sim repo. - - 2) After the cloning is finished, click on the green "Run" button at the top and EOCV-Sim should start. - - \**Please note that this method is not widely supported and you might run into some issues or lack of some functionality.*
- - -## Adding EOCV-Sim as a dependency - - ### Gradle - ```groovy - repositories { - maven { url 'https://jitpack.com' } //add jitpack as a maven repo - } - - dependencies { - implementation 'com.github.deltacv:EOCV-Sim:3.0.0' //add the EOCV-Sim dependency - } - ``` - - ## Maven - - Adding the jitpack maven repo - ```xml - - - jitpack.io - https://jitpack.io - - - ``` - - Adding the EOCV-Sim dependecy - ```xml - - com.github.deltacv - EOCV-Sim - 3.0.0 - - ``` - -# Contact -For any quick troubleshooting or help, you can find me on Discord as *serivesmejia#8237* and on the FTC discord server. I'll be happy to assist you in any issue you might have :)

-For bug reporting or feature requesting, use the [issues tab](https://github.com/serivesmejia/EOCV-Sim/issues) in this repository. - -# Change logs - -### [v3.0.0 - Compiling on the fly! Yay!](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v3.0.0) - - - This is the 9th release for EOCV-Sim - - - Changelog: - - Runtime building! The sim now supports building pipelines on the fly, which allows for more quick and efficient testing. (Running the sim with a JDK is required for this feature to work, since normal JREs don't include a compiler to use) - - Workspaces & VS Code is the new (and recommended) way of developing pipelines. A VS Code workspace template can be created from the sim, see the usage explanation for more details. - - A file watcher was implemented, so when any modification happens in the current workspace under the "source" or "resource" folders specified in the `eocvsim_workspace.json`, a new build will be automatically triggered every 8 seconds. - - VS Code can be executed by the sim if the current system has the `code` command. It is triggered by manually opening it in the top menu bar or when creating a VS Code workspace. - - Files can now be drag and dropped into the sim to add them as Input Sources. The sim will automatically open a create dialog depending on the file extension. - - Added a "Workspace" menu under the top menu bar, which contains the new features regarding the runtime compiling. - - The UI now has smoother icons, by using a smoothing option on Java swing which makes them look a little nicer (but not much). - - Current pipeline state is now stored and reestablished if a restart happens. - - When a build is finished, the simulator tries reinitializes the currently selected pipeline if it exists, to ensure the changes were applied. Or it falls back to the `DefaultPipeline` if the old pipeline doesn't exist anymore, it also saves the state of the old pipeline and tries to apply the snapshot of the pipeline before it was reinitialized if the names of the old and new classes match. - - The sim now uses a `.eocvsim` folder under the user directory to store its files, to avoid annoying the user with unwanted files now that the runtime compiling exists and it has to store the build output somewhere. If the user has previously run an older version of eocv sim which created `eocvsim_sources.json` and/or `eocvsim_config.json` under the user home directory, it automatically migrates them to the new folder. - - Builds created by IntelliJ Idea (the common programming style) are now considered as "dev". This helps to distinguish between official & published builds created in a CI workflow and local builds, when an issue happens and it's reported. - - The sim compiling target was changed back to Java 8, since this is one of the most widely used versions and we weren't really using many Java 9 features that couldn't have been replaced or handle different. This is also more convenient and provides better support for users directly downloading the jar and executing it. - - - Bugfixes: - - Fixed issues with the source selector regarding to selection when a modification or error happens. When a new source is added, it's automatically selected. And when a source is deleted, the previous source in the list is selected - - Fixed the color picker cursor size on non-windows systems - - Fixed pause not working when a tunable field that uses a combo box in the UI is included. - - Fixed an (apparently random, but it's just the garbage collector being weird) null pointer exception with `Enum` fields - - - Internals: - - Improved event handlers to be more idiomatic and less weird. Bye bye KEventListener! - - Improved some messy parts of the internal code and logic - -### [v2.2.1 - JVM crashing hotfix](https://github.com/serivesmejia/releases/tag/v2.2.0) - - - This is the 8th release for EOCV-Sim - - - Changelog: - - Removed "Java memory" message in the title since it's practically useless for the end user - - Updated to Gradle 7.0 for Java 16+ support (#25) - - - Bugfixes: - - Fixed JVM crashing error caused by releasing all mats in a MatRecycler finalization (#26) - - Improved memory usage by deleting unused BufferedImageRecyclers, memory is now slightly freed when allocating or recycling buffered images of different sizes (which means that the memory usage is reduced a little bit when zooming in the viewport) - -### [v2.2.0 - Variable Tuner Upgrade](https://github.com/serivesmejia/releases/tag/v2.2.0) - - - This is the 7th release for EOCV-Sim - - - Changelog: - - - Pipelines now have a timeout all of the three methods so that the main loop doesn't get compromised due to a "stuck" pipeline (using kotlin coroutines) - - processFrame has a timeout of 4.2 seconds - - init is executed in the same scope as processFrame, when it has to be called, the timeout is doubled (16.4) - - When either processFrame or init methods timeout, the sim automatically falls back to the default pipeline and discards any frame that the old timeouted pipeline could return. - - onViewportTapped is still called from the U.I Thread, but it now has a timeout of 4.2 seconds too - - Added EnumField which handles the type Enum (accepts all classes of type enum, including the ones declared by the user) - - Major improvements to the variable tuner, added new features for color picking, tuning with sliders, configuration... See [usage explanation](https://github.com/serivesmejia/EOCV-Sim/blob/master/USAGE.md) for further details. - - GUI improvement: Dropped some external dialogs in favor of simple "popups" for more practicality - - Internals: - - Continued rewrite to kotlin - - Splitted visualizer class components into different classes - - Improved EventHandler doOnce listeners - -### [v2.1.0 - Video Update](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v2.1.0) - - - This is the 6th release for EOCV-Sim - - - Changelog: - - - Added support for VideoSources! You can now input your pipeline with a moving video (*.avi format is the most supported and tested, other codecs might depend on the OS you're using) - - Added support for video recording, accessible at the bottom of the pipeline selector. Save format is AVI - - Added a new TunableField type: RectField, which handles the OpenCV type "Rect" (might be useful for rect pipelines 👀) - - Improved uncaught exception handling and added a crash report generator - - Added support for more themes from FlatLaf - - Added new config option to change the output video recording size - - Added support for EOCV's TimestampedOpenCvPipeline - - Internals: - - Major rewrite to kotlin! (Still mostly Java but that might change soon) - - A bit of code cleaning and restructuring - -### [v2.0.2 - TaskBar hotfix](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v2.0.2) - - - This is the 5th release for EOCV-Sim. - - - Bugfixes: - - - Fixes UnsupportedOperationException with the TaskBar API in some operating system - -### [v2.0.1 - BooleanField hotfix](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v2.0.1) - - - This is the 4th release for EOCV-Sim. - - - Bugfixes: - - - Fixes ArrayIndexOutOfBoundsException when initial value of a boolean field was true which would make the sim enter into a frozen state. - -### [v2.0.0 - Major Update](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v2.0.0) - - - This is the 3rd release for EOCV-Sim. - - - Changelog: - - - Gradle is now used as the main build system - - Added variable tuner for public non-final supported fields in the pipeline, accessible on the bottom part of the image viewport. - - Pipeline pause and resume option to save resources, pauses automatically with image sources for one-shot analysis - - Top Menu bar containing new features/convenient shortcuts: - - Save Mat to disk option in File submenu - - Restart feature in File submenu - - Shortcut for creating input sources under File -> New -> Input Source - - Settings menu under Edit submenu - - "About" information screen under Help submenu - - Appereance themes via the FlatLaf library, selectable in the settings window - - Telemetry now is passed to the pipeline via the constructor rather than an instance variable, check usage explaination for further details - - Mat visualizing into the viewport is now handled in another thread to improve performance - - Pipeline FPS are now capped at 30 - - Zooming viewport is now supported, using mouse wheel while holding Ctrl key - - - Bugfixes: - - - Removed call to the gc in the main loop due to performance issues - - Fixed BufferedImage mem leak by recycling previously used buffered images and trying to flush them - - Some internal code cleaning & reestructuration - - Fixed issues with native lib loading (mostly on Mac) with the OpenCV package provided by OpenPnP - -### [v1.1.0 - Telemetry Update](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v1.1.0) - - - This is the 2rd release for EOCV-Sim. - - - Changelog: - - - Added a Telemetry implementation displayed in the UI. Replicates the FTC SDK one, it can be used directly in pipelines. - - Added an option to define the CameraSource resolution when creation. - - Added MacOS support (thnx Noah) - - Changed default resolution to 320x280 everywhere since it is the most commonly used in EOCV - - Native libs are now downloaded by the simulator from another GitHub repo to avoid bloating the repository with heavy files - - Java libraries, such as classgraph, opencv and gson are now delivered in compiled jars to improve compile times - - - Bug fixes: - - - Fixed a bug where the InputSources would return a BGR Mat instead of RGB, which is the type EOCV gives. - - Regarding the last point, the visualizer now expects for the given mats to be RGB - - Improved general IO error handling everywhere, from file accessing to input sources reading, so that the simulator doesn’t enter in a freeze state if any IO related operation fails - - Improved multi threading handling for changing pipelines and inputsources. - - Fixed issue in Linux where the buttons would be moved to an incorrect position when resizing out and then trying to resize back to the original size - - -### [v1.0.0 - Initial Release](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v1.0.0) - - - Initial EOCV-Sim release. - + + +![Java CI with Gradle](https://github.com/deltacv/EOCV-Sim/workflows/Build%20and%20test%20with%20Gradle/badge.svg) +[![](https://jitpack.io/v/deltacvvesmejia/EOCV-Sim.svg)](https://jitpack.io/#deltacv/EOCV-Sim) +[![Run on Repl.it](https://repl.it/badge/github/deltacv/EOCV-Sim)](https://repl.it/github/deltacv/EOCV-Sim) + + +# Welcome! + +EOCV-Sim (EasyOpenCV Simulator) is a straightforward way to test your pipelines in a +simple user interface directly in your computer, simulating the EasyOpenCV library & a bit of +FTC SDK structure, allowing you to simply copy paste directly your pipeline code once you want to +transfer it onto your robot! + + + +## Learn how to install and use the simulator in the [documentation here](https://deltacv.gitbook.io/eocv-sim/) + +# Compatibility + +Since OpenCV in Java uses a native library, which is platform specific, the simulator is currently limited to the following platforms: + +* Windows x64 (tested) +* Windows x32 (untested) +* MacOS x64 (tested) +* Linux x64 (tested for Ubuntu 20.04)
+ +## Downloading and documentation + +Follow the steps in [this page](https://deltacv.gitbook.io/eocv-sim/basics/downloading-eocv-sim) to download the sim. The rest of the documentation can also be found [there](https://deltacv.gitbook.io/eocv-sim/). + +## Adding EOCV-Sim as a dependency + + ### Gradle + ```groovy + repositories { + maven { url 'https://jitpack.com' } //add jitpack as a maven repo + } + + dependencies { + implementation 'com.github.deltacv:EOCV-Sim:3.2.0' //add the EOCV-Sim dependency + } + ``` + + ## Maven + + Adding the jitpack maven repo + ```xml + + + jitpack.io + https://jitpack.io + + + ``` + + Adding the EOCV-Sim dependecy + ```xml + + com.github.deltacv + EOCV-Sim + 3.2.0 + + ``` + +# Contact +For bug reporting or feature requesting, use the [issues tab](https://github.com/deltacv/EOCV-Sim/issues) in this repository. + +# Change logs + +### [v3.1.0 - Better Error Handling](https://github.com/deltacv/EOCV-Sim/releases/tag/v3.1.0) + + - This is the 10th release for EOCV-Sim + + - Changelog: + - Improved pipeline error handling and error output gui + - Build output was improved and unified with the pipeline error output gui + - Added a SplashScreen with the EOCV-Sim logo while the sim loads + - Settings for changing the max FPS of the video recordings and pipelines, and the max pipeline processing time before it's considered "stuck on processFrame" + - Improved camera source creation dialog by providing a list of the available cameras + - Added file locking so that two EOCV-Sim instances can't exist at the same time + - Updated links to reflect the change to the deltacv organization + +### [v3.0.0 - Compiling on the fly! Yay!](https://github.com/deltacv/EOCV-Sim/releases/tag/v3.0.0) + + - This is the 9th release for EOCV-Sim + + - Changelog: + - Runtime building! The sim now supports building pipelines on the fly, which allows for more quick and efficient testing. (Running the sim with a JDK is required for this feature to work, since normal JREs don't include a compiler to use) + - Workspaces & VS Code is the new (and recommended) way of developing pipelines. A VS Code workspace template can be created from the sim, see the usage explanation for more details. + - A file watcher was implemented, so when any modification happens in the current workspace under the "source" or "resource" folders specified in the `eocvsim_workspace.json`, a new build will be automatically triggered every 8 seconds. + - VS Code can be executed by the sim if the current system has the `code` command. It is triggered by manually opening it in the top menu bar or when creating a VS Code workspace. + - Files can now be drag and dropped into the sim to add them as Input Sources. The sim will automatically open a create dialog depending on the file extension. + - Added a "Workspace" menu under the top menu bar, which contains the new features regarding the runtime compiling. + - The UI now has smoother icons, by using a smoothing option on Java swing which makes them look a little nicer (but not much). + - Current pipeline state is now stored and reestablished if a restart happens. + - When a build is finished, the simulator tries reinitializes the currently selected pipeline if it exists, to ensure the changes were applied. Or it falls back to the `DefaultPipeline` if the old pipeline doesn't exist anymore, it also saves the state of the old pipeline and tries to apply the snapshot of the pipeline before it was reinitialized if the names of the old and new classes match. + - The sim now uses a `.eocvsim` folder under the user directory to store its files, to avoid annoying the user with unwanted files now that the runtime compiling exists and it has to store the build output somewhere. If the user has previously run an older version of eocv sim which created `eocvsim_sources.json` and/or `eocvsim_config.json` under the user home directory, it automatically migrates them to the new folder. + - Builds created by IntelliJ Idea (the common programming style) are now considered as "dev". This helps to distinguish between official & published builds created in a CI workflow and local builds, when an issue happens and it's reported. + - The sim compiling target was changed back to Java 8, since this is one of the most widely used versions and we weren't really using many Java 9 features that couldn't have been replaced or handle different. This is also more convenient and provides better support for users directly downloading the jar and executing it. + + - Bugfixes: + - Fixed issues with the source selector regarding to selection when a modification or error happens. When a new source is added, it's automatically selected. And when a source is deleted, the previous source in the list is selected + - Fixed the color picker cursor size on non-windows systems + - Fixed pause not working when a tunable field that uses a combo box in the UI is included. + - Fixed an (apparently random, but it's just the garbage collector being weird) null pointer exception with `Enum` fields + + - Internals: + - Improved event handlers to be more idiomatic and less weird. Bye bye KEventListener! + - Improved some messy parts of the internal code and logic + +### [v2.2.1 - JVM crashing hotfix](https://github.com/serivesmejia/releases/tag/v2.2.0) + + - This is the 8th release for EOCV-Sim + + - Changelog: + - Removed "Java memory" message in the title since it's practically useless for the end user + - Updated to Gradle 7.0 for Java 16+ support (#25) + + - Bugfixes: + - Fixed JVM crashing error caused by releasing all mats in a MatRecycler finalization (#26) + - Improved memory usage by deleting unused BufferedImageRecyclers, memory is now slightly freed when allocating or recycling buffered images of different sizes (which means that the memory usage is reduced a little bit when zooming in the viewport) + +### [v2.2.0 - Variable Tuner Upgrade](https://github.com/serivesmejia/releases/tag/v2.2.0) + + - This is the 7th release for EOCV-Sim + + - Changelog: + + - Pipelines now have a timeout all of the three methods so that the main loop doesn't get compromised due to a "stuck" pipeline (using kotlin coroutines) + - processFrame has a timeout of 4.2 seconds + - init is executed in the same scope as processFrame, when it has to be called, the timeout is doubled (16.4) + - When either processFrame or init methods timeout, the sim automatically falls back to the default pipeline and discards any frame that the old timeouted pipeline could return. + - onViewportTapped is still called from the U.I Thread, but it now has a timeout of 4.2 seconds too + - Added EnumField which handles the type Enum (accepts all classes of type enum, including the ones declared by the user) + - Major improvements to the variable tuner, added new features for color picking, tuning with sliders, configuration... See [usage explanation](https://github.com/serivesmejia/EOCV-Sim/blob/master/USAGE.md) for further details. + - GUI improvement: Dropped some external dialogs in favor of simple "popups" for more practicality + - Internals: + - Continued rewrite to kotlin + - Splitted visualizer class components into different classes + - Improved EventHandler doOnce listeners + +### [v2.1.0 - Video Update](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v2.1.0) + + - This is the 6th release for EOCV-Sim + + - Changelog: + + - Added support for VideoSources! You can now input your pipeline with a moving video (*.avi format is the most supported and tested, other codecs might depend on the OS you're using) + - Added support for video recording, accessible at the bottom of the pipeline selector. Save format is AVI + - Added a new TunableField type: RectField, which handles the OpenCV type "Rect" (might be useful for rect pipelines 👀) + - Improved uncaught exception handling and added a crash report generator + - Added support for more themes from FlatLaf + - Added new config option to change the output video recording size + - Added support for EOCV's TimestampedOpenCvPipeline + - Internals: + - Major rewrite to kotlin! (Still mostly Java but that might change soon) + - A bit of code cleaning and restructuring + +### [v2.0.2 - TaskBar hotfix](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v2.0.2) + + - This is the 5th release for EOCV-Sim. + + - Bugfixes: + + - Fixes UnsupportedOperationException with the TaskBar API in some operating system + +### [v2.0.1 - BooleanField hotfix](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v2.0.1) + + - This is the 4th release for EOCV-Sim. + + - Bugfixes: + + - Fixes ArrayIndexOutOfBoundsException when initial value of a boolean field was true which would make the sim enter into a frozen state. + +### [v2.0.0 - Major Update](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v2.0.0) + + - This is the 3rd release for EOCV-Sim. + + - Changelog: + + - Gradle is now used as the main build system + - Added variable tuner for public non-final supported fields in the pipeline, accessible on the bottom part of the image viewport. + - Pipeline pause and resume option to save resources, pauses automatically with image sources for one-shot analysis + - Top Menu bar containing new features/convenient shortcuts: + - Save Mat to disk option in File submenu + - Restart feature in File submenu + - Shortcut for creating input sources under File -> New -> Input Source + - Settings menu under Edit submenu + - "About" information screen under Help submenu + - Appereance themes via the FlatLaf library, selectable in the settings window + - Telemetry now is passed to the pipeline via the constructor rather than an instance variable, check usage explaination for further details + - Mat visualizing into the viewport is now handled in another thread to improve performance + - Pipeline FPS are now capped at 30 + - Zooming viewport is now supported, using mouse wheel while holding Ctrl key + + - Bugfixes: + + - Removed call to the gc in the main loop due to performance issues + - Fixed BufferedImage mem leak by recycling previously used buffered images and trying to flush them + - Some internal code cleaning & reestructuration + - Fixed issues with native lib loading (mostly on Mac) with the OpenCV package provided by OpenPnP + +### [v1.1.0 - Telemetry Update](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v1.1.0) + + - This is the 2rd release for EOCV-Sim. + + - Changelog: + + - Added a Telemetry implementation displayed in the UI. Replicates the FTC SDK one, it can be used directly in pipelines. + - Added an option to define the CameraSource resolution when creation. + - Added MacOS support (thnx Noah) + - Changed default resolution to 320x280 everywhere since it is the most commonly used in EOCV + - Native libs are now downloaded by the simulator from another GitHub repo to avoid bloating the repository with heavy files + - Java libraries, such as classgraph, opencv and gson are now delivered in compiled jars to improve compile times + + - Bug fixes: + + - Fixed a bug where the InputSources would return a BGR Mat instead of RGB, which is the type EOCV gives. + - Regarding the last point, the visualizer now expects for the given mats to be RGB + - Improved general IO error handling everywhere, from file accessing to input sources reading, so that the simulator doesn’t enter in a freeze state if any IO related operation fails + - Improved multi threading handling for changing pipelines and inputsources. + - Fixed issue in Linux where the buttons would be moved to an incorrect position when resizing out and then trying to resize back to the original size + + +### [v1.0.0 - Initial Release](https://github.com/serivesmejia/EOCV-Sim/releases/tag/v1.0.0) + + - Initial EOCV-Sim release. + diff --git a/TeamCode/build.gradle b/TeamCode/build.gradle index 7d523cbb..01d8c6ac 100644 --- a/TeamCode/build.gradle +++ b/TeamCode/build.gradle @@ -1,18 +1,21 @@ -plugins { - id 'java' - id 'org.jetbrains.kotlin.jvm' -} - -apply from: '../build.common.gradle' - -dependencies { - implementation 'org.openpnp:opencv:4.3.0-2' - implementation project(':EOCV-Sim') - - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" -} - -task(runSim, dependsOn: 'classes', type: JavaExec) { - main = 'com.github.serivesmejia.eocvsim.Main' - classpath = sourceSets.main.runtimeClasspath -} \ No newline at end of file +import java.nio.file.Paths + +plugins { + id 'java' + id 'org.jetbrains.kotlin.jvm' +} + +apply from: '../build.common.gradle' + +dependencies { + implementation "org.openpnp:opencv:$opencv_version" + implementation project(':EOCV-Sim') + implementation "com.github.deltacv:AprilTagDesktop:$apriltag_plugin_version" + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" +} + +task(runSim, dependsOn: 'classes', type: JavaExec) { + main = 'com.github.serivesmejia.eocvsim.Main' + classpath = sourceSets.main.runtimeClasspath +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/AprilTagDetectionPipeline.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/AprilTagDetectionPipeline.java new file mode 100644 index 00000000..2c2e4519 --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/AprilTagDetectionPipeline.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2021 OpenFTC Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.teamcode; + +import org.firstinspires.ftc.robotcore.external.Telemetry; +import org.opencv.calib3d.Calib3d; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.MatOfDouble; +import org.opencv.core.MatOfPoint2f; +import org.opencv.core.MatOfPoint3f; +import org.opencv.core.Point; +import org.opencv.core.Point3; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; +import org.openftc.apriltag.AprilTagDetection; +import org.openftc.apriltag.AprilTagDetectorJNI; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.util.ArrayList; + +public class AprilTagDetectionPipeline extends OpenCvPipeline +{ + // STATIC CONSTANTS + + public static Scalar blue = new Scalar(7,197,235,255); + public static Scalar red = new Scalar(255,0,0,255); + public static Scalar green = new Scalar(0,255,0,255); + public static Scalar white = new Scalar(255,255,255,255); + + static final double FEET_PER_METER = 3.28084; + + // Lens intrinsics + // UNITS ARE PIXELS + // NOTE: this calibration is for the C920 webcam at 800x448. + // You will need to do your own calibration for other configurations! + public static double fx = 578.272; + public static double fy = 578.272; + public static double cx = 402.145; + public static double cy = 221.506; + + // UNITS ARE METERS + public static double TAG_SIZE = 0.166; + + // instance variables + + private long nativeApriltagPtr; + private Mat grey = new Mat(); + private ArrayList detections = new ArrayList<>(); + + private ArrayList detectionsUpdate = new ArrayList<>(); + private final Object detectionsUpdateSync = new Object(); + + Mat cameraMatrix; + + double tagsizeX = TAG_SIZE; + double tagsizeY = TAG_SIZE; + + private float decimation; + private boolean needToSetDecimation; + private final Object decimationSync = new Object(); + + Telemetry telemetry; + + public AprilTagDetectionPipeline(Telemetry telemetry) { + this.telemetry = telemetry; + constructMatrix(); + } + + @Override + public void init(Mat frame) + { + // Allocate a native context object. See the corresponding deletion in the finalizer + nativeApriltagPtr = AprilTagDetectorJNI.createApriltagDetector(AprilTagDetectorJNI.TagFamily.TAG_36h11.string, 3, 3); + } + + @Override + public void finalize() + { + // Delete the native context we created in the init() function + AprilTagDetectorJNI.releaseApriltagDetector(nativeApriltagPtr); + } + + @Override + public Mat processFrame(Mat input) + { + // Convert to greyscale + Imgproc.cvtColor(input, grey, Imgproc.COLOR_RGBA2GRAY); + + synchronized (decimationSync) + { + if(needToSetDecimation) + { + AprilTagDetectorJNI.setApriltagDetectorDecimation(nativeApriltagPtr, decimation); + needToSetDecimation = false; + } + } + + // Run AprilTag + detections = AprilTagDetectorJNI.runAprilTagDetectorSimple(nativeApriltagPtr, grey, TAG_SIZE, fx, fy, cx, cy); + + synchronized (detectionsUpdateSync) + { + detectionsUpdate = detections; + } + + // For fun, use OpenCV to draw 6DOF markers on the image. We actually recompute the pose using + // OpenCV because I haven't yet figured out how to re-use AprilTag's pose in OpenCV. + for(AprilTagDetection detection : detections) + { + Pose pose = poseFromTrapezoid(detection.corners, cameraMatrix, tagsizeX, tagsizeY); + drawAxisMarker(input, tagsizeY/2.0, 6, pose.rvec, pose.tvec, cameraMatrix); + draw3dCubeMarker(input, tagsizeX, tagsizeX, tagsizeY, 5, pose.rvec, pose.tvec, cameraMatrix); + + telemetry.addLine(String.format("\nDetected tag ID=%d", detection.id)); + telemetry.addLine(String.format("Translation X: %.2f feet", detection.pose.x*FEET_PER_METER)); + telemetry.addLine(String.format("Translation Y: %.2f feet", detection.pose.y*FEET_PER_METER)); + telemetry.addLine(String.format("Translation Z: %.2f feet", detection.pose.z*FEET_PER_METER)); + telemetry.addLine(String.format("Rotation Yaw: %.2f degrees", Math.toDegrees(detection.pose.yaw))); + telemetry.addLine(String.format("Rotation Pitch: %.2f degrees", Math.toDegrees(detection.pose.pitch))); + telemetry.addLine(String.format("Rotation Roll: %.2f degrees", Math.toDegrees(detection.pose.roll))); + } + + telemetry.update(); + + return input; + } + + public void setDecimation(float decimation) + { + synchronized (decimationSync) + { + this.decimation = decimation; + needToSetDecimation = true; + } + } + + public ArrayList getLatestDetections() + { + return detections; + } + + public ArrayList getDetectionsUpdate() + { + synchronized (detectionsUpdateSync) + { + ArrayList ret = detectionsUpdate; + detectionsUpdate = null; + return ret; + } + } + + void constructMatrix() + { + // Construct the camera matrix. + // + // -- -- + // | fx 0 cx | + // | 0 fy cy | + // | 0 0 1 | + // -- -- + // + + cameraMatrix = new Mat(3,3, CvType.CV_32FC1); + + cameraMatrix.put(0,0, fx); + cameraMatrix.put(0,1,0); + cameraMatrix.put(0,2, cx); + + cameraMatrix.put(1,0,0); + cameraMatrix.put(1,1,fy); + cameraMatrix.put(1,2,cy); + + cameraMatrix.put(2, 0, 0); + cameraMatrix.put(2,1,0); + cameraMatrix.put(2,2,1); + } + + /** + * Draw a 3D axis marker on a detection. (Similar to what Vuforia does) + * + * @param buf the RGB buffer on which to draw the marker + * @param length the length of each of the marker 'poles' + * @param rvec the rotation vector of the detection + * @param tvec the translation vector of the detection + * @param cameraMatrix the camera matrix used when finding the detection + */ + void drawAxisMarker(Mat buf, double length, int thickness, Mat rvec, Mat tvec, Mat cameraMatrix) + { + // The points in 3D space we wish to project onto the 2D image plane. + // The origin of the coordinate space is assumed to be in the center of the detection. + MatOfPoint3f axis = new MatOfPoint3f( + new Point3(0,0,0), + new Point3(length,0,0), + new Point3(0,length,0), + new Point3(0,0,-length) + ); + + // Project those points + MatOfPoint2f matProjectedPoints = new MatOfPoint2f(); + Calib3d.projectPoints(axis, rvec, tvec, cameraMatrix, new MatOfDouble(), matProjectedPoints); + Point[] projectedPoints = matProjectedPoints.toArray(); + + // Draw the marker! + Imgproc.line(buf, projectedPoints[0], projectedPoints[1], red, thickness); + Imgproc.line(buf, projectedPoints[0], projectedPoints[2], green, thickness); + Imgproc.line(buf, projectedPoints[0], projectedPoints[3], blue, thickness); + + Imgproc.circle(buf, projectedPoints[0], thickness, white, -1); + } + + void draw3dCubeMarker(Mat buf, double length, double tagWidth, double tagHeight, int thickness, Mat rvec, Mat tvec, Mat cameraMatrix) + { + //axis = np.float32([[0,0,0], [0,3,0], [3,3,0], [3,0,0], + // [0,0,-3],[0,3,-3],[3,3,-3],[3,0,-3] ]) + + // The points in 3D space we wish to project onto the 2D image plane. + // The origin of the coordinate space is assumed to be in the center of the detection. + MatOfPoint3f axis = new MatOfPoint3f( + new Point3(-tagWidth/2, tagHeight/2,0), + new Point3( tagWidth/2, tagHeight/2,0), + new Point3( tagWidth/2,-tagHeight/2,0), + new Point3(-tagWidth/2,-tagHeight/2,0), + new Point3(-tagWidth/2, tagHeight/2,-length), + new Point3( tagWidth/2, tagHeight/2,-length), + new Point3( tagWidth/2,-tagHeight/2,-length), + new Point3(-tagWidth/2,-tagHeight/2,-length)); + + // Project those points + MatOfPoint2f matProjectedPoints = new MatOfPoint2f(); + Calib3d.projectPoints(axis, rvec, tvec, cameraMatrix, new MatOfDouble(), matProjectedPoints); + Point[] projectedPoints = matProjectedPoints.toArray(); + + // Pillars + for(int i = 0; i < 4; i++) + { + Imgproc.line(buf, projectedPoints[i], projectedPoints[i+4], blue, thickness); + } + + // Base lines + //Imgproc.line(buf, projectedPoints[0], projectedPoints[1], blue, thickness); + //Imgproc.line(buf, projectedPoints[1], projectedPoints[2], blue, thickness); + //Imgproc.line(buf, projectedPoints[2], projectedPoints[3], blue, thickness); + //Imgproc.line(buf, projectedPoints[3], projectedPoints[0], blue, thickness); + + // Top lines + Imgproc.line(buf, projectedPoints[4], projectedPoints[5], green, thickness); + Imgproc.line(buf, projectedPoints[5], projectedPoints[6], green, thickness); + Imgproc.line(buf, projectedPoints[6], projectedPoints[7], green, thickness); + Imgproc.line(buf, projectedPoints[4], projectedPoints[7], green, thickness); + } + + /** + * Extracts 6DOF pose from a trapezoid, using a camera intrinsics matrix and the + * original size of the tag. + * + * @param points the points which form the trapezoid + * @param cameraMatrix the camera intrinsics matrix + * @param tagsizeX the original width of the tag + * @param tagsizeY the original height of the tag + * @return the 6DOF pose of the camera relative to the tag + */ + Pose poseFromTrapezoid(Point[] points, Mat cameraMatrix, double tagsizeX , double tagsizeY) + { + // The actual 2d points of the tag detected in the image + MatOfPoint2f points2d = new MatOfPoint2f(points); + + // The 3d points of the tag in an 'ideal projection' + Point3[] arrayPoints3d = new Point3[4]; + arrayPoints3d[0] = new Point3(-tagsizeX/2, tagsizeY/2, 0); + arrayPoints3d[1] = new Point3(tagsizeX/2, tagsizeY/2, 0); + arrayPoints3d[2] = new Point3(tagsizeX/2, -tagsizeY/2, 0); + arrayPoints3d[3] = new Point3(-tagsizeX/2, -tagsizeY/2, 0); + MatOfPoint3f points3d = new MatOfPoint3f(arrayPoints3d); + + // Using this information, actually solve for pose + Pose pose = new Pose(); + Calib3d.solvePnP(points3d, points2d, cameraMatrix, new MatOfDouble(), pose.rvec, pose.tvec, false); + + return pose; + } + + /* + * A simple container to hold both rotation and translation + * vectors, which together form a 6DOF pose. + */ + class Pose + { + Mat rvec; + Mat tvec; + + public Pose() + { + rvec = new Mat(); + tvec = new Mat(); + } + + public Pose(Mat rvec, Mat tvec) + { + this.rvec = rvec; + this.tvec = tvec; + } + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SimpleThresholdPipeline.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SimpleThresholdPipeline.java index 14b27e0e..c68d0858 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SimpleThresholdPipeline.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SimpleThresholdPipeline.java @@ -1,171 +1,171 @@ -/* - * Copyright (c) 2021 Sebastian Erives - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package org.firstinspires.ftc.teamcode; - -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.opencv.core.Core; -import org.opencv.core.Mat; -import org.opencv.core.Scalar; -import org.opencv.imgproc.Imgproc; -import org.openftc.easyopencv.OpenCvPipeline; - -public class SimpleThresholdPipeline extends OpenCvPipeline { - - /* - * These are our variables that will be - * modifiable from the variable tuner. - * - * Scalars in OpenCV are generally used to - * represent color. So our values in the - * lower and upper Scalars here represent - * the Y, Cr and Cb values respectively. - * - * YCbCr, like most color spaces, range - * from 0-255, so we default to those - * min and max values here for now, meaning - * that all pixels will be shown. - */ - public Scalar lower = new Scalar(0, 0, 0); - public Scalar upper = new Scalar(255, 255, 255); - - /** - * This will allow us to choose the color - * space we want to use on the live field - * tuner instead of hardcoding it - */ - public ColorSpace colorSpace = ColorSpace.YCrCb; - - /* - * A good practice when typing EOCV pipelines is - * declaring the Mats you will use here at the top - * of your pipeline, to reuse the same buffers every - * time. This removes the need to call mat.release() - * with every Mat you create on the processFrame method, - * and therefore, reducing the possibility of getting a - * memory leak and causing the app to crash due to an - * "Out of Memory" error. - */ - private Mat ycrcbMat = new Mat(); - private Mat binaryMat = new Mat(); - private Mat maskedInputMat = new Mat(); - - private Telemetry telemetry = null; - - /** - * Enum to choose which color space to choose - * with the live variable tuner isntead of - * hardcoding it. - */ - enum ColorSpace { - /* - * Define our "conversion codes" in the enum - * so that we don't have to do a switch - * statement in the processFrame method. - */ - RGB(Imgproc.COLOR_RGBA2RGB), - HSV(Imgproc.COLOR_RGB2HSV), - YCrCb(Imgproc.COLOR_RGB2YCrCb), - Lab(Imgproc.COLOR_RGB2Lab); - - //store cvtCode in a public var - public int cvtCode = 0; - - //constructor to be used by enum declarations above - ColorSpace(int cvtCode) { - this.cvtCode = cvtCode; - } - } - - public SimpleThresholdPipeline(Telemetry telemetry) { - this.telemetry = telemetry; - } - - @Override - public Mat processFrame(Mat input) { - /* - * Converts our input mat from RGB to - * specified color space by the enum. - * EOCV ALWAYS returns RGB mats, so you'd - * always convert from RGB to the color - * space you want to use. - * - * Takes our "input" mat as an input, and outputs - * to a separate Mat buffer "ycrcbMat" - */ - Imgproc.cvtColor(input, ycrcbMat, colorSpace.cvtCode); - - /* - * This is where our thresholding actually happens. - * Takes our "ycrcbMat" as input and outputs a "binary" - * Mat to "binaryMat" of the same size as our input. - * "Discards" all the pixels outside the bounds specified - * by the scalars above (and modifiable with EOCV-Sim's - * live variable tuner.) - * - * Binary meaning that we have either a 0 or 255 value - * for every pixel. - * - * 0 represents our pixels that were outside the bounds - * 255 represents our pixels that are inside the bounds - */ - Core.inRange(ycrcbMat, lower, upper, binaryMat); - - /* - * Release the reusable Mat so that old data doesn't - * affect the next step in the current processing - */ - maskedInputMat.release(); - - /* - * Now, with our binary Mat, we perform a "bitwise and" - * to our input image, meaning that we will perform a mask - * which will include the pixels from our input Mat which - * are "255" in our binary Mat (meaning that they're inside - * the range) and will discard any other pixel outside the - * range (RGB 0, 0, 0. All discarded pixels will be black) - */ - Core.bitwise_and(input, input, maskedInputMat, binaryMat); - - /** - * Add some nice and informative telemetry messages - */ - telemetry.addData("[>]", "Change these values in tuner menu"); - telemetry.addData("[Color Space]", colorSpace.name()); - telemetry.addData("[Lower Scalar]", lower); - telemetry.addData("[Upper Scalar]", upper); - telemetry.update(); - - /* - * The Mat returned from this method is the - * one displayed on the viewport. - * - * To visualize our threshold, we'll return - * the "masked input mat" which shows the - * pixel from the input Mat that were inside - * the threshold range. - */ - return maskedInputMat; - } - -} +/* + * Copyright (c) 2021 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package org.firstinspires.ftc.teamcode; + +import org.firstinspires.ftc.robotcore.external.Telemetry; +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; +import org.openftc.easyopencv.OpenCvPipeline; + +public class SimpleThresholdPipeline extends OpenCvPipeline { + + /* + * These are our variables that will be + * modifiable from the variable tuner. + * + * Scalars in OpenCV are generally used to + * represent color. So our values in the + * lower and upper Scalars here represent + * the Y, Cr and Cb values respectively. + * + * YCbCr, like most color spaces, range + * from 0-255, so we default to those + * min and max values here for now, meaning + * that all pixels will be shown. + */ + public Scalar lower = new Scalar(0, 0, 0); + public Scalar upper = new Scalar(255, 255, 255); + + /** + * This will allow us to choose the color + * space we want to use on the live field + * tuner instead of hardcoding it + */ + public ColorSpace colorSpace = ColorSpace.YCrCb; + + /* + * A good practice when typing EOCV pipelines is + * declaring the Mats you will use here at the top + * of your pipeline, to reuse the same buffers every + * time. This removes the need to call mat.release() + * with every Mat you create on the processFrame method, + * and therefore, reducing the possibility of getting a + * memory leak and causing the app to crash due to an + * "Out of Memory" error. + */ + private Mat ycrcbMat = new Mat(); + private Mat binaryMat = new Mat(); + private Mat maskedInputMat = new Mat(); + + private Telemetry telemetry = null; + + /** + * Enum to choose which color space to choose + * with the live variable tuner isntead of + * hardcoding it. + */ + enum ColorSpace { + /* + * Define our "conversion codes" in the enum + * so that we don't have to do a switch + * statement in the processFrame method. + */ + RGB(Imgproc.COLOR_RGBA2RGB), + HSV(Imgproc.COLOR_RGB2HSV), + YCrCb(Imgproc.COLOR_RGB2YCrCb), + Lab(Imgproc.COLOR_RGB2Lab); + + //store cvtCode in a public var + public int cvtCode = 0; + + //constructor to be used by enum declarations above + ColorSpace(int cvtCode) { + this.cvtCode = cvtCode; + } + } + + public SimpleThresholdPipeline(Telemetry telemetry) { + this.telemetry = telemetry; + } + + @Override + public Mat processFrame(Mat input) { + /* + * Converts our input mat from RGB to + * specified color space by the enum. + * EOCV ALWAYS returns RGB mats, so you'd + * always convert from RGB to the color + * space you want to use. + * + * Takes our "input" mat as an input, and outputs + * to a separate Mat buffer "ycrcbMat" + */ + Imgproc.cvtColor(input, ycrcbMat, colorSpace.cvtCode); + + /* + * This is where our thresholding actually happens. + * Takes our "ycrcbMat" as input and outputs a "binary" + * Mat to "binaryMat" of the same size as our input. + * "Discards" all the pixels outside the bounds specified + * by the scalars above (and modifiable with EOCV-Sim's + * live variable tuner.) + * + * Binary meaning that we have either a 0 or 255 value + * for every pixel. + * + * 0 represents our pixels that were outside the bounds + * 255 represents our pixels that are inside the bounds + */ + Core.inRange(ycrcbMat, lower, upper, binaryMat); + + /* + * Release the reusable Mat so that old data doesn't + * affect the next step in the current processing + */ + maskedInputMat.release(); + + /* + * Now, with our binary Mat, we perform a "bitwise and" + * to our input image, meaning that we will perform a mask + * which will include the pixels from our input Mat which + * are "255" in our binary Mat (meaning that they're inside + * the range) and will discard any other pixel outside the + * range (RGB 0, 0, 0. All discarded pixels will be black) + */ + Core.bitwise_and(input, input, maskedInputMat, binaryMat); + + /** + * Add some nice and informative telemetry messages + */ + telemetry.addData("[>]", "Change these values in tuner menu"); + telemetry.addData("[Color Space]", colorSpace.name()); + telemetry.addData("[Lower Scalar]", lower); + telemetry.addData("[Upper Scalar]", upper); + telemetry.update(); + + /* + * The Mat returned from this method is the + * one displayed on the viewport. + * + * To visualize our threshold, we'll return + * the "masked input mat" which shows the + * pixel from the input Mat that were inside + * the threshold range. + */ + return maskedInputMat; + } + +} diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SkystoneDeterminationPipeline.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SkystoneDeterminationPipeline.java index cc77c245..c7e92500 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SkystoneDeterminationPipeline.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SkystoneDeterminationPipeline.java @@ -1,308 +1,308 @@ -/* - * Copyright (c) 2020 OpenFTC Team - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.teamcode; - -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.opencv.core.Core; -import org.opencv.core.Mat; -import org.opencv.core.Point; -import org.opencv.core.Rect; -import org.opencv.core.Scalar; -import org.opencv.imgproc.Imgproc; -import org.openftc.easyopencv.OpenCvPipeline; - -public class SkystoneDeterminationPipeline extends OpenCvPipeline { - /* - * An enum to define the skystone position - */ - public enum SkystonePosition - { - LEFT, - CENTER, - RIGHT - } - - /* - * Some color constants - */ - public final Scalar BLUE = new Scalar(0, 0, 255); - public final Scalar GREEN = new Scalar(0, 255, 0); - - /* - * The core values which define the location and size of the sample regions - */ - static final Point REGION1_TOPLEFT_ANCHOR_POINT = new Point(109,98); - static final Point REGION2_TOPLEFT_ANCHOR_POINT = new Point(181,98); - static final Point REGION3_TOPLEFT_ANCHOR_POINT = new Point(253,98); - static final int REGION_WIDTH = 20; - static final int REGION_HEIGHT = 20; - - /* - * Points which actually define the sample region rectangles, derived from above values - * - * Example of how points A and B work to define a rectangle - * - * ------------------------------------ - * | (0,0) Point A | - * | | - * | | - * | | - * | | - * | | - * | | - * | Point B (70,50) | - * ------------------------------------ - * - */ - Point region1_pointA = new Point( - REGION1_TOPLEFT_ANCHOR_POINT.x, - REGION1_TOPLEFT_ANCHOR_POINT.y); - Point region1_pointB = new Point( - REGION1_TOPLEFT_ANCHOR_POINT.x + REGION_WIDTH, - REGION1_TOPLEFT_ANCHOR_POINT.y + REGION_HEIGHT); - Point region2_pointA = new Point( - REGION2_TOPLEFT_ANCHOR_POINT.x, - REGION2_TOPLEFT_ANCHOR_POINT.y); - Point region2_pointB = new Point( - REGION2_TOPLEFT_ANCHOR_POINT.x + REGION_WIDTH, - REGION2_TOPLEFT_ANCHOR_POINT.y + REGION_HEIGHT); - Point region3_pointA = new Point( - REGION3_TOPLEFT_ANCHOR_POINT.x, - REGION3_TOPLEFT_ANCHOR_POINT.y); - Point region3_pointB = new Point( - REGION3_TOPLEFT_ANCHOR_POINT.x + REGION_WIDTH, - REGION3_TOPLEFT_ANCHOR_POINT.y + REGION_HEIGHT); - - /* - * Working variables - */ - Mat region1_Cb, region2_Cb, region3_Cb; - Mat YCrCb = new Mat(); - Mat Cb = new Mat(); - int avg1, avg2, avg3; - - // Volatile since accessed by OpMode thread w/o synchronization - private volatile SkystonePosition position = SkystonePosition.LEFT; - - private Telemetry telemetry; - - public SkystoneDeterminationPipeline(Telemetry telemetry) { - this.telemetry = telemetry; - } - - /* - * This function takes the RGB frame, converts to YCrCb, - * and extracts the Cb channel to the 'Cb' variable - */ - void inputToCb(Mat input) - { - Imgproc.cvtColor(input, YCrCb, Imgproc.COLOR_RGB2YCrCb); - Core.extractChannel(YCrCb, Cb, 2); - } - - @Override - public void init(Mat firstFrame) - { - /* - * We need to call this in order to make sure the 'Cb' - * object is initialized, so that the submats we make - * will still be linked to it on subsequent frames. (If - * the object were to only be initialized in processFrame, - * then the submats would become delinked because the backing - * buffer would be re-allocated the first time a real frame - * was crunched) - */ - inputToCb(firstFrame); - - /* - * Submats are a persistent reference to a region of the parent - * buffer. Any changes to the child affect the parent, and the - * reverse also holds true. - */ - region1_Cb = Cb.submat(new Rect(region1_pointA, region1_pointB)); - region2_Cb = Cb.submat(new Rect(region2_pointA, region2_pointB)); - region3_Cb = Cb.submat(new Rect(region3_pointA, region3_pointB)); - } - - @Override - public Mat processFrame(Mat input) - { - /* - * Overview of what we're doing: - * - * We first convert to YCrCb color space, from RGB color space. - * Why do we do this? Well, in the RGB color space, chroma and - * luma are intertwined. In YCrCb, chroma and luma are separated. - * YCrCb is a 3-channel color space, just like RGB. YCrCb's 3 channels - * are Y, the luma channel (which essentially just a B&W image), the - * Cr channel, which records the difference from red, and the Cb channel, - * which records the difference from blue. Because chroma and luma are - * not related in YCrCb, vision code written to look for certain values - * in the Cr/Cb channels will not be severely affected by differing - * light intensity, since that difference would most likely just be - * reflected in the Y channel. - * - * After we've converted to YCrCb, we extract just the 2nd channel, the - * Cb channel. We do this because stones are bright yellow and contrast - * STRONGLY on the Cb channel against everything else, including SkyStones - * (because SkyStones have a black label). - * - * We then take the average pixel value of 3 different regions on that Cb - * channel, one positioned over each stone. The brightest of the 3 regions - * is where we assume the SkyStone to be, since the normal stones show up - * extremely darkly. - * - * We also draw rectangles on the screen showing where the sample regions - * are, as well as drawing a solid rectangle over top the sample region - * we believe is on top of the SkyStone. - * - * In order for this whole process to work correctly, each sample region - * should be positioned in the center of each of the first 3 stones, and - * be small enough such that only the stone is sampled, and not any of the - * surroundings. - */ - - /* - * Get the Cb channel of the input frame after conversion to YCrCb - */ - inputToCb(input); - - /* - * Compute the average pixel value of each submat region. We're - * taking the average of a single channel buffer, so the value - * we need is at index 0. We could have also taken the average - * pixel value of the 3-channel image, and referenced the value - * at index 2 here. - */ - avg1 = (int) Core.mean(region1_Cb).val[0]; - avg2 = (int) Core.mean(region2_Cb).val[0]; - avg3 = (int) Core.mean(region3_Cb).val[0]; - - /* - * Draw a rectangle showing sample region 1 on the screen. - * Simply a visual aid. Serves no functional purpose. - */ - Imgproc.rectangle( - input, // Buffer to draw on - region1_pointA, // First point which defines the rectangle - region1_pointB, // Second point which defines the rectangle - BLUE, // The color the rectangle is drawn in - 2); // Thickness of the rectangle lines - - /* - * Draw a rectangle showing sample region 2 on the screen. - * Simply a visual aid. Serves no functional purpose. - */ - Imgproc.rectangle( - input, // Buffer to draw on - region2_pointA, // First point which defines the rectangle - region2_pointB, // Second point which defines the rectangle - BLUE, // The color the rectangle is drawn in - 2); // Thickness of the rectangle lines - - /* - * Draw a rectangle showing sample region 3 on the screen. - * Simply a visual aid. Serves no functional purpose. - */ - Imgproc.rectangle( - input, // Buffer to draw on - region3_pointA, // First point which defines the rectangle - region3_pointB, // Second point which defines the rectangle - BLUE, // The color the rectangle is drawn in - 2); // Thickness of the rectangle lines - - - /* - * Find the max of the 3 averages - */ - int maxOneTwo = Math.max(avg1, avg2); - int max = Math.max(maxOneTwo, avg3); - - /* - * Now that we found the max, we actually need to go and - * figure out which sample region that value was from - */ - if(max == avg1) // Was it from region 1? - { - position = SkystonePosition.LEFT; // Record our analysis - - /* - * Draw a solid rectangle on top of the chosen region. - * Simply a visual aid. Serves no functional purpose. - */ - Imgproc.rectangle( - input, // Buffer to draw on - region1_pointA, // First point which defines the rectangle - region1_pointB, // Second point which defines the rectangle - GREEN, // The color the rectangle is drawn in - -1); // Negative thickness means solid fill - } - else if(max == avg2) // Was it from region 2? - { - position = SkystonePosition.CENTER; // Record our analysis - - /* - * Draw a solid rectangle on top of the chosen region. - * Simply a visual aid. Serves no functional purpose. - */ - Imgproc.rectangle( - input, // Buffer to draw on - region2_pointA, // First point which defines the rectangle - region2_pointB, // Second point which defines the rectangle - GREEN, // The color the rectangle is drawn in - -1); // Negative thickness means solid fill - } - else if(max == avg3) // Was it from region 3? - { - position = SkystonePosition.RIGHT; // Record our analysis - - /* - * Draw a solid rectangle on top of the chosen region. - * Simply a visual aid. Serves no functional purpose. - */ - Imgproc.rectangle( - input, // Buffer to draw on - region3_pointA, // First point which defines the rectangle - region3_pointB, // Second point which defines the rectangle - GREEN, // The color the rectangle is drawn in - -1); // Negative thickness means solid fill - } - - telemetry.addData("[Pattern]", position); - telemetry.update(); - - /* - * Render the 'input' buffer to the viewport. But note this is not - * simply rendering the raw camera feed, because we called functions - * to add some annotations to this buffer earlier up. - */ - return input; - } - - /* - * Call this from the OpMode thread to obtain the latest analysis - */ - public SkystonePosition getAnalysis() - { - return position; - } +/* + * Copyright (c) 2020 OpenFTC Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.teamcode; + +import org.firstinspires.ftc.robotcore.external.Telemetry; +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.Point; +import org.opencv.core.Rect; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; +import org.openftc.easyopencv.OpenCvPipeline; + +public class SkystoneDeterminationPipeline extends OpenCvPipeline { + /* + * An enum to define the skystone position + */ + public enum SkystonePosition + { + LEFT, + CENTER, + RIGHT + } + + /* + * Some color constants + */ + public final Scalar BLUE = new Scalar(0, 0, 255); + public final Scalar GREEN = new Scalar(0, 255, 0); + + /* + * The core values which define the location and size of the sample regions + */ + static final Point REGION1_TOPLEFT_ANCHOR_POINT = new Point(109,98); + static final Point REGION2_TOPLEFT_ANCHOR_POINT = new Point(181,98); + static final Point REGION3_TOPLEFT_ANCHOR_POINT = new Point(253,98); + static final int REGION_WIDTH = 20; + static final int REGION_HEIGHT = 20; + + /* + * Points which actually define the sample region rectangles, derived from above values + * + * Example of how points A and B work to define a rectangle + * + * ------------------------------------ + * | (0,0) Point A | + * | | + * | | + * | | + * | | + * | | + * | | + * | Point B (70,50) | + * ------------------------------------ + * + */ + Point region1_pointA = new Point( + REGION1_TOPLEFT_ANCHOR_POINT.x, + REGION1_TOPLEFT_ANCHOR_POINT.y); + Point region1_pointB = new Point( + REGION1_TOPLEFT_ANCHOR_POINT.x + REGION_WIDTH, + REGION1_TOPLEFT_ANCHOR_POINT.y + REGION_HEIGHT); + Point region2_pointA = new Point( + REGION2_TOPLEFT_ANCHOR_POINT.x, + REGION2_TOPLEFT_ANCHOR_POINT.y); + Point region2_pointB = new Point( + REGION2_TOPLEFT_ANCHOR_POINT.x + REGION_WIDTH, + REGION2_TOPLEFT_ANCHOR_POINT.y + REGION_HEIGHT); + Point region3_pointA = new Point( + REGION3_TOPLEFT_ANCHOR_POINT.x, + REGION3_TOPLEFT_ANCHOR_POINT.y); + Point region3_pointB = new Point( + REGION3_TOPLEFT_ANCHOR_POINT.x + REGION_WIDTH, + REGION3_TOPLEFT_ANCHOR_POINT.y + REGION_HEIGHT); + + /* + * Working variables + */ + Mat region1_Cb, region2_Cb, region3_Cb; + Mat YCrCb = new Mat(); + Mat Cb = new Mat(); + int avg1, avg2, avg3; + + // Volatile since accessed by OpMode thread w/o synchronization + private volatile SkystonePosition position = SkystonePosition.LEFT; + + private Telemetry telemetry; + + public SkystoneDeterminationPipeline(Telemetry telemetry) { + this.telemetry = telemetry; + } + + /* + * This function takes the RGB frame, converts to YCrCb, + * and extracts the Cb channel to the 'Cb' variable + */ + void inputToCb(Mat input) + { + Imgproc.cvtColor(input, YCrCb, Imgproc.COLOR_RGB2YCrCb); + Core.extractChannel(YCrCb, Cb, 2); + } + + @Override + public void init(Mat firstFrame) + { + /* + * We need to call this in order to make sure the 'Cb' + * object is initialized, so that the submats we make + * will still be linked to it on subsequent frames. (If + * the object were to only be initialized in processFrame, + * then the submats would become delinked because the backing + * buffer would be re-allocated the first time a real frame + * was crunched) + */ + inputToCb(firstFrame); + + /* + * Submats are a persistent reference to a region of the parent + * buffer. Any changes to the child affect the parent, and the + * reverse also holds true. + */ + region1_Cb = Cb.submat(new Rect(region1_pointA, region1_pointB)); + region2_Cb = Cb.submat(new Rect(region2_pointA, region2_pointB)); + region3_Cb = Cb.submat(new Rect(region3_pointA, region3_pointB)); + } + + @Override + public Mat processFrame(Mat input) + { + /* + * Overview of what we're doing: + * + * We first convert to YCrCb color space, from RGB color space. + * Why do we do this? Well, in the RGB color space, chroma and + * luma are intertwined. In YCrCb, chroma and luma are separated. + * YCrCb is a 3-channel color space, just like RGB. YCrCb's 3 channels + * are Y, the luma channel (which essentially just a B&W image), the + * Cr channel, which records the difference from red, and the Cb channel, + * which records the difference from blue. Because chroma and luma are + * not related in YCrCb, vision code written to look for certain values + * in the Cr/Cb channels will not be severely affected by differing + * light intensity, since that difference would most likely just be + * reflected in the Y channel. + * + * After we've converted to YCrCb, we extract just the 2nd channel, the + * Cb channel. We do this because stones are bright yellow and contrast + * STRONGLY on the Cb channel against everything else, including SkyStones + * (because SkyStones have a black label). + * + * We then take the average pixel value of 3 different regions on that Cb + * channel, one positioned over each stone. The brightest of the 3 regions + * is where we assume the SkyStone to be, since the normal stones show up + * extremely darkly. + * + * We also draw rectangles on the screen showing where the sample regions + * are, as well as drawing a solid rectangle over top the sample region + * we believe is on top of the SkyStone. + * + * In order for this whole process to work correctly, each sample region + * should be positioned in the center of each of the first 3 stones, and + * be small enough such that only the stone is sampled, and not any of the + * surroundings. + */ + + /* + * Get the Cb channel of the input frame after conversion to YCrCb + */ + inputToCb(input); + + /* + * Compute the average pixel value of each submat region. We're + * taking the average of a single channel buffer, so the value + * we need is at index 0. We could have also taken the average + * pixel value of the 3-channel image, and referenced the value + * at index 2 here. + */ + avg1 = (int) Core.mean(region1_Cb).val[0]; + avg2 = (int) Core.mean(region2_Cb).val[0]; + avg3 = (int) Core.mean(region3_Cb).val[0]; + + /* + * Draw a rectangle showing sample region 1 on the screen. + * Simply a visual aid. Serves no functional purpose. + */ + Imgproc.rectangle( + input, // Buffer to draw on + region1_pointA, // First point which defines the rectangle + region1_pointB, // Second point which defines the rectangle + BLUE, // The color the rectangle is drawn in + 2); // Thickness of the rectangle lines + + /* + * Draw a rectangle showing sample region 2 on the screen. + * Simply a visual aid. Serves no functional purpose. + */ + Imgproc.rectangle( + input, // Buffer to draw on + region2_pointA, // First point which defines the rectangle + region2_pointB, // Second point which defines the rectangle + BLUE, // The color the rectangle is drawn in + 2); // Thickness of the rectangle lines + + /* + * Draw a rectangle showing sample region 3 on the screen. + * Simply a visual aid. Serves no functional purpose. + */ + Imgproc.rectangle( + input, // Buffer to draw on + region3_pointA, // First point which defines the rectangle + region3_pointB, // Second point which defines the rectangle + BLUE, // The color the rectangle is drawn in + 2); // Thickness of the rectangle lines + + + /* + * Find the max of the 3 averages + */ + int maxOneTwo = Math.max(avg1, avg2); + int max = Math.max(maxOneTwo, avg3); + + /* + * Now that we found the max, we actually need to go and + * figure out which sample region that value was from + */ + if(max == avg1) // Was it from region 1? + { + position = SkystonePosition.LEFT; // Record our analysis + + /* + * Draw a solid rectangle on top of the chosen region. + * Simply a visual aid. Serves no functional purpose. + */ + Imgproc.rectangle( + input, // Buffer to draw on + region1_pointA, // First point which defines the rectangle + region1_pointB, // Second point which defines the rectangle + GREEN, // The color the rectangle is drawn in + -1); // Negative thickness means solid fill + } + else if(max == avg2) // Was it from region 2? + { + position = SkystonePosition.CENTER; // Record our analysis + + /* + * Draw a solid rectangle on top of the chosen region. + * Simply a visual aid. Serves no functional purpose. + */ + Imgproc.rectangle( + input, // Buffer to draw on + region2_pointA, // First point which defines the rectangle + region2_pointB, // Second point which defines the rectangle + GREEN, // The color the rectangle is drawn in + -1); // Negative thickness means solid fill + } + else if(max == avg3) // Was it from region 3? + { + position = SkystonePosition.RIGHT; // Record our analysis + + /* + * Draw a solid rectangle on top of the chosen region. + * Simply a visual aid. Serves no functional purpose. + */ + Imgproc.rectangle( + input, // Buffer to draw on + region3_pointA, // First point which defines the rectangle + region3_pointB, // Second point which defines the rectangle + GREEN, // The color the rectangle is drawn in + -1); // Negative thickness means solid fill + } + + telemetry.addData("[Pattern]", position); + telemetry.update(); + + /* + * Render the 'input' buffer to the viewport. But note this is not + * simply rendering the raw camera feed, because we called functions + * to add some annotations to this buffer earlier up. + */ + return input; + } + + /* + * Call this from the OpMode thread to obtain the latest analysis + */ + public SkystonePosition getAnalysis() + { + return position; + } } \ No newline at end of file diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StageSwitchingPipeline.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StageSwitchingPipeline.java index d1af40b5..123f8f73 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StageSwitchingPipeline.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StageSwitchingPipeline.java @@ -1,134 +1,134 @@ -/* - * Copyright (c) 2020 OpenFTC Team - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.teamcode; - -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.opencv.core.Core; -import org.opencv.core.Mat; -import org.opencv.core.MatOfPoint; -import org.opencv.core.Scalar; -import org.opencv.imgproc.Imgproc; -import org.openftc.easyopencv.OpenCvPipeline; - -import java.util.ArrayList; -import java.util.List; - -public class StageSwitchingPipeline extends OpenCvPipeline -{ - Mat yCbCrChan2Mat = new Mat(); - Mat thresholdMat = new Mat(); - Mat contoursOnFrameMat = new Mat(); - List contoursList = new ArrayList<>(); - int numContoursFound; - - enum Stage - { - YCbCr_CHAN2, - THRESHOLD, - CONTOURS_OVERLAYED_ON_FRAME, - RAW_IMAGE, - } - - private Stage stageToRenderToViewport = Stage.YCbCr_CHAN2; - private Stage[] stages = Stage.values(); - - private Telemetry telemetry; - - public StageSwitchingPipeline(Telemetry telemetry) { - this.telemetry = telemetry; - } - - @Override - public void onViewportTapped() - { - /* - * Note that this method is invoked from the UI thread - * so whatever we do here, we must do quickly. - */ - - int currentStageNum = stageToRenderToViewport.ordinal(); - - int nextStageNum = currentStageNum + 1; - - if(nextStageNum >= stages.length) - { - nextStageNum = 0; - } - - stageToRenderToViewport = stages[nextStageNum]; - } - - @Override - public Mat processFrame(Mat input) - { - contoursList.clear(); - - /* - * This pipeline finds the contours of yellow blobs such as the Gold Mineral - * from the Rover Ruckus game. - */ - Imgproc.cvtColor(input, yCbCrChan2Mat, Imgproc.COLOR_RGB2YCrCb); - Core.extractChannel(yCbCrChan2Mat, yCbCrChan2Mat, 2); - Imgproc.threshold(yCbCrChan2Mat, thresholdMat, 102, 255, Imgproc.THRESH_BINARY_INV); - Imgproc.findContours(thresholdMat, contoursList, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE); - numContoursFound = contoursList.size(); - input.copyTo(contoursOnFrameMat); - Imgproc.drawContours(contoursOnFrameMat, contoursList, -1, new Scalar(0, 0, 255), 3, 8); - - telemetry.addData("[Stage]", stageToRenderToViewport); - telemetry.addData("[Found Contours]", "%d", numContoursFound); - telemetry.update(); - - switch (stageToRenderToViewport) - { - case YCbCr_CHAN2: - { - return yCbCrChan2Mat; - } - - case THRESHOLD: - { - return thresholdMat; - } - - case CONTOURS_OVERLAYED_ON_FRAME: - { - return contoursOnFrameMat; - } - - case RAW_IMAGE: - { - return input; - } - - default: - { - return input; - } - } - } - - public int getNumContoursFound() - { - return numContoursFound; - } +/* + * Copyright (c) 2020 OpenFTC Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.teamcode; + +import org.firstinspires.ftc.robotcore.external.Telemetry; +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.MatOfPoint; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; +import org.openftc.easyopencv.OpenCvPipeline; + +import java.util.ArrayList; +import java.util.List; + +public class StageSwitchingPipeline extends OpenCvPipeline +{ + Mat yCbCrChan2Mat = new Mat(); + Mat thresholdMat = new Mat(); + Mat contoursOnFrameMat = new Mat(); + List contoursList = new ArrayList<>(); + int numContoursFound; + + enum Stage + { + YCbCr_CHAN2, + THRESHOLD, + CONTOURS_OVERLAYED_ON_FRAME, + RAW_IMAGE, + } + + private Stage stageToRenderToViewport = Stage.YCbCr_CHAN2; + private Stage[] stages = Stage.values(); + + private Telemetry telemetry; + + public StageSwitchingPipeline(Telemetry telemetry) { + this.telemetry = telemetry; + } + + @Override + public void onViewportTapped() + { + /* + * Note that this method is invoked from the UI thread + * so whatever we do here, we must do quickly. + */ + + int currentStageNum = stageToRenderToViewport.ordinal(); + + int nextStageNum = currentStageNum + 1; + + if(nextStageNum >= stages.length) + { + nextStageNum = 0; + } + + stageToRenderToViewport = stages[nextStageNum]; + } + + @Override + public Mat processFrame(Mat input) + { + contoursList.clear(); + + /* + * This pipeline finds the contours of yellow blobs such as the Gold Mineral + * from the Rover Ruckus game. + */ + Imgproc.cvtColor(input, yCbCrChan2Mat, Imgproc.COLOR_RGB2YCrCb); + Core.extractChannel(yCbCrChan2Mat, yCbCrChan2Mat, 2); + Imgproc.threshold(yCbCrChan2Mat, thresholdMat, 102, 255, Imgproc.THRESH_BINARY_INV); + Imgproc.findContours(thresholdMat, contoursList, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE); + numContoursFound = contoursList.size(); + input.copyTo(contoursOnFrameMat); + Imgproc.drawContours(contoursOnFrameMat, contoursList, -1, new Scalar(0, 0, 255), 3, 8); + + telemetry.addData("[Stage]", stageToRenderToViewport); + telemetry.addData("[Found Contours]", "%d", numContoursFound); + telemetry.update(); + + switch (stageToRenderToViewport) + { + case YCbCr_CHAN2: + { + return yCbCrChan2Mat; + } + + case THRESHOLD: + { + return thresholdMat; + } + + case CONTOURS_OVERLAYED_ON_FRAME: + { + return contoursOnFrameMat; + } + + case RAW_IMAGE: + { + return input; + } + + default: + { + return input; + } + } + } + + public int getNumContoursFound() + { + return numContoursFound; + } } \ No newline at end of file diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StoneOrientationAnalysisPipeline.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StoneOrientationAnalysisPipeline.java index ef22cb90..e76cb50b 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StoneOrientationAnalysisPipeline.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/StoneOrientationAnalysisPipeline.java @@ -1,467 +1,467 @@ -/* - * Copyright (c) 2020 OpenFTC Team - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.teamcode; - -import org.openftc.easyopencv.OpenCvPipeline; - -import org.opencv.core.Core; -import org.opencv.core.Mat; -import org.opencv.core.MatOfInt; -import org.opencv.core.MatOfPoint; -import org.opencv.core.MatOfPoint2f; -import org.opencv.core.Point; -import org.opencv.core.RotatedRect; -import org.opencv.core.Scalar; -import org.opencv.core.Size; -import org.opencv.imgproc.Imgproc; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class StoneOrientationAnalysisPipeline extends OpenCvPipeline -{ - /* - * Our working image buffers - */ - Mat cbMat = new Mat(); - Mat thresholdMat = new Mat(); - Mat morphedThreshold = new Mat(); - Mat contoursOnPlainImageMat = new Mat(); - - /* - * Threshold values - */ - static final int CB_CHAN_MASK_THRESHOLD = 80; - static final double DENSITY_UPRIGHT_THRESHOLD = 0.03; - - /* - * The elements we use for noise reduction - */ - Mat erodeElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3)); - Mat dilateElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(6, 6)); - - /* - * Colors - */ - static final Scalar TEAL = new Scalar(3, 148, 252); - static final Scalar PURPLE = new Scalar(158, 52, 235); - static final Scalar RED = new Scalar(255, 0, 0); - static final Scalar GREEN = new Scalar(0, 255, 0); - static final Scalar BLUE = new Scalar(0, 0, 255); - - static final int CONTOUR_LINE_THICKNESS = 2; - static final int CB_CHAN_IDX = 2; - - static class AnalyzedStone - { - StoneOrientation orientation; - double angle; - } - - enum StoneOrientation - { - UPRIGHT, - NOT_UPRIGHT - } - - ArrayList internalStoneList = new ArrayList<>(); - volatile ArrayList clientStoneList = new ArrayList<>(); - - /* - * Some stuff to handle returning our various buffers - */ - enum Stage - { - FINAL, - Cb, - MASK, - MASK_NR, - CONTOURS; - } - - Stage[] stages = Stage.values(); - - // Keep track of what stage the viewport is showing - int stageNum = 0; - - @Override - public void onViewportTapped() - { - /* - * Note that this method is invoked from the UI thread - * so whatever we do here, we must do quickly. - */ - - int nextStageNum = stageNum + 1; - - if(nextStageNum >= stages.length) - { - nextStageNum = 0; - } - - stageNum = nextStageNum; - } - - @Override - public Mat processFrame(Mat input) - { - // We'll be updating this with new data below - internalStoneList.clear(); - - /* - * Run the image processing - */ - for(MatOfPoint contour : findContours(input)) - { - analyzeContour(contour, input); - } - - clientStoneList = new ArrayList<>(internalStoneList); - - /* - * Decide which buffer to send to the viewport - */ - switch (stages[stageNum]) - { - case Cb: - { - return cbMat; - } - - case FINAL: - { - return input; - } - - case MASK: - { - return thresholdMat; - } - - case MASK_NR: - { - return morphedThreshold; - } - - case CONTOURS: - { - return contoursOnPlainImageMat; - } - } - - return input; - } - - public ArrayList getDetectedStones() - { - return clientStoneList; - } - - ArrayList findContours(Mat input) - { - // A list we'll be using to store the contours we find - ArrayList contoursList = new ArrayList<>(); - - // Convert the input image to YCrCb color space, then extract the Cb channel - Imgproc.cvtColor(input, cbMat, Imgproc.COLOR_RGB2YCrCb); - Core.extractChannel(cbMat, cbMat, CB_CHAN_IDX); - - // Threshold the Cb channel to form a mask, then run some noise reduction - Imgproc.threshold(cbMat, thresholdMat, CB_CHAN_MASK_THRESHOLD, 255, Imgproc.THRESH_BINARY_INV); - morphMask(thresholdMat, morphedThreshold); - - // Ok, now actually look for the contours! We only look for external contours. - Imgproc.findContours(morphedThreshold, contoursList, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE); - - // We do draw the contours we find, but not to the main input buffer. - input.copyTo(contoursOnPlainImageMat); - Imgproc.drawContours(contoursOnPlainImageMat, contoursList, -1, BLUE, CONTOUR_LINE_THICKNESS, 8); - - return contoursList; - } - - void morphMask(Mat input, Mat output) - { - /* - * Apply some erosion and dilation for noise reduction - */ - - Imgproc.erode(input, output, erodeElement); - Imgproc.erode(output, output, erodeElement); - - Imgproc.dilate(output, output, dilateElement); - Imgproc.dilate(output, output, dilateElement); - } - - void analyzeContour(MatOfPoint contour, Mat input) - { - // Transform the contour to a different format - Point[] points = contour.toArray(); - MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray()); - - // Do a rect fit to the contour, and draw it on the screen - RotatedRect rotatedRectFitToContour = Imgproc.minAreaRect(contour2f); - drawRotatedRect(rotatedRectFitToContour, input); - - // The angle OpenCV gives us can be ambiguous, so look at the shape of - // the rectangle to fix that. - double rotRectAngle = rotatedRectFitToContour.angle; - if (rotatedRectFitToContour.size.width < rotatedRectFitToContour.size.height) - { - rotRectAngle += 90; - } - - // Figure out the slope of a line which would run through the middle, lengthwise - // (Slope as in m from 'Y = mx + b') - double midlineSlope = Math.tan(Math.toRadians(rotRectAngle)); - - // We're going to split the this contour into two regions: one region for the points - // which fall above the midline, and one region for the points which fall below. - // We'll need a place to store the points as we split them, so we make ArrayLists - ArrayList aboveMidline = new ArrayList<>(points.length/2); - ArrayList belowMidline = new ArrayList<>(points.length/2); - - // Ok, now actually split the contour into those two regions we discussed earlier! - for(Point p : points) - { - if(rotatedRectFitToContour.center.y - p.y > midlineSlope * (rotatedRectFitToContour.center.x - p.x)) - { - aboveMidline.add(p); - } - else - { - belowMidline.add(p); - } - } - - // Now that we've split the contour into those two regions, we analyze each - // region independently. - ContourRegionAnalysis aboveMidlineMetrics = analyzeContourRegion(aboveMidline); - ContourRegionAnalysis belowMidlineMetrics = analyzeContourRegion(belowMidline); - - if(aboveMidlineMetrics == null || belowMidlineMetrics == null) - { - return; // Get out of dodge - } - - // We're going to draw line from the center of the bounding rect, to outside the bounding rect, in the - // direction of the side of the stone with the nubs. - Point displOfOrientationLinePoint2 = computeDisplacementForSecondPointOfStoneOrientationLine(rotatedRectFitToContour, rotRectAngle); - - /* - * If the difference in the densities of the two regions exceeds the threshold, - * then we assume the stone is on its side. Otherwise, if the difference is inside - * of the threshold, we assume it's upright. - */ - if(aboveMidlineMetrics.density < belowMidlineMetrics.density - DENSITY_UPRIGHT_THRESHOLD) - { - /* - * Assume the stone is on its side, with the top contour region being the - * one which contains the nubs - */ - - // Draw that line we were just talking about - Imgproc.line( - input, // Buffer we're drawing on - new Point( // First point of the line (center of bounding rect) - rotatedRectFitToContour.center.x, - rotatedRectFitToContour.center.y), - new Point( // Second point of the line (center - displacement we calculated earlier) - rotatedRectFitToContour.center.x-displOfOrientationLinePoint2.x, - rotatedRectFitToContour.center.y-displOfOrientationLinePoint2.y), - PURPLE, // Color we're drawing the line in - 2); // Thickness of the line we're drawing - - // We outline the contour region that we assumed to be the side with the nubs - Imgproc.drawContours(input, aboveMidlineMetrics.listHolderOfMatOfPoint, -1, TEAL, 2, 8); - - // Compute the absolute angle of the stone - double angle = -(rotRectAngle-90); - - // "Tag" the stone with text stating its absolute angle - drawTagText(rotatedRectFitToContour, Integer.toString((int) Math.round(angle))+" deg", input); - - AnalyzedStone analyzedStone = new AnalyzedStone(); - analyzedStone.angle = angle; - analyzedStone.orientation = StoneOrientation.NOT_UPRIGHT; - internalStoneList.add(analyzedStone); - } - else if(belowMidlineMetrics.density < aboveMidlineMetrics.density - DENSITY_UPRIGHT_THRESHOLD) - { - /* - * Assume the stone is on its side, with the bottom contour region being the - * one which contains the nubs - */ - - // Draw that line we were just talking about - Imgproc.line( - input, // Buffer we're drawing on - new Point( // First point of the line (center + displacement we calculated earlier) - rotatedRectFitToContour.center.x+displOfOrientationLinePoint2.x, - rotatedRectFitToContour.center.y+displOfOrientationLinePoint2.y), - new Point( // Second point of the line (center of bounding rect) - rotatedRectFitToContour.center.x, - rotatedRectFitToContour.center.y), - PURPLE, // Color we're drawing the line in - 2); // Thickness of the line we're drawing - - // We outline the contour region that we assumed to be the side with the nubs - Imgproc.drawContours(input, belowMidlineMetrics.listHolderOfMatOfPoint, -1, TEAL, 2, 8); - - // Compute the absolute angle of the stone - double angle = -(rotRectAngle-270); - - // "Tag" the stone with text stating its absolute angle - drawTagText(rotatedRectFitToContour, Integer.toString((int) Math.round(angle))+" deg", input); - - AnalyzedStone analyzedStone = new AnalyzedStone(); - analyzedStone.angle = angle; - analyzedStone.orientation = StoneOrientation.NOT_UPRIGHT; - internalStoneList.add(analyzedStone); - } - else - { - /* - * Assume the stone is upright - */ - - drawTagText(rotatedRectFitToContour, "UPRIGHT", input); - - AnalyzedStone analyzedStone = new AnalyzedStone(); - analyzedStone.angle = rotRectAngle; - analyzedStone.orientation = StoneOrientation.UPRIGHT; - internalStoneList.add(analyzedStone); - } - } - - static class ContourRegionAnalysis - { - /* - * This class holds the results of analyzeContourRegion() - */ - - double hullArea; - double contourArea; - double density; - List listHolderOfMatOfPoint; - } - - static ContourRegionAnalysis analyzeContourRegion(ArrayList contourPoints) - { - // drawContours() requires a LIST of contours (there's no singular drawContour() - // method), so we have to make a list, even though we're only going to use a single - // position in it... - MatOfPoint matOfPoint = new MatOfPoint(); - matOfPoint.fromList(contourPoints); - List listHolderOfMatOfPoint = Arrays.asList(matOfPoint); - - // Compute the convex hull of the contour - MatOfInt hullMatOfInt = new MatOfInt(); - Imgproc.convexHull(matOfPoint, hullMatOfInt); - - // Was the convex hull calculation successful? - if(hullMatOfInt.toArray().length > 0) - { - // The convex hull calculation tells us the INDEX of the points which - // which were passed in eariler which form the convex hull. That's all - // well and good, but now we need filter out that original list to find - // the actual POINTS which form the convex hull - Point[] hullPoints = new Point[hullMatOfInt.rows()]; - List hullContourIdxList = hullMatOfInt.toList(); - - for (int i = 0; i < hullContourIdxList.size(); i++) - { - hullPoints[i] = contourPoints.get(hullContourIdxList.get(i)); - } - - ContourRegionAnalysis analysis = new ContourRegionAnalysis(); - analysis.listHolderOfMatOfPoint = listHolderOfMatOfPoint; - - // Compute the hull area - analysis.hullArea = Imgproc.contourArea(new MatOfPoint(hullPoints)); - - // Compute the original contour area - analysis.contourArea = Imgproc.contourArea(listHolderOfMatOfPoint.get(0)); - - // Compute the contour density. This is the ratio of the contour area to the - // area of the convex hull formed by the contour - analysis.density = analysis.contourArea / analysis.hullArea; - - return analysis; - } - else - { - return null; - } - } - - static Point computeDisplacementForSecondPointOfStoneOrientationLine(RotatedRect rect, double unambiguousAngle) - { - // Note: we return a point, but really it's not a point in space, we're - // simply using it to hold X & Y displacement values from the middle point - // of the bounding rect. - Point point = new Point(); - - // Figure out the length of the short side of the rect - double shortSideLen = Math.min(rect.size.width, rect.size.height); - - // We draw a line that's 3/4 of the length of the short side of the rect - double lineLength = shortSideLen * .75; - - // The line is to be drawn at 90 deg relative to the midline running through - // the rect lengthwise - point.x = (int) (lineLength * Math.cos(Math.toRadians(unambiguousAngle+90))); - point.y = (int) (lineLength * Math.sin(Math.toRadians(unambiguousAngle+90))); - - return point; - } - - static void drawTagText(RotatedRect rect, String text, Mat mat) - { - Imgproc.putText( - mat, // The buffer we're drawing on - text, // The text we're drawing - new Point( // The anchor point for the text - rect.center.x-50, // x anchor point - rect.center.y+25), // y anchor point - Imgproc.FONT_HERSHEY_PLAIN, // Font - 1, // Font size - TEAL, // Font color - 1); // Font thickness - } - - static void drawRotatedRect(RotatedRect rect, Mat drawOn) - { - /* - * Draws a rotated rect by drawing each of the 4 lines individually - */ - - Point[] points = new Point[4]; - rect.points(points); - - for(int i = 0; i < 4; ++i) - { - Imgproc.line(drawOn, points[i], points[(i+1)%4], RED, 2); - } - } +/* + * Copyright (c) 2020 OpenFTC Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.teamcode; + +import org.openftc.easyopencv.OpenCvPipeline; + +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.MatOfInt; +import org.opencv.core.MatOfPoint; +import org.opencv.core.MatOfPoint2f; +import org.opencv.core.Point; +import org.opencv.core.RotatedRect; +import org.opencv.core.Scalar; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class StoneOrientationAnalysisPipeline extends OpenCvPipeline +{ + /* + * Our working image buffers + */ + Mat cbMat = new Mat(); + Mat thresholdMat = new Mat(); + Mat morphedThreshold = new Mat(); + Mat contoursOnPlainImageMat = new Mat(); + + /* + * Threshold values + */ + static final int CB_CHAN_MASK_THRESHOLD = 80; + static final double DENSITY_UPRIGHT_THRESHOLD = 0.03; + + /* + * The elements we use for noise reduction + */ + Mat erodeElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3)); + Mat dilateElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(6, 6)); + + /* + * Colors + */ + static final Scalar TEAL = new Scalar(3, 148, 252); + static final Scalar PURPLE = new Scalar(158, 52, 235); + static final Scalar RED = new Scalar(255, 0, 0); + static final Scalar GREEN = new Scalar(0, 255, 0); + static final Scalar BLUE = new Scalar(0, 0, 255); + + static final int CONTOUR_LINE_THICKNESS = 2; + static final int CB_CHAN_IDX = 2; + + static class AnalyzedStone + { + StoneOrientation orientation; + double angle; + } + + enum StoneOrientation + { + UPRIGHT, + NOT_UPRIGHT + } + + ArrayList internalStoneList = new ArrayList<>(); + volatile ArrayList clientStoneList = new ArrayList<>(); + + /* + * Some stuff to handle returning our various buffers + */ + enum Stage + { + FINAL, + Cb, + MASK, + MASK_NR, + CONTOURS; + } + + Stage[] stages = Stage.values(); + + // Keep track of what stage the viewport is showing + int stageNum = 0; + + @Override + public void onViewportTapped() + { + /* + * Note that this method is invoked from the UI thread + * so whatever we do here, we must do quickly. + */ + + int nextStageNum = stageNum + 1; + + if(nextStageNum >= stages.length) + { + nextStageNum = 0; + } + + stageNum = nextStageNum; + } + + @Override + public Mat processFrame(Mat input) + { + // We'll be updating this with new data below + internalStoneList.clear(); + + /* + * Run the image processing + */ + for(MatOfPoint contour : findContours(input)) + { + analyzeContour(contour, input); + } + + clientStoneList = new ArrayList<>(internalStoneList); + + /* + * Decide which buffer to send to the viewport + */ + switch (stages[stageNum]) + { + case Cb: + { + return cbMat; + } + + case FINAL: + { + return input; + } + + case MASK: + { + return thresholdMat; + } + + case MASK_NR: + { + return morphedThreshold; + } + + case CONTOURS: + { + return contoursOnPlainImageMat; + } + } + + return input; + } + + public ArrayList getDetectedStones() + { + return clientStoneList; + } + + ArrayList findContours(Mat input) + { + // A list we'll be using to store the contours we find + ArrayList contoursList = new ArrayList<>(); + + // Convert the input image to YCrCb color space, then extract the Cb channel + Imgproc.cvtColor(input, cbMat, Imgproc.COLOR_RGB2YCrCb); + Core.extractChannel(cbMat, cbMat, CB_CHAN_IDX); + + // Threshold the Cb channel to form a mask, then run some noise reduction + Imgproc.threshold(cbMat, thresholdMat, CB_CHAN_MASK_THRESHOLD, 255, Imgproc.THRESH_BINARY_INV); + morphMask(thresholdMat, morphedThreshold); + + // Ok, now actually look for the contours! We only look for external contours. + Imgproc.findContours(morphedThreshold, contoursList, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE); + + // We do draw the contours we find, but not to the main input buffer. + input.copyTo(contoursOnPlainImageMat); + Imgproc.drawContours(contoursOnPlainImageMat, contoursList, -1, BLUE, CONTOUR_LINE_THICKNESS, 8); + + return contoursList; + } + + void morphMask(Mat input, Mat output) + { + /* + * Apply some erosion and dilation for noise reduction + */ + + Imgproc.erode(input, output, erodeElement); + Imgproc.erode(output, output, erodeElement); + + Imgproc.dilate(output, output, dilateElement); + Imgproc.dilate(output, output, dilateElement); + } + + void analyzeContour(MatOfPoint contour, Mat input) + { + // Transform the contour to a different format + Point[] points = contour.toArray(); + MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray()); + + // Do a rect fit to the contour, and draw it on the screen + RotatedRect rotatedRectFitToContour = Imgproc.minAreaRect(contour2f); + drawRotatedRect(rotatedRectFitToContour, input); + + // The angle OpenCV gives us can be ambiguous, so look at the shape of + // the rectangle to fix that. + double rotRectAngle = rotatedRectFitToContour.angle; + if (rotatedRectFitToContour.size.width < rotatedRectFitToContour.size.height) + { + rotRectAngle += 90; + } + + // Figure out the slope of a line which would run through the middle, lengthwise + // (Slope as in m from 'Y = mx + b') + double midlineSlope = Math.tan(Math.toRadians(rotRectAngle)); + + // We're going to split the this contour into two regions: one region for the points + // which fall above the midline, and one region for the points which fall below. + // We'll need a place to store the points as we split them, so we make ArrayLists + ArrayList aboveMidline = new ArrayList<>(points.length/2); + ArrayList belowMidline = new ArrayList<>(points.length/2); + + // Ok, now actually split the contour into those two regions we discussed earlier! + for(Point p : points) + { + if(rotatedRectFitToContour.center.y - p.y > midlineSlope * (rotatedRectFitToContour.center.x - p.x)) + { + aboveMidline.add(p); + } + else + { + belowMidline.add(p); + } + } + + // Now that we've split the contour into those two regions, we analyze each + // region independently. + ContourRegionAnalysis aboveMidlineMetrics = analyzeContourRegion(aboveMidline); + ContourRegionAnalysis belowMidlineMetrics = analyzeContourRegion(belowMidline); + + if(aboveMidlineMetrics == null || belowMidlineMetrics == null) + { + return; // Get out of dodge + } + + // We're going to draw line from the center of the bounding rect, to outside the bounding rect, in the + // direction of the side of the stone with the nubs. + Point displOfOrientationLinePoint2 = computeDisplacementForSecondPointOfStoneOrientationLine(rotatedRectFitToContour, rotRectAngle); + + /* + * If the difference in the densities of the two regions exceeds the threshold, + * then we assume the stone is on its side. Otherwise, if the difference is inside + * of the threshold, we assume it's upright. + */ + if(aboveMidlineMetrics.density < belowMidlineMetrics.density - DENSITY_UPRIGHT_THRESHOLD) + { + /* + * Assume the stone is on its side, with the top contour region being the + * one which contains the nubs + */ + + // Draw that line we were just talking about + Imgproc.line( + input, // Buffer we're drawing on + new Point( // First point of the line (center of bounding rect) + rotatedRectFitToContour.center.x, + rotatedRectFitToContour.center.y), + new Point( // Second point of the line (center - displacement we calculated earlier) + rotatedRectFitToContour.center.x-displOfOrientationLinePoint2.x, + rotatedRectFitToContour.center.y-displOfOrientationLinePoint2.y), + PURPLE, // Color we're drawing the line in + 2); // Thickness of the line we're drawing + + // We outline the contour region that we assumed to be the side with the nubs + Imgproc.drawContours(input, aboveMidlineMetrics.listHolderOfMatOfPoint, -1, TEAL, 2, 8); + + // Compute the absolute angle of the stone + double angle = -(rotRectAngle-90); + + // "Tag" the stone with text stating its absolute angle + drawTagText(rotatedRectFitToContour, Integer.toString((int) Math.round(angle))+" deg", input); + + AnalyzedStone analyzedStone = new AnalyzedStone(); + analyzedStone.angle = angle; + analyzedStone.orientation = StoneOrientation.NOT_UPRIGHT; + internalStoneList.add(analyzedStone); + } + else if(belowMidlineMetrics.density < aboveMidlineMetrics.density - DENSITY_UPRIGHT_THRESHOLD) + { + /* + * Assume the stone is on its side, with the bottom contour region being the + * one which contains the nubs + */ + + // Draw that line we were just talking about + Imgproc.line( + input, // Buffer we're drawing on + new Point( // First point of the line (center + displacement we calculated earlier) + rotatedRectFitToContour.center.x+displOfOrientationLinePoint2.x, + rotatedRectFitToContour.center.y+displOfOrientationLinePoint2.y), + new Point( // Second point of the line (center of bounding rect) + rotatedRectFitToContour.center.x, + rotatedRectFitToContour.center.y), + PURPLE, // Color we're drawing the line in + 2); // Thickness of the line we're drawing + + // We outline the contour region that we assumed to be the side with the nubs + Imgproc.drawContours(input, belowMidlineMetrics.listHolderOfMatOfPoint, -1, TEAL, 2, 8); + + // Compute the absolute angle of the stone + double angle = -(rotRectAngle-270); + + // "Tag" the stone with text stating its absolute angle + drawTagText(rotatedRectFitToContour, Integer.toString((int) Math.round(angle))+" deg", input); + + AnalyzedStone analyzedStone = new AnalyzedStone(); + analyzedStone.angle = angle; + analyzedStone.orientation = StoneOrientation.NOT_UPRIGHT; + internalStoneList.add(analyzedStone); + } + else + { + /* + * Assume the stone is upright + */ + + drawTagText(rotatedRectFitToContour, "UPRIGHT", input); + + AnalyzedStone analyzedStone = new AnalyzedStone(); + analyzedStone.angle = rotRectAngle; + analyzedStone.orientation = StoneOrientation.UPRIGHT; + internalStoneList.add(analyzedStone); + } + } + + static class ContourRegionAnalysis + { + /* + * This class holds the results of analyzeContourRegion() + */ + + double hullArea; + double contourArea; + double density; + List listHolderOfMatOfPoint; + } + + static ContourRegionAnalysis analyzeContourRegion(ArrayList contourPoints) + { + // drawContours() requires a LIST of contours (there's no singular drawContour() + // method), so we have to make a list, even though we're only going to use a single + // position in it... + MatOfPoint matOfPoint = new MatOfPoint(); + matOfPoint.fromList(contourPoints); + List listHolderOfMatOfPoint = Arrays.asList(matOfPoint); + + // Compute the convex hull of the contour + MatOfInt hullMatOfInt = new MatOfInt(); + Imgproc.convexHull(matOfPoint, hullMatOfInt); + + // Was the convex hull calculation successful? + if(hullMatOfInt.toArray().length > 0) + { + // The convex hull calculation tells us the INDEX of the points which + // which were passed in eariler which form the convex hull. That's all + // well and good, but now we need filter out that original list to find + // the actual POINTS which form the convex hull + Point[] hullPoints = new Point[hullMatOfInt.rows()]; + List hullContourIdxList = hullMatOfInt.toList(); + + for (int i = 0; i < hullContourIdxList.size(); i++) + { + hullPoints[i] = contourPoints.get(hullContourIdxList.get(i)); + } + + ContourRegionAnalysis analysis = new ContourRegionAnalysis(); + analysis.listHolderOfMatOfPoint = listHolderOfMatOfPoint; + + // Compute the hull area + analysis.hullArea = Imgproc.contourArea(new MatOfPoint(hullPoints)); + + // Compute the original contour area + analysis.contourArea = Imgproc.contourArea(listHolderOfMatOfPoint.get(0)); + + // Compute the contour density. This is the ratio of the contour area to the + // area of the convex hull formed by the contour + analysis.density = analysis.contourArea / analysis.hullArea; + + return analysis; + } + else + { + return null; + } + } + + static Point computeDisplacementForSecondPointOfStoneOrientationLine(RotatedRect rect, double unambiguousAngle) + { + // Note: we return a point, but really it's not a point in space, we're + // simply using it to hold X & Y displacement values from the middle point + // of the bounding rect. + Point point = new Point(); + + // Figure out the length of the short side of the rect + double shortSideLen = Math.min(rect.size.width, rect.size.height); + + // We draw a line that's 3/4 of the length of the short side of the rect + double lineLength = shortSideLen * .75; + + // The line is to be drawn at 90 deg relative to the midline running through + // the rect lengthwise + point.x = (int) (lineLength * Math.cos(Math.toRadians(unambiguousAngle+90))); + point.y = (int) (lineLength * Math.sin(Math.toRadians(unambiguousAngle+90))); + + return point; + } + + static void drawTagText(RotatedRect rect, String text, Mat mat) + { + Imgproc.putText( + mat, // The buffer we're drawing on + text, // The text we're drawing + new Point( // The anchor point for the text + rect.center.x-50, // x anchor point + rect.center.y+25), // y anchor point + Imgproc.FONT_HERSHEY_PLAIN, // Font + 1, // Font size + TEAL, // Font color + 1); // Font thickness + } + + static void drawRotatedRect(RotatedRect rect, Mat drawOn) + { + /* + * Draws a rotated rect by drawing each of the 4 lines individually + */ + + Point[] points = new Point[4]; + rect.points(points); + + for(int i = 0; i < 4; ++i) + { + Imgproc.line(drawOn, points[i], points[(i+1)%4], RED, 2); + } + } } \ No newline at end of file diff --git a/USAGE.md b/USAGE.md deleted file mode 100644 index 8ed26315..00000000 --- a/USAGE.md +++ /dev/null @@ -1,420 +0,0 @@ -# Usage Explanation - -### This guide is still a work in progress - -## Welcome! - -Thank you for your interest in EOCV-Sim :) - -We made this tool in hopes that it will be useful for all FTC teams seeking a way of learning and developing their seasonal OpenCV algorithms in a easy and straightforward way, while also providing some extra tools to improve the experience of developing such algorithms. - -The main purpose of this software is to simulate the package & class structure of OpenFTC's EasyOpenCV and a little bit of the FTC SDK, while also providing OpenCV functionality and a simple GUI. - -By simulating the aforementioned structure, it allows the imports, class names, etc. to be the same as they would if you were using the FTC SDK with EasyOpenCV, allowing you to simply copy paste your vision code onto your Android Studio project once you want to transfer it to a robot.
- -## Table of Contents -- [Workspaces & VS Code](#workspaces--vs-code) -- [IntelliJ Project Structure (with the old installation method)](#intellij-project-structure-with-the-old-installation-method) - - [Creating pipelines](#creating-pipelines-with-the-old-installation-method) -- [Empty Sample Pipeline](#empty-sample-pipeline) -- [Input Sources](#input-sources) - - [Creating an input source](#creating-an-input-source) -- [Telemetry](#telemetry) -- [Variable Tuner](#variable-tuner) - - [Functionality](#tuner-functionality) - - [Configuration](#tuner-configuration) - - [Sample Usage](#sample-usage-of-the-variable-tuner) - -## Workspaces & VS Code - -**This part is applicable with any installation method** - -### Introduction to workspaces - -Workspaces are the new major feature introduced in v3.0.0! - -A workspace basically consists of a folder containing `.java` source files and resource files, which are compiled on the fly by EOCV-Sim. This removes the need of having to use Gradle for running builds, or even allowing to see code changes in real time within a few seconds or even milliseconds! - -Note that a Java Development Kit (JDK) is needed for using this feature, since Java Runtime Environments (JREs) don't come with a compiler packaged, - -The simulator watches for file changes in the background every 800ms, and triggers a build automatically. This can be greately beneficial for writing pipelines, plus the variable tuner that also allows to change variables in real time to reflect changes immediately. The simulator also tries to snapshot the state of the pipeline selected before the build, and try to apply it to the (possibly new if it was compiled on runtime) selected pipeline if the package and class names of the old and the new pipeline match, therefore values tuned with the variable tuner can be kept between builds even if the source code has changed. - -The simulator creates and selects by default a workspace in the user folder, `~/.eocvsim/default_workspace`, which contains a sample GrayscalePipeline that is compiled and added on runtime, but you can change it by doing the following steps: - -1) Go under the "Pipelines" section, click the "Workspace" and finally "Select workspace". Or alternatively, you can also go to Workspace -> Select workspace - - - -2) Select a folder in the file explorer that pops up - -3) Done! The sim should select the folder as a workspace, create a `eocvsim_workspace.json` file if it doesn't exist, start running a build and watch for file changes in the selected folder - -### VS Code - -### The `eocvsim_workspace.json` file - -## IntelliJ project structure (with the old installation method) - -**This part is only applicable if you downloaded EOCV-Sim with the [old method explained in the README](/README.md#altenative-installation-method-intellij-idea)** - -EOCV-Sim uses Gradle starting from v2.0.0, because of this, the project structure is a bit different. For finding the package in which the pipelines have to be placed:
-1) Pop out the parent EOCV-Sim project folder by clicking on the "*>*" arrow -2) Find the TeamCode module (folder) and pop it out just like before -3) Find the src folder and open it -4) Now you will find the *org.firstinspires.ftc.teamcode* package, in which you should place all your pipelines and some sample pipelines are already there.
- -These steps are illustrated in this gif:
- -
- -### Creating pipelines (with the old installation method) - -**This part is also only applicable if you downloaded EOCV-Sim with the [old method explained in the README](/README.md#altenative-installation-method-intellij-idea)** - -As said before, all of the pipeline classes **should be** placed under the *org.firstinspires.ftc.teamcode* package, in the *TeamCode* module. This way, they will be -automatically detected by the simulator and will be selectionable from the GUI. - -
- -*(Also, the simulator already comes by default with some EasyOpenCV samples)*
- -To create a new java class, follow these steps:
-1) In the project files menu, open the TeamCode module -2) Find the *org.firstinspires.ftc.teamcode* package and right click on it -3) On the context menu, click on *New > Java Class* -4) A new menu will appear, type a name and make sure the *Class* option is selected -5) Once you have typed a name, press enter and the class will be created - -Here's a quick gif illustrating these steps:
- -
- -## Empty sample pipeline - -If you want your class to be a pipeline, it **should also** extend the EOCV's OpenCvPipeline abstract class and override the processFrame() method.

-Here's a empty pipeline template, with the SamplePipeline class we created before: - -```java -package org.firstinspires.ftc.teamcode; - -import org.opencv.core.Mat; -import org.openftc.easyopencv.OpenCvPipeline; - -public class SamplePipeline extends OpenCvPipeline { - - @Override - public void init(Mat input) { - /* Executed once, when the pipeline is selected */ - } - - @Override - public Mat processFrame(Mat input) { - /* Executed each frame, the returned mat will be the one displayed */ - /* Processing and detection stuff here */ - return input; // Return the input mat - // (Or a new, processed mat) - } - - @Override - public void onViewportTapped() { - /* - * Executed everytime when the pipeline view is tapped/clicked. - * This is executed from the UI thread, so whatever you do here, - * it must be done it quickly. - */ - } - -} -``` - -### For more detailed information about pipelines, make sure to check out the [EasyOpenCV docs](https://github.com/OpenFTC/EasyOpenCV/blob/master/doc/user_docs/pipelines_overview.md) - -## Input Sources - -To allow multiple ways to test your pipeline, the simulator comes with *Input Sources*, which are the ones in charge of giving your pipeline the input Mats, As of right now, the sim has three types of Input Sources: - -- **Image Source:** - - These will feed your pipeline with a static image loaded in your computer's hard drive. - - To save resources, your pipeline will just run once when you select an image source, but you can optionally resume the pipeline execution by clicking the "Pause" button under the pipeline selector. -- **Camera Source:** - - These will feed your pipeline with a constantly changing video stream from a specified camera plugged in your computer. - - Unlike the image sources, these will not pause the execution of you pipeline by default, but you can click the "Pause" button to pause it at any time. -- **Video Source:** - - These will feed your pipeline with a constantly changing video stream from a file in your hard drive, pause rules are the same as camera sources. - - Most tested video format is *\*.avi*, although it depends on your operating system's support. - -### Creating an Input Source - -1) From your Operating System's file manager, grab a media file such as an image or a video. - -## Telemetry - -There's also an SDK-like Telemetry implementation in the sim. -In 1.1.0 (when it was introduced) you could simply access it from your pipeline since it was an instance variable ```telemetry```. - -But, starting 2.0.0, to make it more alike to an actual EOCV pipeline, you need to implement a public constructor which takes a Telemetry parameter, then creating and setting an instance variable from that constructor: - -```java -package org.firstinspires.ftc.teamcode; - -import org.opencv.core.Mat; -import org.openftc.easyopencv.OpenCvPipeline; - -import org.firstinspires.ftc.robotcore.external.Telemetry; - -public class TelemetryPipeline extends OpenCvPipeline { - - Telemetry telemetry; - - public TelemetryPipeline(Telemetry telemetry) { - this.telemetry = telemetry; - } - - @Override - public Mat processFrame(Mat input) { - telemetry.addData("[Hello]", "World!"); - telemetry.update(); - return input; // Return the input mat - } - -} -``` - -Which then produces the following result:
- -
- -For further information about telemetry, you can check out the [SDK docs on Telemetry](https://ftctechnh.github.io/ftc_app/doc/javadoc/org/firstinspires/ftc/robotcore/external/Telemetry.html), note that not all the methods are implemented for EOCV-Sim - -## Variable Tuner - -From 2.0.0 and on, there's a variable tuner implemented into the simulator, inspired by the one in FTC Dashboard, it allows to edit public, non-final variables from your pipeline in real time seamlessly through Java reflection.
- -This variable tuner can be found at the bottom part of the sim, click on the divider bar to open it:
- -
-
- -This screenshot is from the DefaultPipeline (the one selected when the simulator opens). This variable controls the blur value for the output Mat. You can play with this value to see the tuner functionality.

-If we look into the DefaultPipeline code, we can see that it is simply a **public int** instance variable, not marked as final (alongside with the Telemetry initialization stuff we explained before):
- -
- -The tuner supports a handful of Java types such as most primivites (int, boolean...) and some other types from OpenCV.
-The full list of types currently supported by the tuner on the latest version is:
- - Java: - - int (or Integer) - - float (or Float) - - double (or Double) - - long (or Long) - - boolean (or Boolean) - - String - - Enums - - OpenCV: - - Scalar - - Rect - - Point - -### Tuner functionality - -In the screenshot above, you might have noticed we have three buttons in the field (these buttons appear on field types with at least one textbox/slider). Those were introduced in 2.1.0 to provide extra functionality to the tuner. We have three buttons (options), five parts: - -
- -1) **Text fields/slider toggle** - - Toggles between sliders and textboxes for setting the value to this field -2) **Config** - - Configures various aspects of this tunable field, such as the slider range and picker's color space -3) **Color picker** - - Turns on "color picker" mode, which allows to grab a single pixel from the image and sets it to the selected tunable field - - Sets the color value to the first four textboxes/sliders of this field, if less than four textboxes/sliders are available, it will set the values to the available ones and discard all of the value(s) that can't be copied into the field -4) **Name** - - Displays the name of the variable (declared in the pipeline) -5) **Text field** - - The part in which you can modify the value for this field. Can be toggled to sliders as mentioned before - - Some fields (such as OpenCV Scalars) might have more than one text field - -### Tuner configuration - -When opening the config for a specific field with the aforementioned button (figure #2), you'll see this popup: - -
- -1) **Slider range** - - Sets the **range for the sliders**, defaults to 0-255 since that's the most commonly used, especially for color tuning. - - Negative & positive values allowed, decimal values are allowed only if the field is decimal (such as floats or doubles) -2) **Color space** - - Sets the **color space** for the color picker to return. Defaults to RGB -3) **Apply to all fields...** - - Applies **this configuration** globally or specifically to this field *(see below for further details)* -4) **Config source** - - Displays the source of this config: default global, global, local or specific *(see below for further details)* - -#### Applying tuner configuration - -When using the variable tuner and making configurations, it's sometimes convenient to have some way to store those configurations so that you don't have to reconfig for every field, or every time you select a new pipeline.
- -For this, the sim has a "apply to all" functionality to store common configurations: - -
- -As you can see in the image, when clicking the "apply to all" button, two options appear: - -- **"Globally"** - - Applies the configuration **globally** (to all fields without an "specific" config) - - Note that this doesn't mean that by clicking this option will override all configurations, see below. -- **"Of this type"** (or "specific") - - Applies the configuration to all fields **of the same type** as the current config one. - - (In the case of the example in the screenshot, the blur field in DefaultPipeline, this config will be applied to all *int* fields) - -#### Tuner configuration priority order - -As mentioned before, by applying a "global" configuration, it doesn't mean that it will override the specific configs.
-Rather, there's an specific priority order to determine the configuration that will be given to each tunable field: - -1) **Local** - - This is the one that applies when you modify the configuration values without applying *"globally"* or *"specifically to this type"* - - Simply means that you modified the config without saving it. It will be reset once you select a different pipeline -2) **Type-specific** - - If there's a specific configuration for the type *(such as **int** in the example)*, it will be the one that gets the most priority - - You can define a "type-specific" configuration by clicking on "Applying to all fields..." -> "Of this type" -3) **Global** - - If there's not a type-specific configuration present, the configuration will default to the "global" one - - You can define a "Global" configuration by clicking on "Applying to all fields..." -> "Globally" -5) **Tunable field suggestion** - - If there's not a global configuration, but the current tunable field suggests a *"mode" (sliders or textboxes)*, then that suggestion will be applied - - For example, with OpenCV's Scalars, the tunable field suggest to use sliders since it's more convenient for tuning this type of field -6) **Default global** - - If there's not any configuration or suggestion at all, the field will default to the *"default global"* configuration - - The default config has a slider range from 0 to 255 and a color space of RGB - -### Sample usage of the variable tuner - -Let's say we need to tune a threshold for finding the ring stack in the 2020-2021 "Ultimate Goal" game. For this, we will use the YCrCb color space since it's one of the most used ones in FTC and it behaves better under different lightning conditions. (see [this article](https://learnopencv.com/color-spaces-in-opencv-cpp-python/) for more extended explaination and comparation of different color spaces).
- -We can write a simple pipeline for achieving this, taking advantage of the variable tuner. Here's an example code with detailed comments: - -```java -package org.firstinspires.ftc.teamcode; - -import org.opencv.core.Core; -import org.opencv.core.Mat; -import org.opencv.core.Scalar; -import org.opencv.imgproc.Imgproc; -import org.openftc.easyopencv.OpenCvPipeline; - -public class SimpleThresholdPipeline extends OpenCvPipeline { - - /* - * These are our variables that will be - * modifiable from the variable tuner. - * - * Scalars in OpenCV are generally used to - * represent color. So our values in the - * lower and upper Scalars here represent - * the Y, Cr and Cb values respectively. - * - * YCbCr, like most color spaces, range - * from 0-255, so we default to those - * min and max values here for now, meaning - * that all pixels will be shown. - */ - public Scalar lower = new Scalar(0, 0, 0); - public Scalar upper = new Scalar(255, 255, 255); - - /* - * A good practice when typing EOCV pipelines is - * declaring the Mats you will use here at the top - * of your pipeline, to reuse the same buffers every - * time. This removes the need to call mat.release() - * with every Mat you create on the processFrame method, - * and therefore, reducing the possibility of getting a - * memory leak and causing the app to crash due to an - * "Out of Memory" error. - */ - private Mat ycrcbMat = new Mat(); - private Mat binaryMat = new Mat(); - private Mat maskedInputMat = new Mat(); - - @Override - public Mat processFrame(Mat input) { - /* - * Converts our input mat from RGB to YCrCb. - * EOCV ALWAYS returns RGB mats, so you'd - * always convert from RGB to the color - * space you want to use. - * - * Takes our "input" mat as an input, and outputs - * to a separate Mat buffer "ycrcbMat" - */ - Imgproc.cvtColor(input, ycrcbMat, Imgproc.COLOR_RGB2YCrCb); - - /* - * This is where our thresholding actually happens. - * Takes our "ycrcbMat" as input and outputs a "binary" - * Mat to "binaryMat" of the same size as our input. - * "Discards" all the pixels outside the bounds specified - * by the scalars above (and modifiable with EOCV-Sim's - * live variable tuner.) - * - * Binary meaning that we have either a 0 or 255 value - * for every pixel. - * - * 0 represents our pixels that were outside the bounds - * 255 represents our pixels that are inside the bounds - */ - Core.inRange(ycrcbMat, lower, upper, binaryMat); - - /* - * Release the reusable Mat so that old data doesn't - * affect the next step in the current processing - */ - maskedInputMat.release(); - - /* - * Now, with our binary Mat, we perform a "bitwise and" - * to our input image, meaning that we will perform a mask - * which will include the pixels from our input Mat which - * are "255" in our binary Mat (meaning that they're inside - * the range) and will discard any other pixel outside the - * range (RGB 0, 0, 0. All discarded pixels will be black) - */ - Core.bitwise_and(input, input, maskedInputMat, binaryMat); - - /* - * The Mat returned from this method is the - * one displayed on the viewport. - * - * To visualize our threshold, we'll return - * the "masked input mat" which shows the - * pixel from the input Mat that were inside - * the threshold range. - */ - return maskedInputMat; - } - -} -``` - -And so, when initially selecting this pipeline in the simulator, it's initial state should look something like this:
- -
- -All pixels from the input Mat are visible entirely, this is because we specified a range of 0-255 for all three channels (see the sliders values). Since those values are the minimum (0%) and maximum (100%) for YCrCb respectively, all pixels are able to go through our "threshold".
- -Other thing to note here is that we have sliders instead of textboxes for both Scalars. This is the "default" behavior when using a variable of this type, since sliders are the most optimal option to tune thresholds. This behavior can be overriden by any user configuration, by toggling off the button located at the top left with the sliders icon.

-If you want to permanently change this, go into the field config by clicking on the button with the gear icon, then click "apply to all" and select whether you wanna apply this config to all fields globally, or specifically to the field type (Scalar in this case), as explained before.
- -Anyways, back to the sample. After a bit of playing around with the sliders, it's possible to come up with some decent values which successfully filter out the orange ring stack out of everything else:
- -
- -A problem with the YCrCb color space, especially this year, is that the difference between red and orange is very subtle. So therefore we need to play with the values for a good while until we find some that filters out the red from the goals but displays the ring stack. Or do some other technique alongside thresholding such as [FTCLib's contour ring pipeline](https://github.com/FTCLib/FTCLib/blob/3a43b191b18581a2f741588f9b8ab60c13b7fb6c/core/vision/src/main/java/com/arcrobotics/ftclib/vision/UGContourRingPipeline.kt#L46) with the "horizon" mechanism.
- -Some other nice features can be added to this sample, such as an enum for choosing the color space and Telemetry: - -
- -To keep this explaination simple, you can find the final pipeline [here](https://github.com/serivesmejia/EOCV-Sim/blob/dev/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/SimpleThresholdPipeline.java) with the new demonstrated features, in the TeamCode module, since serves as a good sample alongside other sample classes from EOCV itself. diff --git a/build.common.gradle b/build.common.gradle index f2485043..128e5e3c 100644 --- a/build.common.gradle +++ b/build.common.gradle @@ -1,12 +1,12 @@ -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -compileKotlin { - kotlinOptions { - jvmTarget = "1.8" - freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" - useIR = true - } -} +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + useIR = true + } +} diff --git a/build.gradle b/build.gradle index 22c9ceac..6fd3d7a8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,61 +1,65 @@ -import java.nio.file.Paths -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter - -buildscript { - ext { - kotlin_version = "1.5.10" - kotlinx_coroutines_version = "1.5.0-native-mt" - - env = findProperty('env') == 'release' ? 'release' : 'dev' - - println("Current build is: $env") - } - - repositories { - mavenCentral() - } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - group 'com.github.serivesmejia' - version '3.1.0' - - ext { - standardVersion = version - } - - repositories { - mavenCentral() - } - - tasks.withType(Jar) { - manifest { - attributes['Main-Class'] = 'com.github.serivesmejia.eocvsim.Main' - } - } - - if(env == 'dev') { - String date = DateTimeFormatter.ofPattern( - "yyMMdd-HHmm" - ).format(LocalDateTime.now()) - - String hash = findProperty('hash') - - version += "-dev-${hash ?: date}" - println("Final version of ${project} is $version") - - File libsFolder = Paths.get( - projectDir.absolutePath, 'build', 'libs' - ).toFile() - - for(file in libsFolder.listFiles()) { - if(file.name.contains("dev") && file.name.endsWith(".jar")) - file.delete() - } - } -} +import java.nio.file.Paths +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +buildscript { + ext { + kotlin_version = "1.5.10" + kotlinx_coroutines_version = "1.5.0-native-mt" + opencv_version = "4.5.1-2" + apriltag_plugin_version = "1.0.0" + + env = findProperty('env') == 'release' ? 'release' : 'dev' + + println("Current build is: $env") + } + + repositories { + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + group 'com.github.deltacv' + version '3.2.0' + + ext { + standardVersion = version + } + + repositories { + mavenCentral() + mavenLocal() + maven { url "https://jitpack.io" } + } + + tasks.withType(Jar) { + manifest { + attributes['Main-Class'] = 'com.github.serivesmejia.eocvsim.Main' + } + } + + if(env == 'dev') { + String date = DateTimeFormatter.ofPattern( + "yyMMdd-HHmm" + ).format(LocalDateTime.now()) + + String hash = findProperty('hash') + + version += "-dev-${hash ?: date}" + println("Final version of ${project} is $version") + + File libsFolder = Paths.get( + projectDir.absolutePath, 'build', 'libs' + ).toFile() + + for(file in libsFolder.listFiles()) { + if(file.name.contains("dev") && file.name.endsWith(".jar")) + file.delete() + } + } +} diff --git a/doc/images/eocvsim_screenshot_installation_1.png b/doc/images/eocvsim_screenshot_installation_1.png deleted file mode 100644 index eedb048a..00000000 Binary files a/doc/images/eocvsim_screenshot_installation_1.png and /dev/null differ diff --git a/doc/images/eocvsim_screenshot_installation_2.png b/doc/images/eocvsim_screenshot_installation_2.png deleted file mode 100644 index a22441b7..00000000 Binary files a/doc/images/eocvsim_screenshot_installation_2.png and /dev/null differ diff --git a/doc/images/eocvsim_screenshot_installation_3.png b/doc/images/eocvsim_screenshot_installation_3.png deleted file mode 100644 index 49c9d790..00000000 Binary files a/doc/images/eocvsim_screenshot_installation_3.png and /dev/null differ diff --git a/doc/images/eocvsim_screenshot_installation_4.png b/doc/images/eocvsim_screenshot_installation_4.png deleted file mode 100644 index 8a88fded..00000000 Binary files a/doc/images/eocvsim_screenshot_installation_4.png and /dev/null differ diff --git a/doc/images/eocvsim_screenshot_installation_5.png b/doc/images/eocvsim_screenshot_installation_5.png deleted file mode 100644 index fffbbc91..00000000 Binary files a/doc/images/eocvsim_screenshot_installation_5.png and /dev/null differ diff --git a/doc/images/eocvsim_screenshot_structure.png b/doc/images/eocvsim_screenshot_structure.png deleted file mode 100644 index b82ab343..00000000 Binary files a/doc/images/eocvsim_screenshot_structure.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_createclass.gif b/doc/images/eocvsim_usage_createclass.gif deleted file mode 100644 index 32f9b6ef..00000000 Binary files a/doc/images/eocvsim_usage_createclass.gif and /dev/null differ diff --git a/doc/images/eocvsim_usage_defaultpipeline.png b/doc/images/eocvsim_usage_defaultpipeline.png deleted file mode 100644 index aeaf87b1..00000000 Binary files a/doc/images/eocvsim_usage_defaultpipeline.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_popup_teamcode.gif b/doc/images/eocvsim_usage_popup_teamcode.gif deleted file mode 100644 index 4c9fadf9..00000000 Binary files a/doc/images/eocvsim_usage_popup_teamcode.gif and /dev/null differ diff --git a/doc/images/eocvsim_usage_telemetry.png b/doc/images/eocvsim_usage_telemetry.png deleted file mode 100644 index d05f2f8f..00000000 Binary files a/doc/images/eocvsim_usage_telemetry.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_tuner_config.png b/doc/images/eocvsim_usage_tuner_config.png deleted file mode 100644 index 2df58e8a..00000000 Binary files a/doc/images/eocvsim_usage_tuner_config.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_tuner_config_apply.png b/doc/images/eocvsim_usage_tuner_config_apply.png deleted file mode 100644 index 40aa06dd..00000000 Binary files a/doc/images/eocvsim_usage_tuner_config_apply.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_tuner_parts.png b/doc/images/eocvsim_usage_tuner_parts.png deleted file mode 100644 index 24f804af..00000000 Binary files a/doc/images/eocvsim_usage_tuner_parts.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_tuner_thresholdsample_1.png b/doc/images/eocvsim_usage_tuner_thresholdsample_1.png deleted file mode 100644 index aa004222..00000000 Binary files a/doc/images/eocvsim_usage_tuner_thresholdsample_1.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_tuner_thresholdsample_2.png b/doc/images/eocvsim_usage_tuner_thresholdsample_2.png deleted file mode 100644 index b395a52f..00000000 Binary files a/doc/images/eocvsim_usage_tuner_thresholdsample_2.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_tuner_thresholdsample_final.png b/doc/images/eocvsim_usage_tuner_thresholdsample_final.png deleted file mode 100644 index 3db882f0..00000000 Binary files a/doc/images/eocvsim_usage_tuner_thresholdsample_final.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_tuneropen.png b/doc/images/eocvsim_usage_tuneropen.png deleted file mode 100644 index d8a0be7c..00000000 Binary files a/doc/images/eocvsim_usage_tuneropen.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_tunerposition.png b/doc/images/eocvsim_usage_tunerposition.png deleted file mode 100644 index 3f526c24..00000000 Binary files a/doc/images/eocvsim_usage_tunerposition.png and /dev/null differ diff --git a/doc/images/eocvsim_usage_workspace_select.gif b/doc/images/eocvsim_usage_workspace_select.gif deleted file mode 100644 index ca83007d..00000000 Binary files a/doc/images/eocvsim_usage_workspace_select.gif and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f371643e..b65adcfb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 index 4f906e0c..cccdd3d5 --- a/gradlew +++ b/gradlew @@ -1,21 +1,5 @@ #!/usr/bin/env sh -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - ############################################################################## ## ## Gradle start up script for UN*X @@ -44,7 +28,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -82,7 +66,6 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -126,11 +109,10 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -156,19 +138,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then else eval `echo args$i`="\"$arg\"" fi - i=`expr $i + 1` + i=$((i+1)) done case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -177,9 +159,14 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=`save "$@"` +APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd32..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,89 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/jitpack.yml b/jitpack.yml index 29b6f0a2..ad940f7c 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,6 +1,6 @@ -jdk: - - openjdk8 -before_install: - - chmod +x gradlew -install: - - ./gradlew :EOCV-Sim:clean :EOCV-Sim:build :EOCV-Sim:publishToMavenLocal -x :EOCV-Sim:test +jdk: + - openjdk8 +before_install: + - chmod +x gradlew +install: + - ./gradlew :EOCV-Sim:clean :EOCV-Sim:build :EOCV-Sim:publishToMavenLocal -x :EOCV-Sim:test diff --git a/settings.gradle b/settings.gradle index 370c45da..8e691391 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,4 +9,4 @@ pluginManagement { rootProject.name = 'EOCV-Sim' include 'TeamCode' -include 'EOCV-Sim' \ No newline at end of file +include 'EOCV-Sim' diff --git a/test-logging.gradle b/test-logging.gradle index 3ba87719..3026cc24 100644 --- a/test-logging.gradle +++ b/test-logging.gradle @@ -1,38 +1,38 @@ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.api.tasks.testing.logging.TestLogEvent - -test { - testLogging { - // set options for log level LIFECYCLE - events TestLogEvent.FAILED, - TestLogEvent.PASSED, - TestLogEvent.SKIPPED, - TestLogEvent.STANDARD_OUT - exceptionFormat TestExceptionFormat.FULL - showExceptions true - showCauses true - showStackTraces true - - // set options for log level DEBUG and INFO - debug { - events TestLogEvent.STARTED, - TestLogEvent.FAILED, - TestLogEvent.PASSED, - TestLogEvent.SKIPPED, - TestLogEvent.STANDARD_ERROR, - TestLogEvent.STANDARD_OUT - exceptionFormat TestExceptionFormat.FULL - } - info.events = debug.events - info.exceptionFormat = debug.exceptionFormat - - afterSuite { desc, result -> - if (!desc.parent) { // will match the outermost suite - def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)" - def startItem = '| ', endItem = ' |' - def repeatLength = startItem.length() + output.length() + endItem.length() - println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) - } - } - } +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent + +test { + testLogging { + // set options for log level LIFECYCLE + events TestLogEvent.FAILED, + TestLogEvent.PASSED, + TestLogEvent.SKIPPED, + TestLogEvent.STANDARD_OUT + exceptionFormat TestExceptionFormat.FULL + showExceptions true + showCauses true + showStackTraces true + + // set options for log level DEBUG and INFO + debug { + events TestLogEvent.STARTED, + TestLogEvent.FAILED, + TestLogEvent.PASSED, + TestLogEvent.SKIPPED, + TestLogEvent.STANDARD_ERROR, + TestLogEvent.STANDARD_OUT + exceptionFormat TestExceptionFormat.FULL + } + info.events = debug.events + info.exceptionFormat = debug.exceptionFormat + + afterSuite { desc, result -> + if (!desc.parent) { // will match the outermost suite + def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)" + def startItem = '| ', endItem = ' |' + def repeatLength = startItem.length() + output.length() + endItem.length() + println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) + } + } + } } \ No newline at end of file