Skip to content

Commit

Permalink
Merge pull request #15 from qupath/strings-options
Browse files Browse the repository at this point in the history
Add examples of setting preferences in QuPath pane and in extension GUI
  • Loading branch information
alanocallaghan authored Mar 14, 2024
2 parents 2900cc5 + 5895d4d commit 778f027
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 10 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
88 changes: 78 additions & 10 deletions src/main/java/qupath/ext/template/DemoExtension.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package qupath.ext.template;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.Property;
import javafx.scene.Scene;
import javafx.scene.control.MenuItem;
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.fx.prefs.controlsfx.PropertyItemBuilder;
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.
Expand Down Expand Up @@ -63,11 +71,35 @@ 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<Integer>} rather than {@link IntegerProperty}
* because of the type of GUI element we use to manage it.
*/
private static Property<Integer> 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<Integer> numThreadsProperty() {
return numThreadsProperty;
}

/**
* Create a stage for the extension to display
*/
private Stage stage;

@Override
public void installExtension(QuPathGUI qupath) {
if (isInstalled) {
Expand All @@ -76,12 +108,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(
Expand All @@ -99,15 +154,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;
Expand Down
55 changes: 55 additions & 0 deletions src/main/java/qupath/ext/template/ui/InterfaceController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package qupath.ext.template.ui;

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;

/**
* Controller for UI pane contained in interface.fxml
*/

public class InterfaceController extends VBox {
private static final ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.template.ui.strings");

@FXML
private Spinner<Integer> threadSpinner;

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();

// 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() {
System.out.println("Demo extension run");
}


}
16 changes: 16 additions & 0 deletions src/main/resources/qupath/ext/template/ui/interface.fxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<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">
<Button onAction="#runDemoExtension" text="Run"/>
<Spinner fx:id="threadSpinner" prefWidth="75.0">
<valueFactory>
<SpinnerValueFactory.IntegerSpinnerValueFactory max="96" min="1" />
</valueFactory>
</Spinner>
</fx:root>
3 changes: 3 additions & 0 deletions src/main/resources/qupath/ext/template/ui/strings.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
title = Demo extension

threads = Threads set to %d

0 comments on commit 778f027

Please sign in to comment.