From 5ec5380c4310fdbc75b4fd0836bf65a1c5be1af5 Mon Sep 17 00:00:00 2001
From: Matthias Arzt <arzt@mpi-cbg.de>
Date: Fri, 4 Sep 2020 16:54:40 +0200
Subject: [PATCH] BdvDefaultCards: fix memory leak

Using KeybordFocusManager.addPropertyChangeListener(...) adds
a permanent listener, that want be garbage collected until it is
removed. The listener in BdvDefaultCards now uses weak references
and removes itself if no longer needed.
---
 src/main/java/bdv/ui/BdvDefaultCards.java | 121 ++++++++++++++--------
 1 file changed, 78 insertions(+), 43 deletions(-)

diff --git a/src/main/java/bdv/ui/BdvDefaultCards.java b/src/main/java/bdv/ui/BdvDefaultCards.java
index f0d1ab1c..3bbc9c57 100644
--- a/src/main/java/bdv/ui/BdvDefaultCards.java
+++ b/src/main/java/bdv/ui/BdvDefaultCards.java
@@ -42,6 +42,7 @@ import java.awt.Insets;
 import java.awt.KeyboardFocusManager;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
+import java.lang.ref.WeakReference;
 import javax.swing.JComponent;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
@@ -104,64 +105,98 @@ public class BdvDefaultCards
 		treePanel.setPreferredSize( new Dimension( 300, 225 ) );
 
 		// -- handle focus --
-		KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener( "focusOwner", new PropertyChangeListener()
+		new FocusListener( tablePanel, table, treePanel, tree );
+
+		cards.addCard( DEFAULT_VIEWERMODES_CARD, "Display Modes", new DisplaySettingsPanel( viewer.state() ), true, new Insets( 0, 4, 4, 0 ) );
+		cards.addCard( DEFAULT_SOURCES_CARD, "Sources", tablePanel, true, new Insets( 0, 4, 0, 0 ) );
+		cards.addCard( DEFAULT_SOURCEGROUPS_CARD, "Groups", treePanel, true, new Insets( 0, 4, 0, 0 ) );
+	}
+
+	private static class FocusListener implements PropertyChangeListener
+	{
+
+		private final KeyboardFocusManager currentKeyboardFocusManager;
+
+		private final WeakReference< JPanel > tablePanel;
+
+		private final WeakReference< SourceTable > table;
+
+		private final WeakReference< JPanel > treePanel;
+
+		private final WeakReference< SourceGroupTree > tree;
+
+		static final int MAX_DEPTH = 8;
+
+		boolean tableFocused;
+
+		boolean treeFocused;
+
+		FocusListener( JPanel tablePanel, SourceTable table, JPanel treePanel, SourceGroupTree tree )
 		{
-			static final int MAX_DEPTH = 8;
-			boolean tableFocused;
-			boolean treeFocused;
+			this.tablePanel = new WeakReference<>( tablePanel );
+			this.table = new WeakReference<>( table );
+			this.treePanel = new WeakReference<>( treePanel );
+			this.tree = new WeakReference<>( tree );
+			currentKeyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
+			currentKeyboardFocusManager.addPropertyChangeListener( "focusOwner", this );
+		}
 
-			void focusTable( boolean focus )
+		void focusTable( final boolean focus )
+		{
+			if ( focus != tableFocused )
 			{
-				if ( focus != tableFocused )
-				{
-					tableFocused = focus;
+				tableFocused = focus;
+				SourceTable table = this.table.get();
+				if ( table != null )
 					table.setSelectionBackground( focus );
-				}
 			}
+		}
 
-			void focusTree( boolean focus )
+		void focusTree( final boolean focus )
+		{
+			if ( focus != treeFocused )
 			{
-				if ( focus != treeFocused )
-				{
-					treeFocused = focus;
+				treeFocused = focus;
+				SourceGroupTree tree = this.tree.get();
+				if ( tree != null )
 					tree.setSelectionBackground( focus );
-				}
+			}
+		}
+
+		@Override
+		public void propertyChange( final PropertyChangeEvent evt )
+		{
+			if ( tablePanel.get() == null && treePanel.get() == null )
+			{
+				currentKeyboardFocusManager.removePropertyChangeListener( "focusOwner", this );
+				return;
 			}
 
-			@Override
-			public void propertyChange( final PropertyChangeEvent evt )
+			if ( evt.getNewValue() instanceof JComponent )
 			{
-				if ( evt.getNewValue() instanceof JComponent )
+				JComponent component = ( JComponent ) evt.getNewValue();
+				for ( int i = 0; i < MAX_DEPTH; ++i )
 				{
-					JComponent component = ( JComponent ) evt.getNewValue();
-					for ( int i = 0; i < MAX_DEPTH; ++i )
+					Container parent = component.getParent();
+					if ( !( parent instanceof JComponent ) )
+						break;
+
+					if ( component == treePanel.get() )
+					{
+						focusTable( false );
+						focusTree( true );
+						return;
+					}
+					else if ( component == tablePanel.get() )
 					{
-						Container parent = component.getParent();
-						if ( ! ( parent instanceof JComponent ) )
-							break;
-
-						component = ( JComponent ) parent;
-						if ( component == treePanel )
-						{
-							focusTable( false );
-							focusTree( true );
-							return;
-						}
-						else if ( component == tablePanel )
-						{
-							focusTable( true );
-							focusTree( false );
-							return;
-						}
+						focusTable( true );
+						focusTree( false );
+						return;
 					}
-					focusTable( false );
-					focusTree( false );
 				}
+				focusTable( false );
+				focusTree( false );
 			}
-		} );
-
-		cards.addCard( DEFAULT_VIEWERMODES_CARD, "Display Modes", new DisplaySettingsPanel( viewer.state() ), true, new Insets( 0, 4, 4, 0 ) );
-		cards.addCard( DEFAULT_SOURCES_CARD, "Sources", tablePanel, true, new Insets( 0, 4, 0, 0 ) );
-		cards.addCard( DEFAULT_SOURCEGROUPS_CARD, "Groups", treePanel, true, new Insets( 0, 4, 0, 0 ) );
+		}
 	}
 }
-- 
GitLab