# -*- coding: utf-8 -*-

# Copyright (c) 2015 - 2018 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Package implementing the pip GUI logic.
"""

from __future__ import unicode_literals
try:
    str = unicode       # __IGNORE_EXCEPTION__
except NameError:
    pass

import os
import sys

from PyQt5.QtCore import pyqtSlot, QObject, QProcess
from PyQt5.QtWidgets import QMenu, QInputDialog, QDialog

from E5Gui import E5MessageBox
from E5Gui.E5Action import E5Action
from E5Gui.E5Application import e5App

from .PipDialog import PipDialog
from . import DefaultIndexUrlXml

import Preferences
import Globals


class Pip(QObject):
    """
    Class implementing the pip GUI logic.
    """
    def __init__(self, plugin, parent=None):
        """
        Constructor
        
        @param plugin reference to the plugin object
        @type PipInterfacePlugin
        @param parent parent
        @type QObject
        """
        super(Pip, self).__init__(parent)
        
        self.__plugin = plugin
        self.__ui = parent
        
        self.__virtualenvManager = e5App().getObject("VirtualEnvManager")
        
        self.__menus = {}   # dictionary with references to menus
        
        self.__plugin.currentEnvironmentChanged.connect(
            self.__handleTearOffMenu)
    
    def initActions(self):
        """
        Public method to define the actions.
        """
        self.actions = []
    
        self.selectEnvironmentAct = E5Action(
            self.tr('Virtual Environment for pip'),
            self.tr('&Virtual Environment for pip'),
            0, 0,
            self, 'pip_select_environment')
        self.selectEnvironmentAct.setStatusTip(self.tr(
            'Selects the virtual environment to be used for pip'))
        self.selectEnvironmentAct.setWhatsThis(self.tr(
            """<b>Virtual Environment for pip</b>"""
            """<p>This selects the virtual environment to be used for pip."""
            """</p>"""
        ))
        self.selectEnvironmentAct.triggered.connect(self.__selectPipVirtualenv)
        self.actions.append(self.selectEnvironmentAct)
        
        ##############################################
        ## Actions for listing packages
        ##############################################
        
        self.listPackagesAct = E5Action(
            self.tr('List Installed Packages'),
            self.tr('&List Installed Packages...'),
            0, 0,
            self, 'pip_list_packages')
        self.listPackagesAct.setStatusTip(self.tr(
            'List all installed packages with versions'))
        self.listPackagesAct.setWhatsThis(self.tr(
            """<b>List Installed Packages</b>"""
            """<p>This lists all the installed packages together"""
            """ with their versions.</p>"""
        ))
        self.listPackagesAct.triggered.connect(self.__listPackages)
        self.actions.append(self.listPackagesAct)
        
        self.listUptodatePackagesAct = E5Action(
            self.tr('List Up-to-date Packages'),
            self.tr('List Up-to-&date Packages...'),
            0, 0,
            self, 'pip_list_uptodate_packages')
        self.listUptodatePackagesAct.setStatusTip(self.tr(
            'List all installed, up-to-date packages with versions'))
        self.listUptodatePackagesAct.setWhatsThis(self.tr(
            """<b>List Up-to-date Packages</b>"""
            """<p>This lists all the installed, up-to-date packages together"""
            """ with their versions.</p>"""
        ))
        self.listUptodatePackagesAct.triggered.connect(
            self.__listUptodatePackages)
        self.actions.append(self.listUptodatePackagesAct)
        
        self.listOutdatedPackagesAct = E5Action(
            self.tr('List Outdated Packages'),
            self.tr('List &Outdated Packages...'),
            0, 0,
            self, 'pip_list_outdated_packages')
        self.listOutdatedPackagesAct.setStatusTip(self.tr(
            'List all installed, outdated packages with versions'))
        self.listOutdatedPackagesAct.setWhatsThis(self.tr(
            """<b>List Up-to-date Packages</b>"""
            """<p>This lists all the installed, outdated packages together"""
            """ with their current and latest versions.</p>"""
        ))
        self.listOutdatedPackagesAct.triggered.connect(
            self.__listOutdatedPackages)
        self.actions.append(self.listOutdatedPackagesAct)
        
        ##############################################
        ## Actions for installing packages
        ##############################################
        
        self.installPackagesAct = E5Action(
            self.tr('Install Packages'),
            self.tr('&Install Packages'),
            0, 0,
            self, 'pip_install_packages')
        self.installPackagesAct.setStatusTip(self.tr(
            'Install packages according to user input'))
        self.installPackagesAct.setWhatsThis(self.tr(
            """<b>Install Packages</b>"""
            """<p>This installs packages according to user input.</p>"""
        ))
        self.installPackagesAct.triggered.connect(self.__installPackages)
        self.actions.append(self.installPackagesAct)
        
        self.installLocalPackageAct = E5Action(
            self.tr('Install Local Package'),
            self.tr('Install Local Package'),
            0, 0,
            self, 'pip_install_local_package')
        self.installLocalPackageAct.setStatusTip(self.tr(
            'Install a package from local storage'))
        self.installLocalPackageAct.setWhatsThis(self.tr(
            """<b>Install Local Package</b>"""
            """<p>This installs a package available on local storage.</p>"""
        ))
        self.installLocalPackageAct.triggered.connect(
            self.__installLocalPackage)
        self.actions.append(self.installLocalPackageAct)
        
        self.installRequirementsAct = E5Action(
            self.tr('Install Requirements'),
            self.tr('Install Requirements'),
            0, 0,
            self, 'pip_install_requirements')
        self.installRequirementsAct.setStatusTip(self.tr(
            'Install packages according to a requirements file'))
        self.installRequirementsAct.setWhatsThis(self.tr(
            """<b>Install Requirements</b>"""
            """<p>This installs packages according to a requirements"""
            """ file.</p>"""
        ))
        self.installRequirementsAct.triggered.connect(
            self.__installRequirements)
        self.actions.append(self.installRequirementsAct)
        
        self.installPipAct = E5Action(
            self.tr('Install Pip'),
            self.tr('Install Pip'),
            0, 0,
            self, 'pip_install_pip')
        self.installPipAct.setStatusTip(self.tr(
            'Install the pip package itself'))
        self.installPipAct.setWhatsThis(self.tr(
            """<b>Install Pip</b>"""
            """<p>This installs the pip package itself.</p>"""
        ))
        self.installPipAct.triggered.connect(self.__installPip)
        self.actions.append(self.installPipAct)
        
        self.repairPipAct = E5Action(
            self.tr('Repair Pip'),
            self.tr('Repair Pip'),
            0, 0,
            self, 'pip_repair_pip')
        self.repairPipAct.setStatusTip(self.tr(
            'Repair the pip package'))
        self.repairPipAct.setWhatsThis(self.tr(
            """<b>Repair Pip</b>"""
            """<p>This repairs the pip package by re-installing it.</p>"""
        ))
        self.repairPipAct.triggered.connect(self.__repairPip)
        self.actions.append(self.repairPipAct)
        
        self.upgradePipAct = E5Action(
            self.tr('Upgrade Pip'),
            self.tr('Upgrade &Pip'),
            0, 0,
            self, 'pip_upgrade_pip')
        self.upgradePipAct.setStatusTip(self.tr(
            'Upgrade the pip package itself'))
        self.upgradePipAct.setWhatsThis(self.tr(
            """<b>Upgrade Pip</b>"""
            """<p>This upgrades the pip package itself.</p>"""
        ))
        self.upgradePipAct.triggered.connect(self.upgradePip)
        self.actions.append(self.upgradePipAct)
        
        self.upgradePackagesAct = E5Action(
            self.tr('Upgrade Packages'),
            self.tr('&Upgrade Packages'),
            0, 0,
            self, 'pip_upgrade_packages')
        self.upgradePackagesAct.setStatusTip(self.tr(
            'Upgrade packages according to user input'))
        self.upgradePackagesAct.setWhatsThis(self.tr(
            """<b>Upgrade Packages</b>"""
            """<p>This upgrades packages according to user input.</p>"""
        ))
        self.upgradePackagesAct.triggered.connect(self.__upgradePackages)
        self.actions.append(self.upgradePackagesAct)
        
        ##############################################
        ## Actions for uninstalling packages
        ##############################################
        
        self.uninstallPackagesAct = E5Action(
            self.tr('Uninstall Packages'),
            self.tr('Uninstall Packages'),
            0, 0,
            self, 'pip_uninstall_packages')
        self.uninstallPackagesAct.setStatusTip(self.tr(
            'Uninstall packages according to user input'))
        self.uninstallPackagesAct.setWhatsThis(self.tr(
            """<b>Uninstall Packages</b>"""
            """<p>This uninstalls packages according to user input.</p>"""
        ))
        self.uninstallPackagesAct.triggered.connect(self.__uninstallPackages)
        self.actions.append(self.uninstallPackagesAct)
        
        self.uninstallRequirementsAct = E5Action(
            self.tr('Uninstall Requirements'),
            self.tr('Uninstall Requirements'),
            0, 0,
            self, 'pip_uninstall_requirements')
        self.uninstallRequirementsAct.setStatusTip(self.tr(
            'Uninstall packages according to a requirements file'))
        self.uninstallRequirementsAct.setWhatsThis(self.tr(
            """<b>Uninstall Requirements</b>"""
            """<p>This uninstalls packages according to a requirements"""
            """ file.</p>"""
        ))
        self.uninstallRequirementsAct.triggered.connect(
            self.__uninstallRequirements)
        self.actions.append(self.uninstallRequirementsAct)
        
        ##############################################
        ## Actions for generating requirements files
        ##############################################
        
        self.generateRequirementsAct = E5Action(
            self.tr('Generate Requirements'),
            self.tr('&Generate Requirements...'),
            0, 0,
            self, 'pip_generate_requirements')
        self.generateRequirementsAct.setStatusTip(self.tr(
            'Generate the contents of a requirements file'))
        self.generateRequirementsAct.setWhatsThis(self.tr(
            """<b>Generate Requirements</b>"""
            """<p>This generates the contents of a requirements file.</p>"""
        ))
        self.generateRequirementsAct.triggered.connect(
            self.__generateRequirements)
        self.actions.append(self.generateRequirementsAct)
        
        ##############################################
        ## Actions for generating requirements files
        ##############################################
        
        self.searchPyPIAct = E5Action(
            self.tr('Search PyPI'),
            self.tr('&Search PyPI...'),
            0, 0,
            self, 'pip_search_pypi')
        self.searchPyPIAct.setStatusTip(self.tr(
            'Open a dialog to search the Python Package Index'))
        self.searchPyPIAct.setWhatsThis(self.tr(
            """<b>Search PyPI</b>"""
            """<p>This opens a dialog to search the Python Package"""
            """ Index.</p>"""
        ))
        self.searchPyPIAct.triggered.connect(self.__searchPyPI)
        self.actions.append(self.searchPyPIAct)
        
        ##############################################
        ## Actions for editing configuration files
        ##############################################
        
        self.editUserConfigAct = E5Action(
            self.tr('Edit User Configuration'),
            self.tr('Edit User Configuration...'),
            0, 0,
            self, 'pip_edit_user_config')
        self.editUserConfigAct.setStatusTip(self.tr(
            'Open the per user configuration file in an editor'))
        self.editUserConfigAct.setWhatsThis(self.tr(
            """<b>Edit User Configuration</b>"""
            """<p>This opens the per user configuration file in an editor."""
            """</p>"""
        ))
        self.editUserConfigAct.triggered.connect(self.__editUserConfiguration)
        self.actions.append(self.editUserConfigAct)
        
        self.editVirtualenvConfigAct = E5Action(
            self.tr('Edit Current Virtualenv Configuration'),
            self.tr('Edit Current Virtualenv Configuration...'),
            0, 0,
            self, 'pip_edit_virtualenv_config')
        self.editVirtualenvConfigAct.setStatusTip(self.tr(
            'Open the current virtualenv configuration file in an editor'))
        self.editVirtualenvConfigAct.setWhatsThis(self.tr(
            """<b>Edit Current Virtualenv Configuration</b>"""
            """<p>This opens the current virtualenv configuration file in"""
            """ an editor. </p>"""
        ))
        self.editVirtualenvConfigAct.triggered.connect(
            self.__editVirtualenvConfiguration)
        self.actions.append(self.editVirtualenvConfigAct)
        
        self.pipConfigAct = E5Action(
            self.tr('Configure'),
            self.tr('Configure...'),
            0, 0, self, 'pip_configure')
        self.pipConfigAct.setStatusTip(self.tr(
            'Show the configuration dialog with the Python Package Management'
            ' page selected'
        ))
        self.pipConfigAct.setWhatsThis(self.tr(
            """<b>Configure</b>"""
            """<p>Show the configuration dialog with the Python Package"""
            """ Management page selected.</p>"""
        ))
        self.pipConfigAct.triggered.connect(self.__pipConfigure)
        self.actions.append(self.pipConfigAct)
    
    def initMenu(self):
        """
        Public slot to initialize the menu.
        
        @return the menu generated
        @rtype QMenu
        """
        self.__menus = {}   # clear menus references
        
        menu = QMenu(self.tr('P&ython Package Management'), self.__ui)
        menu.setTearOffEnabled(True)
        
        menu.addAction(self.selectEnvironmentAct)
        menu.addSeparator()
        menu.addAction(self.listPackagesAct)
        menu.addAction(self.listUptodatePackagesAct)
        menu.addAction(self.listOutdatedPackagesAct)
        menu.addSeparator()
        menu.addAction(self.installPipAct)
        menu.addSeparator()
        menu.addAction(self.installPackagesAct)
        menu.addAction(self.installLocalPackageAct)
        menu.addAction(self.installRequirementsAct)
        menu.addSeparator()
        menu.addAction(self.upgradePipAct)
        menu.addAction(self.upgradePackagesAct)
        menu.addSeparator()
        menu.addAction(self.uninstallPackagesAct)
        menu.addAction(self.uninstallRequirementsAct)
        menu.addSeparator()
        menu.addAction(self.generateRequirementsAct)
        menu.addSeparator()
        menu.addAction(self.searchPyPIAct)
        menu.addSeparator()
        menu.addAction(self.repairPipAct)
        menu.addSeparator()
        menu.addAction(self.editUserConfigAct)
        menu.addAction(self.editVirtualenvConfigAct)
        menu.addSeparator()
        menu.addAction(self.pipConfigAct)
        
        self.__menus["main"] = menu
        
        menu.aboutToShow.connect(self.__aboutToShowMenu)
        
        return menu
    
    def __aboutToShowMenu(self):
        """
        Private slot to set the action enabled status.
        """
        enable = bool(self.__plugin.getPreferences("CurrentEnvironment"))
        for act in self.actions:
            if act not in [self.selectEnvironmentAct,
                           self.installPipAct,
                           self.editUserConfigAct,
                           self.editVirtualenvConfigAct,
                           self.pipConfigAct]:
                act.setEnabled(enable)
    
    def getMenu(self, name):
        """
        Public method to get a reference to the requested menu.
        
        @param name name of the menu
        @type str
        @return reference to the menu or None, if no
            menu with the given name exists
        @rtype QMenu or None
        """
        if name in self.__menus:
            return self.__menus[name]
        else:
            return None
    
    def getMenuNames(self):
        """
        Public method to get the names of all menus.
        
        @return menu names
        @rtype list of str
        """
        return list(self.__menus.keys())
    
    def __handleTearOffMenu(self, venvName):
        """
        Private slot to handle a change of the selected virtual environment.
        
        @param venvName logical name of the virtual environment
        @type str
        """
        if self.__menus["main"].isTearOffMenuVisible():
            # determine, if torn off menu needs to be refreshed
            enabled = self.listPackagesAct.isEnabled()
            if ((bool(venvName) and not enabled) or
                    (not bool(venvName) and enabled)):
                self.__menus["main"].hideTearOffMenu()
    
    ##########################################################################
    ## Methods below implement some utility functions
    ##########################################################################
    
    def runProcess(self, args, interpreter):
        """
        Public method to execute the current pip with the given arguments.
        
        The selected pip executable is called with the given arguments and
        waited for its end.
        
        @param args list of command line arguments
        @type list of str
        @param interpreter path of the Python interpreter to be used
        @type str
        @return tuple containing a flag indicating success and the output
            of the process
        @rtype tuple of (bool, str)
        """
        ioEncoding = Preferences.getSystem("IOEncoding")
        
        process = QProcess()
        process.start(interpreter, args)
        procStarted = process.waitForStarted()
        if procStarted:
            finished = process.waitForFinished(30000)
            if finished:
                if process.exitCode() == 0:
                    output = str(process.readAllStandardOutput(), ioEncoding,
                                 'replace')
                    return True, output
                else:
                    return (False,
                            self.tr("python exited with an error ({0}).")
                            .format(process.exitCode()))
            else:
                process.terminate()
                process.waitForFinished(2000)
                process.kill()
                process.waitForFinished(3000)
                return False, self.tr("python did not finish within"
                                      " 30 seconds.")
        
        return False, self.tr("python could not be started.")
    
    def __getUserConfig(self):
        """
        Private method to get the name of the user configuration file.
        
        @return path of the user configuration file
        @rtype str
        """
        # Unix:     ~/.config/pip/pip.conf
        # OS X:     ~/Library/Application Support/pip/pip.conf
        # Windows:  %APPDATA%\pip\pip.ini
        # Environment: $PIP_CONFIG_FILE
        
        try:
            return os.environ["PIP_CONFIG_FILE"]
        except KeyError:
            pass
        
        if Globals.isWindowsPlatform():
            config = os.path.join(os.environ["APPDATA"], "pip", "pip.ini")
        elif Globals.isMacPlatform():
            config = os.path.expanduser(
                "~/Library/Application Support/pip/pip.conf")
        else:
            config = os.path.expanduser("~/.config/pip/pip.conf")
        
        return config
    
    def __getVirtualenvConfig(self):
        """
        Private method to get the name of the virtualenv configuration file.
        
        @return path of the virtualenv configuration file
        @rtype str
        """
        # Unix, OS X:   $VIRTUAL_ENV/pip.conf
        # Windows:      %VIRTUAL_ENV%\pip.ini
        
        if Globals.isWindowsPlatform():
            pip = "pip.ini"
        else:
            pip = "pip.conf"
        try:
            venvDirectory = os.environ["VIRTUAL_ENV"]
        except KeyError:
            venvName = self.__plugin.getPreferences("CurrentEnvironment")
            if not venvName:
                self.__selectPipVirtualenv()
                venvName = self.__plugin.getPreferences("CurrentEnvironment")
            if self.__virtualenvManager.isGlobalEnvironment(venvName):
                venvDirectory = self.__getUserConfig()
            else:
                venvDirectory = \
                    self.__virtualenvManager.getVirtualenvDirectory(venvName)
        
        return os.path.join(venvDirectory, pip)
    
    def getDefaultEnvironmentString(self):
        """
        Public method to get the string for the default environment.
        
        @return string for the default environment
        @rtype str
        """
        return self.tr("<default>")
    
    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 == self.getDefaultEnvironmentString():
            venvName = self.__plugin.getPreferences("CurrentEnvironment")
        
        interpreter = self.__virtualenvManager.getVirtualenvInterpreter(
            venvName)
        if not interpreter:
            E5MessageBox.critical(
                None,
                self.tr("Interpreter for Virtual Environment"),
                self.tr("""No interpreter configured for the selected"""
                        """ virtual environment."""))
        
        return interpreter
    
    def getVirtualenvNames(self):
        """
        Public method to get a sorted list of virtual environment names.
        
        @return sorted list of virtual environment names
        @rtype list of str
        """
        return sorted(self.__virtualenvManager.getVirtualenvNames())
    
    ##########################################################################
    ## Methods below implement the individual menu entries
    ##########################################################################
    
    def __selectPipVirtualenv(self):
        """
        Private method to select the virtual environment to be used.
        """
        environments = self.getVirtualenvNames()
        if environments:
            currentEnvironment = self.__plugin.getPreferences(
                "CurrentEnvironment")
            try:
                index = environments.index(currentEnvironment)
            except ValueError:
                index = 0
            environment, ok = QInputDialog.getItem(
                None,
                self.tr("Virtual Environment for pip"),
                self.tr("Select the virtual environment to be used:"),
                environments, index, False)
            
            if ok and environment:
                self.__plugin.setPreferences("CurrentEnvironment",
                                             environment)
        else:
            E5MessageBox.warning(
                None,
                self.tr("Virtual Environment for pip"),
                self.tr("""No virtual environments have been configured yet."""
                        """ Please use the Virtualenv Manager to do that."""))
    
    def __listPackages(self):
        """
        Private slot to list all installed packages.
        """
        from .PipListDialog import PipListDialog
        self.__listDialog = PipListDialog(
            self, "list", self.__plugin.getPreferences("PipSearchIndex"),
            self.tr("Installed Packages"))
        self.__listDialog.show()
        self.__listDialog.start()
    
    def __listUptodatePackages(self):
        """
        Private slot to list all installed, up-to-date packages.
        """
        from .PipListDialog import PipListDialog
        self.__listUptodateDialog = PipListDialog(
            self, "uptodate", self.__plugin.getPreferences("PipSearchIndex"),
            self.tr("Up-to-date Packages"))
        self.__listUptodateDialog.show()
        self.__listUptodateDialog.start()
    
    def __listOutdatedPackages(self):
        """
        Private slot to list all installed, up-to-date packages.
        """
        from .PipListDialog import PipListDialog
        self.__listOutdatedDialog = PipListDialog(
            self, "outdated", self.__plugin.getPreferences("PipSearchIndex"),
            self.tr("Outdated Packages"))
        self.__listOutdatedDialog.show()
        self.__listOutdatedDialog.start()
    
    def __editUserConfiguration(self):
        """
        Private slot to edit the user configuration.
        """
        self.__editConfiguration()
    
    def __editVirtualenvConfiguration(self):
        """
        Private slot to edit the current virtualenv configuration.
        """
        self.__editConfiguration(virtualenv=True)
    
    def __editConfiguration(self, virtualenv=False):
        """
        Private method to edit a configuration.
        
        @param virtualenv flag indicating to edit the current virtualenv
            configuration file
        @type bool
        """
        from QScintilla.MiniEditor import MiniEditor
        if virtualenv:
            cfgFile = self.__getVirtualenvConfig()
        else:
            cfgFile = self.__getUserConfig()
        cfgDir = os.path.dirname(cfgFile)
        if not cfgDir:
            E5MessageBox.critical(
                None,
                self.tr("Edit Configuration"),
                self.tr("""No valid configuration path determined."""
                        """ Is a virtual environment selected? Aborting"""))
            return
        
        try:
            if not os.path.isdir(cfgDir):
                os.makedirs(cfgDir)
        except OSError:
            E5MessageBox.critical(
                None,
                self.tr("Edit Configuration"),
                self.tr("""No valid configuration path determined."""
                        """ Is a virtual environment selected? Aborting"""))
            return
        
        if not os.path.exists(cfgFile):
            try:
                f = open(cfgFile, "w")
                f.write("[global]\n")
                f.close()
            except (IOError, OSError):
                # ignore these
                pass
        
        # check, if the destination is writeable
        if not os.access(cfgFile, os.W_OK):
            E5MessageBox.critical(
                None,
                self.tr("Edit Configuration"),
                self.tr("""No valid configuartion path determined."""
                        """ Is a virtual environment selected? Aborting"""))
            return
        
        self.__editor = MiniEditor(cfgFile, "Properties")
        self.__editor.show()
    
    def __installPip(self, userSite=False):
        """
        Private slot to install pip.
        
        @param userSite flag indicating an install to the user install
            directory
        @type bool
        """
        from .PipSelectionDialog import PipSelectionDialog
        dlg = PipSelectionDialog(self)
        if dlg.exec_() != QDialog.Accepted:
            return
        
        venvName, userSite = dlg.getData()
        interpreter = self.getVirtualenvInterpreter(venvName)
        if not interpreter:
            return
        
        dia = PipDialog(self.tr('Install PIP'))
        if userSite:
            commands = [(interpreter, ["-m", "ensurepip", "--user"])]
        else:
            commands = [(interpreter, ["-m", "ensurepip"])]
        if self.__plugin.getPreferences("PipSearchIndex"):
            indexUrl = \
                self.__plugin.getPreferences("PipSearchIndex") + "/simple"
            args = ["-m", "pip", "install", "--index-url", indexUrl,
                    "--upgrade"]
        else:
            args = ["-m", "pip", "install", "--upgrade"]
        if userSite:
            args.append("--user")
        args.append("pip")
        commands.append((interpreter, args[:]))
    
        res = dia.startProcesses(commands)
        if res:
            dia.exec_()
    
    @pyqtSlot()
    def upgradePip(self, venvName="", userSite=False):
        """
        Public method to upgrade pip itself.
        
        @param venvName name of the virtual environment to be used
        @type str
        @param userSite flag indicating an install to the user install
            directory
        @type bool
        @return flag indicating a successful execution
        @rtype bool
        """
        # Upgrading pip needs to be treated specially because
        # it must be done using the python executable
        
        if not venvName:
            from .PipSelectionDialog import PipSelectionDialog
            dlg = PipSelectionDialog(self)
            if dlg.exec_() != QDialog.Accepted:
                return
            
            venvName, userSite = dlg.getData()
        
        interpreter = self.getVirtualenvInterpreter(venvName)
        if not interpreter:
            return
        
        if self.__plugin.getPreferences("PipSearchIndex"):
            indexUrl = \
                self.__plugin.getPreferences("PipSearchIndex") + "/simple"
            args = ["-m", "pip", "install", "--index-url", indexUrl,
                    "--upgrade"]
        else:
            args = ["-m", "pip", "install", "--upgrade"]
        if userSite:
            args.append("--user")
        args.append("pip")
        
        dia = PipDialog(self.tr('Upgrade PIP'))
        res = dia.startProcess(interpreter, args)
        if res:
            dia.exec_()
        return res
    
    @pyqtSlot()
    def __repairPip(self):
        """
        Private method to repair the pip installation.
        
        @return flag indicating a successful execution
        @rtype bool
        """
        from .PipSelectionDialog import PipSelectionDialog
        dlg = PipSelectionDialog(self)
        if dlg.exec_() != QDialog.Accepted:
            return False
        
        venvName, userSite = dlg.getData()
        interpreter = self.getVirtualenvInterpreter(venvName)
        if not interpreter:
            return
        
        # python -m pip install --ignore-installed pip
        if self.__plugin.getPreferences("PipSearchIndex"):
            indexUrl = \
                self.__plugin.getPreferences("PipSearchIndex") + "/simple"
            args = ["-m", "pip", "install", "--index-url", indexUrl,
                    "--ignore-installed"]
        else:
            args = ["-m", "pip", "install", "--ignore-installed"]
        if userSite:
            args.append("--user")
        args.append("pip")
        
        dia = PipDialog(self.tr('Repair PIP'))
        res = dia.startProcess(interpreter, args)
        if res:
            dia.exec_()
    
    def __checkUpgradePyQt(self, packages):
        """
        Private method to check, if an upgrade of PyQt packages is attempted.
        
        @param packages list of packages to upgrade
        @type list of str
        @return flag indicating to abort the upgrade attempt
        @rtype bool
        """
        pyqtPackages = [p for p in packages
                        if p.lower() in ["pyqt5", "qscintilla", "sip"]]
        
        if bool(pyqtPackages):
            abort = not E5MessageBox.yesNo(
                None,
                self.tr("Upgrade Packages"),
                self.tr(
                    """You are trying to upgrade PyQt packages. This will"""
                    """ not work for the current instance of Python ({0})."""
                    """ Do you want to continue?""").format(sys.executable),
                icon=E5MessageBox.Critical)
        else:
            abort = False
        
        return abort
    
    def upgradePackages(self, packages, venvName="", userSite=False):
        """
        Public method to upgrade the given list of packages.
        
        @param packages list of packages to upgrade
        @type list of str
        @param venvName name of the virtual environment to be used
        @type str
        @param userSite flag indicating an install to the user install
            directory
        @type bool
        @return flag indicating a successful execution
        @rtype bool
        """
        if self.__checkUpgradePyQt(packages):
            return False
        
        if not venvName:
            venvName = self.__plugin.getPreferences("CurrentEnvironment")
        interpreter = self.getVirtualenvInterpreter(venvName)
        if not interpreter:
            return
        
        if self.__plugin.getPreferences("PipSearchIndex"):
            indexUrl = \
                self.__plugin.getPreferences("PipSearchIndex") + "/simple"
            args = ["-m", "pip", "install", "--index-url", indexUrl,
                    "--upgrade"]
        else:
            args = ["-m", "pip", "install", "--upgrade"]
        if userSite:
            args.append("--user")
        args += packages
        dia = PipDialog(self.tr('Upgrade Packages'))
        res = dia.startProcess(interpreter, args)
        if res:
            dia.exec_()
        return res
    
    def __upgradePackages(self):
        """
        Private slot to upgrade packages to be given by the user.
        """
        from .PipPackagesInputDialog import PipPackagesInputDialog
        dlg = PipPackagesInputDialog(self, self.tr("Upgrade Packages"))
        if dlg.exec_() == QDialog.Accepted:
            venvName, packages, user = dlg.getData()
            if packages:
                self.upgradePackages(packages, venvName=venvName,
                                     userSite=user)
    
    def installPackages(self, packages, venvName="", userSite=False):
        """
        Public method to install the given list of packages.
        
        @param packages list of packages to install
        @type list of str
        @param venvName name of the virtual environment to be used
        @type str
        @param userSite flag indicating an install to the user install
            directory
        @type bool
        """
        if not venvName:
            venvName = self.__plugin.getPreferences("CurrentEnvironment")
        interpreter = self.getVirtualenvInterpreter(venvName)
        if not interpreter:
            return
        
        if self.__plugin.getPreferences("PipSearchIndex"):
            indexUrl = \
                self.__plugin.getPreferences("PipSearchIndex") + "/simple"
            args = ["-m", "pip", "install", "--index-url", indexUrl]
        else:
            args = ["-m", "pip", "install"]
        if userSite:
            args.append("--user")
        args += packages
        dia = PipDialog(self.tr('Install Packages'))
        res = dia.startProcess(interpreter, args)
        if res:
            dia.exec_()
    
    def __installPackages(self):
        """
        Private slot to install packages to be given by the user.
        """
        from .PipPackagesInputDialog import PipPackagesInputDialog
        dlg = PipPackagesInputDialog(
            self, self.tr("Install Packages"))
        if dlg.exec_() == QDialog.Accepted:
            venvName, packages, user = dlg.getData()
            if packages:
                self.installPackages(packages, venvName=venvName,
                                     userSite=user)
    
    def __installLocalPackage(self):
        """
        Private slot to install a package available on local storage.
        """
        from .PipFileSelectionDialog import PipFileSelectionDialog
        dlg = PipFileSelectionDialog(self, "package")
        if dlg.exec_() == QDialog.Accepted:
            venvName, package, user = dlg.getData()
            if package and os.path.exists(package):
                self.installPackages([package], venvName=venvName,
                                     userSite=user)
    
    def __installRequirements(self):
        """
        Private slot to install packages as given in a requirements file.
        """
        from .PipFileSelectionDialog import PipFileSelectionDialog
        dlg = PipFileSelectionDialog(self, "requirements")
        if dlg.exec_() == QDialog.Accepted:
            venvName, requirements, user = dlg.getData()
            if requirements and os.path.exists(requirements):
                interpreter = self.getVirtualenvInterpreter(venvName)
                if not interpreter:
                    return
                if self.__plugin.getPreferences("PipSearchIndex"):
                    indexUrl = \
                        self.__plugin.getPreferences("PipSearchIndex") + \
                        "/simple"
                    args = ["-m", "pip", "install", "--index-url", indexUrl]
                else:
                    args = ["-m", "pip", "install"]
                if user:
                    args.append("--user")
                args += ["--requirement", requirements]
                dia = PipDialog(self.tr('Install Packages from Requirements'))
                res = dia.startProcess(interpreter, args)
                if res:
                    dia.exec_()
    
    def uninstallPackages(self, packages, venvName=""):
        """
        Public method to uninstall the given list of packages.
        
        @param packages list of packages to uninstall
        @type list of str
        @param venvName name of the virtual environment to be used
        @type str
        @return flag indicating a successful execution
        @rtype bool
        """
        res = False
        if packages:
            from UI.DeleteFilesConfirmationDialog import \
                DeleteFilesConfirmationDialog
            dlg = DeleteFilesConfirmationDialog(
                self.parent(),
                self.tr("Uninstall Packages"),
                self.tr(
                    "Do you really want to uninstall these packages?"),
                packages)
            if dlg.exec_() == QDialog.Accepted:
                if not venvName:
                    venvName = self.__plugin.getPreferences(
                        "CurrentEnvironment")
                interpreter = self.getVirtualenvInterpreter(venvName)
                if not interpreter:
                    return
                args = ["-m", "pip", "uninstall", "--yes"] + packages
                dia = PipDialog(self.tr('Uninstall Packages'))
                res = dia.startProcess(interpreter, args)
                if res:
                    dia.exec_()
        return res
    
    def __uninstallPackages(self):
        """
        Private slot to uninstall packages to be given by the user.
        """
        from .PipPackagesInputDialog import PipPackagesInputDialog
        dlg = PipPackagesInputDialog(
            self, self.tr("Uninstall Packages"), install=False)
        if dlg.exec_() == QDialog.Accepted:
            venvName, packages, _user = dlg.getData()
            if packages:
                self.uninstallPackages(packages, venvName=venvName)
    
    def __uninstallRequirements(self):
        """
        Private slot to uninstall packages as given in a requirements file.
        """
        from .PipFileSelectionDialog import PipFileSelectionDialog
        dlg = PipFileSelectionDialog(self, "requirements",
                                     install=False)
        if dlg.exec_() == QDialog.Accepted:
            venvName, requirements, _user = dlg.getData()
            if requirements and os.path.exists(requirements):
                try:
                    f = open(requirements, "r")
                    reqs = f.read().splitlines()
                    f.close()
                except (OSError, IOError):
                    return
                
                from UI.DeleteFilesConfirmationDialog import \
                    DeleteFilesConfirmationDialog
                dlg = DeleteFilesConfirmationDialog(
                    self.parent(),
                    self.tr("Uninstall Packages"),
                    self.tr(
                        "Do you really want to uninstall these packages?"),
                    reqs)
                if dlg.exec_() == QDialog.Accepted:
                    if not venvName:
                        venvName = self.__plugin.getPreferences(
                            "CurrentEnvironment")
                    interpreter = self.getVirtualenvInterpreter(venvName)
                    if not interpreter:
                        return
                    args = ["-m", "pip", "uninstall", "--requirement",
                            requirements]
                    dia = PipDialog(
                        self.tr('Uninstall Packages from Requirements'))
                    res = dia.startProcess(interpreter, args)
                    if res:
                        dia.exec_()
    
    def __generateRequirements(self):
        """
        Private slot to generate the contents for a requirements file.
        """
        from .PipFreezeDialog import PipFreezeDialog
        self.__freezeDialog = PipFreezeDialog(self)
        self.__freezeDialog.show()
        self.__freezeDialog.start()
    
    def __searchPyPI(self):
        """
        Private slot to search the Python Package Index.
        """
        from .PipSearchDialog import PipSearchDialog
        
        if self.__plugin.getPreferences("PipSearchIndex"):
            indexUrl = self.__plugin.getPreferences("PipSearchIndex") + "/pypi"
        else:
            indexUrl = DefaultIndexUrlXml
        
        self.__searchDialog = PipSearchDialog(self, indexUrl)
        self.__searchDialog.show()
    
    def __pipConfigure(self):
        """
        Private slot to open the configuration page.
        """
        e5App().getObject("UserInterface").showPreferences("pipPage")
