use of qupath.lib.gui.scripting.DefaultScriptEditor in project qupath by qupath.
the class DragDropImportListener method handleFileDropImpl.
private void handleFileDropImpl(final QuPathViewer viewer, final List<File> list) throws IOException {
// Shouldn't occur... but keeps FindBugs happy to check
if (list == null) {
logger.warn("No files given!");
return;
}
// Check if we have only jar files
int nJars = 0;
for (File file : list) {
if (file.getName().toLowerCase().endsWith(".jar"))
nJars++;
}
if (nJars == list.size()) {
if (qupath.canInstallExtensions())
qupath.installExtensions(list);
else
Dialogs.showErrorMessage("Install extensions", "Sorry, extensions can only be installed when QuPath is run as a standalone application.");
return;
}
// Try to get a hierarchy for importing ROIs
ImageData<BufferedImage> imageData = viewer == null ? null : viewer.getImageData();
PathObjectHierarchy hierarchy = imageData == null ? null : imageData.getHierarchy();
// Some consumers can only handle one file
boolean singleFile = list.size() == 1;
// Gather together the extensions - if this has length one, we know all the files have the same extension
Set<String> allExtensions = list.stream().map(f -> GeneralTools.getExtension(f).orElse("")).collect(Collectors.toSet());
// If we have a zipped file, create a set that includes the files within the zip image
// This helps us determine whether or not a zip file contains an image or objects, for example
Set<String> allUnzippedExtensions = allExtensions;
if (allExtensions.contains(".zip")) {
allUnzippedExtensions = list.stream().flatMap(f -> {
try {
return PathIO.unzippedExtensions(f.toPath()).stream();
} catch (IOException e) {
logger.debug(e.getLocalizedMessage(), e);
return Arrays.stream(new String[0]);
}
}).collect(Collectors.toSet());
}
// Extract the first (and possibly only) file
File file = list.get(0);
String fileName = file.getName().toLowerCase();
// Check if this is a hierarchy file
if (singleFile && (fileName.endsWith(PathPrefs.getSerializationExtension()))) {
// If we have a different path, open as a new image
if (viewer == null) {
Dialogs.showErrorMessage("Load data", "Please drag the file onto a specific viewer to open!");
return;
}
try {
// Check if we should be importing objects or opening the file
if (imageData != null) {
var dialog = new Dialog<ButtonType>();
var btOpen = new ButtonType("Open image");
var btImport = new ButtonType("Import objects");
dialog.getDialogPane().getButtonTypes().setAll(btOpen, btImport, ButtonType.CANCEL);
dialog.setTitle("Open data");
dialog.setHeaderText("What do you want to do with the data file?");
dialog.setContentText("You can\n" + " 1. Open the image in the current viewer\n" + " 2. Import objects and add them to the current image");
// dialog.setHeaderText("What do you want to do?");
var choice = dialog.showAndWait().orElse(ButtonType.CANCEL);
if (choice == ButtonType.CANCEL)
return;
if (choice == btImport) {
var pathObjects = PathIO.readObjects(file);
hierarchy.addPathObjects(pathObjects);
return;
}
}
qupath.openSavedData(viewer, file, false, true);
} catch (Exception e) {
Dialogs.showErrorMessage("Load data", e);
}
return;
}
// Check if this is a directory - if so, look for a single project file
if (singleFile && file.isDirectory()) {
// Identify all files in the directory, and also all potential project files
File[] filesInDirectory = file.listFiles(f -> !f.isHidden());
List<File> projectFiles = Arrays.stream(filesInDirectory).filter(f -> f.isFile() && f.getAbsolutePath().toLowerCase().endsWith(ProjectIO.getProjectExtension())).collect(Collectors.toList());
if (projectFiles.size() == 1) {
file = projectFiles.get(0);
logger.warn("Selecting project file {}", file);
} else if (projectFiles.size() > 1) {
// Prompt to select which project file to open
logger.debug("Multiple project files found in directory {}", file);
String[] fileNames = projectFiles.stream().map(f -> f.getName()).toArray(n -> new String[n]);
String selectedName = Dialogs.showChoiceDialog("Select project", "Select project to open", fileNames, fileNames[0]);
if (selectedName == null)
return;
file = new File(file, selectedName);
} else if (filesInDirectory.length == 0) {
// If we have an empty directory, offer to set it as a project
if (Dialogs.showYesNoDialog("Create project", "Create project for empty directory?")) {
Project<BufferedImage> project = Projects.createProject(file, BufferedImage.class);
qupath.setProject(project);
if (!project.isEmpty())
project.syncChanges();
return;
} else
// Can't do anything else with an empty folder
return;
}
}
// Check if this is a project
if (singleFile && (fileName.endsWith(ProjectIO.getProjectExtension()))) {
try {
Project<BufferedImage> project = ProjectIO.loadProject(file, BufferedImage.class);
qupath.setProject(project);
} catch (Exception e) {
// Dialogs.showErrorMessage("Project error", e);
logger.error("Could not open as project file: {}, opening in the Script Editor instead", e);
qupath.getScriptEditor().showScript(file);
}
return;
}
// Check if it is an object file in GeoJSON format (.geojson)
if (PathIO.getObjectFileExtensions(false).containsAll(allUnzippedExtensions)) {
if (imageData == null || hierarchy == null) {
qupath.getScriptEditor().showScript(file);
logger.info("Opening the dragged file in the Script Editor as there is no currently opened image in the viewer");
// Dialogs.showErrorMessage("Open object file", "Please open an image first to import objects!");
return;
}
List<PathObject> pathObjects = new ArrayList<>();
List<WorkflowStep> steps = new ArrayList<>();
for (var tempFile : list) {
try {
var tempObjects = PathIO.readObjects(tempFile);
if (tempObjects.isEmpty()) {
logger.warn("No objects found in {}, opening the dragged file in the Script Editor instead", tempFile.getAbsolutePath());
qupath.getScriptEditor().showScript(file);
return;
}
pathObjects.addAll(tempObjects);
// Add step to workflow
Map<String, String> map = new HashMap<>();
map.put("path", file.getPath());
String method = "Import objects";
String methodString = String.format("%s(%s%s%s)", "importObjectsFromFile", "\"", GeneralTools.escapeFilePath(tempFile.getPath()), "\"");
steps.add(new DefaultScriptableWorkflowStep(method, map, methodString));
} catch (IOException | IllegalArgumentException e) {
Dialogs.showErrorNotification("Object import", e.getLocalizedMessage());
return;
}
}
// Ask confirmation to user
int nObjects = pathObjects.size();
String message = nObjects == 1 ? "Add object to the hierarchy?" : String.format("Add %d objects to the hierarchy?", nObjects);
var confirm = Dialogs.showConfirmDialog("Add to hierarchy", message);
if (!confirm)
return;
// Add objects to hierarchy
hierarchy.addPathObjects(pathObjects);
imageData.getHistoryWorkflow().addSteps(steps);
return;
}
// Check if this is TMA dearraying data file
if (singleFile && (fileName.endsWith(TMADataIO.TMA_DEARRAYING_DATA_EXTENSION))) {
if (hierarchy == null)
Dialogs.showErrorMessage("TMA grid import", "Please open an image first before importing a dearrayed TMA grid!");
else {
TMAGrid tmaGrid = TMADataIO.importDearrayedTMAData(file);
if (tmaGrid != null) {
if (hierarchy.isEmpty() || Dialogs.showYesNoDialog("TMA grid import", "Set TMA grid for existing hierarchy?"))
hierarchy.setTMAGrid(tmaGrid);
} else
Dialogs.showErrorMessage("TMA grid import", "Could not parse TMA grid from " + file.getName());
}
return;
}
// Open file with an extension supported by the Script Editor
ScriptEditor scriptEditor = qupath.getScriptEditor();
if (scriptEditor instanceof DefaultScriptEditor && ((DefaultScriptEditor) scriptEditor).supportsFile(file)) {
scriptEditor.showScript(file);
return;
}
// Check handlers
for (DropHandler<File> handler : dropHandlers) {
if (handler.handleDrop(viewer, list))
return;
}
// Assume we have images
if (singleFile && file.isFile()) {
// Try to open as an image, if the extension is known
if (viewer == null) {
Dialogs.showErrorMessage("Open image", "Please drag the file only a specific viewer to open!");
return;
}
qupath.openImage(viewer, file.getAbsolutePath(), true, true);
return;
} else if (qupath.getProject() != null) {
// Try importing multiple images to a project
String[] potentialFiles = list.stream().filter(f -> f.isFile()).map(f -> f.getAbsolutePath()).toArray(String[]::new);
if (potentialFiles.length > 0) {
ProjectCommands.promptToImportImages(qupath, potentialFiles);
return;
}
}
if (qupath.getProject() == null) {
if (list.size() > 1) {
Dialogs.showErrorMessage("Drag & drop", "Could not handle multiple file drop - if you want to handle multiple images, you need to create a project first");
return;
}
}
if (list.size() > 1)
Dialogs.showErrorMessage("Drag & drop", "Sorry, I couldn't figure out what to do with these files - try opening one at a time");
else
Dialogs.showErrorMessage("Drag & drop", "Sorry, I couldn't figure out what to do with " + list.get(0).getName());
}
use of qupath.lib.gui.scripting.DefaultScriptEditor in project qupath by qupath.
the class RichScriptEditor method getNewEditor.
@Override
protected ScriptEditorControl getNewEditor() {
try {
CodeArea codeArea = new CustomCodeArea();
CodeAreaControl control = new CodeAreaControl(codeArea);
/*
* Using LineNumberFactory.get(codeArea) gives errors related to the new paragraph folding introduced in RichTextFX 0.10.6.
* java.lang.IllegalArgumentException: Visible paragraphs' last index is [-1] but visibleParIndex was [0]
*
* To replicate
* - Run using codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea));
* - Add some code (including line breaks) to the code area
* - Select all text (Ctrl/Cmd + A)
* - Delete text (backspace)
* - Add more text
*
* The change below avoids code folding being used.
*/
codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea, digits -> "%1$" + digits + "s", null, null));
// codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea));
codeArea.setStyle("-fx-background-color: -fx-control-inner-background;");
// Catch key typed events for special character handling (which should be platform-agnostic)
codeArea.addEventFilter(KeyEvent.KEY_TYPED, e -> {
if (e.isConsumed())
return;
var scriptSyntax = currentLanguage.get().getSyntax();
if ("(".equals(e.getCharacter())) {
scriptSyntax.handleLeftParenthesis(control, smartEditing.get());
e.consume();
} else if (")".equals(e.getCharacter())) {
scriptSyntax.handleRightParenthesis(control, smartEditing.get());
e.consume();
} else if ("\"".equals(e.getCharacter())) {
scriptSyntax.handleQuotes(control, true, smartEditing.get());
e.consume();
} else if ("\'".equals(e.getCharacter())) {
scriptSyntax.handleQuotes(control, false, smartEditing.get());
e.consume();
}
});
// TODO: Check if DefaultScriptEditor does any of these? It should be able to at least do syntaxing/auto-completion
codeArea.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.isConsumed())
return;
var scriptSyntax = currentLanguage.get().getSyntax();
if (scriptSyntax != null) {
if (e.getCode() == KeyCode.TAB) {
scriptSyntax.handleTabPress(control, e.isShiftDown());
e.consume();
} else if (e.isShortcutDown() && e.getCode() == KeyCode.SLASH) {
scriptSyntax.handleLineComment(control);
e.consume();
} else if (e.getCode() == KeyCode.ENTER && codeArea.getSelectedText().length() == 0) {
scriptSyntax.handleNewLine(control, smartEditing.get());
e.consume();
} else if (e.getCode() == KeyCode.BACK_SPACE) {
if (scriptSyntax.handleBackspace(control, smartEditing.get()) && !e.isShortcutDown() && !e.isShiftDown())
e.consume();
} else if (beautifyerCodeCombination.match(e)) {
getCurrentTextComponent().setText(scriptSyntax.beautify(getCurrentText()));
e.isConsumed();
}
}
var scriptAutoCompletor = currentLanguage.get().getAutoCompletor();
if (scriptAutoCompletor != null) {
if (completionCodeCombination.match(e)) {
scriptAutoCompletor.applyNextCompletion(control);
e.isConsumed();
} else if (!e.isControlDown())
scriptAutoCompletor.resetCompletion();
}
});
codeArea.setOnContextMenuRequested(e -> menu.show(codeArea.getScene().getWindow(), e.getScreenX(), e.getScreenY()));
@SuppressWarnings("unused") var cleanup = codeArea.multiPlainChanges().successionEnds(Duration.ofMillis(delayMillis)).supplyTask(() -> {
Task<StyleSpans<Collection<String>>> task = new Task<>() {
@Override
protected StyleSpans<Collection<String>> call() {
return scriptHighlighter.get().computeEditorHighlighting(codeArea.getText());
}
};
executor.execute(task);
return task;
}).awaitLatest(codeArea.multiPlainChanges()).filterMap(t -> {
if (t.isSuccess())
return Optional.of(t.get());
var exception = t.getFailure();
String message = exception.getLocalizedMessage() == null ? exception.getClass().getSimpleName() : exception.getLocalizedMessage();
logger.error("Error applying syntax highlighting: {}", message);
logger.debug("{}", t);
return Optional.empty();
}).subscribe(change -> codeArea.setStyleSpans(0, change));
codeArea.getStylesheets().add(getClass().getClassLoader().getResource("scripting_styles.css").toExternalForm());
scriptHighlighter.bind(Bindings.createObjectBinding(() -> ScriptHighlighterProvider.getHighlighterFromLanguage(getCurrentLanguage()), currentLanguage));
// Triggered whenever the script styling changes (e.g. change of language)
scriptHighlighter.addListener((v, o, n) -> {
if (n == null || (o != null && o.getClass().equals(n.getClass())))
return;
StyleSpans<Collection<String>> changes = scriptHighlighter.get().computeEditorHighlighting(codeArea.getText());
codeArea.setStyleSpans(0, changes);
});
return control;
} catch (Exception e) {
// Default to superclass implementation
logger.error("Unable to create code area", e);
return super.getNewEditor();
}
}
Aggregations