diff --git a/.github/workflows/jpackage.yml b/.github/workflows/jpackage.yml
index ae389bc..173edfd 100644
--- a/.github/workflows/jpackage.yml
+++ b/.github/workflows/jpackage.yml
@@ -19,10 +19,10 @@ jobs:
- uses: actions/checkout@v4
- - name: Set up JDK 17 # TODO: check Java version
+ - name: Set up JDK 21 # TODO: check Java version
uses: actions/setup-java@v4
with:
- java-version: '17'
+ java-version: '21'
distribution: 'temurin'
- name: Validate Gradle wrapper
diff --git a/README.md b/README.md
index 9ccdf0b..038f18f 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,15 @@
This repo contains a template and instructions to help create a new extension for [QuPath](https://qupath.github.io).
-It already contains two minimal extensions, so the first task is to make sure that they work.
+It already contains two minimal extensions - one using Java, one using Groovy - so the first task is to make sure that they work.
Then, it's a matter of customizing the code to make it more useful.
-> There are two extensions to show that you can use either Java or Groovy.
+> **Update!**
+> For QuPath v0.6.0 this repo switched to use Kotlin DSL for Gradle build files -
+> and also to use the [QuPath Gradle Plugin](https://github.com/qupath/qupath-gradle-plugin).
+>
+> The outcome is that the build files are _much_ simpler.
+
## Build the extension
@@ -33,39 +38,88 @@ QuPath.
> If you don't do that, you'll need to drag *all* the extra dependences onto QuPath to install them as well.
-## Set up in an IDE (optional)
+## Configure the extension
-During development, things are likely to be much easier if you work within an IDE.
+Edit `settings.gradle.kts` to specify which version of QuPath your extension should be compatible with, e.g.
-QuPath itself is developed using IntelliJ, and you can import the extension template there.
+```kotlin
+qupath {
+ version = "0.6.0-SNAPSHOT"
+}
+```
+
+Edit `build.gradle.kts` to specify the details of your extension
-However, for development and testing, it can help to import QuPath *and* the extension and have them in your IDE side-by-side.
+```kotlin
+qupathExtension {
+ name = "qupath-extension-template"
+ group = "io.github.qupath"
+ version = "0.1.0-SNAPSHOT"
+ description = "A simple QuPath extension"
+ automaticModule = "io.github.qupath.extension.template"
+}
+```
-In IntelliJ, you can do this in a few steps:
-* Get QuPath's source code, as described at https://qupath.readthedocs.io/en/stable/docs/reference/building.html
-* Store your extension code in a directory *beside* QuPath's code. So it should be located next to the `qupath` code directory.
-* Import QuPath into IntelliJ as a Gradle project (you don't need to import the extension yet!)
- * See https://www.jetbrains.com/help/idea/work-with-gradle-projects.html
-* Within `qupath/settings.gradle` add the line `includeFlat 'your-extension-code-directory'` (updating the code directory as needed)
-* Refresh the Gradle project in IntelliJ, and your extension code should appear
-* Create a [Run configuration](https://www.jetbrains.com/help/idea/run-debug-configuration.html) in IntelliJ to launch QuPath. An example of how that looks is shown below:
-
+## Run QuPath + the extension
-Now when you run QuPath from IntelliJ, your extension should (hopefully) be found - there's no need to add it by drag & drop.
+During development, your probably want to run QuPath easily with your extension installed for debugging.
-## Customize the extension
+### 0. Make sure you have Java installed
+You'll need to install Java first.
-There are a few fixed steps to customizing the extension, and then the main creative part where you add your own code.
+At the time of writing, we use a Java 21 JDK downloaded from https://adoptium.net/
-### Update `settings.gradle`
+> Java 21 is a 'Long Term Support' release - which is why we use it instead of the very latest version.
+
+### 1. Get QuPath's source code
+You can find instructions at https://qupath.readthedocs.io/en/stable/docs/reference/building.html
+
+### 2. Create an `include-extra` file
+Create a file called `include-extra` in the root directory of the QuPath source code (*not* the extension code!).
+
+Set the contents of this file to:
+```
+[includeBuild]
+/path/to/your/extension
+
+[dependencies]
+extension-group:extension-name
+```
+replacing the default lines where needed.
+
+For example, to build the extension with the names given above you'd use
+```
+[includeBuild]
+../qupath-extension-template
-Open `settings.gradle` and check the comment lines flagged with `\\TODO`.
-These point you towards parts you may well need to change.
+[dependencies]
+io.github.qupath:qupath-extension-template
+```
+
+### 3. Run QuPath
+Run QuPath from the command line using
+```
+gradlew run
+```
+If all goes well, QuPath should launch and you can check the *Extensions* mention to confirm the extension is installed.
+
+
+## Set up in an IDE (optional)
+
+During development, things are likely to be much easier if you work within an IDE.
+
+QuPath itself is developed using IntelliJ, and you can import the extension template there.
+
+The setup process is as above, and you'll need a a [Run configuration](https://www.jetbrains.com/help/idea/run-debug-configuration.html)
+to call `gradlew run`.
+
+
+## Customize the extension
-### Update `build.gradle`
+Now you're ready for the creative part.
-Open `build.gradle` and follow a similar process to with `settings.gradle`, to update the bits flagged with `\\TODO`.
+You can develop the extension using either Java or Groovy - the template includes examples of both.
### Create the extension Java or Groovy file(s)
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 23807b0..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,170 +0,0 @@
-plugins {
- // Main gradle plugin for building a Java library
- id 'java-library'
- // Support writing the extension in Groovy (remove this if you don't want to)
- id 'groovy'
- // To create a shadow/fat jar that bundle up all dependencies
- id 'com.github.johnrengelman.shadow' version '8.1.1'
- // Include this plugin to avoid downloading JavaCPP dependencies for all platforms
- id 'org.bytedeco.gradle-javacpp-platform'
- id 'org.openjfx.javafxplugin' version '0.1.0'
-}
-
-// TODO: Change the module name
-ext.moduleName = 'io.github.qupath.extension.template'
-
-// TODO: Define the extension name & version, and provide a short description
-base {
- archivesName = rootProject.name
- version = '0.0.1-SNAPSHOT'
- description = 'A simple QuPath extension template'
-}
-
-// TODO: Specify the QuPath version, compatible with the extension.
-// The default 'gradle.ext.qupathVersion' reads this from settings.gradle.
-ext.qupathVersion = gradle.ext.qupathVersion
-
-// TODO: Specify the Java version compatible with the extension
-// Should be Java 17 for QuPath v0.5.0
-ext.qupathJavaVersion = 17
-
-/**
- * Define dependencies.
- * - Using 'shadow' indicates that they are already part of QuPath, so you don't need
- * to include them in your extension. If creating a single 'shadow jar' containing your
- * extension and all dependencies, these won't be added.
- * - Using 'implementation' indicates that you need the dependency for the extension to work,
- * and it isn't part of QuPath already. If you are creating a single 'shadow jar', the
- * dependency should be bundled up in the extension.
- * - Using 'testImplementation' indicates that the dependency is only needed for testing,
- * but shouldn't be bundled up for use in the extension.
- */
-dependencies {
-
- // Main QuPath user interface jar.
- // Automatically includes other QuPath jars as subdependencies.
- shadow "io.github.qupath:qupath-gui-fx:${qupathVersion}"
-
- // For logging - the version comes from QuPath's version catalog at
- // https://github.com/qupath/qupath/blob/main/gradle/libs.versions.toml
- // See https://docs.gradle.org/current/userguide/platforms.html
- shadow libs.slf4j
-
- // For JavaFX
- shadow libs.qupath.fxtras
-
- // If you aren't using Groovy, this can be removed
- shadow libs.bundles.groovy
-
- testImplementation "io.github.qupath:qupath-gui-fx:${qupathVersion}"
- testImplementation libs.junit
-}
-
-/*
- * Manifest info
- */
-jar {
- manifest {
- attributes("Implementation-Title": project.name,
- "Implementation-Version": archiveVersion,
- "Automatic-Module-Name": moduleName)
- }
-}
-
-/**
- * Copy necessary attributes, see
- * - https://github.com/qupath/qupath-extension-template/issues/9
- * - https://github.com/openjfx/javafx-gradle-plugin#variants
- */
-configurations.shadow {
- def runtimeAttributes = configurations.runtimeClasspath.attributes
- runtimeAttributes.keySet().each { key ->
- if (key in [Usage.USAGE_ATTRIBUTE, OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, MachineArchitecture.ARCHITECTURE_ATTRIBUTE])
- attributes.attribute(key, runtimeAttributes.getAttribute(key))
- }
-}
-
-/*
- * Copy the LICENSE file into the jar... if we have one (we should!)
- */
-processResources {
- from ("${projectDir}/LICENSE") {
- into 'licenses/'
- }
-}
-
-/*
- * Define extra 'copyDependencies' task to copy dependencies into the build directory.
- */
-tasks.register("copyDependencies", Copy) {
- description "Copy dependencies into the build directory for use elsewhere"
- group "QuPath"
-
- from configurations.default
- into 'build/libs'
-}
-
-/*
- * Ensure Java 17 compatibility, and include sources and javadocs when building.
- */
-java {
- toolchain {
- languageVersion = JavaLanguageVersion.of(qupathJavaVersion)
- }
- withSourcesJar()
- withJavadocJar()
-}
-
-/*
- * Create javadocs for all modules/packages in one place.
- * Use -PstrictJavadoc=true to fail on error with doclint (which is rather strict).
- */
-tasks.withType(Javadoc) {
- options.encoding = 'UTF-8'
- def strictJavadoc = findProperty('strictJavadoc')
- if (!strictJavadoc) {
- options.addStringOption('Xdoclint:none', '-quiet')
- }
-}
-
-/*
- * Specify that the encoding should be UTF-8 for source files
- */
-tasks.named('compileJava') {
- options.encoding = 'UTF-8'
-}
-
-/*
- * Avoid 'Entry .gitkeep is a duplicate but no duplicate handling strategy has been set.'
- * when using withSourcesJar()
- */
-tasks.withType(org.gradle.jvm.tasks.Jar) {
- duplicatesStrategy = DuplicatesStrategy.INCLUDE
-}
-
-/*
- * Support tests with JUnit.
- */
-tasks.named('test') {
- useJUnitPlatform()
-}
-
-// Looks redundant to include this here and in settings.gradle,
-// but helps overcome some gradle trouble when including this as a subproject
-// within QuPath itself (which is useful during development).
-repositories {
- // Add this if you need access to dependencies only installed locally
- // mavenLocal()
-
- mavenCentral()
-
- // Add scijava - which is where QuPath's jars are hosted
- maven {
- url "https://maven.scijava.org/content/repositories/releases"
- }
-
- maven {
- url "https://maven.scijava.org/content/repositories/snapshots"
- }
-
-}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..134683f
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,34 @@
+plugins {
+ // Support writing the extension in Groovy (remove this if you don't want to)
+ groovy
+ // To optionally create a shadow/fat jar that bundle up any non-core dependencies
+ id("com.gradleup.shadow") version "8.3.5"
+ // QuPath Gradle extension convention plugin
+ id("qupath-conventions")
+}
+
+// TODO: Configure your extension here (please change the defaults!)
+qupathExtension {
+ name = "qupath-extension-template"
+ group = "io.github.qupath"
+ version = "0.1.0-SNAPSHOT"
+ description = "A simple QuPath extension"
+ automaticModule = "io.github.qupath.extension.template"
+}
+
+// TODO: Define your dependencies here
+dependencies {
+
+ // Main dependencies for most QuPath extensions
+ shadow(libs.bundles.qupath)
+ shadow(libs.bundles.logging)
+ shadow(libs.qupath.fxtras)
+
+ // If you aren't using Groovy, this can be removed
+ shadow(libs.bundles.groovy)
+
+ // For testing
+ testImplementation(libs.bundles.qupath)
+ testImplementation(libs.junit)
+
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index d64cd49..a4b76b9 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9355b41..cea7a79 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index 1aa94a4..f5feea6 100755
--- a/gradlew
+++ b/gradlew
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
diff --git a/gradlew.bat b/gradlew.bat
index 6689b85..9b42019 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 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.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +59,11 @@ 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.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
diff --git a/qupath-intellij.png b/qupath-intellij.png
deleted file mode 100644
index bcbd526..0000000
Binary files a/qupath-intellij.png and /dev/null differ
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index ed3fa38..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,42 +0,0 @@
-pluginManagement {
- plugins {
- // Gradle is awkward about declaring versions for plugins
- // Specifying it here, rather than build.gradle, makes it possible
- // to include the extension as a subproject of QuPath itself
- // (which is useful during development)
- id 'org.bytedeco.gradle-javacpp-platform' version '1.5.9'
- }
-}
-
-// TODO: Name your QuPath extension here!
-rootProject.name = 'my-qupath-extension'
-
-// TODO: Define the QuPath version compatible with the extension
-// Note that the QuPath API isn't stable; something designed for
-// 0.X.a should work with 0.X.b, but not necessarily with 0.Y.a.
-gradle.ext.qupathVersion = "0.5.0"
-
-dependencyResolutionManagement {
-
- // Access QuPath's version catalog for dependency versions
- versionCatalogs {
- libs {
- from("io.github.qupath:qupath-catalog:${gradle.ext.qupathVersion}")
- }
- }
-
- repositories {
-
- mavenCentral()
-
- // Add scijava - which is where QuPath's jars are hosted
- maven {
- url "https://maven.scijava.org/content/repositories/releases"
- }
-
- maven {
- url "https://maven.scijava.org/content/repositories/snapshots"
- }
-
- }
-}
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..5c6ca66
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ maven {
+ url = uri("https://maven.scijava.org/content/repositories/releases")
+ }
+ }
+}
+
+// TODO: Specify which version of QuPath the extension is targeting here
+qupath {
+ version = "0.5.1"
+}
+
+// Apply QuPath Gradle settings plugin to handle configuration
+plugins {
+ id("io.github.qupath.qupath-extension-settings") version "0.2.1"
+}
diff --git a/src/main/java/qupath/ext/template/ui/InterfaceController.java b/src/main/java/qupath/ext/template/ui/InterfaceController.java
index b0ab335..ef919d6 100644
--- a/src/main/java/qupath/ext/template/ui/InterfaceController.java
+++ b/src/main/java/qupath/ext/template/ui/InterfaceController.java
@@ -2,9 +2,7 @@
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
-import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Spinner;
-import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import qupath.ext.template.DemoExtension;
import qupath.fx.dialogs.Dialogs;
@@ -22,6 +20,11 @@ public class InterfaceController extends VBox {
@FXML
private Spinner threadSpinner;
+ /**
+ * Create a new instance of the interface controller.
+ * @return a new instance of the interface controller
+ * @throws IOException
+ */
public static InterfaceController createInstance() throws IOException {
return new InterfaceController();
}