diff --git a/src/main/java/bdv/tools/VisibilityAndGroupingDialog.java b/src/main/java/bdv/tools/VisibilityAndGroupingDialog.java index 09b2d22cc00601bbc8a81116f5026dad568895ab..b4a42dca5980fff34b6b93abacda8e8eb0ad09f1 100644 --- a/src/main/java/bdv/tools/VisibilityAndGroupingDialog.java +++ b/src/main/java/bdv/tools/VisibilityAndGroupingDialog.java @@ -32,7 +32,6 @@ import bdv.viewer.SourceAndConverter; import bdv.viewer.ViewerState; import bdv.viewer.VisibilityAndGrouping; import bdv.viewer.SourceGroup; -import bdv.viewer.SynchronizedViewerState; import bdv.viewer.ViewerStateChange; import bdv.viewer.ViewerStateChangeListener; import java.awt.BorderLayout; @@ -42,12 +41,16 @@ import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Window; import java.awt.event.ActionEvent; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BooleanSupplier; import java.util.function.Consumer; import javax.swing.AbstractAction; import javax.swing.Action; @@ -89,7 +92,7 @@ public class VisibilityAndGroupingDialog extends JDialog { super( owner, "visibility and grouping", false ); - visibilityPanel = new VisibilityPanel( state ); + visibilityPanel = new VisibilityPanel( state, this::isVisible ); visibilityPanel.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), BorderFactory.createCompoundBorder( @@ -98,7 +101,7 @@ public class VisibilityAndGroupingDialog extends JDialog "visibility" ), BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); - groupingPanel = new GroupingPanel( state ); + groupingPanel = new GroupingPanel( state, this::isVisible ); groupingPanel.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder( 4, 2, 4, 2 ), BorderFactory.createCompoundBorder( @@ -107,7 +110,7 @@ public class VisibilityAndGroupingDialog extends JDialog "grouping" ), BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) ) ); - modePanel = new ModePanel( state ); + modePanel = new ModePanel( state, this::isVisible ); final JPanel content = new JPanel(); content.setLayout( new BoxLayout( content, BoxLayout.PAGE_AXIS ) ); @@ -132,6 +135,17 @@ public class VisibilityAndGroupingDialog extends JDialog im.put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), hideKey ); am.put( hideKey, hideAction ); + addComponentListener( new ComponentAdapter() + { + @Override + public void componentShown( final ComponentEvent e ) + { + visibilityPanel.shown(); + groupingPanel.shown(); + modePanel.shown(); + } + } ); + pack(); setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE ); } @@ -155,91 +169,128 @@ public class VisibilityAndGroupingDialog extends JDialog private final ArrayList< Consumer< Set< SourceAndConverter< ? > > > > updateVisibleBoxes = new ArrayList<>(); - public VisibilityPanel( final ViewerState state ) + private final BooleanSupplier isVisible; + + public VisibilityPanel( final ViewerState state, BooleanSupplier isVisible ) { super( new GridBagLayout() ); this.state = state; + this.isVisible = isVisible; state.changeListeners().add( this ); - synchronized ( state ) - { - recreateContent(); - update(); - } } - protected void recreateContent() + private void shown() { - removeAll(); - currentButtonsMap.clear(); - updateActiveBoxes.clear(); - updateVisibleBoxes.clear(); + if ( recreateContentPending.getAndSet( false ) ) + recreateContentNow(); + if ( updatePending.getAndSet( false ) ) + updateNow(); + } - final List< SourceAndConverter < ? > > sources = state.getSources(); + private final AtomicBoolean recreateContentPending = new AtomicBoolean( true ); - final GridBagConstraints c = new GridBagConstraints(); - c.insets = new Insets( 0, 5, 0, 5 ); - - // source names - c.gridx = 0; - c.gridy = 0; - add( new JLabel( "source" ), c ); - c.anchor = GridBagConstraints.LINE_END; - c.gridy = GridBagConstraints.RELATIVE; - for ( final SourceAndConverter< ? > source : sources ) - add( new JLabel( source.getSpimSource().getName() ), c ); - - // "current" radio-buttons - c.anchor = GridBagConstraints.CENTER; - c.gridx = 1; - c.gridy = 0; - add( new JLabel( "current" ), c ); - c.gridy = GridBagConstraints.RELATIVE; - final ButtonGroup currentButtonGroup = new ButtonGroup(); - for ( final SourceAndConverter< ? > source : sources ) + private void recreateContent() + { + if ( isVisible.getAsBoolean() ) { - final JRadioButton b = new JRadioButton(); - b.addActionListener( e -> { - if ( b.isSelected() ) - state.setCurrentSource( source ); - } ); - currentButtonsMap.put( source, b ); - currentButtonGroup.add( b ); - add( b, c ); + recreateContentNow(); + recreateContentPending.set( false ); } + else + recreateContentPending.set( true ); + } - // "active in fused" check-boxes - c.gridx = 2; - c.gridy = 0; - add( new JLabel( "active in fused" ), c ); - c.gridy = GridBagConstraints.RELATIVE; - for ( final SourceAndConverter< ? > source : sources ) + private void recreateContentNow() + { + synchronized ( state ) { - final JCheckBox b = new JCheckBox(); - b.addActionListener( e -> state.setSourceActive( source, b.isSelected() ) ); - updateActiveBoxes.add( active -> b.setSelected( active.contains( source ) ) ); - add( b, c ); + removeAll(); + currentButtonsMap.clear(); + updateActiveBoxes.clear(); + updateVisibleBoxes.clear(); + + final List< SourceAndConverter< ? > > sources = state.getSources(); + + final GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets( 0, 5, 0, 5 ); + + // source names + c.gridx = 0; + c.gridy = 0; + add( new JLabel( "source" ), c ); + c.anchor = GridBagConstraints.LINE_END; + c.gridy = GridBagConstraints.RELATIVE; + for ( final SourceAndConverter< ? > source : sources ) + add( new JLabel( source.getSpimSource().getName() ), c ); + + // "current" radio-buttons + c.anchor = GridBagConstraints.CENTER; + c.gridx = 1; + c.gridy = 0; + add( new JLabel( "current" ), c ); + c.gridy = GridBagConstraints.RELATIVE; + final ButtonGroup currentButtonGroup = new ButtonGroup(); + for ( final SourceAndConverter< ? > source : sources ) + { + final JRadioButton b = new JRadioButton(); + b.addActionListener( e -> { + if ( b.isSelected() ) + state.setCurrentSource( source ); + } ); + currentButtonsMap.put( source, b ); + currentButtonGroup.add( b ); + add( b, c ); + } + + // "active in fused" check-boxes + c.gridx = 2; + c.gridy = 0; + add( new JLabel( "active in fused" ), c ); + c.gridy = GridBagConstraints.RELATIVE; + for ( final SourceAndConverter< ? > source : sources ) + { + final JCheckBox b = new JCheckBox(); + b.addActionListener( e -> state.setSourceActive( source, b.isSelected() ) ); + updateActiveBoxes.add( active -> b.setSelected( active.contains( source ) ) ); + add( b, c ); + } + + // "currently visible" check-boxes + c.gridx = 3; + c.gridy = 0; + add( new JLabel( "visible" ), c ); + c.gridy = GridBagConstraints.RELATIVE; + for ( final SourceAndConverter< ? > source : sources ) + { + final JCheckBox b = new JCheckBox(); + updateVisibleBoxes.add( visible -> b.setSelected( visible.contains( source ) ) ); + b.setEnabled( false ); + add( b, c ); + } + + invalidate(); + final Window frame = SwingUtilities.getWindowAncestor( this ); + if ( frame != null ) + frame.pack(); + + update(); } + } - // "currently visible" check-boxes - c.gridx = 3; - c.gridy = 0; - add( new JLabel( "visible" ), c ); - c.gridy = GridBagConstraints.RELATIVE; - for ( final SourceAndConverter< ? > source : sources ) + private final AtomicBoolean updatePending = new AtomicBoolean( true ); + + private void update() + { + if ( isVisible.getAsBoolean() ) { - final JCheckBox b = new JCheckBox(); - updateVisibleBoxes.add( visible -> b.setSelected( visible.contains( source ) ) ); - b.setEnabled( false ); - add( b, c ); + updateNow(); + updatePending.set( false ); } - - invalidate(); - final Window frame = SwingUtilities.getWindowAncestor( this ); - if ( frame != null ) - frame.pack(); + else + updatePending.set( true ); } - protected void update() + private void updateNow() { final SourceAndConverter< ? > currentSource = state.getCurrentSource(); if ( currentSource == null ) @@ -257,6 +308,9 @@ public class VisibilityAndGroupingDialog extends JDialog @Override public void viewerStateChanged( final ViewerStateChange change ) { + final AtomicBoolean pendingUpdate = new AtomicBoolean(); + final AtomicBoolean pendingRecreate = new AtomicBoolean(); + switch ( change ) { case CURRENT_SOURCE_CHANGED: @@ -265,13 +319,7 @@ public class VisibilityAndGroupingDialog extends JDialog SwingUtilities.invokeLater( this::update ); break; case NUM_SOURCES_CHANGED: - SwingUtilities.invokeLater( () -> { - synchronized ( state ) - { - recreateContent(); - update(); - } - } ); + SwingUtilities.invokeLater( this::recreateContent ); break; } } @@ -287,10 +335,13 @@ public class VisibilityAndGroupingDialog extends JDialog private JCheckBox fusedModeBox; - public ModePanel( final ViewerState state ) + private final BooleanSupplier isVisible; + + public ModePanel( final ViewerState state, BooleanSupplier isVisible ) { super( new GridBagLayout() ); this.state = state; + this.isVisible = isVisible; state.changeListeners().add( this ); synchronized ( state ) { @@ -299,6 +350,12 @@ public class VisibilityAndGroupingDialog extends JDialog } } + private void shown() + { + if ( updatePending.getAndSet( false ) ) + updateNow(); + } + private void recreateContent() { final GridBagConstraints c = new GridBagConstraints(); @@ -333,7 +390,20 @@ public class VisibilityAndGroupingDialog extends JDialog add( new JLabel("enable fused mode"), c ); } + private final AtomicBoolean updatePending = new AtomicBoolean( true ); + private void update() + { + if ( isVisible.getAsBoolean() ) + { + updateNow(); + updatePending.set( false ); + } + else + updatePending.set( true ); + } + + private void updateNow() { groupingBox.setSelected( state.getDisplayMode().hasGrouping() ); fusedModeBox.setSelected( state.getDisplayMode().hasFused() ); @@ -361,154 +431,184 @@ public class VisibilityAndGroupingDialog extends JDialog private final ViewerState state; - public GroupingPanel( final ViewerState state ) + private final BooleanSupplier isVisible; + + public GroupingPanel( final ViewerState state, BooleanSupplier isVisible ) { super( new GridBagLayout() ); this.state = state; + this.isVisible = isVisible; state.changeListeners().add( this ); - synchronized ( state ) - { - recreateContent(); - update(); - } } - private void recreateContent() + private void shown() { - removeAll(); - updateNames.clear(); - currentButtonsMap.clear(); - updateActiveBoxes.clear(); - updateAssignBoxes.clear(); - - final GridBagConstraints c = new GridBagConstraints(); - c.insets = new Insets( 0, 5, 0, 5 ); - - final List< SourceAndConverter< ? > > sources = state.getSources(); - final List< SourceGroup > groups = state.getGroups(); - - // source shortcuts - // TODO: shortcut "names" should not be hard-coded here! - c.gridx = 0; - c.gridy = 0; - add( new JLabel( "shortcut" ), c ); - c.anchor = GridBagConstraints.LINE_END; - c.gridy = GridBagConstraints.RELATIVE; - final int nShortcuts = Math.min( groups.size(), 10 ); - for ( int i = 0; i < nShortcuts; ++i ) - add( new JLabel( Integer.toString( i == 10 ? 0 : i + 1 ) ), c ); - - // source names - c.gridx = 1; - c.gridy = 0; - c.anchor = GridBagConstraints.CENTER; - add( new JLabel( "group name" ), c ); - c.anchor = GridBagConstraints.LINE_END; - c.gridy = GridBagConstraints.RELATIVE; - for ( final SourceGroup group : groups ) - { - final JTextField tf = new JTextField( state.getGroupName( group ), 10 ); - tf.getDocument().addDocumentListener( new DocumentListener() - { - private void doit() - { - state.setGroupName( group, tf.getText() ); - } - - @Override - public void removeUpdate( final DocumentEvent e ) - { - doit(); - } + if ( recreateContentPending.getAndSet( false ) ) + recreateContentNow(); + if ( updateCurrentGroupPending.getAndSet( false ) ) + updateCurrentGroupNow(); + if ( updateGroupNamesPending.getAndSet( false ) ) + updateGroupNamesNow(); + if ( updateGroupActivityPending.getAndSet( false ) ) + updateGroupActivityNow(); + if ( updateGroupAssignmentsPending.getAndSet( false ) ) + updateGroupAssignmentsNow(); + } - @Override - public void insertUpdate( final DocumentEvent e ) - { - doit(); - } + private final AtomicBoolean recreateContentPending = new AtomicBoolean( true ); - @Override - public void changedUpdate( final DocumentEvent e ) - { - doit(); - } - } ); - updateNames.add( () -> { - final String name = state.getGroupName( group ); - if ( !tf.getText().equals( name ) ) - { - tf.setText( name ); - } - } ); - add( tf, c ); - } - - // "current" radio-buttons - c.anchor = GridBagConstraints.CENTER; - c.gridx = 2; - c.gridy = 0; - add( new JLabel( "current" ), c ); - c.gridy = GridBagConstraints.RELATIVE; - final ButtonGroup currentButtonGroup = new ButtonGroup(); - for ( final SourceGroup group : groups ) + private void recreateContent() + { + if ( isVisible.getAsBoolean() ) { - final JRadioButton b = new JRadioButton(); - b.addActionListener( e -> { - if ( b.isSelected() ) - state.setCurrentGroup( group ); - } ); - currentButtonsMap.put( group, b ); - currentButtonGroup.add( b ); - add( b, c ); + recreateContentNow(); + recreateContentPending.set( false ); } + else + recreateContentPending.set( true ); + } - // "active in fused" check-boxes - c.gridx = 3; - c.gridy = 0; - c.anchor = GridBagConstraints.CENTER; - add( new JLabel( "active in fused" ), c ); - c.gridy = GridBagConstraints.RELATIVE; - for ( final SourceGroup group : groups ) + private void recreateContentNow() + { + synchronized ( state ) { - final JCheckBox b = new JCheckBox(); - b.addActionListener( e -> state.setGroupActive( group, b.isSelected() ) ); - updateActiveBoxes.add( active -> b.setSelected( active.contains( group ) ) ); - add( b, c ); - } + removeAll(); + updateNames.clear(); + currentButtonsMap.clear(); + updateActiveBoxes.clear(); + updateAssignBoxes.clear(); + + final GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets( 0, 5, 0, 5 ); + + final List< SourceAndConverter< ? > > sources = state.getSources(); + final List< SourceGroup > groups = state.getGroups(); + + // source shortcuts + // TODO: shortcut "names" should not be hard-coded here! + c.gridx = 0; + c.gridy = 0; + add( new JLabel( "shortcut" ), c ); + c.anchor = GridBagConstraints.LINE_END; + c.gridy = GridBagConstraints.RELATIVE; + final int nShortcuts = Math.min( groups.size(), 10 ); + for ( int i = 0; i < nShortcuts; ++i ) + add( new JLabel( Integer.toString( i == 10 ? 0 : i + 1 ) ), c ); + + // source names + c.gridx = 1; + c.gridy = 0; + c.anchor = GridBagConstraints.CENTER; + add( new JLabel( "group name" ), c ); + c.anchor = GridBagConstraints.LINE_END; + c.gridy = GridBagConstraints.RELATIVE; + for ( final SourceGroup group : groups ) + { + final JTextField tf = new JTextField( state.getGroupName( group ), 10 ); + tf.getDocument().addDocumentListener( new DocumentListener() + { + private void doit() + { + state.setGroupName( group, tf.getText() ); + } + + @Override + public void removeUpdate( final DocumentEvent e ) + { + doit(); + } + + @Override + public void insertUpdate( final DocumentEvent e ) + { + doit(); + } + + @Override + public void changedUpdate( final DocumentEvent e ) + { + doit(); + } + } ); + updateNames.add( () -> { + final String name = state.getGroupName( group ); + if ( !tf.getText().equals( name ) ) + { + tf.setText( name ); + } + } ); + add( tf, c ); + } - // setup-to-group assignments - c.gridx = 4; - c.gridy = 0; - c.gridwidth = sources.size(); - c.anchor = GridBagConstraints.CENTER; - add( new JLabel( "assigned sources" ), c ); - c.gridwidth = 1; - c.anchor = GridBagConstraints.LINE_END; - for ( final SourceAndConverter< ? > source : sources ) - { - c.gridy = 1; + // "current" radio-buttons + c.anchor = GridBagConstraints.CENTER; + c.gridx = 2; + c.gridy = 0; + add( new JLabel( "current" ), c ); + c.gridy = GridBagConstraints.RELATIVE; + final ButtonGroup currentButtonGroup = new ButtonGroup(); for ( final SourceGroup group : groups ) { - final JCheckBox b = new JCheckBox(); + final JRadioButton b = new JRadioButton(); b.addActionListener( e -> { if ( b.isSelected() ) - state.addSourceToGroup( source, group ); - else - state.removeSourceFromGroup( source, group ); - } ); - updateAssignBoxes.add( () -> { - b.setSelected( state.getSourcesInGroup( group ).contains( source ) ); + state.setCurrentGroup( group ); } ); + currentButtonsMap.put( group, b ); + currentButtonGroup.add( b ); add( b, c ); - c.gridy++; } - c.gridx++; - } - invalidate(); - final Window frame = SwingUtilities.getWindowAncestor( this ); - if ( frame != null ) - frame.pack(); + // "active in fused" check-boxes + c.gridx = 3; + c.gridy = 0; + c.anchor = GridBagConstraints.CENTER; + add( new JLabel( "active in fused" ), c ); + c.gridy = GridBagConstraints.RELATIVE; + for ( final SourceGroup group : groups ) + { + final JCheckBox b = new JCheckBox(); + b.addActionListener( e -> state.setGroupActive( group, b.isSelected() ) ); + updateActiveBoxes.add( active -> b.setSelected( active.contains( group ) ) ); + add( b, c ); + } + + // setup-to-group assignments + c.gridx = 4; + c.gridy = 0; + c.gridwidth = sources.size(); + c.anchor = GridBagConstraints.CENTER; + add( new JLabel( "assigned sources" ), c ); + c.gridwidth = 1; + c.anchor = GridBagConstraints.LINE_END; + for ( final SourceAndConverter< ? > source : sources ) + { + c.gridy = 1; + for ( final SourceGroup group : groups ) + { + final JCheckBox b = new JCheckBox(); + b.addActionListener( e -> { + if ( b.isSelected() ) + state.addSourceToGroup( source, group ); + else + state.removeSourceFromGroup( source, group ); + } ); + updateAssignBoxes.add( () -> { + b.setSelected( state.getSourcesInGroup( group ).contains( source ) ); + } ); + add( b, c ); + c.gridy++; + } + c.gridx++; + } + + invalidate(); + final Window frame = SwingUtilities.getWindowAncestor( this ); + if ( frame != null ) + frame.pack(); + + update(); + } } private void update() @@ -519,23 +619,75 @@ public class VisibilityAndGroupingDialog extends JDialog updateGroupAssignments(); } + private final AtomicBoolean updateGroupNamesPending = new AtomicBoolean( true ); + private void updateGroupNames() + { + if ( isVisible.getAsBoolean() ) + { + updateGroupNamesNow(); + updateGroupNamesPending.set( false ); + } + else + updateGroupNamesPending.set( true ); + } + + private void updateGroupNamesNow() { updateNames.forEach( Runnable::run ); } + private final AtomicBoolean updateGroupAssignmentsPending = new AtomicBoolean( true ); + private void updateGroupAssignments() + { + if ( isVisible.getAsBoolean() ) + { + updateGroupAssignmentsNow(); + updateGroupAssignmentsPending.set( false ); + } + else + updateGroupAssignmentsPending.set( true ); + } + + private void updateGroupAssignmentsNow() { updateAssignBoxes.forEach( Runnable::run ); } + private final AtomicBoolean updateGroupActivityPending = new AtomicBoolean( true ); + private void updateGroupActivity() + { + if ( isVisible.getAsBoolean() ) + { + updateGroupActivityNow(); + updateGroupActivityPending.set( false ); + } + else + updateGroupActivityPending.set( true ); + } + + private void updateGroupActivityNow() { final Set< SourceGroup > activeGroups = state.getActiveGroups(); updateActiveBoxes.forEach( c -> c.accept( activeGroups ) ); } + private final AtomicBoolean updateCurrentGroupPending = new AtomicBoolean( true ); + private void updateCurrentGroup() + { + if ( isVisible.getAsBoolean() ) + { + updateCurrentGroupNow(); + updateCurrentGroupPending.set( false ); + } + else + updateCurrentGroupPending.set( true ); + } + + private void updateCurrentGroupNow() { final SourceGroup currentGroup = state.getCurrentGroup(); if ( currentGroup == null ) @@ -563,13 +715,7 @@ public class VisibilityAndGroupingDialog extends JDialog break; case NUM_GROUPS_CHANGED: case NUM_SOURCES_CHANGED: - SwingUtilities.invokeLater( () -> { - synchronized ( state ) - { - recreateContent(); - update(); - } - } ); + SwingUtilities.invokeLater( this::recreateContent ); break; } } diff --git a/src/main/java/bdv/tools/brightness/BrightnessDialog.java b/src/main/java/bdv/tools/brightness/BrightnessDialog.java index 0cd6be4dc339814e606df9e70536c38ccb3a8158..1e71f960cdaec7ba438f14de6c7fe35903ae15c1 100644 --- a/src/main/java/bdv/tools/brightness/BrightnessDialog.java +++ b/src/main/java/bdv/tools/brightness/BrightnessDialog.java @@ -42,6 +42,7 @@ import java.awt.event.KeyEvent; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; @@ -103,6 +104,8 @@ public class BrightnessDialog extends JDialog im.put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), hideKey ); am.put( hideKey, hideAction ); + final AtomicBoolean recreateContentPending = new AtomicBoolean(); + setupAssignments.setUpdateListener( new SetupAssignments.UpdateListener() { @Override @@ -111,8 +114,17 @@ public class BrightnessDialog extends JDialog try { InvokeOnEDT.invokeAndWait( () -> { - colorsPanel.recreateContent(); - minMaxPanels.recreateContent(); + if ( isVisible() ) + { + System.out.println( "colorsPanel.recreateContent()" ); + colorsPanel.recreateContent(); + minMaxPanels.recreateContent(); + recreateContentPending.set( false ); + } + else + { + recreateContentPending.set( true ); + } } ); } catch ( InvocationTargetException | InterruptedException e ) @@ -122,6 +134,19 @@ public class BrightnessDialog extends JDialog } } ); + addComponentListener( new ComponentAdapter() + { + @Override + public void componentShown( final ComponentEvent e ) + { + if ( recreateContentPending.getAndSet( false ) ) + { + colorsPanel.recreateContent(); + minMaxPanels.recreateContent(); + } + } + } ); + pack(); setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE ); }