Skip to content

Commit 4c148e0

Browse files
Improve code quality of extension (#23)
- Improve the size, spacing and alignment of the dialog box. - Clarify the purpose of buttons and options, and add tooltips. Also remove "threads" and make more generic - Clarify what "run" button does (and make it do something). - Consistently use resources - Add dialog title and set owner to the QuPath GUI - Remove deprecated API usage - Remove redundant addPreference method. - Make fields final if possible
1 parent 6b1d4dd commit 4c148e0

File tree

4 files changed

+75
-44
lines changed

4 files changed

+75
-44
lines changed

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

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import qupath.lib.gui.prefs.PathPrefs;
1919

2020
import java.io.IOException;
21+
import java.util.ResourceBundle;
2122

2223

2324
/**
@@ -33,20 +34,25 @@
3334
* </pre>
3435
*/
3536
public class DemoExtension implements QuPathExtension, GitHubProject {
36-
37+
// TODO: add and modify strings to this resource bundle as needed
38+
/**
39+
* A resource bundle containing all the text used by the extension. This may be useful for translation to other languages.
40+
* Note that this is optional and you can define the text within the code and FXML files that you use.
41+
*/
42+
private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.template.ui.strings");
3743
private static final Logger logger = LoggerFactory.getLogger(DemoExtension.class);
3844

3945
/**
4046
* Display name for your extension
4147
* TODO: define this
4248
*/
43-
private static final String EXTENSION_NAME = "My Java extension";
49+
private static final String EXTENSION_NAME = resources.getString("name");
4450

4551
/**
4652
* Short description, used under 'Extensions > Installed extensions'
4753
* TODO: define this
4854
*/
49-
private static final String EXTENSION_DESCRIPTION = "This is just a demo to show how extensions work";
55+
private static final String EXTENSION_DESCRIPTION = resources.getString("description");
5056

5157
/**
5258
* QuPath version that the extension is designed to work with.
@@ -74,25 +80,24 @@ public class DemoExtension implements QuPathExtension, GitHubProject {
7480
* A 'persistent preference' - showing how to create a property that is stored whenever QuPath is closed.
7581
* This preference will be managed in the main QuPath GUI preferences window.
7682
*/
77-
private static BooleanProperty enableExtensionProperty = PathPrefs.createPersistentPreference(
83+
private static final BooleanProperty enableExtensionProperty = PathPrefs.createPersistentPreference(
7884
"enableExtension", true);
7985

80-
8186
/**
8287
* Another 'persistent preference'.
8388
* This one will be managed using a GUI element created by the extension.
8489
* We use {@link Property<Integer>} rather than {@link IntegerProperty}
8590
* because of the type of GUI element we use to manage it.
8691
*/
87-
private static Property<Integer> numThreadsProperty = PathPrefs.createPersistentPreference(
88-
"demo.num.threads", 1).asObject();
92+
private static final Property<Integer> integerOption = PathPrefs.createPersistentPreference(
93+
"demo.num.option", 1).asObject();
8994

9095
/**
9196
* An example of how to expose persistent preferences to other classes in your extension.
9297
* @return The persistent preference, so that it can be read or set somewhere else.
9398
*/
94-
public static Property<Integer> numThreadsProperty() {
95-
return numThreadsProperty;
99+
public static Property<Integer> integerOptionProperty() {
100+
return integerOption;
96101
}
97102

98103
/**
@@ -107,7 +112,6 @@ public void installExtension(QuPathGUI qupath) {
107112
return;
108113
}
109114
isInstalled = true;
110-
addPreference(qupath);
111115
addPreferenceToPane(qupath);
112116
addMenuItem(qupath);
113117
}
@@ -119,8 +123,8 @@ public void installExtension(QuPathGUI qupath) {
119123
* @param qupath The currently running QuPathGUI instance.
120124
*/
121125
private void addPreferenceToPane(QuPathGUI qupath) {
122-
var propertyItem = new PropertyItemBuilder<>(enableExtensionProperty, Boolean.class)
123-
.name("Enable extension")
126+
var propertyItem = new PropertyItemBuilder<>(enableExtensionProperty, Boolean.class)
127+
.name(resources.getString("menu.enable"))
124128
.category("Demo extension")
125129
.description("Enable the demo extension")
126130
.build();
@@ -130,26 +134,10 @@ private void addPreferenceToPane(QuPathGUI qupath) {
130134
.add(propertyItem);
131135
}
132136

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.
140-
*/
141-
private void addPreference(QuPathGUI qupath) {
142-
qupath.getPreferencePane().addPropertyPreference(
143-
enableExtensionProperty,
144-
Boolean.class,
145-
"Enable my extension",
146-
EXTENSION_NAME,
147-
"Enable my extension");
148-
}
149137

150138
/**
151139
* Demo showing how a new command can be added to a QuPath menu.
152-
* @param qupath
140+
* @param qupath The QuPath GUI
153141
*/
154142
private void addMenuItem(QuPathGUI qupath) {
155143
var menu = qupath.getMenu("Extensions>" + EXTENSION_NAME, true);
@@ -167,15 +155,19 @@ private void createStage() {
167155
try {
168156
stage = new Stage();
169157
Scene scene = new Scene(InterfaceController.createInstance());
158+
stage.initOwner(QuPathGUI.getInstance().getStage());
159+
stage.setTitle(resources.getString("stage.title"));
170160
stage.setScene(scene);
161+
stage.setResizable(false);
171162
} catch (IOException e) {
172-
Dialogs.showErrorMessage("Extension Error", "GUI loading failed");
163+
Dialogs.showErrorMessage(resources.getString("error"), resources.getString("error.gui-loading-failed"));
173164
logger.error("Unable to load extension interface FXML", e);
174165
}
175166
}
176167
stage.show();
177168
}
178169

170+
179171
@Override
180172
public String getName() {
181173
return EXTENSION_NAME;

src/main/java/qupath/ext/template/ui/InterfaceController.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,29 @@
1313
/**
1414
* Controller for UI pane contained in interface.fxml
1515
*/
16-
1716
public class InterfaceController extends VBox {
1817
private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.template.ui.strings");
1918

2019
@FXML
21-
private Spinner<Integer> threadSpinner;
20+
private Spinner<Integer> integerOptionSpinner;
2221

2322
/**
2423
* Create a new instance of the interface controller.
2524
* @return a new instance of the interface controller
26-
* @throws IOException
25+
* @throws IOException If reading the extension FXML files fails.
2726
*/
2827
public static InterfaceController createInstance() throws IOException {
2928
return new InterfaceController();
3029
}
3130

31+
/**
32+
* This method reads an FXML file. These are markup files containing the structure of a UI element.
33+
* <p>
34+
* Fields in this class tagged with <code>@FXML</code> correspond to UI elements, and methods tagged with <code>@FXML</code> are methods triggered by actions on the UI (e.g., mouse clicks).
35+
* <p>
36+
* We consider the use of FXML to be "best practice" for UI creation, as it separates logic from layout and enables easier use of CSS. However, it is not mandatory, and you could instead define the layout of the UI using code.
37+
* @throws IOException If the FXML can't be read successfully.
38+
*/
3239
private InterfaceController() throws IOException {
3340
var url = InterfaceController.class.getResource("interface.fxml");
3441
FXMLLoader loader = new FXMLLoader(url, resources);
@@ -41,17 +48,20 @@ private InterfaceController() throws IOException {
4148
// it may be better to present them all to the user in the main extension GUI,
4249
// binding them to GUI elements, so they are updated when the user interacts with
4350
// the GUI, and so that the GUI elements are updated if the preference changes
44-
threadSpinner.getValueFactory().valueProperty().bindBidirectional(DemoExtension.numThreadsProperty());
45-
threadSpinner.getValueFactory().valueProperty().addListener((observableValue, oldValue, newValue) -> {
51+
integerOptionSpinner.getValueFactory().valueProperty().bindBidirectional(DemoExtension.integerOptionProperty());
52+
integerOptionSpinner.getValueFactory().valueProperty().addListener((observableValue, oldValue, newValue) -> {
4653
Dialogs.showInfoNotification(
4754
resources.getString("title"),
48-
String.format(resources.getString("threads"), newValue));
55+
String.format(resources.getString("option.integer.option-set-to"), newValue));
4956
});
5057
}
5158

5259
@FXML
5360
private void runDemoExtension() {
54-
System.out.println("Demo extension run");
61+
Dialogs.showInfoNotification(
62+
resources.getString("run.title"),
63+
resources.getString("run.message")
64+
);
5565
}
5666

5767

src/main/resources/qupath/ext/template/ui/interface.fxml

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,24 @@
66
<?import javafx.scene.control.*?>
77
<?import javafx.scene.layout.*?>
88

9+
<?import javafx.geometry.Insets?>
910
<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>
11+
<VBox spacing="5" alignment="CENTER_RIGHT">
12+
<padding>
13+
<Insets topRightBottomLeft="3"/>
14+
</padding>
15+
<HBox spacing="2" alignment="CENTER_LEFT">
16+
<Label text="%option.integer.text">
17+
<Tooltip text="%option.integer.tooltip"/>
18+
</Label>
19+
<Spinner fx:id="integerOptionSpinner" prefWidth="75.0">
20+
<valueFactory>
21+
<SpinnerValueFactory.IntegerSpinnerValueFactory max="100" min="1" />
22+
</valueFactory>
23+
</Spinner>
24+
</HBox>
25+
<Button onAction="#runDemoExtension" text="%run.button.text">
26+
<Tooltip text="%run.button.tooltip"/>
27+
</Button>
28+
</VBox>
1629
</fx:root>
Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
11
title = Demo extension
22

3-
threads = Threads set to %d
3+
name = My Java extension
4+
description = This is just a demo to show how extensions work
5+
menu.enable = Enable my extension
6+
menu.description = Click this buton to enable the demo extension
7+
8+
stage.title = Demo dialog
9+
option.integer.text = An option:
10+
option.integer.tooltip = A numeric option that you can set (it has no effect here).
11+
option.integer.option-set-to = Option set to %d
12+
run.button.text = Run
13+
run.button.tooltip = An example of a button that causes the extension to do something.\
14+
Here it does nothing.
15+
run.title = Run completed!
16+
run.message = You can inform users of the outcome here
17+
18+
error = Demo extension error
19+
error.gui-loading-failed = GUI loading failed

0 commit comments

Comments
 (0)