use of qupath.lib.images.ImageData.ImageType in project qupath by qupath.
the class ProjectImportImagesCommand method promptToImportImages.
/**
* Prompt to import images to the current project.
*
* @param qupath QuPath instance, used to access the current project and stage
* @param builder if not null, this will be used to create the servers. If null, a combobox will be shown to choose an installed builder.
* @param defaultPaths URIs to use to prepopulate the list
* @return
*/
static List<ProjectImageEntry<BufferedImage>> promptToImportImages(QuPathGUI qupath, ImageServerBuilder<BufferedImage> builder, String... defaultPaths) {
var project = qupath.getProject();
if (project == null) {
Dialogs.showNoProjectError(commandName);
return Collections.emptyList();
}
ListView<String> listView = new ListView<>();
listView.setPrefWidth(480);
listView.setMinHeight(100);
listView.getItems().addAll(defaultPaths);
listView.setPlaceholder(new Label("Drag & drop image or project files for import, \nor choose from the options below"));
Button btnFile = new Button("Choose files");
btnFile.setOnAction(e -> loadFromFileChooser(listView.getItems()));
Button btnURL = new Button("Input URL");
btnURL.setOnAction(e -> loadFromSingleURL(listView.getItems()));
Button btnClipboard = new Button("From clipboard");
btnClipboard.setOnAction(e -> loadFromClipboard(listView.getItems()));
Button btnFileList = new Button("From path list");
btnFileList.setOnAction(e -> loadFromTextFile(listView.getItems()));
TitledPane paneList = new TitledPane("Image paths", listView);
paneList.setCollapsible(false);
BorderPane paneImages = new BorderPane();
class BuilderListCell extends ListCell<ImageServerBuilder<BufferedImage>> {
@Override
protected void updateItem(ImageServerBuilder<BufferedImage> item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
if (item == null)
setText("Default (let QuPath decide)");
else
setText(item.getName());
}
}
}
boolean requestBuilder = builder == null;
ComboBox<ImageServerBuilder<BufferedImage>> comboBuilder = new ComboBox<>();
Label labelBuilder = new Label("Image provider");
if (requestBuilder) {
comboBuilder.setCellFactory(p -> new BuilderListCell());
comboBuilder.setButtonCell(new BuilderListCell());
List<ImageServerBuilder<BufferedImage>> availableBuilders = new ArrayList<>(ImageServerProvider.getInstalledImageServerBuilders(BufferedImage.class));
if (!availableBuilders.contains(null))
availableBuilders.add(0, null);
comboBuilder.getItems().setAll(availableBuilders);
comboBuilder.getSelectionModel().selectFirst();
labelBuilder.setLabelFor(comboBuilder);
labelBuilder.setMinWidth(Label.USE_PREF_SIZE);
}
ComboBox<ImageType> comboType = new ComboBox<>();
comboType.getItems().setAll(ImageType.values());
Label labelType = new Label("Set image type");
labelType.setLabelFor(comboType);
labelType.setMinWidth(Label.USE_PREF_SIZE);
ComboBox<Rotation> comboRotate = new ComboBox<>();
comboRotate.getItems().setAll(Rotation.values());
Label labelRotate = new Label("Rotate image");
labelRotate.setLabelFor(comboRotate);
labelRotate.setMinWidth(Label.USE_PREF_SIZE);
TextField tfArgs = new TextField();
Label labelArgs = new Label("Optional args");
labelArgs.setLabelFor(tfArgs);
labelArgs.setMinWidth(Label.USE_PREF_SIZE);
CheckBox cbPyramidalize = new CheckBox("Auto-generate pyramids");
cbPyramidalize.setSelected(pyramidalizeProperty.get());
CheckBox cbImportObjects = new CheckBox("Import objects");
cbImportObjects.setSelected(importObjectsProperty.get());
PaneTools.setMaxWidth(Double.MAX_VALUE, comboBuilder, comboType, comboRotate, cbPyramidalize, cbImportObjects, tfArgs);
PaneTools.setFillWidth(Boolean.TRUE, comboBuilder, comboType, comboRotate, cbPyramidalize, cbImportObjects, tfArgs);
PaneTools.setHGrowPriority(Priority.ALWAYS, comboBuilder, comboType, comboRotate, cbPyramidalize, cbImportObjects, tfArgs);
GridPane paneType = new GridPane();
paneType.setPadding(new Insets(5));
paneType.setHgap(5);
paneType.setVgap(5);
int row = 0;
if (requestBuilder)
PaneTools.addGridRow(paneType, row++, 0, "Specify the library used to open images", labelBuilder, comboBuilder);
PaneTools.addGridRow(paneType, row++, 0, "Specify the default image type for all images being imported (required for analysis, can be changed later under the 'Image' tab)", labelType, comboType);
PaneTools.addGridRow(paneType, row++, 0, "Optionally rotate images on import", labelRotate, comboRotate);
PaneTools.addGridRow(paneType, row++, 0, "Optionally pass reader-specific arguments to the image provider.\nUsually this should just be left empty.", labelArgs, tfArgs);
PaneTools.addGridRow(paneType, row++, 0, "Dynamically create image pyramids for large, single-resolution images", cbPyramidalize, cbPyramidalize);
PaneTools.addGridRow(paneType, row++, 0, "Read and import objects (e.g. annotations) from the image file, if possible", cbImportObjects, cbImportObjects);
paneImages.setCenter(paneList);
paneImages.setBottom(paneType);
// TilePane paneButtons = new TilePane();
// paneButtons.getChildren().addAll(btnFile, btnURL, btnClipboard, btnFileList);
GridPane paneButtons = PaneTools.createColumnGridControls(btnFile, btnURL, btnClipboard, btnFileList);
paneButtons.setHgap(5);
paneButtons.setPadding(new Insets(5));
BorderPane pane = new BorderPane();
pane.setCenter(paneImages);
pane.setBottom(paneButtons);
// Support drag & drop for files
pane.setOnDragOver(e -> {
e.acceptTransferModes(TransferMode.COPY);
e.consume();
});
pane.setOnDragDropped(e -> {
Dragboard dragboard = e.getDragboard();
if (dragboard.hasFiles()) {
logger.trace("Files dragged onto project import dialog");
try {
var paths = dragboard.getFiles().stream().filter(f -> f.isFile() && !f.isHidden()).map(f -> f.getAbsolutePath()).collect(Collectors.toList());
paths.removeAll(listView.getItems());
if (!paths.isEmpty())
listView.getItems().addAll(paths);
} catch (Exception ex) {
Dialogs.showErrorMessage("Drag & Drop", ex);
}
}
e.setDropCompleted(true);
e.consume();
});
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setResizable(true);
dialog.initOwner(qupath.getStage());
dialog.setTitle("Import images to project");
ButtonType typeImport = new ButtonType("Import", ButtonData.OK_DONE);
dialog.getDialogPane().getButtonTypes().addAll(typeImport, ButtonType.CANCEL);
ScrollPane scroll = new ScrollPane(pane);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
dialog.getDialogPane().setContent(scroll);
Optional<ButtonType> result = dialog.showAndWait();
if (!result.isPresent() || result.get() != typeImport)
return Collections.emptyList();
// // Do the actual import
// List<String> pathSucceeded = new ArrayList<>();
// List<String> pathFailed = new ArrayList<>();
// for (String path : listView.getItems()) {
// if (qupath.getProject().addImage(path.trim()))
// pathSucceeded.add(path);
// else
// pathFailed.add(path);
// }
ImageType type = comboType.getValue();
Rotation rotation = comboRotate.getValue();
boolean pyramidalize = cbPyramidalize.isSelected();
boolean importObjects = cbImportObjects.isSelected();
pyramidalizeProperty.set(pyramidalize);
importObjectsProperty.set(importObjects);
ImageServerBuilder<BufferedImage> requestedBuilder = requestBuilder ? comboBuilder.getSelectionModel().getSelectedItem() : builder;
List<String> argsList = new ArrayList<>();
String argsString = tfArgs.getText();
// TODO: Use a smarter approach to splitting! Currently we support so few arguments that splitting on spaces should be ok... for now.
String[] argsSplit = argsString == null || argsString.isBlank() ? new String[0] : argsString.split(" ");
for (var a : argsSplit) {
argsList.add(a);
}
if (rotation != null && rotation != Rotation.ROTATE_NONE) {
argsList.add("--rotate");
argsList.add(rotation.toString());
}
if (!argsList.isEmpty())
logger.debug("Args: [{}]", argsList.stream().collect(Collectors.joining(", ")));
String[] args = argsList.toArray(String[]::new);
List<String> pathSucceeded = new ArrayList<>();
List<String> pathFailed = new ArrayList<>();
List<ProjectImageEntry<BufferedImage>> entries = new ArrayList<>();
Task<Collection<ProjectImageEntry<BufferedImage>>> worker = new Task<>() {
@Override
protected Collection<ProjectImageEntry<BufferedImage>> call() throws Exception {
AtomicLong counter = new AtomicLong(0L);
List<String> items = new ArrayList<>(listView.getItems());
updateMessage("Checking for compatible image readers...");
// Limit the size of the thread pool
// The previous use of a cached thread pool caused trouble when importing may large, non-pyramidal images
var pool = Executors.newFixedThreadPool(ThreadTools.getParallelism(), ThreadTools.createThreadFactory("project-import", true));
// var pool = Executors.newCachedThreadPool(ThreadTools.createThreadFactory("project-import", true));
List<Future<List<ServerBuilder<BufferedImage>>>> results = new ArrayList<>();
List<ProjectImageEntry<BufferedImage>> projectImages = new ArrayList<>();
for (var item : items) {
// Try to load items from a project if possible
if (item.toLowerCase().endsWith(ProjectIO.DEFAULT_PROJECT_EXTENSION)) {
try {
var tempProject = ProjectIO.loadProject(GeneralTools.toURI(item), BufferedImage.class);
projectImages.addAll(tempProject.getImageList());
} catch (Exception e) {
logger.error("Unable to add images from {} ({})", item, e.getLocalizedMessage());
}
continue;
}
results.add(pool.submit(() -> {
try {
var uri = GeneralTools.toURI(item);
UriImageSupport<BufferedImage> support;
if (requestedBuilder == null)
support = ImageServers.getImageSupport(uri, args);
else
support = ImageServers.getImageSupport(requestedBuilder, uri, args);
if (support != null)
return support.getBuilders();
} catch (Exception e) {
logger.error("Unable to add {}");
logger.error(e.getLocalizedMessage(), e);
}
return new ArrayList<ServerBuilder<BufferedImage>>();
}));
}
List<ProjectImageEntry<BufferedImage>> failures = Collections.synchronizedList(new ArrayList<>());
// If we have projects, try adding images from these first
if (!projectImages.isEmpty()) {
if (projectImages.size() == 1)
updateMessage("Importing 1 image from existing projects");
else
updateMessage("Importing " + projectImages.size() + " images from existing projects");
for (var temp : projectImages) {
try {
project.addDuplicate(temp, true);
} catch (Exception e) {
failures.add(temp);
}
}
}
// If we have 'standard' image paths, use these next
List<ServerBuilder<BufferedImage>> builders = new ArrayList<>();
for (var result : results) {
try {
builders.addAll(result.get());
} catch (ExecutionException e) {
logger.error("Execution exception importing image", e);
}
}
long max = builders.size();
if (!builders.isEmpty()) {
if (max == 1)
updateMessage("Adding 1 image to project");
else
updateMessage("Adding " + max + " images to project");
// Add everything in order first
List<ProjectImageEntry<BufferedImage>> entries = new ArrayList<>();
for (var builder : builders) {
// if (rotation != null && rotation != Rotation.ROTATE_NONE)
// builder = RotatedImageServer.getRotatedBuilder(builder, rotation);
// if (swapRedBlue)
// builder = RearrangeRGBImageServer.getSwapRedBlueBuilder(builder);
entries.add(project.addImage(builder));
}
// Initialize (the slow bit)
int n = builders.size();
for (var entry : entries) {
pool.submit(() -> {
try {
initializeEntry(entry, type, pyramidalize, importObjects);
} catch (Exception e) {
failures.add(entry);
logger.warn("Exception adding " + entry, e);
} finally {
long i = counter.incrementAndGet();
updateProgress(i, max);
String name = entry.getImageName();
if (name != null) {
updateMessage("Added " + i + "/" + n + " - " + name);
}
}
});
}
}
pool.shutdown();
try {
pool.awaitTermination(60, TimeUnit.MINUTES);
} catch (Exception e) {
logger.error("Exception waiting for project import to complete: " + e.getLocalizedMessage(), e);
}
if (!failures.isEmpty()) {
String message;
if (failures.size() == 1)
message = "Failed to load one image.";
else
message = "Failed to load " + failures.size() + " images.";
if (requestedBuilder != null)
message += "\nThe image type might not be supported by '" + requestedBuilder.getName() + "'";
Dialogs.showErrorMessage("Import images", message);
var toRemove = failures.stream().filter(p -> project.getImageList().contains(p)).collect(Collectors.toList());
project.removeAllImages(toRemove, true);
}
// Now save changes
project.syncChanges();
// builders.parallelStream().forEach(builder -> {
// // builders.parallelStream().forEach(builder -> {
// try (var server = builder.build()) {
// var entry = addSingleImageToProject(project, server);
// updateMessage("Added " + entry.getImageName());
// } catch (Exception e) {
// logger.warn("Exception adding " + builder, e);
// } finally {
// updateProgress(counter.incrementAndGet(), max);
// }
// });
updateProgress(max, max);
return entries;
}
};
ProgressDialog progress = new ProgressDialog(worker);
progress.setTitle("Project import");
qupath.submitShortTask(worker);
progress.showAndWait();
try {
project.syncChanges();
} catch (IOException e1) {
Dialogs.showErrorMessage("Sync project", e1);
}
qupath.refreshProject();
StringBuilder sb = new StringBuilder();
if (!pathSucceeded.isEmpty()) {
sb.append("Successfully imported " + pathSucceeded.size() + " paths:\n");
for (String path : pathSucceeded) sb.append("\t" + path + "\n");
sb.append("\n");
qupath.refreshProject();
ProjectBrowser.syncProject(qupath.getProject());
}
if (!pathFailed.isEmpty()) {
sb.append("Unable to import " + pathFailed.size() + " paths:\n");
for (String path : pathFailed) sb.append("\t" + path + "\n");
sb.append("\n");
TextArea textArea = new TextArea();
textArea.setText(sb.toString());
if (pathSucceeded.isEmpty())
Dialogs.showErrorMessage(commandName, textArea);
else
Dialogs.showMessageDialog(commandName, textArea);
}
// TODO: Add failed and successful paths to pathFailed/pathSucceeded, so the line below prints something
if (sb.length() > 0)
logger.info(sb.toString());
return entries;
}
use of qupath.lib.images.ImageData.ImageType in project qupath by qupath.
the class ImageDetailsPane method promptToSetImageType.
// /**
// * Prompt the user to set the {@link ImageType} for the image.
// * @param imageData
// * @return
// */
// public static boolean promptToSetImageType(ImageData<BufferedImage> imageData) {
// List<ImageType> values = Arrays.asList(ImageType.values());
// if (!imageData.getServer().isRGB()) {
// values = new ArrayList<>(values);
// values.remove(ImageType.BRIGHTFIELD_H_DAB);
// values.remove(ImageType.BRIGHTFIELD_H_E);
// values.remove(ImageType.BRIGHTFIELD_OTHER);
// }
//
// var dialog = new ChoiceDialog<>(imageData.getImageType(), values);
// dialog.setTitle("Image type");
// if (QuPathGUI.getInstance() != null)
// dialog.initOwner(QuPathGUI.getInstance().getStage());
// dialog.getDialogPane().setHeaderText(null);
// dialog.getDialogPane().setContentText("Set image type");
// dialog.getDialogPane().setPrefWidth(400);
//
// var labelExplain = new Label("The image type influences some commands (e.g. cell detection) and should be set for every image. "
// + "\n\nSelect an option below or in the preferences to customize how QuPath handles setting the image type when opening an image."
// + "\n\n'Auto-estimate' is convenient to reduce annoying prompts, but the estimates are sometimes wrong. "
// + "When this happens you can correct them by double-clicking "
// + "the type under the 'Image' tab.");
// labelExplain.setWrapText(true);
// labelExplain.setPrefWidth(400);
// labelExplain.setMinHeight(Label.USE_PREF_SIZE);
//
// var comboSetType = new ComboBox<ImageTypeSetting>();
// comboSetType.getItems().setAll(ImageTypeSetting.values());
// comboSetType.getSelectionModel().select(PathPrefs.imageTypeSettingProperty().get());
// comboSetType.setMaxWidth(Double.MAX_VALUE);
// labelExplain.setPadding(new Insets(0, 0, 10, 0));
// var expandablePane = new BorderPane(labelExplain);
// expandablePane.setBottom(comboSetType);
//
// dialog.getDialogPane().setExpandableContent(expandablePane);
//
// var result = dialog.showAndWait();
// ImageType type = result.orElse(null);
// if (type == null)
// return false;
//
// if (comboSetType.getSelectionModel().getSelectedItem() != null)
// PathPrefs.imageTypeSettingProperty().set(comboSetType.getSelectionModel().getSelectedItem());
//
// if (type != imageData.getImageType()) {
// imageData.setImageType(type);
// return true;
// }
// return false;
// }
/**
* Prompt the user to set the {@link ImageType} for the image.
* @param imageData the image data for which the type should be set
* @param defaultType the default type (selected when the dialog is shown)
* @return true if the type was changed, false otherwise
*/
public static boolean promptToSetImageType(ImageData<BufferedImage> imageData, ImageType defaultType) {
double size = 32;
var group = new ToggleGroup();
boolean isRGB = imageData.getServer().isRGB();
if (defaultType == null)
defaultType = ImageType.UNSET;
var buttonMap = new LinkedHashMap<ImageType, ToggleButton>();
// TODO: Create a nicer icon for unspecified type
var iconUnspecified = (Group) createImageTypeCell(Color.GRAY, null, null, size);
if (isRGB) {
buttonMap.put(ImageType.BRIGHTFIELD_H_E, createImageTypeButton(ImageType.BRIGHTFIELD_H_E, "Brightfield\nH&E", createImageTypeCell(Color.WHITE, Color.PINK, Color.DARKBLUE, size), "Brightfield image with hematoylin & eosin stains\n(8-bit RGB only)", isRGB));
buttonMap.put(ImageType.BRIGHTFIELD_H_DAB, createImageTypeButton(ImageType.BRIGHTFIELD_H_DAB, "Brightfield\nH-DAB", createImageTypeCell(Color.WHITE, Color.rgb(200, 200, 220), Color.rgb(120, 50, 20), size), "Brightfield image with hematoylin & DAB stains\n(8-bit RGB only)", isRGB));
buttonMap.put(ImageType.BRIGHTFIELD_OTHER, createImageTypeButton(ImageType.BRIGHTFIELD_OTHER, "Brightfield\nOther", createImageTypeCell(Color.WHITE, Color.ORANGE, Color.FIREBRICK, size), "Brightfield image with other chromogenic stains\n(8-bit RGB only)", isRGB));
}
buttonMap.put(ImageType.FLUORESCENCE, createImageTypeButton(ImageType.FLUORESCENCE, "Fluorescence", createImageTypeCell(Color.BLACK, Color.LIGHTGREEN, Color.BLUE, size), "Fluorescence or fluorescence-like image with a dark background\n" + "Also suitable for imaging mass cytometry", true));
buttonMap.put(ImageType.OTHER, createImageTypeButton(ImageType.OTHER, "Other", createImageTypeCell(Color.BLACK, Color.WHITE, Color.GRAY, size), "Any other image type", true));
buttonMap.put(ImageType.UNSET, createImageTypeButton(ImageType.UNSET, "Unspecified", iconUnspecified, "Do not set the image type (not recommended for analysis)", true));
var buttons = buttonMap.values().toArray(ToggleButton[]::new);
for (var btn : buttons) {
if (btn.isDisabled()) {
btn.getTooltip().setText("Image type is not supported because image is not RGB");
}
}
var buttonList = Arrays.asList(buttons);
group.getToggles().setAll(buttons);
group.selectedToggleProperty().addListener((v, o, n) -> {
// Ensure that we can't deselect all buttons
if (n == null)
o.setSelected(true);
});
PaneTools.setMaxWidth(Double.MAX_VALUE, buttons);
PaneTools.setMaxHeight(Double.MAX_VALUE, buttons);
var selectedButton = buttonMap.get(defaultType);
group.selectToggle(selectedButton);
var grid = new GridPane();
int nHorizontal = 3;
int nVertical = (int) Math.ceil(buttons.length / (double) nHorizontal);
grid.getColumnConstraints().setAll(IntStream.range(0, nHorizontal).mapToObj(i -> {
var c = new ColumnConstraints();
c.setPercentWidth(100.0 / nHorizontal);
return c;
}).collect(Collectors.toList()));
grid.getRowConstraints().setAll(IntStream.range(0, nVertical).mapToObj(i -> {
var c = new RowConstraints();
c.setPercentHeight(100.0 / nVertical);
return c;
}).collect(Collectors.toList()));
grid.setVgap(5);
// grid.setHgap(5);
grid.setMaxWidth(Double.MAX_VALUE);
for (int i = 0; i < buttons.length; i++) {
grid.add(buttons[i], i % nHorizontal, i / nHorizontal);
}
// grid.getChildren().setAll(buttons);
var content = new BorderPane(grid);
var comboOptions = new ComboBox<ImageTypeSetting>();
comboOptions.getItems().setAll(ImageTypeSetting.values());
var prompts = Map.of(ImageTypeSetting.AUTO_ESTIMATE, "Always auto-estimate type (don't prompt)", ImageTypeSetting.PROMPT, "Always prompt me to set type", ImageTypeSetting.NONE, "Don't set the image type");
comboOptions.setButtonCell(GuiTools.createCustomListCell(p -> prompts.get(p)));
comboOptions.setCellFactory(c -> GuiTools.createCustomListCell(p -> prompts.get(p)));
comboOptions.setTooltip(new Tooltip("Choose whether you want to see these prompts " + "when opening an image for the first time"));
comboOptions.setMaxWidth(Double.MAX_VALUE);
// comboOptions.prefWidthProperty().bind(grid.widthProperty().subtract(100));
comboOptions.getSelectionModel().select(PathPrefs.imageTypeSettingProperty().get());
if (nVertical > 1)
BorderPane.setMargin(comboOptions, new Insets(5, 0, 0, 0));
else
BorderPane.setMargin(comboOptions, new Insets(10, 0, 0, 0));
content.setBottom(comboOptions);
var labelDetails = new Label("The image type is used for stain separation " + "by some commands, e.g. 'Cell detection'.\n" + "Brightfield types are only available for 8-bit RGB images.");
// + "For 'Brightfield' images you can set the color stain vectors.");
labelDetails.setWrapText(true);
labelDetails.prefWidthProperty().bind(grid.widthProperty().subtract(10));
labelDetails.setMaxHeight(Double.MAX_VALUE);
labelDetails.setPrefHeight(Label.USE_COMPUTED_SIZE);
labelDetails.setPrefHeight(100);
labelDetails.setAlignment(Pos.CENTER);
labelDetails.setTextAlignment(TextAlignment.CENTER);
var dialog = Dialogs.builder().title("Set image type").headerText("What type of image is this?").content(content).buttons(ButtonType.APPLY, ButtonType.CANCEL).expandableContent(labelDetails).build();
// Try to make it easier to dismiss the dialog in a variety of ways
var btnApply = dialog.getDialogPane().lookupButton(ButtonType.APPLY);
Platform.runLater(() -> selectedButton.requestFocus());
for (var btn : buttons) {
btn.setOnMouseClicked(e -> {
if (!btn.isDisabled() && e.getClickCount() == 2) {
btnApply.fireEvent(new ActionEvent());
e.consume();
}
});
}
var enterPressed = new KeyCodeCombination(KeyCode.ENTER);
var spacePressed = new KeyCodeCombination(KeyCode.SPACE);
dialog.getDialogPane().addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (enterPressed.match(e) || spacePressed.match(e)) {
btnApply.fireEvent(new ActionEvent());
e.consume();
} else if (e.getCode() == KeyCode.UP || e.getCode() == KeyCode.DOWN || e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.RIGHT) {
var selected = (ToggleButton) group.getSelectedToggle();
var ind = buttonList.indexOf(selected);
var newSelected = selected;
if (e.getCode() == KeyCode.UP && ind >= nHorizontal) {
newSelected = buttonList.get(ind - nHorizontal);
}
if (e.getCode() == KeyCode.LEFT && ind > 0) {
newSelected = buttonList.get(ind - 1);
}
if (e.getCode() == KeyCode.RIGHT && ind < buttonList.size() - 1) {
newSelected = buttonList.get(ind + 1);
}
if (e.getCode() == KeyCode.DOWN && ind < buttonList.size() - nHorizontal) {
newSelected = buttonList.get(ind + nHorizontal);
}
newSelected.requestFocus();
group.selectToggle(newSelected);
e.consume();
}
});
var response = dialog.showAndWait();
if (response.orElse(ButtonType.CANCEL) == ButtonType.APPLY) {
PathPrefs.imageTypeSettingProperty().set(comboOptions.getSelectionModel().getSelectedItem());
var selectedType = (ImageType) group.getSelectedToggle().getUserData();
if (selectedType != imageData.getImageType()) {
imageData.setImageType(selectedType);
return true;
}
}
return false;
}
Aggregations