From 1c06c6645d53d4e0913e73e14a70f836659c85c3 Mon Sep 17 00:00:00 2001
From: tpietzsch <tobias.pietzsch@gmail.com>
Date: Thu, 27 Aug 2020 23:02:25 +0200
Subject: [PATCH] Update old brightness and visibility dialogs only when they
 are currently visible

Otherwise, delay update request until they become visible the next time.
---
 .../tools/VisibilityAndGroupingDialog.java    | 568 +++++++++++-------
 .../tools/brightness/BrightnessDialog.java    |  29 +-
 2 files changed, 384 insertions(+), 213 deletions(-)

diff --git a/src/main/java/bdv/tools/VisibilityAndGroupingDialog.java b/src/main/java/bdv/tools/VisibilityAndGroupingDialog.java
index 09b2d22c..b4a42dca 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 0cd6be4d..1e71f960 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 );
 	}
-- 
GitLab