package org.janelia.saalfeldlab.paintera.ui.opendialog.meta; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableObjectValue; import javafx.beans.value.ObservableStringValue; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.Separator; import javafx.scene.control.TextField; import javafx.scene.control.TextFormatter.Change; import javafx.scene.control.TitledPane; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.text.TextAlignment; import org.janelia.saalfeldlab.fx.Buttons; import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.invoke.MethodHandles; import java.util.Arrays; import java.util.HashSet; import java.util.function.UnaryOperator; import java.util.stream.Stream; public class MetaPanel { private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final double GRID_HGAP = 0; private static final double TEXTFIELD_WIDTH = 100; private static final String X_STRING = "X"; private static final String Y_STRING = "Y"; private static final String Z_STRING = "Z"; private final SpatialInformation resolution; private final SpatialInformation offset; private final TextField min = new TextField(""); private final TextField max = new TextField(""); private final VBox content = new VBox(); private final ScrollPane cc = new ScrollPane(content); private final TitledPane pane = new TitledPane("meta", cc); private final VBox rawMeta = new VBox(); private final VBox labelMeta = new VBox(); private final VBox channelMeta = new VBox(); private final HashSet<Node> additionalMeta = new HashSet<>(); private final SimpleObjectProperty<TYPE> dataType = new SimpleObjectProperty<>(null); private final SimpleObjectProperty<long[]> dimensionsProperty = new SimpleObjectProperty<>(null); private final Button revertButton = Buttons.withTooltip("_Revert", "Revert array attributes", e -> {}); private final ChannelInformation channelInfo = new ChannelInformation(); public MetaPanel() { this.resolution = new SpatialInformation( TEXTFIELD_WIDTH, X_STRING, Y_STRING, Z_STRING, v -> v > 0, SpatialInformation.Submit.ON_ENTER, SpatialInformation.Submit.ON_FOCUS_LOST); this.resolution.textX().setText("1.0"); this.resolution.textY().setText("1.0"); this.resolution.textZ().setText("1.0"); this.offset = new SpatialInformation( TEXTFIELD_WIDTH, X_STRING, Y_STRING, Z_STRING, v -> true, SpatialInformation.Submit.ON_ENTER, SpatialInformation.Submit.ON_FOCUS_LOST); cc.setFitToWidth(true); final GridPane spatialInfo = new GridPane(); spatialInfo.setHgap(GRID_HGAP); final Label empty = new Label(""); final Label xLabel = new Label(X_STRING); final Label yLabel = new Label(Y_STRING); final Label zLabel = new Label(Z_STRING); formatLabels(empty, xLabel, yLabel, zLabel); addToGrid(spatialInfo, 0, 0, empty, xLabel, yLabel, zLabel); addToGrid( spatialInfo, 0, 1, new Label("Resolution "), resolution.textX(), resolution.textY(), resolution.textZ() ); addToGrid(spatialInfo, 0, 2, new Label("Offset"), offset.textX(), offset.textY(), offset.textZ()); spatialInfo.add(revertButton, 3, 3); revertButton.setPrefWidth(TEXTFIELD_WIDTH); final ColumnConstraints cc = new ColumnConstraints(); cc.setHgrow(Priority.ALWAYS); spatialInfo.getColumnConstraints().addAll(cc); StackPane dimensionInfo = new StackPane(); // max num of labels StackPane channelInfoPane = new StackPane(); this.dimensionsProperty.addListener((obs, oldv, newv) -> { if (newv == null) { InvokeOnJavaFXApplicationThread.invoke(dimensionInfo.getChildren()::clear); InvokeOnJavaFXApplicationThread.invoke(channelInfoPane.getChildren()::clear); } else { Label[] labels = Stream.generate(Label::new).limit(newv.length).toArray(Label[]::new); Stream.of(labels).forEach(l -> l.setTextAlignment(TextAlignment.CENTER)); Stream.of(labels).forEach(l -> l.setAlignment(Pos.CENTER)); Stream.of(labels).forEach(l -> l.setPrefWidth(TEXTFIELD_WIDTH)); GridPane grid = new GridPane(); for (int d = 0; d < newv.length; ++d) { final TextField lbl = new TextField("" + newv[d]); lbl.setEditable(false); grid.add(labels[d], d + 1, 0); grid.add(lbl, d + 1, 1); lbl.setPrefWidth(TEXTFIELD_WIDTH); } channelInfo.numChannelsProperty().set(newv.length < 4 ? 0 : (int) newv[3]); InvokeOnJavaFXApplicationThread.invoke(() -> dimensionInfo.getChildren().setAll(grid)); if (channelInfo.numChannelsProperty().get() > 0) { final Node channelInfoNode = channelInfo.getNode(); InvokeOnJavaFXApplicationThread.invoke(() -> channelInfoPane.getChildren().setAll(channelInfoNode)); } else InvokeOnJavaFXApplicationThread.invoke(() -> channelInfoPane.getChildren().clear()); } }); content.getChildren().addAll( spatialInfo, new Separator(Orientation.HORIZONTAL), dimensionInfo, new Separator(Orientation.HORIZONTAL), channelInfoPane, new Separator(Orientation.HORIZONTAL)); this.dataType.addListener((obs, oldv, newv) -> { if (newv != null) InvokeOnJavaFXApplicationThread.invoke(() -> { final ObservableList<Node> children = this.content.getChildren(); children.removeAll(this.additionalMeta); this.additionalMeta.clear(); switch (newv) { case RAW: children.add(this.rawMeta); this.additionalMeta.add(this.rawMeta); break; case LABEL: children.add(this.labelMeta); this.additionalMeta.add(this.labelMeta); break; default: break; } }); }); final GridPane rawMinMax = new GridPane(); rawMinMax.getColumnConstraints().add(cc); rawMinMax.add(new Label("Intensity Range"), 0, 0); rawMinMax.add(this.min, 1, 0); rawMinMax.add(this.max, 2, 0); this.min.setPromptText("min"); this.max.setPromptText("max"); this.min.setPrefWidth(TEXTFIELD_WIDTH); this.max.setPrefWidth(TEXTFIELD_WIDTH); this.rawMeta.getChildren().add(rawMinMax); } public void listenOnResolution(final DoubleProperty x, final DoubleProperty y, final DoubleProperty z) { this.resolution.bindTo(x, y, z); } public void listenOnOffset(final DoubleProperty x, final DoubleProperty y, final DoubleProperty z) { this.offset.bindTo(x, y, z); } public void listenOnDimensions(final ObservableObjectValue<long[]> dimensions) { this.dimensionsProperty.bind(dimensions); } public void listenOnMinMax(final DoubleProperty min, final DoubleProperty max) { min.addListener((obs, oldv, newv) -> { if (Double.isFinite(newv.doubleValue())) this.min.setText(Double.toString(newv.doubleValue())); }); max.addListener((obs, oldv, newv) -> { if (Double.isFinite(newv.doubleValue())) this.max.setText(Double.toString(newv.doubleValue())); }); } public Node getPane() { return pane; } public static enum TYPE { RAW, LABEL } public static class DoubleFilter implements UnaryOperator<Change> { @Override public Change apply(final Change t) { final String input = t.getText(); return input.matches("\\d*(\\.\\d*)?") ? t : null; } } public double[] getResolution() { return asArray( resolution.textX().textProperty(), resolution.textY().textProperty(), resolution.textZ().textProperty() ); } public double[] getOffset() { return asArray(offset.textX().textProperty(), offset.textY().textProperty(), offset.textZ().textProperty()); } public double[] asArray(final ObservableStringValue... values) { return Arrays.stream(values).map(ObservableValue::getValue).mapToDouble(Double::parseDouble).toArray(); } public double min() { final String text = min.getText(); return text.length() > 0 ? Double.parseDouble(min.getText()) : Double.NaN; } public double max() { final String text = max.getText(); return text.length() > 0 ? Double.parseDouble(max.getText()) : Double.NaN; } public void bindDataTypeTo(final ObjectProperty<TYPE> dataType) { this.dataType.bind(dataType); } public ChannelInformation channelInformation() { return this.channelInfo; } private static void addToGrid(final GridPane grid, final int startCol, final int row, final Node... nodes) { for (int col = startCol, i = 0; i < nodes.length; ++i, ++col) grid.add(nodes[i], col, row); } private static void formatLabels(final Label... labels) { for (Label label : labels) { label.setAlignment(Pos.BASELINE_CENTER); label.setPrefWidth(TEXTFIELD_WIDTH); } } public Button getRevertButton() { return revertButton; } }