diff --git a/src/main/java/bdv/ui/BdvDefaultCards.java b/src/main/java/bdv/ui/BdvDefaultCards.java index b95a273b..3bfc77de 100644 --- a/src/main/java/bdv/ui/BdvDefaultCards.java +++ b/src/main/java/bdv/ui/BdvDefaultCards.java @@ -35,6 +35,7 @@ import bdv.viewer.AbstractViewerPanel; import bdv.viewer.ConverterSetups; import bdv.viewer.ViewerState; + import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; @@ -63,6 +64,8 @@ public class BdvDefaultCards public static final String DEFAULT_VIEWERMODES_CARD = "default bdv viewer modes card"; + public static final String DEFAULT_LOCATIONS_CARD = "default bdv locations card"; + public static void setup( final CardPanel cards, final AbstractViewerPanel viewer, final ConverterSetups converterSetups ) { final ViewerState state = viewer.state(); diff --git a/src/main/java/bdv/ui/appearance/Appearance.java b/src/main/java/bdv/ui/appearance/Appearance.java index 8ddee19b..1ed96dcc 100644 --- a/src/main/java/bdv/ui/appearance/Appearance.java +++ b/src/main/java/bdv/ui/appearance/Appearance.java @@ -58,6 +58,7 @@ public class Appearance private int scaleBarColor = 0xffffffff; private int scaleBarBgColor = 0x88000000; private LookAndFeelInfo lookAndFeel = DONT_MODIFY_LOOK_AND_FEEL; + private boolean showSourceInfoToolBar = false; public interface UpdateListener { @@ -81,6 +82,7 @@ public void set( final Appearance other ) this.scaleBarColor = other.scaleBarColor; this.scaleBarBgColor = other.scaleBarBgColor; this.lookAndFeel = other.lookAndFeel; + this.showSourceInfoToolBar = other.showSourceInfoToolBar; notifyListeners(); } @@ -222,6 +224,15 @@ public static LookAndFeelInfo lookAndFeelInfoForName( final String name ) return DONT_MODIFY_LOOK_AND_FEEL; } + public boolean showSourceInfoToolBar() { + return showSourceInfoToolBar; + } + + public void setShowSourceInfoToolBar(final boolean showSourceInfoToolBar) { + this.showSourceInfoToolBar = showSourceInfoToolBar; + notifyListeners(); + } + @Override public String toString() { @@ -234,6 +245,7 @@ public String toString() sb.append( ", scaleBarColor=" ).append( scaleBarColor ); sb.append( ", scaleBarBgColor=" ).append( scaleBarBgColor ); sb.append( ", lookAndFeel=" ).append( lookAndFeel ); + sb.append( ", showSourceInfoToolBar=" ).append(showSourceInfoToolBar); sb.append( '}' ); return sb.toString(); } diff --git a/src/main/java/bdv/ui/appearance/AppearanceIO.java b/src/main/java/bdv/ui/appearance/AppearanceIO.java index db07170e..609e9e59 100644 --- a/src/main/java/bdv/ui/appearance/AppearanceIO.java +++ b/src/main/java/bdv/ui/appearance/AppearanceIO.java @@ -112,6 +112,7 @@ public Node representData( final Object data ) mapping.put( "scaleBarColor", hex( a.scaleBarColor() ) ); mapping.put( "scaleBarBgColor", hex( a.scaleBarBgColor() ) ); mapping.put( "lookAndFeel", a.lookAndFeel() ); + mapping.put( "showSourceInfoToolBar", a.showSourceInfoToolBar() ); return representMapping( APPEARANCE_TAG, mapping, getDefaultFlowStyle() ); } } @@ -178,6 +179,7 @@ public Object construct( final Node node ) a.setScaleBarColor( hexColor( mapping.get( "scaleBarColor" ) ) ); a.setScaleBarBgColor( hexColor( mapping.get( "scaleBarBgColor" ) ) ); a.setLookAndFeel( ( LookAndFeelInfo ) mapping.get( "lookAndFeel" ) ); + a.setShowSourceInfoToolBar(( Boolean ) mapping.get("showSourceInfoToolBar" ) ); return a; } catch( final Exception e ) diff --git a/src/main/java/bdv/ui/appearance/AppearanceManager.java b/src/main/java/bdv/ui/appearance/AppearanceManager.java index 0c6023df..5a85e296 100644 --- a/src/main/java/bdv/ui/appearance/AppearanceManager.java +++ b/src/main/java/bdv/ui/appearance/AppearanceManager.java @@ -179,7 +179,8 @@ public void toPrefs() Prefs.sourceNameOverlayPosition( appearance.sourceNameOverlayPosition() ); Prefs.scaleBarColor( appearance.scaleBarColor() ); Prefs.scaleBarBgColor( appearance.scaleBarBgColor() ); - } + Prefs.showSourceInfoToolBar( appearance.showSourceInfoToolBar() ); + } /** * @deprecated Prefs will be replaced eventually by directly using {@code Appearance} in BDV @@ -194,5 +195,6 @@ public void fromPrefs() appearance.setSourceNameOverlayPosition( Prefs.sourceNameOverlayPosition() ); appearance.setScaleBarColor( Prefs.scaleBarColor() ); appearance.setScaleBarBgColor( Prefs.scaleBarBgColor() ); + appearance.setShowSourceInfoToolBar(Prefs.showSourceInfoToolBar() ); } } diff --git a/src/main/java/bdv/ui/appearance/AppearanceSettingsPage.java b/src/main/java/bdv/ui/appearance/AppearanceSettingsPage.java index d86243ab..abc0f936 100644 --- a/src/main/java/bdv/ui/appearance/AppearanceSettingsPage.java +++ b/src/main/java/bdv/ui/appearance/AppearanceSettingsPage.java @@ -137,6 +137,7 @@ public AppearancePanel( final Appearance appearance ) booleanElement( "show scalebar in movies", appearance::showScaleBarInMovie, appearance::setShowScaleBarInMovie ), colorElement( "scalebar foreground", appearance::scaleBarColor, appearance::setScaleBarColor ), colorElement( "scalebar background", appearance::scaleBarBgColor, appearance::setScaleBarBgColor ), + booleanElement("show source info tool bar", appearance::showSourceInfoToolBar, appearance::setShowSourceInfoToolBar), separator(), booleanElement( "show minimap", appearance::showMultibox, appearance::setShowMultibox ), booleanElement( "show source info", appearance::showTextOverlay, appearance::setShowTextOverlay ), diff --git a/src/main/java/bdv/util/Prefs.java b/src/main/java/bdv/util/Prefs.java index cdced155..dee4b76c 100644 --- a/src/main/java/bdv/util/Prefs.java +++ b/src/main/java/bdv/util/Prefs.java @@ -43,6 +43,11 @@ public static boolean showScaleBar() return getInstance().showScaleBar; } + public static boolean showSourceInfoToolBar() + { + return getInstance().showSourceInfoToolBar; + } + public static boolean showMultibox() { return getInstance().showMultibox; @@ -108,6 +113,11 @@ public static void scaleBarBgColor( final int color ) getInstance().scaleBarBgColor = color; } + public static void showSourceInfoToolBar(final boolean show ) + { + getInstance().showSourceInfoToolBar = show; + } + private static Prefs instance; public static Prefs getInstance() @@ -132,7 +142,7 @@ public enum OverlayPosition private static final String SHOW_SCALE_BAR_IN_MOVIE = "show-scale-bar-in-movie"; private static final String SCALE_BAR_COLOR = "scale-bar-color"; private static final String SCALE_BAR_BG_COLOR = "scale-bar-bg-color"; - + private static final String SHOW_SOURCE_INFO_TOOL_BAR = "show-source-info-tool-bar"; private boolean showScaleBar; private boolean showMultibox; private boolean showTextOverlay; @@ -140,6 +150,7 @@ public enum OverlayPosition private boolean showScaleBarInMovie; private int scaleBarColor; private int scaleBarBgColor; + private boolean showSourceInfoToolBar; private Prefs( final Properties p ) { @@ -150,6 +161,7 @@ private Prefs( final Properties p ) showScaleBarInMovie = getBoolean( p, SHOW_SCALE_BAR_IN_MOVIE, false ); scaleBarColor = getInt( p, SCALE_BAR_COLOR, 0xffffffff ); scaleBarBgColor = getInt( p, SCALE_BAR_BG_COLOR, 0x88000000 ); + showSourceInfoToolBar = getBoolean(p, SHOW_SOURCE_INFO_TOOL_BAR, false ); } private boolean getBoolean( final Properties p, final String key, final boolean defaultValue ) @@ -244,6 +256,7 @@ public static Properties getDefaultProperties() properties.put( SHOW_SCALE_BAR_IN_MOVIE, "" + prefs.showScaleBarInMovie ); properties.put( SCALE_BAR_COLOR, "" + prefs.scaleBarColor ); properties.put( SCALE_BAR_BG_COLOR, "" + prefs.scaleBarBgColor ); + properties.put( SHOW_SOURCE_INFO_TOOL_BAR, "" + prefs.showSourceInfoToolBar ); return properties; } diff --git a/src/main/java/bdv/viewer/ViewerFrame.java b/src/main/java/bdv/viewer/ViewerFrame.java index 38b41b71..870c1a1f 100644 --- a/src/main/java/bdv/viewer/ViewerFrame.java +++ b/src/main/java/bdv/viewer/ViewerFrame.java @@ -52,6 +52,7 @@ import bdv.ui.CardPanel; import bdv.ui.splitpanel.SplitPanel; import bdv.util.AWTUtils; +import bdv.viewer.location.SourceInfoToolbarAndLocationCardManager; /** * A {@link JFrame} containing a {@link ViewerPanel} and associated @@ -123,6 +124,11 @@ public ViewerFrame( BdvDefaultCards.setup( cards, viewer, setups ); splitPanel = new SplitPanel( viewer, cards ); + final SourceInfoToolbarAndLocationCardManager manager = + new SourceInfoToolbarAndLocationCardManager(appearanceManager, viewer); + manager.addToolbarToViewerFrame(this); + manager.addLocationCardToSplitPanel(splitPanel, cards); + getRootPane().setDoubleBuffered( true ); // add( viewer, BorderLayout.CENTER ); add( splitPanel, BorderLayout.CENTER ); diff --git a/src/main/java/bdv/viewer/ViewerPanel.java b/src/main/java/bdv/viewer/ViewerPanel.java index 98111dc9..8892cc2b 100644 --- a/src/main/java/bdv/viewer/ViewerPanel.java +++ b/src/main/java/bdv/viewer/ViewerPanel.java @@ -458,6 +458,14 @@ public void displayToGlobalCoordinates( final double x, final double y, final Re state.getViewerTransform().applyInverse( gPos, lPos ); } + public double[] getDisplayCenterCoordinates() { + return new double[] { + getDisplay().getWidth() / 2.0, + getDisplay().getHeight() / 2.0, + 0.0 + }; + } + @Override public void paint() { @@ -513,7 +521,7 @@ public void drawOverlays( final Graphics g ) requiresRepaint = multiBoxOverlayRenderer.isHighlightInProgress(); } - if ( Prefs.showTextOverlay() ) + if ( Prefs.showTextOverlay() && (! Prefs.showSourceInfoToolBar()) ) { final Font font = UIUtils.getFont( "monospaced.small.font" ); sourceInfoOverlayRenderer.setViewerState( state ); @@ -1033,4 +1041,24 @@ public DebugTilingOverlay showDebugTileOverlay() display.overlays().add( overlay ); return overlay; } + + /** Centers the viewer at the given global position in the specified dimension. */ + public void centerViewAt(final double globalPosition, + final int dimension) { + + // NOTE: getViewerTransform() transforms from global to display (window) coordinates + final double[] displayCenter = getDisplayCenterCoordinates(); + final double[] gCenterPos = new double[3]; + state().getViewerTransform().applyInverse(gCenterPos, displayCenter); + + final double deltaPos = gCenterPos[dimension] - globalPosition; + + final AffineTransform3D invertedViewerTransform = state().getViewerTransform().inverse(); + final double q = invertedViewerTransform.get(dimension, 3) - deltaPos; + invertedViewerTransform.set(q, dimension, 3); + + final AffineTransform3D updatedViewerTransform = invertedViewerTransform.inverse(); + state().setViewerTransform(updatedViewerTransform); + } + } diff --git a/src/main/java/bdv/viewer/location/DimensionCoordinateComponents.java b/src/main/java/bdv/viewer/location/DimensionCoordinateComponents.java new file mode 100644 index 00000000..38b348f2 --- /dev/null +++ b/src/main/java/bdv/viewer/location/DimensionCoordinateComponents.java @@ -0,0 +1,247 @@ +/* + * #%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.Toolkit; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + +import javax.swing.JLabel; +import javax.swing.JSlider; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +/** + * UI components for displaying and editing coordinates for one dimension of a volume. + * + * @author Eric Trautman + */ +public class DimensionCoordinateComponents { + + public static final String[] DEFAULT_NAMES = { "x", "y", "z", "t" }; + public static final String DEFAULT_VALUE_FORMAT = "%1.0f"; + + private final int dimension; + private double minPosition; + private double maxPosition; + private final JLabel valueLabel; + private final JTextField valueTextField; + private final JSlider valueSlider; + private final ChangeListener sliderChangeListener; + + private ChangeListener dimensionValueChangeListener; + private String valueFormat; + private double position; + private DimensionCoordinateComponents nextDimensionComponents; + + public DimensionCoordinateComponents(final int dimension, + final double minPosition, + final double maxPosition) { + this(getDefaultDimensionName(dimension), dimension, minPosition, maxPosition); + } + + public DimensionCoordinateComponents(final String name, + final int dimension, + final double minPosition, + final double maxPosition) { + this(name, dimension, minPosition, maxPosition, DEFAULT_VALUE_FORMAT); + } + + public DimensionCoordinateComponents(final String name, + final int dimension, + final double minPosition, + final double maxPosition, + final String valueFormat) { + + this.dimension = dimension; + this.dimensionValueChangeListener = null; + this.valueFormat = valueFormat; + this.position = 0.0; + + this.valueLabel = new JLabel(name + ": "); + this.valueTextField = new JTextField(5); + this.valueTextField.setHorizontalAlignment(JTextField.RIGHT); + + // parse text and notify listeners when return is hit in text field + this.valueTextField.addActionListener(e -> stopEditingTextField()); + this.valueTextField.setToolTipText("Enter a value and hit return to change the current position.
" + + "Enter multiple values separated by spaces or commas to change multiple dimensions at once."); + + this.valueSlider = new JSlider(); + this.valueSlider.setMajorTickSpacing(25); + this.valueSlider.setPaintTicks(true); + this.valueSlider.setPaintLabels(true); + this.setMinAndMaxPosition(minPosition, maxPosition); + + this.sliderChangeListener = e -> { + setPosition(getSliderPosition()); + if (! valueSlider.getValueIsAdjusting()) { + notifyExternalListener(); + } + }; + + this.valueSlider.addChangeListener(this.sliderChangeListener); + + this.valueSlider.setToolTipText("Drag slider to change the current position."); + + // init valueTextArea to be same as valueSlider + this.valueTextField.setText(formatValueAsDouble(getSliderPosition())); + + this.nextDimensionComponents = null; + } + + public int getDimension() { + return dimension; + } + + public double getPosition() { + return position; + } + + public JLabel getValueLabel() { + return valueLabel; + } + + public JTextField getValueTextField() { + return valueTextField; + } + + public JSlider getValueSlider() { + return valueSlider; + } + + public void setMinAndMaxPosition(final double minPosition, + final double maxPosition) { + this.minPosition = minPosition; + this.maxPosition = maxPosition; + final Hashtable labelTable = new Hashtable<>(); + labelTable.put(0, new JLabel(formatSliderValue(minPosition))); + labelTable.put(100, new JLabel(formatSliderValue(maxPosition))); + this.valueSlider.setLabelTable(labelTable); + } + + public void setNextDimensionComponents(final DimensionCoordinateComponents nextDimensionComponents) { + this.nextDimensionComponents = nextDimensionComponents; + } + + @SuppressWarnings("unused") + public void setValueFormat(final String valueFormat) { + if (! valueFormat.equals(this.valueFormat)) { + this.valueFormat = valueFormat; + this.valueTextField.setText(formatValueAsDouble(position)); + } + } + + private void notifyExternalListener() { + if (dimensionValueChangeListener != null) { + dimensionValueChangeListener.stateChanged(new ChangeEvent(this)); + } + } + + private double getSliderPosition() { + final double sliderPosition = valueSlider.getValue(); + return (sliderPosition / 100.0) * (maxPosition - minPosition) + minPosition; + } + + private void stopEditingTextField() { + final List textValues = Arrays.asList(MULTI_VALUE_PATTERN.split(valueTextField.getText())); + setTextValues(textValues); + } + + public void setTextValues(final List textValues) { + final int numberOfValues = textValues.size(); + if (numberOfValues > 0) { + try { + final double updatedPosition = Double.parseDouble(textValues.get(0)); + setPosition(updatedPosition); + notifyExternalListener(); + if ((nextDimensionComponents != null) && numberOfValues > 1) { + nextDimensionComponents.setTextValues(textValues.subList(1, numberOfValues)); + } + } catch (NumberFormatException e) { + // ignore and leave position unchanged + Toolkit.getDefaultToolkit().beep(); + } + } + } + + public void setDimensionValueChangeListener(final ChangeListener dimensionValueChangeListener) { + this.dimensionValueChangeListener = dimensionValueChangeListener; + } + + public void setName(final String name) { + this.valueLabel.setText(name + ": "); + } + + public void setPosition(final double position) { + if (position != this.position) { + // prevent external position change from triggering change events + valueSlider.removeChangeListener(sliderChangeListener); + + this.position = position; + + valueTextField.setText(formatValueAsDouble(position)); + + final double sliderPosition; + if (position < minPosition) { + sliderPosition = 0.0; + } else if (position > maxPosition) { + sliderPosition = 100.0; + } else { + sliderPosition = ((position - minPosition) / (maxPosition - minPosition)) * 100.0; + } + this.valueSlider.setValue((int) Math.round(sliderPosition)); + + // restore listener now that we are done + valueSlider.addChangeListener(sliderChangeListener); + } + } + + public String formatValueAsDouble(final double value) { + return String.format(Locale.ROOT, valueFormat, value); + } + + public String formatSliderValue(final double value) { + final String longString = String.valueOf(Double.valueOf(value).longValue()); + return longString.length() < 8 ? longString : String.format(Locale.ROOT, "%1.1e", value); + } + + private static final Pattern MULTI_VALUE_PATTERN = Pattern.compile("[\\s,]++"); + + /** + * @return default name for the specified dimension. + */ + public static String getDefaultDimensionName(final int dimension) { + return (dimension < DEFAULT_NAMES.length) ? DEFAULT_NAMES[dimension] : "d" + dimension; + } +} diff --git a/src/main/java/bdv/viewer/location/LocationPanel.java b/src/main/java/bdv/viewer/location/LocationPanel.java new file mode 100644 index 00000000..cd8e4bc0 --- /dev/null +++ b/src/main/java/bdv/viewer/location/LocationPanel.java @@ -0,0 +1,153 @@ +/* + * #%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.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JPanel; +import javax.swing.event.ChangeListener; + +import net.imglib2.Interval; + +/** + * Panel containing UI components for displaying and editing location coordinates. + * + * @author Eric Trautman + */ +public class LocationPanel + extends JPanel { + + private final List 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 }); + } + +}