Repository: spyder-ide/spyder

Issue:
PYTHONPATH Manager opens multiple windows
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

1. Select PYTHONPATH Manager from the toolbar or from the menu bar
1. PYTHONPATH Manager popup window is launched
1. Select again PYTHONPATH Manager from the toolbar or from the menu bar
1. Another PYTHONPATH Manager popup window instance is launched instead of returning focus to original instance

![Pythonpath windows](https://user-images.githubusercontent.com/9618975/108913432-cd26e580-75de-11eb-9f2b-ba52f6b70dd0.gif)

## Versions

* Spyder version: 4.2.2 
* Python version: 3.9.1
* Qt version: 5.12.10
* PyQt5 version: 5.12.3
* Operating System: Darwin 19.6.0

### Dependencies

```

# Mandatory:
applaunchservices >=0.1.7       :  0.2.1 (OK)
atomicwrites >=1.2.0            :  1.4.0 (OK)
chardet >=2.0.0                 :  4.0.0 (OK)
cloudpickle >=0.5.0             :  1.6.0 (OK)
diff_match_patch >=20181111     :  20200713 (OK)
intervaltree >=3.0.2            :  3.1.0 (OK)
IPython >=7.6.0                 :  7.20.0 (OK)
jedi =0.17.2                    :  0.17.2 (OK)
jsonschema >=3.2.0              :  3.2.0 (OK)
keyring >=17.0.0                :  22.0.1 (OK)
nbconvert >=4.0                 :  6.0.7 (OK)
numpydoc >=0.6.0                :  1.1.0 (OK)
parso =0.7.0                    :  0.7.0 (OK)
pexpect >=4.4.0                 :  4.8.0 (OK)
pickleshare >=0.4               :  0.7.5 (OK)
psutil >=5.3                    :  5.8.0 (OK)
pygments >=2.0                  :  2.8.0 (OK)
pylint >=1.0                    :  2.7.0 (OK)
pyls >=0.36.2;<1.0.0            :  0.36.2 (OK)
pyls_black >=0.4.6              :  0.4.6 (OK)
pyls_spyder >=0.3.2             :  0.3.2 (OK)
qdarkstyle >=2.8                :  2.8.1 (OK)
qtawesome >=0.5.7               :  1.0.2 (OK)
qtconsole >=5.0.1               :  5.0.2 (OK)
qtpy >=1.5.0                    :  1.9.0 (OK)
setuptools >=39.0.0             :  52.0.0 (OK)
sphinx >=0.6.6                  :  3.5.1 (OK)
spyder_kernels >=1.10.2;<1.11.0 :  1.10.2 (OK)
textdistance >=4.2.0            :  4.2.1 (OK)
three_merge >=0.1.1             :  0.1.1 (OK)
watchdog >=0.10.3;<2.0.0        :  1.0.2 (OK)
zmq >=17                        :  22.0.3 (OK)

# Optional:
cython >=0.21                   :  None (NOK)
matplotlib >=2.0.0              :  None (NOK)
numpy >=1.7                     :  None (NOK)
pandas >=1.1.1                  :  None (NOK)
scipy >=0.17.0                  :  None (NOK)
sympy >=0.7.3                   :  None (NOK)
```



Discussion:
Hi @mrclary thanks for the feedback! I was able to reproduce this, and I think the reason is basically that for the path manager the dialog is created every time you click/trigger the action (in comparison for example with the preferences dialog where you only have one instance at a time). Probably we should use some sort of single instance that is created and then maintained/updated to prevent this to happen (or something similar to what is being done for the preferences dialog). What do you guys think @spyder-ide/core-developers ?
I think @mrclary is right and we should only show one instance of that dialog even if users click on that button multiple times.

Resolving patch:
diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py
--- a/spyder/app/mainwindow.py
+++ b/spyder/app/mainwindow.py
@@ -582,6 +582,7 @@ def signal_handler(signum, frame=None):
         self.path = ()
         self.not_active_path = ()
         self.project_path = ()
+        self._path_manager = None
 
         # New API
         self._APPLICATION_TOOLBARS = OrderedDict()
@@ -1743,19 +1744,29 @@ def update_python_path(self, new_path_dict):
     @Slot()
     def show_path_manager(self):
         """Show path manager dialog."""
-        from spyder.widgets.pathmanager import PathManager
-        projects = self.get_plugin(Plugins.Projects, error=False)
-
-        read_only_path = ()
-        if projects:
-            read_only_path = tuple(projects.get_pythonpath())
-
-        dialog = PathManager(self, self.path, read_only_path,
-                             self.not_active_path, sync=True)
-        self._path_manager = dialog
-        dialog.sig_path_changed.connect(self.update_python_path)
-        dialog.redirect_stdio.connect(self.redirect_internalshell_stdio)
-        dialog.show()
+        def _dialog_finished(result_code):
+            """Restore path manager dialog instance variable."""
+            self._path_manager = None
+
+        if self._path_manager is None:
+            from spyder.widgets.pathmanager import PathManager
+            projects = self.get_plugin(Plugins.Projects, error=False)
+            read_only_path = ()
+            if projects:
+                read_only_path = tuple(projects.get_pythonpath())
+
+            dialog = PathManager(self, self.path, read_only_path,
+                                 self.not_active_path, sync=True)
+            self._path_manager = dialog
+            dialog.sig_path_changed.connect(self.update_python_path)
+            dialog.redirect_stdio.connect(self.redirect_internalshell_stdio)
+            dialog.finished.connect(_dialog_finished)
+            dialog.show()
+        else:
+            self._path_manager.show()
+            self._path_manager.activateWindow()
+            self._path_manager.raise_()
+            self._path_manager.setFocus()
 
     def pythonpath_changed(self):
         """Project's PYTHONPATH contribution has changed."""


Test patch: