Skip to content

Commit 778f027

Browse files
Merge pull request #15 from qupath/strings-options
Add examples of setting preferences in QuPath pane and in extension GUI
2 parents 2900cc5 + 5895d4d commit 778f027

File tree

5 files changed

+155
-10
lines changed

5 files changed

+155
-10
lines changed

build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ dependencies {
5050
// See https://docs.gradle.org/current/userguide/platforms.html
5151
shadow libs.slf4j
5252

53+
// For JavaFX
54+
shadow libs.qupath.fxtras
55+
5356
// If you aren't using Groovy, this can be removed
5457
shadow libs.bundles.groovy
5558

src/main/java/qupath/ext/template/DemoExtension.java

+78-10
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
package qupath.ext.template;
22

33
import javafx.beans.property.BooleanProperty;
4+
import javafx.beans.property.IntegerProperty;
5+
import javafx.beans.property.Property;
6+
import javafx.scene.Scene;
47
import javafx.scene.control.MenuItem;
8+
import javafx.stage.Stage;
59
import org.slf4j.Logger;
610
import org.slf4j.LoggerFactory;
11+
import qupath.ext.template.ui.InterfaceController;
12+
import qupath.fx.dialogs.Dialogs;
13+
import qupath.fx.prefs.controlsfx.PropertyItemBuilder;
714
import qupath.lib.common.Version;
815
import qupath.lib.gui.QuPathGUI;
9-
import qupath.lib.gui.dialogs.Dialogs;
1016
import qupath.lib.gui.extensions.GitHubProject;
1117
import qupath.lib.gui.extensions.QuPathExtension;
1218
import qupath.lib.gui.prefs.PathPrefs;
1319

20+
import java.io.IOException;
21+
1422

1523
/**
1624
* This is a demo to provide a template for creating a new QuPath extension.
@@ -63,11 +71,35 @@ public class DemoExtension implements QuPathExtension, GitHubProject {
6371
private boolean isInstalled = false;
6472

6573
/**
66-
* A 'persistent preference' - showing how to create a property that is stored whenever QuPath is closed
74+
* A 'persistent preference' - showing how to create a property that is stored whenever QuPath is closed.
75+
* This preference will be managed in the main QuPath GUI preferences window.
6776
*/
68-
private BooleanProperty enableExtensionProperty = PathPrefs.createPersistentPreference(
77+
private static BooleanProperty enableExtensionProperty = PathPrefs.createPersistentPreference(
6978
"enableExtension", true);
7079

80+
81+
/**
82+
* Another 'persistent preference'.
83+
* This one will be managed using a GUI element created by the extension.
84+
* We use {@link Property<Integer>} rather than {@link IntegerProperty}
85+
* because of the type of GUI element we use to manage it.
86+
*/
87+
private static Property<Integer> numThreadsProperty = PathPrefs.createPersistentPreference(
88+
"demo.num.threads", 1).asObject();
89+
90+
/**
91+
* An example of how to expose persistent preferences to other classes in your extension.
92+
* @return The persistent preference, so that it can be read or set somewhere else.
93+
*/
94+
public static Property<Integer> numThreadsProperty() {
95+
return numThreadsProperty;
96+
}
97+
98+
/**
99+
* Create a stage for the extension to display
100+
*/
101+
private Stage stage;
102+
71103
@Override
72104
public void installExtension(QuPathGUI qupath) {
73105
if (isInstalled) {
@@ -76,12 +108,35 @@ public void installExtension(QuPathGUI qupath) {
76108
}
77109
isInstalled = true;
78110
addPreference(qupath);
111+
addPreferenceToPane(qupath);
79112
addMenuItem(qupath);
80113
}
81114

82115
/**
83116
* Demo showing how to add a persistent preference to the QuPath preferences pane.
84-
* @param qupath
117+
* The preference will be in a section of the preference pane based on the
118+
* category you set. The description is used as a tooltip.
119+
* @param qupath The currently running QuPathGUI instance.
120+
*/
121+
private void addPreferenceToPane(QuPathGUI qupath) {
122+
var propertyItem = new PropertyItemBuilder<>(enableExtensionProperty, Boolean.class)
123+
.name("Enable extension")
124+
.category("Demo extension")
125+
.description("Enable the demo extension")
126+
.build();
127+
qupath.getPreferencePane()
128+
.getPropertySheet()
129+
.getItems()
130+
.add(propertyItem);
131+
}
132+
133+
/**
134+
* Demo showing how to add a persistent preference.
135+
* This will be loaded whenever QuPath launches, with the value retained unless
136+
* the preferences are reset.
137+
* However, users will not be able to edit it unless you create a GUI
138+
* element that corresponds with it
139+
* @param qupath The currently running QuPathGUI instance.
85140
*/
86141
private void addPreference(QuPathGUI qupath) {
87142
qupath.getPreferencePane().addPropertyPreference(
@@ -99,15 +154,28 @@ private void addPreference(QuPathGUI qupath) {
99154
private void addMenuItem(QuPathGUI qupath) {
100155
var menu = qupath.getMenu("Extensions>" + EXTENSION_NAME, true);
101156
MenuItem menuItem = new MenuItem("My menu item");
102-
menuItem.setOnAction(e -> {
103-
Dialogs.showMessageDialog(EXTENSION_NAME,
104-
"Hello! This is my Java extension.");
105-
});
157+
menuItem.setOnAction(e -> createStage());
106158
menuItem.disableProperty().bind(enableExtensionProperty.not());
107159
menu.getItems().add(menuItem);
108160
}
109-
110-
161+
162+
/**
163+
* Demo showing how to create a new stage with a JavaFX FXML interface.
164+
*/
165+
private void createStage() {
166+
if (stage == null) {
167+
try {
168+
stage = new Stage();
169+
Scene scene = new Scene(InterfaceController.createInstance());
170+
stage.setScene(scene);
171+
} catch (IOException e) {
172+
Dialogs.showErrorMessage("Extension Error", "GUI loading failed");
173+
logger.error("Unable to load extension interface FXML", e);
174+
}
175+
}
176+
stage.show();
177+
}
178+
111179
@Override
112180
public String getName() {
113181
return EXTENSION_NAME;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package qupath.ext.template.ui;
2+
3+
import javafx.fxml.FXML;
4+
import javafx.fxml.FXMLLoader;
5+
import javafx.scene.control.ChoiceBox;
6+
import javafx.scene.control.Spinner;
7+
import javafx.scene.control.TextField;
8+
import javafx.scene.layout.VBox;
9+
import qupath.ext.template.DemoExtension;
10+
import qupath.fx.dialogs.Dialogs;
11+
12+
import java.io.IOException;
13+
import java.util.ResourceBundle;
14+
15+
/**
16+
* Controller for UI pane contained in interface.fxml
17+
*/
18+
19+
public class InterfaceController extends VBox {
20+
private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.template.ui.strings");
21+
22+
@FXML
23+
private Spinner<Integer> threadSpinner;
24+
25+
public static InterfaceController createInstance() throws IOException {
26+
return new InterfaceController();
27+
}
28+
29+
private InterfaceController() throws IOException {
30+
var url = InterfaceController.class.getResource("interface.fxml");
31+
FXMLLoader loader = new FXMLLoader(url, resources);
32+
loader.setRoot(this);
33+
loader.setController(this);
34+
loader.load();
35+
36+
// For extensions with a small number of options,
37+
// or with options that are very important for how the extension works,
38+
// it may be better to present them all to the user in the main extension GUI,
39+
// binding them to GUI elements, so they are updated when the user interacts with
40+
// the GUI, and so that the GUI elements are updated if the preference changes
41+
threadSpinner.getValueFactory().valueProperty().bindBidirectional(DemoExtension.numThreadsProperty());
42+
threadSpinner.getValueFactory().valueProperty().addListener((observableValue, oldValue, newValue) -> {
43+
Dialogs.showInfoNotification(
44+
resources.getString("title"),
45+
String.format(resources.getString("threads"), newValue));
46+
});
47+
}
48+
49+
@FXML
50+
private void runDemoExtension() {
51+
System.out.println("Demo extension run");
52+
}
53+
54+
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<?import java.lang.*?>
4+
<?import java.util.*?>
5+
<?import javafx.scene.*?>
6+
<?import javafx.scene.control.*?>
7+
<?import javafx.scene.layout.*?>
8+
9+
<fx:root type="VBox" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/20" xmlns:fx="http://javafx.com/fxml/1">
10+
<Button onAction="#runDemoExtension" text="Run"/>
11+
<Spinner fx:id="threadSpinner" prefWidth="75.0">
12+
<valueFactory>
13+
<SpinnerValueFactory.IntegerSpinnerValueFactory max="96" min="1" />
14+
</valueFactory>
15+
</Spinner>
16+
</fx:root>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
title = Demo extension
2+
3+
threads = Threads set to %d

0 commit comments

Comments
 (0)