Repository: spyder-ide/spyder

Issue:
Add the possibility to disable entries in the PYTHONPATH manager
_From [eng...@gmail.com](https://code.google.com/u/113881073069619832939/) on 2012-06-29T01:58:32Z_

It would be really great if it was possible to disable entries on the pythonpath instead of removing them.

I was thinking about something with a check box next to the different entries and then when unchecked the entry becomes grey and does not go on the pythonpath.

_Original issue: http://code.google.com/p/spyderlib/issues/detail?id=1105_



Discussion:
_From [ccordoba12](https://code.google.com/u/ccordoba12/) on 2012-07-03T09:54:53Z_

It would be a good addition indeed. But unfortunately we don't have time right now to implement it. Maybe later, sorry.

**Summary:** Add the possibility to disable entries in the PYTHONPATH manager  
  **Status:** HelpNeeded  

_From [ccordoba12](https://code.google.com/u/ccordoba12/) on 2013-02-13T09:09:38Z_

**Labels:** MS-v2.3  

_From [pierre.raybaut](https://code.google.com/u/pierre.raybaut/) on 2013-05-11T05:57:08Z_

**Labels:** -MS-v2.3 MS-v2.4  

_Accidentaly posted as a separate issue first. Sorry.)_

If I want to test the same code with different versions of the same library it's not very handy to completely remove an item from the list and then add it again
![image](https://cloud.githubusercontent.com/assets/53390/14910063/d416981c-0df2-11e6-8392-8ca4ad96b73f.png)

@julianrolon

Looks like something I could do. The checked/unchecked path status will need to be save somewhere on disk. Where should it be? With the spyder paths in the "path" configuration file or elsewhere?
In the config I guess? @ccordoba12 ?

Resolving patch:
diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py
--- a/spyder/app/mainwindow.py
+++ b/spyder/app/mainwindow.py
@@ -223,6 +223,7 @@ class MainWindow(QMainWindow):
     """Spyder main window"""
     DOCKOPTIONS = QMainWindow.AllowTabbedDocks|QMainWindow.AllowNestedDocks
     SPYDER_PATH = get_conf_path('path')
+    SPYDER_NOT_ACTIVE_PATH = get_conf_path('not_active_path')
     BOOKMARKS = (
          ('numpy', "http://docs.scipy.org/doc/",
           _("Numpy and Scipy documentation")),
@@ -297,10 +298,16 @@ def signal_handler(signum, frame=None):
 
         # Loading Spyder path
         self.path = []
+        self.not_active_path = []
         self.project_path = []
         if osp.isfile(self.SPYDER_PATH):
             self.path, _x = encoding.readlines(self.SPYDER_PATH)
             self.path = [name for name in self.path if osp.isdir(name)]
+        if osp.isfile(self.SPYDER_NOT_ACTIVE_PATH):
+            self.not_active_path, _x = \
+                encoding.readlines(self.SPYDER_NOT_ACTIVE_PATH)
+            self.not_active_path = \
+                [name for name in self.not_active_path if osp.isdir(name)]
         self.remove_path_from_sys_path()
         self.add_path_to_sys_path()
 
@@ -2482,7 +2489,8 @@ def get_spyder_pythonpath(self):
     def add_path_to_sys_path(self):
         """Add Spyder path to sys.path"""
         for path in reversed(self.get_spyder_pythonpath()):
-            sys.path.insert(1, path)
+            if path not in self.not_active_path:
+                sys.path.insert(1, path)
 
     def remove_path_from_sys_path(self):
         """Remove Spyder path from sys.path"""
@@ -2496,11 +2504,13 @@ def path_manager_callback(self):
         from spyder.widgets.pathmanager import PathManager
         self.remove_path_from_sys_path()
         project_path = self.projects.get_pythonpath()
-        dialog = PathManager(self, self.path, project_path, sync=True)
+        dialog = PathManager(self, self.path, project_path,
+                             self.not_active_path, sync=True)
         dialog.redirect_stdio.connect(self.redirect_internalshell_stdio)
         dialog.exec_()
         self.add_path_to_sys_path()
         encoding.writelines(self.path, self.SPYDER_PATH) # Saving path
+        encoding.writelines(self.not_active_path, self.SPYDER_NOT_ACTIVE_PATH)
         self.sig_pythonpath_changed.emit()
 
     def pythonpath_changed(self):
diff --git a/spyder/widgets/pathmanager.py b/spyder/widgets/pathmanager.py
--- a/spyder/widgets/pathmanager.py
+++ b/spyder/widgets/pathmanager.py
@@ -17,7 +17,7 @@
 from qtpy.QtCore import Qt, Signal, Slot
 from qtpy.QtWidgets import (QDialog, QDialogButtonBox, QHBoxLayout,
                             QListWidget, QListWidgetItem, QMessageBox,
-                            QVBoxLayout)
+                            QVBoxLayout, QCheckBox)
 
 # Local imports
 from spyder.config.base import _
@@ -29,7 +29,8 @@
 class PathManager(QDialog):
     redirect_stdio = Signal(bool)
     
-    def __init__(self, parent=None, pathlist=None, ro_pathlist=None, sync=True):
+    def __init__(self, parent=None, pathlist=None, ro_pathlist=None,
+                 not_active_pathlist=None, sync=True):
         QDialog.__init__(self, parent)
         
         # Destroying the C++ object right after closing the dialog box,
@@ -40,6 +41,9 @@ def __init__(self, parent=None, pathlist=None, ro_pathlist=None, sync=True):
         
         assert isinstance(pathlist, list)
         self.pathlist = pathlist
+        if not_active_pathlist is None:
+            not_active_pathlist = []
+        self.not_active_pathlist = not_active_pathlist
         if ro_pathlist is None:
             ro_pathlist = []
         self.ro_pathlist = ro_pathlist
@@ -61,6 +65,7 @@ def __init__(self, parent=None, pathlist=None, ro_pathlist=None, sync=True):
 
         self.listwidget = QListWidget(self)
         self.listwidget.currentRowChanged.connect(self.refresh)
+        self.listwidget.itemChanged.connect(self.update_not_active_pathlist)
         layout.addWidget(self.listwidget)
 
         bottom_layout = QHBoxLayout()
@@ -174,6 +179,21 @@ def synchronize(self):
     def get_path_list(self):
         """Return path list (does not include the read-only path list)"""
         return self.pathlist
+
+    def update_not_active_pathlist(self, item):
+        path = item.text()
+        if bool(item.checkState()) is True:
+            self.remove_from_not_active_pathlist(path)
+        else:
+            self.add_to_not_active_pathlist(path)
+
+    def add_to_not_active_pathlist(self, path):
+        if path not in self.not_active_pathlist:
+            self.not_active_pathlist.append(path)
+
+    def remove_from_not_active_pathlist(self, path):
+        if path in self.not_active_pathlist:
+            self.not_active_pathlist.remove(path)
         
     def update_list(self):
         """Update path list"""
@@ -182,7 +202,14 @@ def update_list(self):
             item = QListWidgetItem(name)
             item.setIcon(ima.icon('DirClosedIcon'))
             if name in self.ro_pathlist:
-                item.setFlags(Qt.NoItemFlags)
+                item.setFlags(Qt.NoItemFlags | Qt.ItemIsUserCheckable)
+                item.setCheckState(Qt.Checked)
+            elif name in self.not_active_pathlist:
+                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
+                item.setCheckState(Qt.Unchecked)
+            else:
+                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
+                item.setCheckState(Qt.Checked)
             self.listwidget.addItem(item)
         self.refresh()
         
@@ -216,6 +243,8 @@ def remove_path(self):
             QMessageBox.Yes | QMessageBox.No)
         if answer == QMessageBox.Yes:
             self.pathlist.pop(self.listwidget.currentRow())
+            self.remove_from_not_active_pathlist(
+                    self.listwidget.currentItem().text())
             self.update_list()
 
     @Slot()
@@ -228,6 +257,8 @@ def add_path(self):
             directory = osp.abspath(directory)
             self.last_path = directory
             if directory in self.pathlist:
+                item = self.listwidget.findItems(directory, Qt.MatchExactly)[0]
+                item.setCheckState(Qt.Checked)
                 answer = QMessageBox.question(self, _("Add path"),
                     _("This directory is already included in Spyder path "
                             "list.<br>Do you want to move it to the top of "


Test patch:
diff --git a/spyder/widgets/tests/test_pathmanager.py b/spyder/widgets/tests/test_pathmanager.py
--- a/spyder/widgets/tests/test_pathmanager.py
+++ b/spyder/widgets/tests/test_pathmanager.py
@@ -12,10 +12,12 @@
 
 # Test library imports
 import pytest
+from qtpy.QtCore import Qt
 
 # Local imports
 from spyder.widgets.pathmanager import PathManager
 
+
 @pytest.fixture
 def setup_pathmanager(qtbot, parent=None, pathlist=None, ro_pathlist=None,
                       sync=True):
@@ -24,6 +26,7 @@ def setup_pathmanager(qtbot, parent=None, pathlist=None, ro_pathlist=None,
     qtbot.addWidget(widget)
     return widget
 
+
 def test_pathmanager(qtbot):
     """Run PathManager test"""
     pathmanager = setup_pathmanager(qtbot, None, pathlist=sys.path[:-10],
@@ -32,5 +35,28 @@ def test_pathmanager(qtbot):
     assert pathmanager
 
 
+def test_check_uncheck_path(qtbot):
+    """
+    Test that checking and unchecking a path in the PathManager correctly
+    update the not active path list.
+    """
+    pathmanager = setup_pathmanager(qtbot, None, pathlist=sys.path[:-10],
+                                    ro_pathlist=sys.path[-10:])
+
+    # Assert that all paths are checked.
+    for row in range(pathmanager.listwidget.count()):
+        assert pathmanager.listwidget.item(row).checkState() == Qt.Checked
+
+    # Uncheck a path and assert that it is added to the not active path list.
+    pathmanager.listwidget.item(3).setCheckState(Qt.Unchecked)
+    assert pathmanager.not_active_pathlist == [sys.path[3]]
+
+    # Check an uncheked path and assert that it is removed from the not active
+    # path list.
+    pathmanager.listwidget.item(3).setCheckState(Qt.Checked)
+    assert pathmanager.not_active_pathlist == []
+
+
 if __name__ == "__main__":
-    pytest.main()
+    import os
+    pytest.main([os.path.basename(__file__)])