Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,36 @@
*/
public abstract class BaseAcquisitionSettings implements AcquisitionSettings {

private static final String ILLEGAL_FILENAME_CHARS = "\\/:*?\"<>|";

/**
* Returns why the save name cannot be used as a Windows file name, or null when it can.
*
* @param name the save name
* @return the problem, or null
*/
public static String saveNamePrefixProblem(final String name) {
if (name == null || name.isEmpty()) {
return "The save name is empty";
}
final char last = name.charAt(name.length() - 1);
if (last == ' ' || last == '.') {
return "The save name \"" + name + "\" ends with a space or a period";
}
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) {
return "The save name contains a control character";
}
if (ILLEGAL_FILENAME_CHARS.indexOf(c) >= 0) {
return "The save name \"" + name + "\" contains \"" + c
+ "\", which Windows does not allow in file names";
}
}
return null;
}

public abstract static class Builder<T extends Builder<T>> implements AcquisitionSettings.Builder<T> {

private String saveDirectory = System.getProperty("user.home");
Expand Down Expand Up @@ -80,9 +110,14 @@ public T saveDirectory(final String directory) {
* Sets the folder name.
*
* @param name the name of the folder
* @throws IllegalArgumentException if the name cannot be used as a Windows file name
*/
@Override
public T saveNamePrefix(final String name) {
final String problem = saveNamePrefixProblem(name);
if (problem != null) {
throw new IllegalArgumentException(problem);
}
saveNamePrefix = name;
return self();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ private DefaultAutofocusSettings(Builder builder) {
channel = builder.channel;
}

// Note: used by GSON library for deserialization
private DefaultAutofocusSettings() {
this(new Builder());
}

public static Builder builder() {
return new Builder();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import javax.swing.event.DocumentListener;
import java.awt.Color;
import java.util.function.Consumer;
import java.util.function.Function;

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_;
Expand All @@ -35,16 +35,18 @@ public void registerListener(final Runnable listener) {
}

/**
* Registers a listener that validates the current text as a Windows
* filename whenever the document changes.
* Registers a listener that checks the current text whenever the document changes,
* and shows the error color while the text has a problem.
*
* @param listener receives {@code true} if the current text is valid
* @param problemOf returns why the text is invalid, or null when it is valid
* @param listener receives the problem, or null when the current text is valid
*/
public void registerFilenameValidationListener(final Consumer<Boolean> listener) {
public void registerValidationListener(final Function<String, String> problemOf,
final Consumer<String> listener) {
final Runnable validate = () -> {
final boolean isValid = isValidWindowsFilename(getText());
setBackground(isValid ? defaultColor_ : ERROR_COLOR);
listener.accept(isValid);
final String problem = problemOf.apply(getText());
setBackground(problem == null ? defaultColor_ : ERROR_COLOR);
listener.accept(problem);
};

final DocumentListener docListener = new DocumentListener() {
Expand Down Expand Up @@ -77,34 +79,4 @@ public void changedUpdate(DocumentEvent e) {
public void setValid(final boolean isValid) {
setBackground(isValid ? defaultColor_ : ERROR_COLOR);
}

/**
* 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.micromanager.lightsheetmanager.LightSheetManagerFrame;
import org.micromanager.lightsheetmanager.api.AcquisitionSettings;
import org.micromanager.lightsheetmanager.api.data.SaveMode;
import org.micromanager.lightsheetmanager.api.internal.BaseAcquisitionSettings;
import org.micromanager.lightsheetmanager.api.internal.ScapeAcquisitionSettings;
import org.micromanager.lightsheetmanager.gui.components.Button;
import org.micromanager.lightsheetmanager.gui.components.CheckBox;
Expand Down Expand Up @@ -194,12 +195,14 @@ public void createEventHandlers() {
}
});

txtSaveFileName_.registerFilenameValidationListener(isValid -> {
if (isValid) {
model_.acquisitions().settingsBuilder()
.saveNamePrefix(txtSaveFileName_.getText());
}
});
txtSaveFileName_.registerValidationListener(BaseAcquisitionSettings::saveNamePrefixProblem,
problem -> {
txtSaveFileName_.setToolTipText(problem);
if (problem == null) {
model_.acquisitions().settingsBuilder()
.saveNamePrefix(txtSaveFileName_.getText());
}
});

cbxSaveMode_.registerListener(
() -> model_.acquisitions().settingsBuilder()
Expand Down Expand Up @@ -230,7 +233,13 @@ public void createEventHandlers() {
);
if (file != null) {
final String json = FileUtils.readFileToString(file.toString());
model_.userSettings().loadFromJson(json, true);
try {
model_.userSettings().loadFromJson(json, true);
} catch (RuntimeException e) {
model_.studio().logs().showError(e,
"Could not load the acquisition settings from " + file);
return;
}
model_.studio().logs().logMessage("Acquisition settings loaded from: " + file);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ public void load() {
public void loadFromJson(final String json, final boolean notify) {
// TODO: switch this based on microscope geometry type
var settings = ScapeAcquisitionSettings.fromJson(json, ScapeAcquisitionSettings.class);
if (settings == null) {
throw new IllegalArgumentException("the text holds no acquisition settings");
}
// useHardwareTimePoints is a DERIVED value. It is serialized as part of the acquisition settings,
// but must never be trusted when reloaded: a stale "true" persisted from an earlier short-interval run
// would otherwise seed acqSettings_ across a plugin restart and be read before the next run recomputes it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.micromanager.lightsheetmanager.api.AcquisitionSettings;
import org.micromanager.lightsheetmanager.api.TimingSettings;
import org.micromanager.lightsheetmanager.api.data.AcquisitionMode;
import org.micromanager.lightsheetmanager.api.internal.BaseAcquisitionSettings;
import org.micromanager.lightsheetmanager.api.internal.ScapeAcquisitionSettings;
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.DurationPanel;
import org.micromanager.lightsheetmanager.model.autofocus.AutofocusAdapter;
Expand Down Expand Up @@ -101,22 +102,31 @@ protected boolean validateSaveLocation() {
final String saveNamePrefix = acqSettings_.saveNamePrefix();
if (saveNamePrefix == null || saveNamePrefix.trim().isEmpty()) {
model_.logging().reportError("The save name prefix is empty.\n\n"
+ "Set a name on the Datastore panel, or uncheck \"Save images during acquisition\".");
+ "Set a name on the Save Settings panel, or uncheck "
+ "\"Save images during acquisition\".");
return false;
}

// the builder refuses these names, but loaded settings bypass it
final String saveNameProblem = BaseAcquisitionSettings.saveNamePrefixProblem(saveNamePrefix);
if (saveNameProblem != null) {
model_.logging().reportError(saveNameProblem + ".\n\n"
+ "Change the name on the Save Settings panel.");
return false;
}

final String saveDirectory = acqSettings_.saveDirectory();
if (saveDirectory == null || saveDirectory.trim().isEmpty()) {
model_.logging().reportError("The save directory is not set.\n\n"
+ "Set a directory on the Datastore panel, or uncheck "
+ "Set a directory on the Save Settings panel, or uncheck "
+ "\"Save images during acquisition\".");
return false;
}

final File directory = new File(saveDirectory);
if (!directory.exists()) {
model_.logging().reportError("The save directory does not exist:\n\n" + saveDirectory
+ "\n\nCreate it, or choose another directory on the Datastore panel.");
+ "\n\nCreate it, or choose another directory on the Save Settings panel.");
return false;
}
if (!directory.isDirectory()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1806,7 +1806,9 @@ public DefaultTimingSettings.Builder getTimingFromExposure() {
delayBeforeScanMs = 0.0;
break;
default:
studio_.logs().showError("Invalid camera mode");
studio_.logs().showError("The camera trigger mode \"" + cameraMode
+ "\" has no stage scan timing.\n\n"
+ "Select another mode in the Trigger Mode menu on the Acquisition tab.");
break;
}

Expand Down Expand Up @@ -1992,7 +1994,9 @@ public DefaultTimingSettings.Builder getTimingFromPeriodAndLightExposure() {
}
break;
default:
studio_.logs().showError("Invalid camera mode");
studio_.logs().showError("The camera trigger mode \"" + cameraMode
+ "\" has no slice timing.\n\n"
+ "Select another mode in the Trigger Mode menu on the Acquisition tab.");
break;
}

Expand Down
Loading