diff --git a/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java b/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java index 2c4aa771..bacc1e97 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java +++ b/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java @@ -14,7 +14,7 @@ public class LightSheetManagerPlugin implements MenuPlugin, SciJavaPlugin { public static final String copyright = "Applied Scientific Instrumentation (ASI), 2022-2026"; public static final String description = "A plugin to control various types of light sheet microscopes."; public static final String menuName = "Light Sheet Manager"; - public static final String version = "0.6.8"; + public static final String version = "0.6.9"; private Studio studio_; private LightSheetManager model_; diff --git a/src/main/java/org/micromanager/lightsheetmanager/gui/components/TextField.java b/src/main/java/org/micromanager/lightsheetmanager/gui/components/TextField.java index 6e0e49aa..fc263381 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/components/TextField.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/components/TextField.java @@ -1,24 +1,99 @@ package org.micromanager.lightsheetmanager.gui.components; import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.Color; +import java.util.function.Consumer; public class TextField extends JTextField { + private static final String ILLEGAL_FILENAME_CHARS = "\\/:*?\"<>|"; + private static final Color ERROR_COLOR = new Color(110, 25, 25); + + private final Color defaultColor_; + public TextField() { setColumns(5); + defaultColor_ = getBackground(); } public TextField(final int size) { setColumns(size); + defaultColor_ = getBackground(); } public TextField(final String text, final int size) { super(text); setColumns(size); + defaultColor_ = getBackground(); } public void registerListener(final Runnable listener) { + // fires when enter is pressed addActionListener(e -> listener.run()); } + /** + * Registers a listener that validates the current text as a Windows + * filename whenever the document changes. + * + * @param listener receives {@code true} if the current text is valid + */ + public void registerFilenameValidationListener(final Consumer listener) { + final Runnable validate = () -> { + final boolean isValid = isValidWindowsFilename(getText()); + setBackground(isValid ? defaultColor_ : ERROR_COLOR); + listener.accept(isValid); + }; + + final DocumentListener docListener = new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + validate.run(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + validate.run(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + validate.run(); + } + }; + + getDocument().addDocumentListener(docListener); + } + + /** + * Returns {@code true} if the filename is valid on Windows. + * + * @param name the name to check + * @return {@code true} if the filename is valid + */ + private static boolean isValidWindowsFilename(final String name) { + // empty names are invalid + if (name.isEmpty()) { + return false; + } + + // trailing space or period + final char last = name.charAt(name.length() - 1); + if (last == ' ' || last == '.') { + return false; + } + + // check for illegal characters and control characters + for (int i = 0; i < name.length(); i++) { + final char c = name.charAt(i); + // c < 32 rejects all ASCII control characters (0x00–0x1F) + if (c < 32 || ILLEGAL_FILENAME_CHARS.indexOf(c) >= 0) { + return false; + } + } + + return true; + } } diff --git a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SavePanel.java b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SavePanel.java index dfa10e51..c6df7662 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SavePanel.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SavePanel.java @@ -164,6 +164,13 @@ public void createEventHandlers() { () -> model_.acquisitions().settingsBuilder() .saveNamePrefix(txtSaveFileName_.getText())); + txtSaveFileName_.registerFilenameValidationListener(isValid -> { + if (isValid) { + model_.acquisitions().settingsBuilder() + .saveNamePrefix(txtSaveFileName_.getText()); + } + }); + cbxSaveMode_.registerListener( () -> model_.acquisitions().settingsBuilder() .saveMode(cbxSaveMode_.getSelected())); diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java index 02a3a28c..670280c2 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java @@ -241,7 +241,9 @@ boolean run() { // TODO: put this in AcquisitionEngine base class, between setup and run once structure is better // save settings as JSON to the save directory - FileUtils.writeStringToFile(saveDir + File.separator + "acq_settings.json", settingsJson); + if (model_.acquisitions().settings().isSavingImagesDuringAcquisition()) { + FileUtils.writeStringToFile(saveDir + File.separator + "acq_settings.json", settingsJson); + } // write the position list if we are using multiple positions if (model_.acquisitions().settings().isUsingMultiplePositions()) {