VirtualEnv: finished implementing a virtualenv manager.

Sun, 10 Jun 2018 16:55:39 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 10 Jun 2018 16:55:39 +0200
changeset 6338
104ee21d765d
parent 6337
c6af560e0039
child 6339
d765c3204c71

VirtualEnv: finished implementing a virtualenv manager.

UI/UserInterface.py file | annotate | diff | comparison | revisions
VirtualEnv/VirtualenvAddEditDialog.py file | annotate | diff | comparison | revisions
VirtualEnv/VirtualenvAddEditDialog.ui file | annotate | diff | comparison | revisions
VirtualEnv/VirtualenvConfigurationDialog.py file | annotate | diff | comparison | revisions
VirtualEnv/VirtualenvInterpreterSelectionDialog.ui file | annotate | diff | comparison | revisions
VirtualEnv/VirtualenvManager.py file | annotate | diff | comparison | revisions
VirtualEnv/VirtualenvManagerDialog.py file | annotate | diff | comparison | revisions
VirtualEnv/VirtualenvManagerDialog.ui file | annotate | diff | comparison | revisions
changelog file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
icons/default/virtualenvConfig.png file | annotate | diff | comparison | revisions
--- a/UI/UserInterface.py	Sat Jun 09 17:19:37 2018 +0200
+++ b/UI/UserInterface.py	Sun Jun 10 16:55:39 2018 +0200
@@ -2418,10 +2418,27 @@
         self.pluginRepoAct.triggered.connect(self.showPluginsAvailable)
         self.actions.append(self.pluginRepoAct)
         
+        self.virtualenvManagerAct = E5Action(
+            self.tr('Virtualenv Manager'),
+            UI.PixmapCache.getIcon("virtualenv.png"),
+            self.tr('&Virtualenv Manager...'),
+            0, 0, self,
+            'virtualenv_manager')
+        self.virtualenvManagerAct.setStatusTip(self.tr(
+            'Virtualenv Manager'))
+        self.virtualenvManagerAct.setWhatsThis(self.tr(
+            """<b>Virtualenv Manager</b>"""
+            """<p>This opens a dialog to manage the defined Python virtual"""
+            """ environments.</p>"""
+        ))
+        self.virtualenvManagerAct.triggered.connect(
+            self.virtualenvManager.showVirtualenvManagerDialog)
+        self.actions.append(self.virtualenvManagerAct)
+        
         self.virtualenvConfigAct = E5Action(
             self.tr('Virtualenv Configurator'),
-            UI.PixmapCache.getIcon("virtualenv.png"),
-            self.tr('&Virtualenv Configurator...'),
+            UI.PixmapCache.getIcon("virtualenvConfig.png"),
+            self.tr('Virtualenv &Configurator...'),
             0, 0, self,
             'virtualenv_configurator')
         self.virtualenvConfigAct.setStatusTip(self.tr(
@@ -2694,6 +2711,7 @@
         self.__menus["macros"] = self.viewmanager.initMacroMenu()
         self.__menus["extras"].addMenu(self.__menus["macros"])
         self.__menus["extras"].addSeparator()
+        self.__menus["extras"].addAction(self.virtualenvManagerAct)
         self.__menus["extras"].addAction(self.virtualenvConfigAct)
         self.toolGroupsMenu = QMenu(self.tr("Select Tool Group"), self)
         self.toolGroupsMenu.aboutToShow.connect(self.__showToolGroupsMenu)
@@ -2895,6 +2913,7 @@
         toolstb.addAction(self.iconEditorAct)
         toolstb.addAction(self.snapshotAct)
         toolstb.addSeparator()
+        toolstb.addAction(self.virtualenvManagerAct)
         toolstb.addAction(self.virtualenvConfigAct)
         if self.webBrowserAct:
             toolstb.addSeparator()
@@ -6463,6 +6482,8 @@
         
         self.pluginManager.doShutdown()
         
+        self.virtualenvManager.shutdown()
+        
         if self.layoutType == "Sidebars":
             self.leftSidebar.shutdown()
             self.bottomSidebar.shutdown()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualEnv/VirtualenvAddEditDialog.py	Sun Jun 10 16:55:39 2018 +0200
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to enter the data of a virtual environment.
+"""
+
+from __future__ import unicode_literals
+
+import os
+import sys
+
+from PyQt5.QtCore import pyqtSlot
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox
+
+from E5Gui.E5PathPicker import E5PathPickerModes
+
+from .Ui_VirtualenvAddEditDialog import Ui_VirtualenvAddEditDialog
+
+import Utilities
+
+
+class VirtualenvAddEditDialog(QDialog, Ui_VirtualenvAddEditDialog):
+    """
+    Class implementing a dialog to enter the data of a virtual environment.
+    """
+    def __init__(self, manager, venvName="", venvDirectory="",
+                 venvInterpreter="", parent=None):
+        """
+        Constructor
+        
+        @param manager reference to the virtual environment manager
+        @type VirtualenvManager
+        @param venvName logical name of a virtual environment for editing
+        @type str
+        @param venvDirectory directory of the virtual environment
+        @type str
+        @param venvInterpreter Python interpreter of the virtual environment
+        @type str
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(VirtualenvAddEditDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.__venvName = venvName
+        self.__manager = manager
+        self.__editMode = bool(venvName)
+        
+        self.targetDirectoryPicker.setMode(E5PathPickerModes.DirectoryMode)
+        self.targetDirectoryPicker.setWindowTitle(
+            self.tr("Virtualenv Target Directory"))
+        self.targetDirectoryPicker.setDefaultDirectory(Utilities.getHomeDir())
+        
+        self.pythonExecPicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.pythonExecPicker.setWindowTitle(
+            self.tr("Python Interpreter"))
+        self.pythonExecPicker.setDefaultDirectory(
+            sys.executable.replace("w.exe", ".exe"))
+        
+        self.nameEdit.setText(venvName)
+        self.targetDirectoryPicker.setText(venvDirectory)
+        self.pythonExecPicker.setText(venvInterpreter)
+        
+        self.__updateOk()
+    
+    def __updateOk(self):
+        """
+        Private slot to update the state of the OK button.
+        """
+        if self.__editMode:
+            enable = (
+                bool(self.nameEdit.text()) and
+                (self.nameEdit.text() == self.__venvName or
+                 self.__manager.isUnique(self.nameEdit.text()))
+            )
+        else:
+            enable = (
+                bool(self.nameEdit.text()) and
+                self.__manager.isUnique(self.nameEdit.text())
+            )
+            
+        enable = (
+            enable and
+            bool(self.targetDirectoryPicker.text()) and
+            os.path.exists(self.targetDirectoryPicker.text()) and
+            bool(self.pythonExecPicker.text()) and
+            os.access(self.pythonExecPicker.text(), os.X_OK)
+        )
+        
+        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable)
+    
+    @pyqtSlot(str)
+    def on_nameEdit_textChanged(self, txt):
+        """
+        Private slot to handle changes of the logical name.
+        
+        @param txt current logical name
+        @type str
+        """
+        self.__updateOk()
+    
+    @pyqtSlot(str)
+    def on_targetDirectoryPicker_textChanged(self, txt):
+        """
+        Private slot to handle changes of the virtual environment directory.
+        
+        @param txt virtual environment directory
+        @type str
+        """
+        self.__updateOk()
+        
+        if txt:
+            self.pythonExecPicker.setDefaultDirectory(txt)
+        else:
+            self.pythonExecPicker.setDefaultDirectory(
+                sys.executable.replace("w.exe", ".exe"))
+    
+    @pyqtSlot(str)
+    def on_pythonExecPicker_textChanged(self, txt):
+        """
+        Private slot to handle changes of the virtual environment interpreter.
+        
+        @param txt virtual environment interpreter
+        @type str
+        """
+        self.__updateOk()
+    
+    def getData(self):
+        """
+        Public method to retrieve the entered data.
+        
+        @return tuple containing the logical name, the directory and the
+            interpreter of the virtual environment
+        @rtype tuple of (str, str, str)
+        """
+        return (
+            self.nameEdit.text(),
+            self.targetDirectoryPicker.text(),
+            self.pythonExecPicker.text(),
+        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualEnv/VirtualenvAddEditDialog.ui	Sun Jun 10 16:55:39 2018 +0200
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VirtualenvAddEditDialog</class>
+ <widget class="QDialog" name="VirtualenvAddEditDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>700</width>
+    <height>124</height>
+   </rect>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Logical Name:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QLineEdit" name="nameEdit">
+     <property name="toolTip">
+      <string>Enter a unique name for the virtual environment</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Directory:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="E5PathPicker" name="targetDirectoryPicker" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="focusPolicy">
+      <enum>Qt::WheelFocus</enum>
+     </property>
+     <property name="toolTip">
+      <string>Enter the directory of the virtual environment</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>Python Interpreter:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="E5PathPicker" name="pythonExecPicker" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="focusPolicy">
+      <enum>Qt::WheelFocus</enum>
+     </property>
+     <property name="toolTip">
+      <string>Enter the Python interpreter of the virtual environment</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0" colspan="2">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5PathPicker</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5PathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>nameEdit</tabstop>
+  <tabstop>targetDirectoryPicker</tabstop>
+  <tabstop>pythonExecPicker</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>VirtualenvAddEditDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>VirtualenvAddEditDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- a/VirtualEnv/VirtualenvConfigurationDialog.py	Sat Jun 09 17:19:37 2018 +0200
+++ b/VirtualEnv/VirtualenvConfigurationDialog.py	Sun Jun 10 16:55:39 2018 +0200
@@ -86,8 +86,8 @@
         """
         self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(
             (self.__virtualenvFound or self.__pyvenvFound) and
-            bool(self.targetDirectoryPicker.text() and
-            bool(self.nameEdit.text()))
+            bool(self.targetDirectoryPicker.text()) and
+            bool(self.nameEdit.text())
         )
     
     def __updateUi(self):
--- a/VirtualEnv/VirtualenvInterpreterSelectionDialog.ui	Sat Jun 09 17:19:37 2018 +0200
+++ b/VirtualEnv/VirtualenvInterpreterSelectionDialog.ui	Sun Jun 10 16:55:39 2018 +0200
@@ -54,7 +54,7 @@
       <enum>Qt::WheelFocus</enum>
      </property>
      <property name="toolTip">
-      <string>Enter the Python interpreter for the virtual environment</string>
+      <string>Enter the Python interpreter of the virtual environment</string>
      </property>
     </widget>
    </item>
--- a/VirtualEnv/VirtualenvManager.py	Sat Jun 09 17:19:37 2018 +0200
+++ b/VirtualEnv/VirtualenvManager.py	Sun Jun 10 16:55:39 2018 +0200
@@ -54,6 +54,8 @@
             self.__virtualEnvironments["<default>"] = ""
         
         self.__updateSettings()
+        
+        self.__virtualenvManagerDialog = None
     
     def __updateSettings(self):
         """
@@ -86,14 +88,15 @@
             dia.start(args)
             dia.exec_()
     
-    def addVirtualEnv(self, venvName, venvDirectory):
+    def addVirtualEnv(self, venvName, venvDirectory, venvInterpreter=""):
         """
         Public method to add a virtual environment.
         
         @param venvName logical name for the virtual environment
         @type str
-        @param venvDirectory directory of the virtual envoronment
+        @param venvDirectory directory of the virtual environment
         @type str
+        @param venvInterpreter interpreter of the virtual environment
         """
         if venvName in self.__virtualEnvironments:
             ok = E5MessageBox.yesNo(
@@ -106,33 +109,215 @@
             if not ok:
                 return
         
-        from .VirtualenvInterpreterSelectionDialog import \
-            VirtualenvInterpreterSelectionDialog
-        dlg = VirtualenvInterpreterSelectionDialog(venvName, venvDirectory)
-        if dlg.exec_() == QDialog.Accepted:
-            venvExe = dlg.getData()
-            self.__virtualEnvironmentInterpreters[venvName] = venvExe
+        if not venvInterpreter:
+            from .VirtualenvInterpreterSelectionDialog import \
+                VirtualenvInterpreterSelectionDialog
+            dlg = VirtualenvInterpreterSelectionDialog(venvName, venvDirectory)
+            if dlg.exec_() == QDialog.Accepted:
+                venvInterpreter = dlg.getData()
+        
+        if venvInterpreter:
+            self.__virtualEnvironmentInterpreters[venvName] = venvInterpreter
             self.__virtualEnvironments[venvName] = venvDirectory
             
             self.__updateSettings()
+            
+            if self.__virtualenvManagerDialog:
+                self.__virtualenvManagerDialog.refresh()
     
-    def deleteVirtualEnv(self, venvName):
+    def setVirtualEnv(self, venvName, venvDirectory, venvInterpreter=""):
+        """
+        Public method to change a virtual environment.
+        
+        @param venvName logical name of the virtual environment
+        @type str
+        @param venvDirectory directory of the virtual environment
+        @type str
+        @param venvInterpreter interpreter of the virtual environment
+        """
+        if venvName not in self.__virtualEnvironments:
+            E5MessageBox.yesNo(
+                None,
+                self.tr("Change Virtual Environment"),
+                self.tr("""A virtual environment named <b>{0}</b> does not"""
+                        """ exist. Aborting!""")
+                .format(venvName),
+                icon=E5MessageBox.Warning)
+            return
+        
+        self.__virtualEnvironmentInterpreters[venvName] = venvInterpreter
+        self.__virtualEnvironments[venvName] = venvDirectory
+        
+        self.__updateSettings()
+        
+        if self.__virtualenvManagerDialog:
+            self.__virtualenvManagerDialog.refresh()
+    
+    def renameVirtualEnv(self, oldVenvName, venvName, venvDirectory,
+                         venvInterpreter):
+        """
+        Public method to substitute a virtual environment entry with a new
+        name.
+        
+        @param oldVenvName old name of the virtual environment
+        @type str
+        @param venvName logical name for the virtual environment
+        @type str
+        @param venvDirectory directory of the virtual environment
+        @type str
+        @param venvInterpreter interpreter of the virtual environment
+        """
+        if oldVenvName not in self.__virtualEnvironments:
+            E5MessageBox.yesNo(
+                None,
+                self.tr("Rename Virtual Environment"),
+                self.tr("""A virtual environment named <b>{0}</b> does not"""
+                        """ exist. Aborting!""")
+                .format(oldVenvName),
+                icon=E5MessageBox.Warning)
+            return
+        
+        del self.__virtualEnvironments[oldVenvName]
+        del self.__virtualEnvironmentInterpreters[oldVenvName]
+        self.addVirtualEnv(venvName, venvDirectory, venvInterpreter)
+    
+    def deleteVirtualEnvs(self, venvNames):
+        """
+        Public method to delete virtual environments from the list and disk.
+        
+        @param venvNames list of logical names for the virtual environments
+        @type list of str
         """
-        Public method to delete a virtual environment from disk.
+        venvMessages = []
+        for venvName in venvNames:
+            if venvName in self.__virtualEnvironments and \
+                    bool(self.__virtualEnvironments[venvName]):
+                venvMessages.append(self.tr("{0} - {1}").format(
+                    venvName, self.__virtualEnvironments[venvName]))
+        if venvMessages:
+            from UI.DeleteFilesConfirmationDialog import \
+                DeleteFilesConfirmationDialog
+            dlg = DeleteFilesConfirmationDialog(
+                None,
+                self.tr("Delete Virtual Environments"),
+                self.tr("""Do you really want to delete these virtual"""
+                        """ environments?"""),
+                venvMessages
+            )
+            if dlg.exec_() == QDialog.Accepted:
+                if venvName in self.__virtualEnvironments and \
+                        bool(self.__virtualEnvironments[venvName]):
+                    shutil.rmtree(self.__virtualEnvironments[venvName], True)
+                    del self.__virtualEnvironments[venvName]
+                    del self.__virtualEnvironmentInterpreters[venvName]
+                
+                self.__updateSettings()
+                
+                if self.__virtualenvManagerDialog:
+                    self.__virtualenvManagerDialog.refresh()
+    
+    def removeVirtualEnvs(self, venvNames):
+        """
+        Public method to delete virtuals environment from the list.
+        
+        @param venvNames list of logical names for the virtual environments
+        @type list of str
+        """
+        venvMessages = []
+        for venvName in venvNames:
+            if venvName in self.__virtualEnvironments and \
+                    bool(self.__virtualEnvironments[venvName]):
+                venvMessages.append(self.tr("{0} - {1}").format(
+                    venvName, self.__virtualEnvironments[venvName]))
+        if venvMessages:
+            from UI.DeleteFilesConfirmationDialog import \
+                DeleteFilesConfirmationDialog
+            dlg = DeleteFilesConfirmationDialog(
+                None,
+                self.tr("Remove Virtual Environments"),
+                self.tr("""Do you really want to remove these virtual"""
+                        """ environments?"""),
+                venvMessages
+            )
+            if dlg.exec_() == QDialog.Accepted:
+                if venvName in self.__virtualEnvironments and \
+                        bool(self.__virtualEnvironments[venvName]):
+                    del self.__virtualEnvironments[venvName]
+                    del self.__virtualEnvironmentInterpreters[venvName]
+                
+                self.__updateSettings()
+                
+                if self.__virtualenvManagerDialog:
+                    self.__virtualenvManagerDialog.refresh()
+    
+    def getEnvironmentEntries(self):
+        """
+        Public method to a dictionary containing the defined virtual
+        environment entries.
+        
+        @return dictionary containing tuples of the environment path and
+            the associated interpreter
+        @rtype dict of (str, str)
+        """
+        environments = {}
+        for venvName in self.__virtualEnvironments:
+            environments[venvName] = (
+                self.__virtualEnvironments[venvName],
+                self.__virtualEnvironmentInterpreters[venvName],
+            )
+        
+        return environments
+    
+    @pyqtSlot()
+    def showVirtualenvManagerDialog(self):
+        """
+        Public slot to show the virtual environment manager dialog.
+        """
+        if self.__virtualenvManagerDialog is None:
+            from .VirtualenvManagerDialog import VirtualenvManagerDialog
+            self.__virtualenvManagerDialog = VirtualenvManagerDialog(
+                self, self.__ui)
+        
+        self.__virtualenvManagerDialog.show()
+    
+    def shutdown(self):
+        """
+        Public method to shutdown the manager.
+        """
+        if self.__virtualenvManagerDialog is not None:
+            self.__virtualenvManagerDialog.close()
+            self.__virtualenvManagerDialog = None
+    
+    def isUnique(self, venvName):
+        """
+        Public method to check, if the give logical name is unique.
         
         @param venvName logical name for the virtual environment
         @type str
+        @return flag indicating uniqueness
+        @rtype bool
         """
-        if venvName in self.__virtualEnvironments:
-            ok = E5MessageBox.yesNo(
-                None,
-                self.tr("Delete Virtual Environment"),
-                self.tr("""Do you really want to delete the virtual"""
-                        """ environment <b>{0}</b>?<br>Path: {1}""")
-                .format(venvName, self.__virtualEnvironments[venvName]))
-            if ok:
-                shutil.rmtree(self.__virtualEnvironments[venvName], True)
-                del self.__virtualEnvironments[venvName]
-                del self.__virtualEnvironmentInterpreters[venvName]
-                
-                self.__updateSettings()
+        return venvName not in self.__virtualEnvironments
+    
+    def getVirtualenvInterpreter(self, venvName):
+        """
+        Public method to get the interpreter for a virtual environment.
+        
+        @param venvName logical name for the virtual environment
+        @type str
+        @return interpreter path
+        @rtype str
+        """
+        if venvName in self.__virtualEnvironmentInterpreters:
+            return self.__virtualEnvironmentInterpreters[venvName]
+        else:
+            return ""
+    
+    def getVirtualenvNames(self):
+        """
+        Public method to get a list of defined virtual environments.
+        
+        @return list of defined virtual environments
+        @rtype list of str
+        """
+        return list(self.__virtualEnvironmentInterpreters.keys())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualEnv/VirtualenvManagerDialog.py	Sun Jun 10 16:55:39 2018 +0200
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to manage the list of defined virtual
+environments.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import pyqtSlot, Qt
+from PyQt5.QtWidgets import QDialog, QTreeWidgetItem, QHeaderView
+
+from .Ui_VirtualenvManagerDialog import Ui_VirtualenvManagerDialog
+
+
+class VirtualenvManagerDialog(QDialog, Ui_VirtualenvManagerDialog):
+    """
+    Class implementing a dialog to manage the list of defined virtual
+    environments.
+    """
+    def __init__(self, manager, parent=None):
+        """
+        Constructor
+        
+        @param manager reference to the virtual environment manager
+        @type VirtualenvManager
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(VirtualenvManagerDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.__manager = manager
+        
+        self.__populateVenvList()
+        self.__updateButtons()
+        
+        self.venvList.header().setSortIndicator(0, Qt.AscendingOrder)
+    
+    def __updateButtons(self):
+        """
+        Private method to update the enabled state of the various buttons.
+        """
+        selectedItemsCount = len(self.venvList.selectedItems())
+        topLevelItemCount = self.venvList.topLevelItemCount()
+        
+        canBeDeleted = (
+            selectedItemsCount == 1 and
+            self.venvList.selectedItems()[0].text(0) != "<default>"
+        )
+        canAllBeDeleted = (
+            topLevelItemCount == 1 and
+            self.venvList.topLevelItem(0).text(0) != "<default>"
+        )
+        
+        self.editButton.setEnabled(selectedItemsCount == 1)
+        self.removeButton.setEnabled(selectedItemsCount > 1 or canBeDeleted)
+        self.removeAllButton.setEnabled(
+            topLevelItemCount > 1 or canAllBeDeleted)
+        self.deleteButton.setEnabled(selectedItemsCount > 1 or canBeDeleted)
+        self.deleteAllButton.setEnabled(
+            topLevelItemCount > 1 or canAllBeDeleted)
+    
+    @pyqtSlot()
+    def on_addButton_clicked(self):
+        """
+        Private slot to add a new entry.
+        """
+        from .VirtualenvAddEditDialog import VirtualenvAddEditDialog
+        dlg = VirtualenvAddEditDialog(self.__manager)
+        if dlg.exec_() == QDialog.Accepted:
+            venvName, venvDirectory, venvInterpreter = dlg.getData()
+            
+            self.__manager.addVirtualEnv(venvName, venvDirectory,
+                                         venvInterpreter)
+    
+    @pyqtSlot()
+    def on_newButton_clicked(self):
+        """
+        Private slot to create a new virtual environment.
+        """
+        self.__manager.createVirtualEnv()
+    
+    @pyqtSlot()
+    def on_editButton_clicked(self):
+        """
+        Private slot to edit the selected entry.
+        """
+        selectedItem = self.venvList.selectedItems()[0]
+        oldVenvName = selectedItem.text(0)
+        
+        from .VirtualenvAddEditDialog import VirtualenvAddEditDialog
+        dlg = VirtualenvAddEditDialog(self.__manager, selectedItem.text(0),
+                                      selectedItem.text(1),
+                                      selectedItem.text(2))
+        if dlg.exec_() == QDialog.Accepted:
+            venvName, venvDirectory, venvInterpreter = dlg.getData()
+            if venvName != oldVenvName:
+                self.__manager.renameVirtualEnv(
+                    oldVenvName, venvName, venvDirectory, venvInterpreter)
+            else:
+                self.__manager.setVirtualEnv(
+                    venvName, venvDirectory, venvInterpreter)
+    
+    @pyqtSlot()
+    def on_removeButton_clicked(self):
+        """
+        Private slot to remove all selected entries from the list but keep
+        their directories.
+        """
+        selectedVenvs = []
+        for itm in self.venvList.selectedItems():
+            selectedVenvs.append(itm.text(0))
+        
+        if selectedVenvs:
+            self.__manager.removeVirtualEnvs(selectedVenvs)
+    
+    @pyqtSlot()
+    def on_removeAllButton_clicked(self):
+        """
+        Private slot to remove all entries from the list but keep their
+        directories.
+        """
+        venvNames = []
+        for index in range(self.venvList.topLevelItemCount()):
+            itm = self.venvList.topLevelItem(index)
+            venvNames.append(itm.text(0))
+        
+        if venvNames:
+            self.__manager.removeVirtualEnvs(venvNames)
+    
+    @pyqtSlot()
+    def on_deleteButton_clicked(self):
+        """
+        Private slot to delete all selected entries from the list and disk.
+        """
+        selectedVenvs = []
+        for itm in self.venvList.selectedItems():
+            selectedVenvs.append(itm.text(0))
+        
+        if selectedVenvs:
+            self.__manager.deleteVirtualEnvs(selectedVenvs)
+    
+    @pyqtSlot()
+    def on_deleteAllButton_clicked(self):
+        """
+        Private slot to delete all entries from the list and disk.
+        """
+        venvNames = []
+        for index in range(self.venvList.topLevelItemCount()):
+            itm = self.venvList.topLevelItem(index)
+            venvNames.append(itm.text(0))
+        
+        if venvNames:
+            self.__manager.deleteVirtualEnvs(venvNames)
+    
+    @pyqtSlot()
+    def on_venvList_itemSelectionChanged(self):
+        """
+        Private slot handling a change of the selected items.
+        """
+        self.__updateButtons()
+    
+    @pyqtSlot()
+    def refresh(self):
+        """
+        Public slot to refresh the list of shown items.
+        """
+        # 1. remember selected entries
+        selectedVenvs = []
+        for itm in self.venvList.selectedItems():
+            selectedVenvs.append(itm.text(0))
+        
+        # 2. clear the list
+        self.venvList.clear()
+        
+        # 3. re-populate the list
+        self.__populateVenvList()
+        
+        # 4. re-establish selection
+        for venvName in selectedVenvs:
+            itms = self.venvList.findItems(venvName, Qt.MatchExactly, 0)
+            if itms:
+                itms[0].setSelected(True)
+    
+    def __populateVenvList(self):
+        """
+        Private method to populate the list of virtual environments.
+        """
+        environments = self.__manager.getEnvironmentEntries()
+        for venvName in environments:
+            QTreeWidgetItem(self.venvList, [
+                venvName,
+                environments[venvName][0],
+                environments[venvName][1],
+            ])
+        
+        self.__resizeSections()
+    
+    def __resizeSections(self):
+        """
+        Private method to resize the sections of the environment list to their
+        contents.
+        """
+        self.venvList.header().resizeSections(
+            QHeaderView.ResizeToContents)
+        self.venvList.header().setStretchLastSection(True)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualEnv/VirtualenvManagerDialog.ui	Sun Jun 10 16:55:39 2018 +0200
@@ -0,0 +1,240 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VirtualenvManagerDialog</class>
+ <widget class="QDialog" name="VirtualenvManagerDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>700</width>
+    <height>500</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Manage Virtual Environments</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QTreeWidget" name="venvList">
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::ExtendedSelection</enum>
+     </property>
+     <property name="rootIsDecorated">
+      <bool>false</bool>
+     </property>
+     <property name="itemsExpandable">
+      <bool>false</bool>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+     <property name="allColumnsShowFocus">
+      <bool>true</bool>
+     </property>
+     <column>
+      <property name="text">
+       <string>Name</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Directory</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Interpreter</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QPushButton" name="addButton">
+       <property name="toolTip">
+        <string>Press to add an existing virtual environment</string>
+       </property>
+       <property name="text">
+        <string>Add...</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="newButton">
+       <property name="toolTip">
+        <string>Press to create a new virtual environment</string>
+       </property>
+       <property name="text">
+        <string>New...</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="Line" name="line_6">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="editButton">
+       <property name="toolTip">
+        <string>Press to edit the selected virtual environment</string>
+       </property>
+       <property name="text">
+        <string>Edit...</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="Line" name="line_3">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="removeButton">
+       <property name="toolTip">
+        <string>Press to remove the selected virtual environments</string>
+       </property>
+       <property name="text">
+        <string>Remove</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="removeAllButton">
+       <property name="toolTip">
+        <string>Press to remove all virtual environments</string>
+       </property>
+       <property name="text">
+        <string>Remove All</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="Line" name="line_4">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="deleteButton">
+       <property name="toolTip">
+        <string>Press to remove the selected virtual environments and delete them</string>
+       </property>
+       <property name="text">
+        <string>Delete</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="deleteAllButton">
+       <property name="toolTip">
+        <string>Press to remove all virtual environments and delete them</string>
+       </property>
+       <property name="text">
+        <string>Delete All</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="verticalSpacer_3">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>228</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item row="1" column="0" colspan="2">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>venvList</tabstop>
+  <tabstop>addButton</tabstop>
+  <tabstop>newButton</tabstop>
+  <tabstop>editButton</tabstop>
+  <tabstop>removeButton</tabstop>
+  <tabstop>removeAllButton</tabstop>
+  <tabstop>deleteButton</tabstop>
+  <tabstop>deleteAllButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>VirtualenvManagerDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>VirtualenvManagerDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- a/changelog	Sat Jun 09 17:19:37 2018 +0200
+++ b/changelog	Sun Jun 10 16:55:39 2018 +0200
@@ -7,6 +7,8 @@
      remote repository
 - pip Interface
   -- added support for the '--user' option of install and list commands
+- VirtualEnv Manager
+  -- added a manager for virtual environments
 
 Version 18.06:
 - bug fixes
--- a/eric6.e4p	Sat Jun 09 17:19:37 2018 +0200
+++ b/eric6.e4p	Sun Jun 10 16:55:39 2018 +0200
@@ -1396,10 +1396,12 @@
     <Source>ViewManager/BookmarkedFilesDialog.py</Source>
     <Source>ViewManager/ViewManager.py</Source>
     <Source>ViewManager/__init__.py</Source>
+    <Source>VirtualEnv/VirtualenvAddEditDialog.py</Source>
     <Source>VirtualEnv/VirtualenvConfigurationDialog.py</Source>
     <Source>VirtualEnv/VirtualenvExecDialog.py</Source>
     <Source>VirtualEnv/VirtualenvInterpreterSelectionDialog.py</Source>
     <Source>VirtualEnv/VirtualenvManager.py</Source>
+    <Source>VirtualEnv/VirtualenvManagerDialog.py</Source>
     <Source>VirtualEnv/__init__.py</Source>
     <Source>WebBrowser/AdBlock/AdBlockDialog.py</Source>
     <Source>WebBrowser/AdBlock/AdBlockExceptionsDialog.py</Source>
@@ -2109,9 +2111,11 @@
     <Form>VCS/CommandOptionsDialog.ui</Form>
     <Form>VCS/RepositoryInfoDialog.ui</Form>
     <Form>ViewManager/BookmarkedFilesDialog.ui</Form>
+    <Form>VirtualEnv/VirtualenvAddEditDialog.ui</Form>
     <Form>VirtualEnv/VirtualenvConfigurationDialog.ui</Form>
     <Form>VirtualEnv/VirtualenvExecDialog.ui</Form>
     <Form>VirtualEnv/VirtualenvInterpreterSelectionDialog.ui</Form>
+    <Form>VirtualEnv/VirtualenvManagerDialog.ui</Form>
     <Form>WebBrowser/AdBlock/AdBlockDialog.ui</Form>
     <Form>WebBrowser/AdBlock/AdBlockExceptionsDialog.ui</Form>
     <Form>WebBrowser/Bookmarks/AddBookmarkDialog.ui</Form>
Binary file icons/default/virtualenvConfig.png has changed

eric ide

mercurial