Launching script functions via a new button on the toolbar (Groovy code included)

Hello,

here’s some Groovy code I put together to add a button to the QuPath toolbar. It’s a toggle button with a (right-click) context menu with two items, one of which is a check menu item. When the script is relaunched, any additions to the toolbar are first removed before being recreated by the script. This avoids clutter during development. An icon for the button can be packaged as a base64 encoded GIF image with a transparent background (smaller than a PNG) and added right into the code. There are tools online to do this, like this one I used here.

I’ve put this together with some welcome help from @Research_Associate, to add my cell density map as a handy shortcut right in the toolbar. When I finish cleaning-up the code, I’ll update my last comment with it.

I always loved the fact that you could add your buttons in ImageJ or launch XTension scripts from a menu in Imaris, this is an extension of the same idea. Do let me know if I went overboard with this, if there are easier way to achieve some of the steps, and if someone knows if it is possible to add a keyboard shortcut to the button.

@Research_Associate also mentions it is possible to save this script as “startup.groovy” and place it in the QuPath user folder. When enabling startup scripts in Preferences → General, the button will appear in QuPath without further user intervention. Very cool, thanks mate! :slight_smile:

// a unique identifier to tag the items we will add to the toolbar
def btnId = "custom"

// rocket borrowed from https://stackoverflow.com/questions/11537434/javafx-embedding-encoded-image-in-fxml-file
// resized, saved to a 32x32 gif with transparent background and converted using an online image to base64 encoder
String rocketImgStr = "R0lGODlhIAAgAIQAMQUCBASWvJkCBDw+PIyOjJTY2NDQz0QCBOTm5BwiJ0xHRoxKTHBqbPf492NRTy4GB/kCBPxKTLe3uEg0MJz5+IR6eAxeXPza3LsCBHICBKSkpDcsK9/f3xQaHND+/MLCwyH5BAEAAAAALAAAAAAgACAABAXoICCOZGmeaCoWmeqiDSS/tGhFMlS/zZXnu1Tsh9EFTYufrCg4ljDQqNQ5alivWCsVgEV4v1pqtht2YgebzaCBKAfXV4difnZqrJxNgNKQdAxsGk4fVhITfA0UExJsEk4cV2kGAQkJVgYGg1hpaVYIBo5Hd16ekKWCRwpsYJ4NBggOTh2rbVdthAliYHJXjE4QGbQNvLcNGUY0xxAeq8OqHx8GHkUPNTmIbbyYDQUyTTQQ1VwIkA4MfYAiB8gqD8gJHAQfkgyoIuwp7iRXFRUNkCTwpRCoguAJgygQlvhWowWNA0HEbTkSAgA7"

ByteArrayInputStream rocketInputStream = new ByteArrayInputStream(rocketImgStr.decodeBase64())
Image rocketImg = new Image(rocketInputStream,QuPathGUI.TOOLBAR_ICON_SIZE, QuPathGUI.TOOLBAR_ICON_SIZE, true, true)


// Remove all the additions made to the toolbar based on the id above
def RemoveToolItems(toolbar, id) {
    while(1) {
        hasElements = false
        for (var tbItem : toolbar.getItems()) {
            if (tbItem.getId() == id) {
                toolbar.getItems().remove(tbItem)
                hasElements = true
                break
            }
        }
        if (!hasElements) break
    }
}

Platform.runLater {
    gui = QuPathGUI.getInstance()
    toolbar = gui.getToolBar()

    // First we remove the items already in place    
    RemoveToolItems(toolbar,btnId)

    // Here we add a separator
    sepCustom = new Separator(Orientation.VERTICAL)
    sepCustom.setId(btnId)
    toolbar.getItems().add(sepCustom)    
        
    // Here we add a toggle button
    btnCustom = new ToggleButton()
    btnCustom.setId(btnId)
    toolbar.getItems().add(btnCustom)
    
    // The button is given an icon encoded as base64 above
    ImageView imageView = new ImageView(rocketImg)
    btnCustom.setGraphic(imageView)
    btnCustom.setTooltip(new Tooltip("A custom button"))

    // This is a Toggle button, so need to know if the button was pressed or depressed
    btnCustom.setOnAction {
        if (btnCustom.isSelected()) {
            Dialogs.showInfoNotification("Custom button", "Pressed ON!")
        } else {
            Dialogs.showInfoNotification("Custom button","Pressed OFF!")
        }
    }
    
    // Here we add a Context menu (right click)
    ContextMenu contextMenu = new ContextMenu()
    // Creating the menu Items for the context menu (Option 2 is check menu item)
    MenuItem item1 = new MenuItem("Option 1")
    CheckMenuItem item2 = new CheckMenuItem("Option 2")
    contextMenu.getItems().addAll(item1, item2)
    // Setting the ContextMenuItem to the button
    btnCustom.setContextMenu(contextMenu)
    // Setting action to the context menu item
    item1.setOnAction {
        Dialogs.showInfoNotification("Custom button", "Option 1!")
    }
    item2.setOnAction {
        Dialogs.showInfoNotification("Custom button", "Option 2 selected: "+item2.isSelected())
    }
}

import javafx.application.Platform
import javafx.stage.Stage
import javafx.scene.Scene
import javafx.geometry.Insets
import javafx.geometry.Pos
import javafx.geometry.Orientation
import javafx.scene.control.*
import javafx.scene.layout.*
import javafx.scene.input.MouseEvent
import javafx.beans.value.ChangeListener
import javafx.scene.image.Image
import javafx.scene.image.ImageView
import qupath.lib.gui.QuPathGUI
3 Likes

Thank you for sharing, this is very useful. :smile:

1 Like