Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Source Info Tool Bar and Location Card #167

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
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
Next Next commit
Prototype location toolbar: this first draft is not so pretty, but th…
…e basics work so I wanted to commit it before refactoring UI.
trautmane committed Oct 17, 2023
commit 2fa8e87711c60b68c2ec82575ae724f13281df0b
12 changes: 12 additions & 0 deletions src/main/java/bdv/ui/appearance/Appearance.java
Original file line number Diff line number Diff line change
@@ -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 showLocationBar = 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.showLocationBar = other.showLocationBar;
notifyListeners();
}

@@ -222,6 +224,15 @@ public static LookAndFeelInfo lookAndFeelInfoForName( final String name )
return DONT_MODIFY_LOOK_AND_FEEL;
}

public boolean showLocationBar() {
return showLocationBar;
}

public void setShowLocationBar(final boolean showLocationBar) {
this.showLocationBar = showLocationBar;
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( ", showLocationBar=" ).append( showLocationBar );
sb.append( '}' );
return sb.toString();
}
2 changes: 2 additions & 0 deletions src/main/java/bdv/ui/appearance/AppearanceIO.java
Original file line number Diff line number Diff line change
@@ -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( "showLocationBar", a.showLocationBar() );
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.setShowLocationBar( ( Boolean ) mapping.get( "showLocationBar" ) );
return a;
}
catch( final Exception e )
3 changes: 2 additions & 1 deletion src/main/java/bdv/ui/appearance/AppearanceManager.java
Original file line number Diff line number Diff line change
@@ -179,7 +179,8 @@ public void toPrefs()
Prefs.sourceNameOverlayPosition( appearance.sourceNameOverlayPosition() );
Prefs.scaleBarColor( appearance.scaleBarColor() );
Prefs.scaleBarBgColor( appearance.scaleBarBgColor() );
}
Prefs.showLocationBar( appearance.showLocationBar() );
}

/**
* @deprecated Prefs will be replaced eventually by directly using {@code Appearance} in BDV
Original file line number Diff line number Diff line change
@@ -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 location bar", appearance::showLocationBar, appearance::setShowLocationBar ),
separator(),
booleanElement( "show minimap", appearance::showMultibox, appearance::setShowMultibox ),
booleanElement( "show source info", appearance::showTextOverlay, appearance::setShowTextOverlay ),
14 changes: 14 additions & 0 deletions src/main/java/bdv/util/Prefs.java
Original file line number Diff line number Diff line change
@@ -43,6 +43,11 @@ public static boolean showScaleBar()
return getInstance().showScaleBar;
}

public static boolean showLocationBar()
{
return getInstance().showLocationBar;
}

public static boolean showMultibox()
{
return getInstance().showMultibox;
@@ -108,6 +113,11 @@ public static void scaleBarBgColor( final int color )
getInstance().scaleBarBgColor = color;
}

public static void showLocationBar( final boolean show )
{
getInstance().showLocationBar = show;
}

private static Prefs instance;

public static Prefs getInstance()
@@ -132,6 +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_LOCATION_BAR = "show-location-bar";

private boolean showScaleBar;
private boolean showMultibox;
@@ -140,6 +151,7 @@ public enum OverlayPosition
private boolean showScaleBarInMovie;
private int scaleBarColor;
private int scaleBarBgColor;
private boolean showLocationBar;

private Prefs( final Properties p )
{
@@ -150,6 +162,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 );
showLocationBar = getBoolean( p, SHOW_LOCATION_BAR, false );
}

private boolean getBoolean( final Properties p, final String key, final boolean defaultValue )
@@ -244,6 +257,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_LOCATION_BAR, "" + prefs.showLocationBar );
return properties;
}

76 changes: 65 additions & 11 deletions src/main/java/bdv/viewer/ViewerPanel.java
Original file line number Diff line number Diff line change
@@ -28,10 +28,6 @@
*/
package bdv.viewer;

import static bdv.ui.UIUtils.TextPosition.TOP_RIGHT;
import static bdv.viewer.DisplayMode.SINGLE;
import static bdv.viewer.Interpolation.NEARESTNEIGHBOR;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Font;
@@ -69,6 +65,8 @@
import bdv.viewer.animate.OverlayAnimator;
import bdv.viewer.animate.TextOverlayAnimator;
import bdv.viewer.animate.TextOverlayAnimator.TextPosition;
import bdv.viewer.location.DimensionCoordinatePanel;
import bdv.viewer.location.LocationToolBar;
import bdv.viewer.overlay.MultiBoxOverlayRenderer;
import bdv.viewer.overlay.ScaleBarOverlayRenderer;
import bdv.viewer.overlay.SourceInfoOverlayRenderer;
@@ -83,6 +81,10 @@
import net.imglib2.RealPositionable;
import net.imglib2.realtransform.AffineTransform3D;

import static bdv.ui.UIUtils.TextPosition.TOP_RIGHT;
import static bdv.viewer.DisplayMode.SINGLE;
import static bdv.viewer.Interpolation.NEARESTNEIGHBOR;

/**
* A JPanel for viewing multiple of {@link Source}s. The panel contains a
* {@link InteractiveDisplayCanvas canvas} and a time slider (if there
@@ -134,6 +136,8 @@ public class ViewerPanel extends AbstractViewerPanel implements OverlayRenderer,
*/
private final ScaleBarOverlayRenderer scaleBarOverlayRenderer;

private final LocationToolBar locationToolBar;

private final TransformEventHandler transformEventHandler;

/**
@@ -260,6 +264,10 @@ public ViewerPanel( final List< SourceAndConverter< ? > > sources, final int num

display.addHandler( mouseCoordinates );

// add location toolbar to viewer - will be visible/hidden depending on Prefs.showLocationBar()
locationToolBar = new LocationToolBar(buildCenterViewListener());
add(locationToolBar, BorderLayout.NORTH);

sliderTime = new JSlider( SwingConstants.HORIZONTAL, 0, numTimepoints - 1, 0 );
sliderTime.addChangeListener( e -> {
if ( !blockSliderTimeEvents )
@@ -458,6 +466,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,21 +529,34 @@ public void drawOverlays( final Graphics g )
requiresRepaint = multiBoxOverlayRenderer.isHighlightInProgress();
}

final boolean showLocationBar = Prefs.showLocationBar();
locationToolBar.setVisible(showLocationBar);

final double[] gMousePos = new double[3];
getGlobalMouseCoordinates(RealPoint.wrap(gMousePos));

if (showLocationBar) {
final double[] gCenterPos = new double[3];
state().getViewerTransform().applyInverse(gCenterPos, getDisplayCenterCoordinates());
locationToolBar.setPositions(gCenterPos, gMousePos);
}

if ( Prefs.showTextOverlay() )
{
final Font font = UIUtils.getFont( "monospaced.small.font" );
sourceInfoOverlayRenderer.setViewerState( state );
sourceInfoOverlayRenderer.setSourceNameOverlayPosition( Prefs.sourceNameOverlayPosition() );
sourceInfoOverlayRenderer.paint( ( Graphics2D ) g );

final double[] gPos = new double[ 3 ];
getGlobalMouseCoordinates( RealPoint.wrap( gPos ) );
final String mousePosGlobalString = options.is2D()
? String.format( Locale.ROOT, "%6.1f, %6.1f", gPos[ 0 ], gPos[ 1 ] )
: String.format( Locale.ROOT, "%6.1f, %6.1f, %6.1f", gPos[ 0 ], gPos[ 1 ], gPos[ 2 ] );
if (! showLocationBar) {
final String mousePosGlobalString = options.is2D()
? String.format(Locale.ROOT, "%6.1f, %6.1f", gMousePos[0], gMousePos[1])
:
String.format(Locale.ROOT, "%6.1f, %6.1f, %6.1f", gMousePos[0], gMousePos[1], gMousePos[2]);

g.setFont( font );
UIUtils.drawString( g, TOP_RIGHT, 1, mousePosGlobalString );
g.setFont(font);
UIUtils.drawString(g, TOP_RIGHT, 1, mousePosGlobalString);
}
}

if ( Prefs.showScaleBar() )
@@ -1033,4 +1062,29 @@ 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);
}

/** @return a listener that centers the viewer at the given global position in the specified dimension. */
public DimensionCoordinatePanel.ChangeListener buildCenterViewListener() {
final ViewerPanel viewerPanel = this;
return viewerPanel::centerViewAt;
}
}
127 changes: 127 additions & 0 deletions src/main/java/bdv/viewer/location/DimensionCoordinatePanel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package bdv.viewer.location;

import java.awt.Cursor;
import java.awt.FlowLayout;
import java.util.Locale;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

/**
* Panel containing UI components for displaying and editing coordinates for one dimension of a volume.
*
* @author Eric Trautman
*/
public class DimensionCoordinatePanel
extends JPanel {

public interface ChangeListener {
void changeLocation(final double toGlobalPosition,
final int forDimension);
}

private String name;
private final int dimension;
private final boolean isReadOnly;
private final ChangeListener changeListener;
private final String valueFormat;
private double position;
private final JButton readOnlyValue;
private final JLabel labelForWritableValue;
private final JTextField valueTextField;

public DimensionCoordinatePanel(final String name,
final int dimension,
final boolean isReadOnly,
final ChangeListener changeListener) {

super(new FlowLayout(FlowLayout.LEFT, 0, 0));

this.name = name;
this.dimension = dimension;
this.isReadOnly = isReadOnly;
this.changeListener = changeListener;
this.valueFormat = "%1.1f"; // TODO: add support for changing this based upon volume size and display scale/size

this.readOnlyValue = new JButton();
this.readOnlyValue.setBorderPainted(false);
this.readOnlyValue.setFocusPainted(false);
this.readOnlyValue.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
this.readOnlyValue.setOpaque(false);
this.readOnlyValue.setHorizontalTextPosition(JButton.LEFT);
this.add(this.readOnlyValue);

if (isReadOnly) {

this.readOnlyValue.setEnabled(false);
this.labelForWritableValue = null;
this.valueTextField = null;

} else {

this.labelForWritableValue = new JLabel(name + ": ");
this.add(this.labelForWritableValue);
this.labelForWritableValue.setVisible(false);

this.valueTextField = new JTextField(10);
this.add(this.valueTextField);
this.valueTextField.setVisible(false);

this.readOnlyValue.addActionListener(e -> {
this.labelForWritableValue.setVisible(true);
this.valueTextField.setText(String.format(Locale.ROOT, valueFormat, position));
this.valueTextField.setEditable(true);
this.readOnlyValue.setVisible(false);
this.valueTextField.setVisible(true);
this.valueTextField.requestFocus();
});

this.valueTextField.addActionListener(e -> stopEditing());

final DimensionCoordinatePanel thisPanel = this;
this.valueTextField.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(java.awt.event.FocusEvent evt) {
thisPanel.stopEditing();
}
});
}
}

private void stopEditing() {
if (valueTextField.isEditable()) {
try {
final double updatedPosition = Double.parseDouble(valueTextField.getText());
changeListener.changeLocation(updatedPosition, dimension);
} catch (NumberFormatException e) {
// ignore and leave position unchanged
}
closeEditor();
}
}

private void closeEditor() {
this.labelForWritableValue.setVisible(false);
this.valueTextField.setEditable(false);
this.valueTextField.setVisible(false);
this.readOnlyValue.setVisible(true);
}

public void setName(final String name) {
this.name = name;
this.labelForWritableValue.setText(name + ": ");
}

public void setPosition(final double position) {
this.position = position;
final String formattedValue = String.format(Locale.ROOT, valueFormat, position);
if (isReadOnly) {
this.readOnlyValue.setText(name + ": " + formattedValue);
} else if (this.readOnlyValue.isVisible()) {
this.readOnlyValue.setText(name + ": " + formattedValue);
this.valueTextField.setText(formattedValue);
}
}

}
83 changes: 83 additions & 0 deletions src/main/java/bdv/viewer/location/LocationToolBar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package bdv.viewer.location;

import java.awt.FlowLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.JPanel;
import javax.swing.JToolBar;

/**
* Toolbar containing location information along with UI components to change it.
*
* @author Eric Trautman
*/
public class LocationToolBar extends JToolBar {

private final DimensionCoordinatePanel.ChangeListener dimensionChangeListener;
private final JPanel dimensionPanel;
private final List<String> dimensionNames;
private final List<DimensionCoordinatePanel> centerCoordinatePanelList;
private final List<DimensionCoordinatePanel> mouseCoordinatePanelList;

public LocationToolBar(final DimensionCoordinatePanel.ChangeListener dimensionChangeListener) {
super("Location Tools");
this.setFloatable(false);

this.dimensionChangeListener = dimensionChangeListener;
this.dimensionPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 1));
this.add(this.dimensionPanel);

this.dimensionNames = Arrays.asList("x", "y", "z", "t");
this.centerCoordinatePanelList = new ArrayList<>();
this.mouseCoordinatePanelList = new ArrayList<>();
}

public void setDimensionNames(final List<String> dimensionNames) {
this.dimensionNames.clear();
this.dimensionNames.addAll(dimensionNames);

for (int i = 0; i < this.centerCoordinatePanelList.size(); i++) {
this.centerCoordinatePanelList.get(i).setName(getDimensionName(i));
this.mouseCoordinatePanelList.get(i).setName(getDimensionName(i));
}
}

public void setPositions(final double[] centerPosition,
final double[] mousePosition) {
if (centerPosition.length != this.centerCoordinatePanelList.size()) {
setNumberOfDimensions(centerPosition.length);
}
for (int i = 0; i < centerPosition.length; i++) {
this.centerCoordinatePanelList.get(i).setPosition(centerPosition[i]);
this.mouseCoordinatePanelList.get(i).setPosition(mousePosition[i]);
}
}

private synchronized void setNumberOfDimensions(final int numberOfDimensions) {
if (numberOfDimensions != centerCoordinatePanelList.size()) {
dimensionPanel.removeAll();
addDimensions(numberOfDimensions, false, centerCoordinatePanelList);
addDimensions(numberOfDimensions, true, mouseCoordinatePanelList);
dimensionPanel.revalidate(); // need this to make panel components visible
}
}

private void addDimensions(final int numberOfDimensions,
final boolean isReadOnly,
final List<DimensionCoordinatePanel> coordinatePanelList) {
for (int i = 0; i < numberOfDimensions; i++) {
final DimensionCoordinatePanel coordinatePanel = new DimensionCoordinatePanel(getDimensionName(i),
i,
isReadOnly,
dimensionChangeListener);
coordinatePanelList.add(coordinatePanel);
dimensionPanel.add(coordinatePanel);
}
}

private String getDimensionName(final int dimensionIndex) {
return dimensionIndex < this.dimensionNames.size() ? this.dimensionNames.get(dimensionIndex) : "d" + dimensionIndex;
}
}