dimensionComponentsList;
+
+ public LocationPanel(final Interval sourceInterval) {
+
+ super(new GridBagLayout());
+
+ this.dimensionComponentsList = new ArrayList<>();
+ for (int dimension = 0; dimension < sourceInterval.numDimensions(); dimension++) {
+ addDimension(dimension,
+ sourceInterval.min(dimension),
+ sourceInterval.max(dimension));
+ }
+ }
+
+ /**
+ * Set the single listener to be called whenever a dimension value is changed.
+ * Typical usage is
+ *
+ * e -> {
+ * dcc = (DimensionCoordinateComponents) e.getSource();
+ * viewerPanel.centerViewAt(dcc.getPosition(), dcc.getDimension());
+ * }
+ *
+ */
+ public void setDimensionValueChangeListener(final ChangeListener changeListener) {
+ for (final DimensionCoordinateComponents dimensionComponents : dimensionComponentsList) {
+ dimensionComponents.setDimensionValueChangeListener(changeListener);
+ }
+ }
+
+ public void setSourceInterval(final Interval sourceInterval) {
+
+ for (int dimension = 0; dimension < sourceInterval.numDimensions(); dimension++) {
+ if (dimension < dimensionComponentsList.size()) {
+ dimensionComponentsList.get(dimension).setMinAndMaxPosition(sourceInterval.min(dimension),
+ sourceInterval.max(dimension));
+ } else {
+ addDimension(dimension,
+ sourceInterval.min(dimension),
+ sourceInterval.max(dimension));
+ }
+ }
+
+ for (int dimension = dimensionComponentsList.size(); dimension > sourceInterval.numDimensions() ; dimension--) {
+ final DimensionCoordinateComponents removedComponents = dimensionComponentsList.remove(dimension);
+ this.remove(removedComponents.getValueLabel());
+ this.remove(removedComponents.getValueTextField());
+ this.remove(removedComponents.getValueSlider());
+ }
+ }
+
+ public void requestFocusOnFirstComponent() {
+ if (! dimensionComponentsList.isEmpty()) {
+ this.dimensionComponentsList.get(0).getValueTextField().requestFocus();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public void setNames(final String[] dimensionNames) {
+ for (int i = 0; i < dimensionNames.length && i < this.dimensionComponentsList.size(); i++) {
+ this.dimensionComponentsList.get(i).setName(dimensionNames[i]);
+ }
+ }
+
+ public void setCenterPosition(final double[] centerPosition) {
+ for (int i = 0; i < centerPosition.length && i < dimensionComponentsList.size(); i++) {
+ dimensionComponentsList.get(i).setPosition(centerPosition[i]);
+ }
+ }
+
+ private void addDimension(final int dimension,
+ final double minPosition,
+ final double maxPosition) {
+
+ final DimensionCoordinateComponents coordinateComponents =
+ new DimensionCoordinateComponents(dimension,
+ minPosition,
+ maxPosition);
+
+ this.dimensionComponentsList.add(coordinateComponents);
+
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.gridy = dimension * 2;
+ c.weightx = 0.01;
+ this.add(coordinateComponents.getValueLabel(), c);
+
+ // text field should grow with card width
+ c.gridx = 1;
+ c.weightx = 0.99;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ this.add(coordinateComponents.getValueTextField(), c);
+
+ c.gridy++;
+ c.weightx = 0.99;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ this.add(coordinateComponents.getValueSlider(), c);
+
+ if (dimension > 0) {
+ // connect dimension components to support multi-value paste
+ final DimensionCoordinateComponents prior = dimensionComponentsList.get(dimension - 1);
+ prior.setNextDimensionComponents(coordinateComponents);
+ }
+ }
+}
diff --git a/src/main/java/bdv/viewer/location/SourceInfoToolBar.java b/src/main/java/bdv/viewer/location/SourceInfoToolBar.java
new file mode 100644
index 00000000..40336d3d
--- /dev/null
+++ b/src/main/java/bdv/viewer/location/SourceInfoToolBar.java
@@ -0,0 +1,184 @@
+/*
+ * #%L
+ * BigDataViewer core classes with minimal dependencies.
+ * %%
+ * Copyright (C) 2012 - 2023 BigDataViewer developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package bdv.viewer.location;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Insets;
+import java.awt.event.ActionListener;
+import java.net.URL;
+import java.util.Locale;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JToolBar;
+
+/**
+ * Toolbar containing source information.
+ *
+ * @author Eric Trautman
+ */
+public class SourceInfoToolBar extends JToolBar {
+
+ private final JLabel sourceAndGroupNameLabel;
+ private final JLabel centerCoordinatesLabel;
+ private final JLabel timepointLabel;
+ private final JButton editCoordinatesButton;
+ private final JLabel mouseCoordinatesLabel;
+
+ private ActionListener editCoordinatesActionListener;
+
+ public SourceInfoToolBar() {
+ super("Source Information");
+ this.setFloatable(false);
+
+ final JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
+ this.add(panel);
+
+ this.sourceAndGroupNameLabel = new JLabel();
+
+ this.timepointLabel = new JLabel();
+ this.timepointLabel.setToolTipText("Timepoint");
+
+ final Font monospacedFont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
+ this.centerCoordinatesLabel = new JLabel();
+ this.centerCoordinatesLabel.setToolTipText("Center coordinates");
+ this.centerCoordinatesLabel.setHorizontalAlignment(JLabel.LEFT);
+ this.centerCoordinatesLabel.setFont(monospacedFont);
+
+ final URL url = this.getClass().getResource("/bdv/ui/location/edit_pencil_20.png");
+ if (url == null) {
+ this.editCoordinatesButton = new JButton("Edit");
+ } else {
+ this.editCoordinatesButton = new JButton(new ImageIcon(url));
+ this.editCoordinatesButton.setMargin(new Insets(0, 0, 0, 0));
+ this.editCoordinatesButton.setContentAreaFilled(false);
+ }
+ this.editCoordinatesButton.setToolTipText("Edit center coordinates");
+ this.editCoordinatesButton.setVisible(false);
+
+ this.mouseCoordinatesLabel = new JLabel();
+ this.mouseCoordinatesLabel.setToolTipText("Mouse coordinates");
+ this.mouseCoordinatesLabel.setForeground(Color.MAGENTA);
+ this.mouseCoordinatesLabel.setVisible(false);
+ this.mouseCoordinatesLabel.setHorizontalAlignment(JLabel.LEFT);
+ this.mouseCoordinatesLabel.setFont(monospacedFont);
+
+ panel.add(this.sourceAndGroupNameLabel);
+ panel.add(Box.createHorizontalStrut(10));
+ panel.add(this.centerCoordinatesLabel);
+ panel.add(Box.createHorizontalStrut(10));
+ panel.add(this.editCoordinatesButton);
+ panel.add(Box.createHorizontalStrut(10));
+ panel.add(this.mouseCoordinatesLabel);
+ panel.add(Box.createHorizontalGlue());
+ panel.add(this.timepointLabel);
+ }
+
+ public void updateSource(final String sourceName,
+ final String groupName,
+ final String timepointString) {
+ if ((sourceName != null) && (! sourceName.isEmpty())) {
+ String sourceAndGroupName = sourceName;
+ if ((groupName != null) && (! groupName.isEmpty())) {
+ sourceAndGroupName = sourceAndGroupName + " | " + groupName;
+ }
+ if (sourceAndGroupName.length() > 20) {
+ sourceAndGroupNameLabel.setToolTipText(sourceAndGroupName);
+ sourceAndGroupName = sourceAndGroupName.substring(0, 17) + "...";
+ } else {
+ sourceAndGroupNameLabel.setToolTipText("source and group name");
+ }
+ sourceAndGroupNameLabel.setText(sourceAndGroupName);
+ sourceAndGroupNameLabel.setVisible(true);
+ } else {
+ sourceAndGroupNameLabel.setVisible(false);
+ }
+
+ if ((timepointString != null) && (! timepointString.isEmpty())) {
+ timepointLabel.setText(timepointString);
+ timepointLabel.setVisible(true);
+ } else {
+ timepointLabel.setVisible(false);
+ }
+ }
+
+ public void setEditActionListener(final ActionListener editCoordinatesActionListener) {
+ if (this.editCoordinatesActionListener != null) {
+ this.editCoordinatesButton.removeActionListener(this.editCoordinatesActionListener);
+ }
+ this.editCoordinatesActionListener = editCoordinatesActionListener;
+ if (editCoordinatesActionListener == null) {
+ this.editCoordinatesButton.setVisible(false);
+ } else {
+ this.editCoordinatesButton.addActionListener(editCoordinatesActionListener);
+ this.editCoordinatesButton.setVisible(true);
+ }
+ }
+
+ public void setCenterPosition(final double[] centerPosition) {
+ for (int i = 0; i < centerPosition.length; i++) {
+ this.centerCoordinatesLabel.setText(formatPosition(centerPosition));
+ }
+ }
+
+ public void setMousePosition(final double[] mousePosition) {
+ for (int i = 0; i < mousePosition.length; i++) {
+ this.mouseCoordinatesLabel.setText(formatPosition(mousePosition));
+ }
+ }
+
+ public void setMouseCoordinatesVisible(final boolean visible) {
+ this.mouseCoordinatesLabel.setVisible(visible);
+ }
+
+ private String formatPosition(final double[] position) {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < position.length; i++) {
+ if (i > 0) {
+ sb.append(" ");
+ }
+ sb.append(formatCoordinate(i, position[i]));
+ }
+ return sb.toString();
+ }
+
+ private String formatCoordinate(final int dimension,
+ final double value) {
+ return String.format(Locale.ROOT, "%s: % 8.1f",
+ DimensionCoordinateComponents.getDefaultDimensionName(dimension),
+ value);
+ }
+
+}
diff --git a/src/main/java/bdv/viewer/location/SourceInfoToolbarAndLocationCardManager.java b/src/main/java/bdv/viewer/location/SourceInfoToolbarAndLocationCardManager.java
new file mode 100644
index 00000000..31695ec5
--- /dev/null
+++ b/src/main/java/bdv/viewer/location/SourceInfoToolbarAndLocationCardManager.java
@@ -0,0 +1,225 @@
+/*
+ * #%L
+ * BigDataViewer core classes with minimal dependencies.
+ * %%
+ * Copyright (C) 2012 - 2023 BigDataViewer developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package bdv.viewer.location;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Insets;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import org.scijava.listeners.Listeners;
+
+import bdv.ui.BdvDefaultCards;
+import bdv.ui.CardPanel;
+import bdv.ui.appearance.Appearance;
+import bdv.ui.appearance.AppearanceManager;
+import bdv.ui.splitpanel.SplitPanel;
+import bdv.viewer.Source;
+import bdv.viewer.SourceAndConverter;
+import bdv.viewer.ViewerFrame;
+import bdv.viewer.ViewerPanel;
+import bdv.viewer.ViewerStateChange;
+import bdv.viewer.ViewerStateChangeListener;
+import bdv.viewer.overlay.SourceInfoOverlayRenderer;
+import net.imglib2.Interval;
+import net.imglib2.RealPoint;
+import net.imglib2.util.Intervals;
+
+/**
+ * Helper class to set up the highly-coupled SourceInfoToolBar and LocationPanel UI components.
+ *
+ * @author Eric Trautman
+ */
+public class SourceInfoToolbarAndLocationCardManager implements ViewerStateChangeListener {
+ private final AppearanceManager appearanceManager;
+ private final ViewerPanel viewer;
+ private final SourceInfoToolBar sourceInfoToolBar;
+ private final LocationPanel locationPanel;
+
+ public SourceInfoToolbarAndLocationCardManager(final AppearanceManager appearanceManager,
+ final ViewerPanel viewer) {
+ this.appearanceManager = appearanceManager;
+ this.viewer = viewer;
+
+ this.sourceInfoToolBar = new SourceInfoToolBar();
+ sourceInfoToolBar.setVisible(showSourceInfoToolBar());
+
+ final Listeners updateListeners = appearanceManager.appearance().updateListeners();
+ updateListeners.add(() -> sourceInfoToolBar.setVisible(showSourceInfoToolBar()));
+
+ // create card locationPanel and connect it to sourceInfoToolBar
+ this.locationPanel = new LocationPanel(getCurrentSourceInterval());
+ locationPanel.setDimensionValueChangeListener(e -> {
+ final DimensionCoordinateComponents coordinateComponents = (DimensionCoordinateComponents) e.getSource();
+ viewer.centerViewAt(coordinateComponents.getPosition(),
+ coordinateComponents.getDimension());
+ final double[] gCenterPos = new double[3];
+ viewer.state().getViewerTransform().applyInverse(gCenterPos, viewer.getDisplayCenterCoordinates());
+ sourceInfoToolBar.setCenterPosition(gCenterPos);
+ sourceInfoToolBar.revalidate();
+ });
+
+ // register for (source) state change updates
+ viewer.state().changeListeners().add(this);
+
+ // register for mouse events
+ addViewerMouseListeners();
+
+ // populate everything with starting location info
+ updateSourceInfo();
+ updateCenterPosition();
+ updateMousePosition();
+ }
+
+ public void addToolbarToViewerFrame(final ViewerFrame viewerFrame) {
+ viewerFrame.add(sourceInfoToolBar, BorderLayout.NORTH);
+ }
+
+ public void addLocationCardToSplitPanel(final SplitPanel splitPanel,
+ final CardPanel cards) {
+
+ cards.addCard(BdvDefaultCards.DEFAULT_LOCATIONS_CARD,
+ "Locations",
+ locationPanel,
+ false,
+ new Insets(10, 4, 0, 10));
+
+ // add hook to expand card panel and locations card when edit button in source info toolbar is clicked
+ sourceInfoToolBar.setEditActionListener(e -> {
+ // expand card panel and location card
+ splitPanel.setCollapsed(false);
+ cards.setCardExpanded(BdvDefaultCards.DEFAULT_SOURCES_CARD, false);
+ cards.setCardExpanded(BdvDefaultCards.DEFAULT_SOURCEGROUPS_CARD, false);
+ cards.setCardExpanded(BdvDefaultCards.DEFAULT_LOCATIONS_CARD, true);
+ locationPanel.requestFocusOnFirstComponent();
+ });
+ }
+
+ @Override
+ public void viewerStateChanged(final ViewerStateChange change) {
+ switch (change) {
+ case CURRENT_SOURCE_CHANGED:
+ case GROUP_NAME_CHANGED:
+ case CURRENT_GROUP_CHANGED:
+ case CURRENT_TIMEPOINT_CHANGED:
+ case NUM_TIMEPOINTS_CHANGED:
+ updateSourceInfo();
+ locationPanel.setSourceInterval(getCurrentSourceInterval());
+ break;
+ case VIEWER_TRANSFORM_CHANGED:
+ updateCenterPosition();
+ updateMousePosition();
+ }
+ }
+
+ private Interval getCurrentSourceInterval() {
+ Interval interval = null;
+ final SourceAndConverter> currentSource = viewer.state().getCurrentSource();
+ if (currentSource != null) {
+ final Source> spimSource = currentSource.getSpimSource();
+ if (spimSource != null) {
+ final int timePoint = viewer.state().getCurrentTimepoint();
+ interval = spimSource.getSource(timePoint, 0);
+ }
+ }
+ if (interval == null) {
+ interval = Intervals.createMinMax(0, 0, 0, 0, 0, 0);
+ }
+ return interval;
+ }
+
+ private boolean showSourceInfoToolBar() {
+ return appearanceManager.appearance().showSourceInfoToolBar();
+ }
+
+ private void updateSourceInfo() {
+ if (showSourceInfoToolBar()) {
+ final SourceInfoOverlayRenderer sourceInfoOverlayRenderer = viewer.getSourceInfoOverlayRenderer();
+ sourceInfoOverlayRenderer.setViewerState(viewer.state());
+ sourceInfoToolBar.updateSource(sourceInfoOverlayRenderer.getSourceName(),
+ sourceInfoOverlayRenderer.getGroupName(),
+ sourceInfoOverlayRenderer.getTimepointString());
+ }
+ }
+
+ private void updateCenterPosition() {
+ final double[] gCenterPos = new double[3];
+ viewer.state().getViewerTransform().applyInverse(gCenterPos, viewer.getDisplayCenterCoordinates());
+ locationPanel.setCenterPosition(gCenterPos);
+ if (showSourceInfoToolBar()) {
+ sourceInfoToolBar.setCenterPosition(gCenterPos);
+ }
+ }
+
+ private void updateMousePosition() {
+ if (showSourceInfoToolBar()) {
+ final double[] gMousePos = new double[3];
+ viewer.getGlobalMouseCoordinates(RealPoint.wrap(gMousePos));
+ sourceInfoToolBar.setMousePosition(gMousePos);
+ }
+ }
+
+ private void addViewerMouseListeners() {
+
+ final Component viewerDisplay = viewer.getDisplayComponent();
+
+ viewerDisplay.addMouseMotionListener(new MouseAdapter() {
+ @Override
+ public void mouseMoved(final MouseEvent e) {
+ updateMousePosition();
+ }
+ @Override
+ public void mouseDragged(final MouseEvent e) {
+ updateMousePosition();
+ }
+ });
+
+ viewerDisplay.addMouseWheelListener(e -> {
+ updateCenterPosition();
+ updateMousePosition();
+ });
+
+ viewerDisplay.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(final MouseEvent e) {
+ sourceInfoToolBar.setMouseCoordinatesVisible(true);
+ }
+ @Override
+ public void mouseExited(final MouseEvent e) {
+ sourceInfoToolBar.setMouseCoordinatesVisible(false);
+ }
+ @Override
+ public void mouseReleased(final MouseEvent e) {
+ updateCenterPosition();
+ updateMousePosition();
+ }
+ });
+ }
+}
diff --git a/src/main/java/bdv/viewer/overlay/SourceInfoOverlayRenderer.java b/src/main/java/bdv/viewer/overlay/SourceInfoOverlayRenderer.java
index 6d9dc682..c07007fc 100644
--- a/src/main/java/bdv/viewer/overlay/SourceInfoOverlayRenderer.java
+++ b/src/main/java/bdv/viewer/overlay/SourceInfoOverlayRenderer.java
@@ -83,7 +83,19 @@ public synchronized void paint( final Graphics2D g )
}
}
- public synchronized void setTimePointsOrdered( final List< TimePoint > timePointsOrdered )
+ public String getSourceName() {
+ return sourceName;
+ }
+
+ public String getGroupName() {
+ return groupName;
+ }
+
+ public String getTimepointString() {
+ return timepointString;
+ }
+
+ public synchronized void setTimePointsOrdered(final List< TimePoint > timePointsOrdered )
{
this.timePointsOrdered = timePointsOrdered;
}
diff --git a/src/main/resources/bdv/ui/location/edit_pencil_20.png b/src/main/resources/bdv/ui/location/edit_pencil_20.png
new file mode 100644
index 00000000..9c163d28
Binary files /dev/null and b/src/main/resources/bdv/ui/location/edit_pencil_20.png differ
diff --git a/src/main/resources/bdv/ui/location/edit_pencil_40.png b/src/main/resources/bdv/ui/location/edit_pencil_40.png
new file mode 100644
index 00000000..bc740bb0
Binary files /dev/null and b/src/main/resources/bdv/ui/location/edit_pencil_40.png differ
diff --git a/src/test/java/bdv/viewer/location/GridViewerTest.java b/src/test/java/bdv/viewer/location/GridViewerTest.java
new file mode 100644
index 00000000..1d46634c
--- /dev/null
+++ b/src/test/java/bdv/viewer/location/GridViewerTest.java
@@ -0,0 +1,128 @@
+/*-
+ * #%L
+ * BigDataViewer core classes with minimal dependencies.
+ * %%
+ * Copyright (C) 2012 - 2023 BigDataViewer developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package bdv.viewer.location;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.function.BiConsumer;
+
+import mpicbg.spim.data.SpimDataException;
+
+import bdv.BigDataViewer;
+import bdv.cache.CacheControl;
+import bdv.export.ProgressWriterConsole;
+import bdv.tools.brightness.ConverterSetup;
+import bdv.tools.brightness.RealARGBColorConverterSetup;
+import bdv.util.RealRandomAccessibleSource;
+import bdv.viewer.SourceAndConverter;
+import bdv.viewer.ViewerOptions;
+import net.imglib2.FinalInterval;
+import net.imglib2.Interval;
+import net.imglib2.RealLocalizable;
+import net.imglib2.display.RealARGBColorConverter;
+import net.imglib2.position.FunctionRealRandomAccessible;
+import net.imglib2.type.numeric.integer.IntType;
+
+public class GridViewerTest {
+
+ public static void main(String[] args)
+ throws IOException, SpimDataException {
+
+ final ArrayList converterSetups = new ArrayList<>();
+ final ArrayList> sources = new ArrayList<>();
+ final CacheControl cache = new CacheControl.Dummy();
+ final ProgressWriterConsole progressWriter = new ProgressWriterConsole();
+ final ViewerOptions viewerOptions = ViewerOptions.options();
+
+ final String[] sourceNames = {
+ "Grid A",
+ "Grid B has a name longer than 20 characters",
+ "Grid C",
+ };
+
+ for (int i = 0; i < sourceNames.length; i++) {
+ final RealARGBColorConverter converter = RealARGBColorConverter.create(new IntType(), 0, 127);
+ final ConverterSetup converterSetup = new RealARGBColorConverterSetup(i, converter);
+ converterSetups.add(converterSetup);
+
+ final int numDimensions = 3; // (i < 3) ? 3 : 2;
+ final int size = 100 * (i + 1);
+ final RealRandomAccessibleSource source = buildGridSource(numDimensions, sourceNames[i], size);
+ final SourceAndConverter soc = new SourceAndConverter<>(source, converter);
+ sources.add(soc);
+ }
+
+ BigDataViewer.open(converterSetups,
+ sources,
+ 1,
+ cache,
+ "Viewer Example",
+ progressWriter,
+ viewerOptions);
+ }
+
+ public static BiConsumer GRID_FUNCTION = (pos, pixels) -> {
+ int i = 0;
+ int xPos = (int) Math.round(pos.getDoublePosition(0));
+ int yPos = (int) Math.round(pos.getDoublePosition(1));
+ if ((xPos == 0) || (yPos == 0)) {
+ i = 120;
+ } else {
+ if (xPos % 10 == 0) {
+ i = 60;
+ } else if (yPos % 10 == 0) {
+ i = 30;
+ }
+ }
+ pixels.set(i);
+ };
+
+ public static RealRandomAccessibleSource buildGridSource(final int numDimensions,
+ final String name,
+ final int size) {
+ final FunctionRealRandomAccessible grid =
+ new FunctionRealRandomAccessible<>(numDimensions, GRID_FUNCTION, IntType::new);
+
+ final long[] min = new long[numDimensions];
+ final long[] max = new long[numDimensions];
+ for (int d = 0; d < numDimensions; d++) {
+ min[d] = -size;
+ max[d] = size;
+ }
+
+ return new RealRandomAccessibleSource(grid, new IntType(), name) {
+ private final Interval interval = new FinalInterval(min, max);
+ @Override
+ public Interval getInterval(final int t,
+ final int level) {
+ return interval;
+ }
+ };
+ }
+}
diff --git a/src/test/java/bdv/viewer/location/LocationPanelTest.java b/src/test/java/bdv/viewer/location/LocationPanelTest.java
new file mode 100644
index 00000000..4672ef5d
--- /dev/null
+++ b/src/test/java/bdv/viewer/location/LocationPanelTest.java
@@ -0,0 +1,62 @@
+/*-
+ * #%L
+ * BigDataViewer core classes with minimal dependencies.
+ * %%
+ * Copyright (C) 2012 - 2023 BigDataViewer developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package bdv.viewer.location;
+
+import javax.swing.JFrame;
+import javax.swing.event.ChangeListener;
+
+import net.imglib2.Interval;
+import net.imglib2.util.Intervals;
+
+public class LocationPanelTest {
+
+ public static void main(String[] args) {
+
+ JFrame frame = new JFrame("LocationPanelTest");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ final Interval interval = Intervals.createMinMax(0, -111222333444L, -98765, 100, 66, 4321);
+ final LocationPanel locationPanel = new LocationPanel(interval);
+
+ final ChangeListener changeListener = e -> {
+ final DimensionCoordinateComponents source = (DimensionCoordinateComponents) e.getSource();
+ System.out.println("coordinateChangeListener: " + source.getPosition() + ", " + source.getDimension());
+ };
+ locationPanel.setDimensionValueChangeListener(changeListener);
+
+ frame.setContentPane(locationPanel);
+
+ //Display the window.
+ frame.pack();
+ frame.setVisible(true);
+
+ locationPanel.setCenterPosition(new double[] {35.67, -111222333444.5, -222 });
+ }
+
+}