Repository: napari/napari

Issue:
Exceptions on menu close
## 🐛 Bug

<!-- A clear and concise description of what the bug is. -->

`closeEvent` of `NapariMenu` is called on every hide of the menu, not when destroying viewer.

```
~/Projekty/napari/napari/_qt/menus/_util.py in closeEvent(self=<napari._qt.menus.window_menu.WindowMenu object>, event=<PyQt5.QtGui.QCloseEvent object>)
    151             ax.setData(None)
    152 
--> 153         self._INSTANCES.remove(self)
        self._INSTANCES.remove = <built-in method remove of list object at 0x7f39156d2980>
        self = <napari._qt.menus.window_menu.WindowMenu object at 0x7f3829a831f0>
    154         super().closeEvent(event)

ValueError: list.remove(x): x not in list
```


## To Reproduce

Steps to reproduce the behavior:

1. open viewer 
2. open one menu two times
3. see exception

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->

## Expected behavior

Opening menu multiple times should not produce exceptions

<!-- A clear and concise description of what you expected to happen. -->

## Environment

 - Please copy and paste the information at napari info option in help menubar here:

 - Any other relevant information:
napari: 0.4.12.dev40+g4c554cec2
Platform: Linux-5.11.0-36-generic-x86_64-with-glibc2.2.5
System: Ubuntu 21.04
Python: 3.8.3 (default, Sep 3 2021, 01:19:46) [GCC 10.3.0]
Qt: 5.15.2
PyQt5: 5.15.4
NumPy: 1.19.5
SciPy: 1.7.1
Dask: 2021.09.0
VisPy: 0.8.1

OpenGL:
- GL version: 4.6.0 NVIDIA 470.63.01
- MAX_TEXTURE_SIZE: 32768

Screens:
- screen 1: resolution 2560x1440, scale 1.0
- screen 2: resolution 1920x1080, scale 1.0
- screen 3: resolution 1920x1080, scale 1.0

Plugins:
- animation: 0.0.2
- console: 0.0.4
- napari-allencell-segmenter: 1.1.2
- napari_nucleaizer: 0.1.8
- ome-types: 0.2.9
- platelet-unet-watershed: 0.0.1
- scikit-image: 0.4.12.dev40+g4c554cec2
- svg: 0.1.5
## Additional context


Bug introduced in  #3363 @goanpeca 
<!-- Add any other context about the problem here. -->



Discussion:

Resolving patch:
diff --git a/napari/_qt/menus/_util.py b/napari/_qt/menus/_util.py
--- a/napari/_qt/menus/_util.py
+++ b/napari/_qt/menus/_util.py
@@ -136,6 +136,19 @@ def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self._INSTANCES.append(self)
 
+    def _destroy(self):
+        """Clean up action data to avoid widget leaks."""
+        for ax in self.actions():
+            ax.setData(None)
+
+            try:
+                ax._destroy()
+            except AttributeError:
+                pass
+
+        if self in self._INSTANCES:
+            self._INSTANCES.remove(self)
+
     def update(self, event=None):
         """Update action enabled/disabled state based on action data."""
         for ax in self.actions():
@@ -143,11 +156,3 @@ def update(self, event=None):
             if data:
                 enabled_func = data.get('enabled', lambda event: True)
                 ax.setEnabled(bool(enabled_func(event)))
-
-    def closeEvent(self, event):
-        """Override qt method to clean up action data."""
-        for ax in self.actions():
-            ax.setData(None)
-
-        self._INSTANCES.remove(self)
-        super().closeEvent(event)
diff --git a/napari/_qt/qt_main_window.py b/napari/_qt/qt_main_window.py
--- a/napari/_qt/qt_main_window.py
+++ b/napari/_qt/qt_main_window.py
@@ -1180,7 +1180,7 @@ def _teardown(self):
         self.qt_viewer.viewer.layers.events.disconnect(self.file_menu.update)
         for menu in self.file_menu._INSTANCES:
             try:
-                menu.close()
+                menu._destroy()
             except RuntimeError:
                 pass
 


Test patch: