From 69f0cc23203b63439452b96f9fc2e46fcf3412a8 Mon Sep 17 00:00:00 2001
From: finglis <42358257+finglis@users.noreply.github.com>
Date: Tue, 6 Feb 2024 17:07:36 +0000
Subject: [PATCH 1/6] interface & controller added
---
build.gradle | 3 ++
.../qupath/ext/template/DemoExtension.java | 37 +++++++++++++++----
.../ext/template/InterfaceController.java | 37 +++++++++++++++++++
src/main/resources/interface.fxml | 14 +++++++
4 files changed, 84 insertions(+), 7 deletions(-)
create mode 100644 src/main/java/qupath/ext/template/InterfaceController.java
create mode 100644 src/main/resources/interface.fxml
diff --git a/build.gradle b/build.gradle
index cf9a9b8..23807b0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -50,6 +50,9 @@ dependencies {
// 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
diff --git a/src/main/java/qupath/ext/template/DemoExtension.java b/src/main/java/qupath/ext/template/DemoExtension.java
index 07de097..b724df4 100644
--- a/src/main/java/qupath/ext/template/DemoExtension.java
+++ b/src/main/java/qupath/ext/template/DemoExtension.java
@@ -1,16 +1,21 @@
package qupath.ext.template;
import javafx.beans.property.BooleanProperty;
+//import javafx.beans.property.StringProperty;
+import javafx.scene.Scene;
import javafx.scene.control.MenuItem;
+import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import qupath.fx.dialogs.Dialogs;
import qupath.lib.common.Version;
import qupath.lib.gui.QuPathGUI;
-import qupath.lib.gui.dialogs.Dialogs;
import qupath.lib.gui.extensions.GitHubProject;
import qupath.lib.gui.extensions.QuPathExtension;
import qupath.lib.gui.prefs.PathPrefs;
+import java.io.IOException;
+
/**
* This is a demo to provide a template for creating a new QuPath extension.
@@ -68,6 +73,11 @@ public class DemoExtension implements QuPathExtension, GitHubProject {
private BooleanProperty enableExtensionProperty = PathPrefs.createPersistentPreference(
"enableExtension", true);
+ /**
+ * Create a stage for the extension to display
+ */
+ private Stage stage;
+
@Override
public void installExtension(QuPathGUI qupath) {
if (isInstalled) {
@@ -99,15 +109,28 @@ private void addPreference(QuPathGUI qupath) {
private void addMenuItem(QuPathGUI qupath) {
var menu = qupath.getMenu("Extensions>" + EXTENSION_NAME, true);
MenuItem menuItem = new MenuItem("My menu item");
- menuItem.setOnAction(e -> {
- Dialogs.showMessageDialog(EXTENSION_NAME,
- "Hello! This is my Java extension.");
- });
+ menuItem.setOnAction(e -> createStage());
menuItem.disableProperty().bind(enableExtensionProperty.not());
menu.getItems().add(menuItem);
}
-
-
+
+ /**
+ * Demo showing how to create a new stage with a JavaFX FXML interface.
+ */
+ private void createStage() {
+ if (stage == null) {
+ try {
+ stage = new Stage();
+ Scene scene = new Scene(InterfaceController.createInstance());
+ stage.setScene(scene);
+ } catch (IOException e) {
+ Dialogs.showErrorMessage("Extension Error", "GUI loading failed");
+ logger.error("Unable to load extension interface FXML", e);
+ }
+ }
+ stage.show();
+ }
+
@Override
public String getName() {
return EXTENSION_NAME;
diff --git a/src/main/java/qupath/ext/template/InterfaceController.java b/src/main/java/qupath/ext/template/InterfaceController.java
new file mode 100644
index 0000000..f4f3e78
--- /dev/null
+++ b/src/main/java/qupath/ext/template/InterfaceController.java
@@ -0,0 +1,37 @@
+package qupath.ext.template;
+
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.VBox;
+
+import java.io.IOException;
+import java.util.ResourceBundle;
+
+/**
+ * Controller for UI pane contained in interface.fxml
+ */
+
+public class InterfaceController extends VBox {
+ private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.instanseg.ui.strings");
+
+ @FXML
+ private TextField tfModelDirectory;
+
+ public static InterfaceController createInstance() throws IOException {
+ return new InterfaceController();
+ }
+
+ private InterfaceController() throws IOException {
+ var url = InterfaceController.class.getResource("interface.fxml");
+ FXMLLoader loader = new FXMLLoader(url, resources);
+ loader.setRoot(this);
+ loader.setController(this);
+ loader.load();
+ }
+
+ @FXML
+ private void runDemoExtension() {
+// ExtensionCommand.runDemoExtension(tfModelDirectory.getText());
+ }
+}
diff --git a/src/main/resources/interface.fxml b/src/main/resources/interface.fxml
new file mode 100644
index 0000000..0ba8e1c
--- /dev/null
+++ b/src/main/resources/interface.fxml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
From e61096851ce6aa044ef632b6be878c09d1cf2d3c Mon Sep 17 00:00:00 2001
From: finglis <42358257+finglis@users.noreply.github.com>
Date: Wed, 7 Feb 2024 12:13:40 +0000
Subject: [PATCH 2/6] updates
---
.../qupath/ext/template/InterfaceController.java | 4 ++++
src/main/resources/interface.fxml | 13 ++++++++-----
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/src/main/java/qupath/ext/template/InterfaceController.java b/src/main/java/qupath/ext/template/InterfaceController.java
index f4f3e78..4763dd4 100644
--- a/src/main/java/qupath/ext/template/InterfaceController.java
+++ b/src/main/java/qupath/ext/template/InterfaceController.java
@@ -15,6 +15,7 @@
public class InterfaceController extends VBox {
private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.instanseg.ui.strings");
+ //TBC if needed in template
@FXML
private TextField tfModelDirectory;
@@ -33,5 +34,8 @@ private InterfaceController() throws IOException {
@FXML
private void runDemoExtension() {
// ExtensionCommand.runDemoExtension(tfModelDirectory.getText());
+ System.out.println("Demo extension run");
}
+
+
}
diff --git a/src/main/resources/interface.fxml b/src/main/resources/interface.fxml
index 0ba8e1c..d0ece76 100644
--- a/src/main/resources/interface.fxml
+++ b/src/main/resources/interface.fxml
@@ -6,9 +6,12 @@
-
+
+
+
-
+
+
+
+
+
From ed98bb156ae8d42754fd781fc1d61f56ebac7d4e Mon Sep 17 00:00:00 2001
From: finglis <42358257+finglis@users.noreply.github.com>
Date: Wed, 7 Feb 2024 12:44:17 +0000
Subject: [PATCH 3/6] folder structure update
---
src/main/resources/{ => qupath/ext/template/ui}/interface.fxml | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename src/main/resources/{ => qupath/ext/template/ui}/interface.fxml (100%)
diff --git a/src/main/resources/interface.fxml b/src/main/resources/qupath/ext/template/ui/interface.fxml
similarity index 100%
rename from src/main/resources/interface.fxml
rename to src/main/resources/qupath/ext/template/ui/interface.fxml
From e8b02cf87f43d841bfad33917f489c40480d983b Mon Sep 17 00:00:00 2001
From: finglis <42358257+finglis@users.noreply.github.com>
Date: Wed, 7 Feb 2024 13:09:04 +0000
Subject: [PATCH 4/6] fxml ui working
---
src/main/java/qupath/ext/template/DemoExtension.java | 1 +
.../qupath/ext/template/{ => ui}/InterfaceController.java | 2 +-
src/main/resources/qupath/ext/template/ui/interface.fxml | 6 ------
3 files changed, 2 insertions(+), 7 deletions(-)
rename src/main/java/qupath/ext/template/{ => ui}/InterfaceController.java (97%)
diff --git a/src/main/java/qupath/ext/template/DemoExtension.java b/src/main/java/qupath/ext/template/DemoExtension.java
index b724df4..1b8d2c8 100644
--- a/src/main/java/qupath/ext/template/DemoExtension.java
+++ b/src/main/java/qupath/ext/template/DemoExtension.java
@@ -7,6 +7,7 @@
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import qupath.ext.template.ui.InterfaceController;
import qupath.fx.dialogs.Dialogs;
import qupath.lib.common.Version;
import qupath.lib.gui.QuPathGUI;
diff --git a/src/main/java/qupath/ext/template/InterfaceController.java b/src/main/java/qupath/ext/template/ui/InterfaceController.java
similarity index 97%
rename from src/main/java/qupath/ext/template/InterfaceController.java
rename to src/main/java/qupath/ext/template/ui/InterfaceController.java
index 4763dd4..d25c6d8 100644
--- a/src/main/java/qupath/ext/template/InterfaceController.java
+++ b/src/main/java/qupath/ext/template/ui/InterfaceController.java
@@ -1,4 +1,4 @@
-package qupath.ext.template;
+package qupath.ext.template.ui;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
diff --git a/src/main/resources/qupath/ext/template/ui/interface.fxml b/src/main/resources/qupath/ext/template/ui/interface.fxml
index d0ece76..05d2a19 100644
--- a/src/main/resources/qupath/ext/template/ui/interface.fxml
+++ b/src/main/resources/qupath/ext/template/ui/interface.fxml
@@ -9,9 +9,3 @@
-
-
-
-
-
-
From 77e30fad8a1786f52cd9b98087f3f9860700de8b Mon Sep 17 00:00:00 2001
From: Alan O'Callaghan
Date: Wed, 7 Feb 2024 17:27:52 +0000
Subject: [PATCH 5/6] Add externalised strings and two examples of preferences
in GUI
---
.../qupath/ext/template/DemoExtension.java | 51 +++++++++++++++++--
.../ext/template/ui/InterfaceController.java | 22 ++++++--
.../qupath/ext/template/ui/interface.fxml | 5 ++
.../qupath/ext/template/ui/strings.properties | 3 ++
4 files changed, 74 insertions(+), 7 deletions(-)
create mode 100644 src/main/resources/qupath/ext/template/ui/strings.properties
diff --git a/src/main/java/qupath/ext/template/DemoExtension.java b/src/main/java/qupath/ext/template/DemoExtension.java
index 1b8d2c8..16a537b 100644
--- a/src/main/java/qupath/ext/template/DemoExtension.java
+++ b/src/main/java/qupath/ext/template/DemoExtension.java
@@ -2,6 +2,8 @@
import javafx.beans.property.BooleanProperty;
//import javafx.beans.property.StringProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.Property;
import javafx.scene.Scene;
import javafx.scene.control.MenuItem;
import javafx.stage.Stage;
@@ -9,6 +11,7 @@
import org.slf4j.LoggerFactory;
import qupath.ext.template.ui.InterfaceController;
import qupath.fx.dialogs.Dialogs;
+import qupath.fx.prefs.controlsfx.PropertyItemBuilder;
import qupath.lib.common.Version;
import qupath.lib.gui.QuPathGUI;
import qupath.lib.gui.extensions.GitHubProject;
@@ -69,11 +72,30 @@ public class DemoExtension implements QuPathExtension, GitHubProject {
private boolean isInstalled = false;
/**
- * A 'persistent preference' - showing how to create a property that is stored whenever QuPath is closed
+ * A 'persistent preference' - showing how to create a property that is stored whenever QuPath is closed.
+ * This preference will be managed in the main QuPath GUI preferences window.
*/
- private BooleanProperty enableExtensionProperty = PathPrefs.createPersistentPreference(
+ private static BooleanProperty enableExtensionProperty = PathPrefs.createPersistentPreference(
"enableExtension", true);
+
+ /**
+ * Another 'persistent preference'.
+ * This one will be managed using a GUI element created by the extension.
+ * We use {@link Property} rather than {@link IntegerProperty}
+ * because of the type of GUI element we use to manage it.
+ */
+ private static Property numThreadsProperty = PathPrefs.createPersistentPreference(
+ "demo.num.threads", 1).asObject();
+
+ /**
+ * An example of how to expose persistent preferences to other classes in your extension.
+ * @return The persistent preference, so that it can be read or set somewhere else.
+ */
+ public static Property numThreadsProperty() {
+ return numThreadsProperty;
+ }
+
/**
* Create a stage for the extension to display
*/
@@ -87,12 +109,35 @@ public void installExtension(QuPathGUI qupath) {
}
isInstalled = true;
addPreference(qupath);
+ addPreferenceToPane(qupath);
addMenuItem(qupath);
}
/**
* Demo showing how to add a persistent preference to the QuPath preferences pane.
- * @param qupath
+ * The preference will be in a section of the preference pane based on the
+ * category you set. The description is used as a tooltip.
+ * @param qupath The currently running QuPathGUI instance.
+ */
+ private void addPreferenceToPane(QuPathGUI qupath) {
+ var propertyItem = new PropertyItemBuilder<>(enableExtensionProperty, Boolean.class)
+ .name("Enable extension")
+ .category("Demo extension")
+ .description("Enable the demo extension")
+ .build();
+ qupath.getPreferencePane()
+ .getPropertySheet()
+ .getItems()
+ .add(propertyItem);
+ }
+
+ /**
+ * Demo showing how to add a persistent preference.
+ * This will be loaded whenever QuPath launches, with the value retained unless
+ * the preferences are reset.
+ * However, users will not be able to edit it unless you create a GUI
+ * element that corresponds with it
+ * @param qupath The currently running QuPathGUI instance.
*/
private void addPreference(QuPathGUI qupath) {
qupath.getPreferencePane().addPropertyPreference(
diff --git a/src/main/java/qupath/ext/template/ui/InterfaceController.java b/src/main/java/qupath/ext/template/ui/InterfaceController.java
index d25c6d8..b0ab335 100644
--- a/src/main/java/qupath/ext/template/ui/InterfaceController.java
+++ b/src/main/java/qupath/ext/template/ui/InterfaceController.java
@@ -2,8 +2,12 @@
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;
import java.io.IOException;
import java.util.ResourceBundle;
@@ -13,11 +17,10 @@
*/
public class InterfaceController extends VBox {
- private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.instanseg.ui.strings");
+ private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.template.ui.strings");
- //TBC if needed in template
@FXML
- private TextField tfModelDirectory;
+ private Spinner threadSpinner;
public static InterfaceController createInstance() throws IOException {
return new InterfaceController();
@@ -29,11 +32,22 @@ private InterfaceController() throws IOException {
loader.setRoot(this);
loader.setController(this);
loader.load();
+
+ // For extensions with a small number of options,
+ // or with options that are very important for how the extension works,
+ // it may be better to present them all to the user in the main extension GUI,
+ // binding them to GUI elements, so they are updated when the user interacts with
+ // the GUI, and so that the GUI elements are updated if the preference changes
+ threadSpinner.getValueFactory().valueProperty().bindBidirectional(DemoExtension.numThreadsProperty());
+ threadSpinner.getValueFactory().valueProperty().addListener((observableValue, oldValue, newValue) -> {
+ Dialogs.showInfoNotification(
+ resources.getString("title"),
+ String.format(resources.getString("threads"), newValue));
+ });
}
@FXML
private void runDemoExtension() {
-// ExtensionCommand.runDemoExtension(tfModelDirectory.getText());
System.out.println("Demo extension run");
}
diff --git a/src/main/resources/qupath/ext/template/ui/interface.fxml b/src/main/resources/qupath/ext/template/ui/interface.fxml
index 05d2a19..69f6322 100644
--- a/src/main/resources/qupath/ext/template/ui/interface.fxml
+++ b/src/main/resources/qupath/ext/template/ui/interface.fxml
@@ -8,4 +8,9 @@
+
+
+
+
+
diff --git a/src/main/resources/qupath/ext/template/ui/strings.properties b/src/main/resources/qupath/ext/template/ui/strings.properties
new file mode 100644
index 0000000..c506f28
--- /dev/null
+++ b/src/main/resources/qupath/ext/template/ui/strings.properties
@@ -0,0 +1,3 @@
+title = Demo extension
+
+threads = Threads set to %d
\ No newline at end of file
From 5895d4d74d86a459b51f77a795533c64edf0d258 Mon Sep 17 00:00:00 2001
From: Alan O'Callaghan
Date: Thu, 14 Mar 2024 13:37:24 +0000
Subject: [PATCH 6/6] Remove commented property
---
src/main/java/qupath/ext/template/DemoExtension.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/java/qupath/ext/template/DemoExtension.java b/src/main/java/qupath/ext/template/DemoExtension.java
index 16a537b..42d5d58 100644
--- a/src/main/java/qupath/ext/template/DemoExtension.java
+++ b/src/main/java/qupath/ext/template/DemoExtension.java
@@ -1,7 +1,6 @@
package qupath.ext.template;
import javafx.beans.property.BooleanProperty;
-//import javafx.beans.property.StringProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.Property;
import javafx.scene.Scene;