Wed, 13 Feb 2019 20:39:58 +0100
Merged with default branch.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/Conda.py Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,734 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing the conda GUI logic. +""" + +from __future__ import unicode_literals +try: + str = unicode # __IGNORE_EXCEPTION__ +except NameError: + pass + +import json +import os + +from PyQt5.QtCore import pyqtSignal, QObject, QProcess, QCoreApplication +from PyQt5.QtWidgets import QDialog + +from E5Gui import E5MessageBox + +import Globals +import Preferences + +from . import rootPrefix, condaVersion +from .CondaExecDialog import CondaExecDialog + + +class Conda(QObject): + """ + Class implementing the conda GUI logic. + + @signal condaEnvironmentCreated() emitted to indicate the creation of + a new environment + @signal condaEnvironmentRemoved() emitted to indicate the removal of + an environment + """ + condaEnvironmentCreated = pyqtSignal() + condaEnvironmentRemoved = pyqtSignal() + + RootName = QCoreApplication.translate("Conda", "<root>") + + def __init__(self, parent=None): + """ + Constructor + + @param parent parent + @type QObject + """ + super(Conda, self).__init__(parent) + + self.__ui = parent + + ####################################################################### + ## environment related methods below + ####################################################################### + + def createCondaEnvironment(self, arguments): + """ + Public method to create a conda environment. + + @param arguments list of command line arguments + @type list of str + @return tuple containing a flag indicating success, the directory of + the created environment (aka. prefix) and the corresponding Python + interpreter + @rtype tuple of (bool, str, str) + """ + args = ["create", "--json", "--yes"] + arguments + + dlg = CondaExecDialog("create", self.__ui) + dlg.start(args) + dlg.exec_() + ok, resultDict = dlg.getResult() + + if ok: + if "actions" in resultDict and \ + "PREFIX" in resultDict["actions"]: + prefix = resultDict["actions"]["PREFIX"] + elif "prefix" in resultDict: + prefix = resultDict["prefix"] + elif "dst_prefix" in resultDict: + prefix = resultDict["dst_prefix"] + else: + prefix = "" + + # determine Python executable + if prefix: + pathPrefixes = [ + prefix, + rootPrefix() + ] + else: + pathPrefixes = [ + rootPrefix() + ] + for pathPrefix in pathPrefixes: + if Globals.isWindowsPlatform(): + python = os.path.join(pathPrefix, "python.exe") + else: + python = os.path.join(pathPrefix, "bin", "python") + if os.path.exists(python): + break + else: + python = "" + + self.condaEnvironmentCreated.emit() + return True, prefix, python + else: + return False, "", "" + + def removeCondaEnvironment(self, name="", prefix=""): + """ + Public method to remove a conda environment. + + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return flag indicating success + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + args = [ + "remove", + "--json", + "--quiet", + "--all", + ] + if name: + args.extend(["--name", name]) + elif prefix: + args.extend(["--prefix", prefix]) + + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + proc = QProcess() + proc.start(exe, args) + if not proc.waitForStarted(15000): + E5MessageBox.critical( + self.__ui, + self.tr("conda remove"), + self.tr("""The conda executable could not be started.""")) + return False + else: + proc.waitForFinished(15000) + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + jsonDict = json.loads(output) + except Exception: + E5MessageBox.critical( + self.__ui, + self.tr("conda remove"), + self.tr("""The conda executable returned invalid data.""")) + return False + + if "error" in jsonDict: + E5MessageBox.critical( + self.__ui, + self.tr("conda remove"), + self.tr("<p>The conda executable returned an error.</p>" + "<p>{0}</p>").format(jsonDict["message"])) + return False + + if jsonDict["success"]: + self.condaEnvironmentRemoved.emit() + + return jsonDict["success"] + + return False + + def getCondaEnvironmentsList(self): + """ + Public method to get a list of all Conda environments. + + @return list of tuples containing the environment name and prefix + @rtype list of tuples of (str, str) + """ + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + environmentsList = [] + + proc = QProcess() + proc.start(exe, ["info", "--json"]) + if proc.waitForStarted(15000): + if proc.waitForFinished(15000): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + jsonDict = json.loads(output) + except Exception: + jsonDict = {} + + if "envs" in jsonDict: + for prefix in jsonDict["envs"][:]: + if prefix == jsonDict["root_prefix"]: + if not jsonDict["root_writable"]: + # root prefix is listed but not writable + continue + name = self.RootName + else: + name = os.path.basename(prefix) + + environmentsList.append((name, prefix)) + + return environmentsList + + ####################################################################### + ## package related methods below + ####################################################################### + + def getInstalledPackages(self, name="", prefix=""): + """ + Public method to get a list of installed packages of a conda + environment. + + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return list of installed packages. Each entry is a tuple containing + the package name, version and build. + @rtype list of tuples of (str, str, str) + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + args = [ + "list", + "--json", + ] + if name: + args.extend(["--name", name]) + elif prefix: + args.extend(["--prefix", prefix]) + + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + packages = [] + + proc = QProcess() + proc.start(exe, args) + if proc.waitForStarted(15000): + if proc.waitForFinished(30000): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + jsonList = json.loads(output) + except Exception: + jsonList = [] + + for package in jsonList: + if isinstance(package, dict): + packages.append(( + package["name"], + package["version"], + package["build_string"] + )) + else: + packages.append(tuple(package.rsplit("-", 2))) + + return packages + + def getUpdateablePackages(self, name="", prefix=""): + """ + Public method to get a list of updateable packages of a conda + environment. + + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return list of installed packages. Each entry is a tuple containing + the package name, version and build. + @rtype list of tuples of (str, str, str) + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + args = [ + "update", + "--json", + "--quiet", + "--all", + "--dry-run", + ] + if name: + args.extend(["--name", name]) + elif prefix: + args.extend(["--prefix", prefix]) + + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + packages = [] + + proc = QProcess() + proc.start(exe, args) + if proc.waitForStarted(15000): + if proc.waitForFinished(30000): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + jsonDict = json.loads(output) + except Exception: + jsonDict = {} + + if "actions" in jsonDict and "LINK" in jsonDict["actions"]: + for linkEntry in jsonDict["actions"]["LINK"]: + if isinstance(linkEntry, dict): + packages.append(( + linkEntry["name"], + linkEntry["version"], + linkEntry["build_string"] + )) + else: + package = linkEntry.split()[0] + packages.append(tuple(package.rsplit("-", 2))) + + return packages + + def updatePackages(self, packages, name="", prefix=""): + """ + Public method to update packages of a conda environment. + + @param packages list of package names to be updated + @type list of str + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return flag indicating success + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + if packages: + args = [ + "update", + "--json", + "--yes", + ] + if name: + args.extend(["--name", name]) + elif prefix: + args.extend(["--prefix", prefix]) + args.extend(packages) + + dlg = CondaExecDialog("update", self.__ui) + dlg.start(args) + dlg.exec_() + ok, _ = dlg.getResult() + else: + ok = False + + return ok + + def updateAllPackages(self, name="", prefix=""): + """ + Public method to update all packages of a conda environment. + + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return flag indicating success + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + args = [ + "update", + "--json", + "--yes", + "--all" + ] + if name: + args.extend(["--name", name]) + elif prefix: + args.extend(["--prefix", prefix]) + + dlg = CondaExecDialog("update", self.__ui) + dlg.start(args) + dlg.exec_() + ok, _ = dlg.getResult() + + return ok + + def installPackages(self, packages, name="", prefix=""): + """ + Public method to install packages into a conda environment. + + @param packages list of package names to be installed + @type list of str + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return flag indicating success + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + if packages: + args = [ + "install", + "--json", + "--yes", + ] + if name: + args.extend(["--name", name]) + elif prefix: + args.extend(["--prefix", prefix]) + args.extend(packages) + + dlg = CondaExecDialog("install", self.__ui) + dlg.start(args) + dlg.exec_() + ok, _ = dlg.getResult() + else: + ok = False + + return ok + + def uninstallPackages(self, packages, name="", prefix=""): + """ + Public method to uninstall packages of a conda environment (including + all no longer needed dependencies). + + @param packages list of package names to be uninstalled + @type list of str + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return flag indicating success + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + 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 and" + " their dependencies?"), + packages) + if dlg.exec_() == QDialog.Accepted: + args = [ + "remove", + "--json", + "--yes", + ] + if condaVersion() >= (4, 4, 0): + args.append("--prune",) + if name: + args.extend(["--name", name]) + elif prefix: + args.extend(["--prefix", prefix]) + args.extend(packages) + + dlg = CondaExecDialog("remove", self.__ui) + dlg.start(args) + dlg.exec_() + ok, _ = dlg.getResult() + else: + ok = False + else: + ok = False + + return ok + + def searchPackages(self, pattern, fullNameOnly=False, packageSpec=False, + platform="", name="", prefix=""): + """ + Public method to search for a package pattern of a conda environment. + + @param pattern package search pattern + @type str + @param fullNameOnly flag indicating to search for full names only + @type bool + @param packageSpec flag indicating to search a package specification + @type bool + @param platform type of platform to be searched for + @type str + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return flag indicating success and a dictionary with package name as + key and list of dictionaries containing detailed data for the found + packages as values + @rtype tuple of (bool, dict of list of dict) + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + args = [ + "search", + "--json", + ] + if fullNameOnly: + args.append("--full-name") + if packageSpec: + args.append("--spec") + if platform: + args.extend(["--platform", platform]) + if name: + args.extend(["--name", name]) + elif prefix: + args.extend(["--prefix", prefix]) + args.append(pattern) + + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + packages = {} + ok = False + + proc = QProcess() + proc.start(exe, args) + if proc.waitForStarted(15000): + if proc.waitForFinished(30000): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + packages = json.loads(output) + ok = "error" not in packages + except Exception: + # return values for errors is already set + pass + + return ok, packages + + ####################################################################### + ## special methods below + ####################################################################### + + def updateConda(self): + """ + Public method to update conda itself. + + @return flag indicating success + @rtype bool + """ + args = [ + "update", + "--json", + "--yes", + "conda" + ] + + dlg = CondaExecDialog("update", self.__ui) + dlg.start(args) + dlg.exec_() + ok, _ = dlg.getResult() + + return ok + + def writeDefaultConfiguration(self): + """ + Public method to create a conda configuration with default values. + """ + args = [ + "config", + "--write-default", + "--quiet" + ] + + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + proc = QProcess() + proc.start(exe, args) + proc.waitForStarted(15000) + proc.waitForFinished(30000) + + def getCondaInformation(self): + """ + Public method to get a dictionary containing information about conda. + + @return dictionary containing information about conda + @rtype dict + """ + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + infoDict = {} + + proc = QProcess() + proc.start(exe, ["info", "--json"]) + if proc.waitForStarted(15000): + if proc.waitForFinished(30000): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + infoDict = json.loads(output) + except Exception: + infoDict = {} + + return infoDict + + def runProcess(self, args): + """ + Public method to execute the conda with the given arguments. + + The conda executable is called with the given arguments and + waited for its end. + + @param args list of command line arguments + @type list of str + @return tuple containing a flag indicating success and the output + of the process + @rtype tuple of (bool, str) + """ + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + process = QProcess() + process.start(exe, args) + procStarted = process.waitForStarted(15000) + if procStarted: + finished = process.waitForFinished(30000) + if finished: + if process.exitCode() == 0: + output = str(process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + return True, output + else: + return (False, + self.tr("conda exited with an error ({0}).") + .format(process.exitCode())) + else: + process.terminate() + process.waitForFinished(2000) + process.kill() + process.waitForFinished(3000) + return False, self.tr("conda did not finish within" + " 30 seconds.") + + return False, self.tr("conda could not be started.") + + def cleanConda(self, cleanAction): + """ + Public method to update conda itself. + + @param cleanAction cleaning action to be performed (must be one of + the command line parameters without '--') + @type str + """ + args = [ + "clean", + "--yes", + "--{0}".format(cleanAction), + ] + + dlg = CondaExecDialog("clean", self.__ui) + dlg.start(args) + dlg.exec_()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaExecDialog.py Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,294 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to show the output of a conda execution. +""" + +from __future__ import unicode_literals +try: + str = unicode +except NameError: + pass + +import json + +from PyQt5.QtCore import pyqtSlot, QProcess, QTimer +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton + +from E5Gui import E5MessageBox + +from .Ui_CondaExecDialog import Ui_CondaExecDialog + +import Preferences +import Globals + + +class CondaExecDialog(QDialog, Ui_CondaExecDialog): + """ + Class implementing a dialog to show the output of a conda execution. + """ + def __init__(self, command, parent=None): + """ + Constructor + + @param command conda command executed + @type str + @param parent reference to the parent widget + @type QWidget + """ + super(CondaExecDialog, self).__init__(parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) + + self.__condaCommand = command + + self.__process = None + self.__condaExe = Preferences.getConda("CondaExecutable") + if not self.__condaExe: + self.__condaExe = "conda" + + @pyqtSlot(QAbstractButton) + def on_buttonBox_clicked(self, button): + """ + Private slot called by a button of the button box clicked. + + @param button button that was clicked + @type QAbstractButton + """ + if button == self.buttonBox.button(QDialogButtonBox.Close): + self.accept() + elif button == self.buttonBox.button(QDialogButtonBox.Cancel): + self.__finish(1, 0) + + def start(self, arguments): + """ + Public slot to start the conda command. + + @param arguments commandline arguments for conda program + @type list of str + """ + self.errorGroup.hide() + self.progressLabel.hide() + self.progressBar.hide() + + self.contents.clear() + self.errors.clear() + self.progressLabel.clear() + self.progressBar.setValue(0) + + self.__bufferedStdout = None + self.__json = "--json" in arguments + self.__firstProgress = True + self.__lastFetchFile = "" + + self.__statusOk = False + self.__result = None + + self.__logOutput(self.__condaExe + " " + " ".join(arguments) + "\n\n") + + self.__process = QProcess() + self.__process.readyReadStandardOutput.connect(self.__readStdout) + self.__process.readyReadStandardError.connect(self.__readStderr) + self.__process.finished.connect(self.__finish) + + self.__process.start(self.__condaExe, arguments) + procStarted = self.__process.waitForStarted(5000) + if not procStarted: + E5MessageBox.critical( + self, + self.tr("Conda Execution"), + self.tr("""The conda executable could not be started. Is it""" + """ configured correctly?""")) + self.__finish(1, 0) + else: + self.__logOutput(self.tr("Operation started.\n")) + + def __finish(self, exitCode, exitStatus, giveUp=False): + """ + Private slot called when the process finished. + + It is called when the process finished or + the user pressed the button. + + @param exitCode exit code of the process + @type int + @param exitStatus exit status of the process + @type QProcess.ExitStatus + @param giveUp flag indicating to not start another attempt + @type bool + """ + if self.__process is not None and \ + self.__process.state() != QProcess.NotRunning: + self.__process.terminate() + QTimer.singleShot(2000, self.__process.kill) + self.__process.waitForFinished(3000) + + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) + + self.progressLabel.hide() + self.progressBar.hide() + + self.__statusOk = exitCode == 0 + + self.__logOutput(self.tr("Operation finished.\n")) + if not self.__json and self.__bufferedStdout: + self.__logOutput(self.__bufferedStdout) + + if self.__json and self.__bufferedStdout: + index = self.__bufferedStdout.find("{") + rindex = self.__bufferedStdout.rfind("}") + self.__bufferedStdout = self.__bufferedStdout[index:rindex + 1] + try: + self.__result = json.loads(self.__bufferedStdout) + except Exception as error: + self.__result = {} + self.__logError(str(error)) + return + + if "error" in self.__result: + self.__logError(self.__result["error"]) + self.__statusOk = False + elif "success" in self.__result and \ + not self.__result["success"]: + self.__logError( + self.tr("Conda command '{0}' did not return success.") + .format(self.__condaCommand)) + if "message" in self.__result: + self.__logError("\n") + self.__logError( + self.tr("\nConda Message: {0}").format( + self.__result["message"])) + self.__statusOk = False + elif "message" in self.__result: + self.__logOutput( + self.tr("\nConda Message: {0}").format( + self.__result["message"])) + + def getResult(self): + """ + Public method to the result of the command execution. + + @return tuple containing a flag indicating success and the result data. + @rtype tuple of (bool, dict) + """ + return self.__statusOk, self.__result + + def __setProgressValues(self, jsonDict, progressType): + """ + Private method to set the value of the progress bar. + + @param jsonDict dictionary containing the progress info + @type dict + @param progressType action type to check for + @type str + @return flag indicating success + @rtype bool + """ + if progressType in jsonDict and "progress" in jsonDict: + if jsonDict["maxval"] == 1: + self.progressBar.setMaximum(100) + # percent values + self.progressBar.setValue( + int(jsonDict["progress"] * 100)) + parts = jsonDict["fetch"].split("|") + filename = parts[0].strip() + filesize = parts[1].strip() + else: + self.progressBar.setMaximum(jsonDict["maxval"]) + self.progressBar.setValue(jsonDict["progress"]) + filename = jsonDict["fetch"].strip() + filesize = Globals.dataString(int(jsonDict["maxval"])) + + self.progressLabel.setText( + self.tr("{0} (Size: {1})").format(filename, filesize)) + + if progressType == "fetch": + if filename != self.__lastFetchFile: + self.__logOutput( + self.tr("Fetching {0} ...").format(filename)) + self.__lastFetchFile = filename + elif jsonDict["finished"]: + self.__logOutput(self.tr(" Done.\n")) + + if self.__firstProgress: + self.progressLabel.show() + self.progressBar.show() + self.__firstProgress = False + + return True + + return False + + def __readStdout(self): + """ + Private slot to handle the readyReadStandardOutput signal. + + It reads the output of the process, formats it and inserts it into + the contents pane. + """ + all_stdout = str(self.__process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace') + all_stdout = all_stdout.replace("\x00", "") + if self.__json: + for stdout in all_stdout.splitlines(): + try: + jsonDict = json.loads(stdout.replace("\x00", "").strip()) + if self.__setProgressValues(jsonDict, "fetch"): + # nothing to do anymore + pass + elif "progress" not in jsonDict: + if self.__bufferedStdout is None: + self.__bufferedStdout = stdout + else: + self.__bufferedStdout += stdout + except (TypeError, ValueError): + if self.__bufferedStdout is None: + self.__bufferedStdout = stdout + else: + self.__bufferedStdout += stdout + else: + self.__logOutput(all_stdout) + + def __readStderr(self): + """ + Private slot to handle the readyReadStandardError signal. + + It reads the error output of the process and inserts it into the + error pane. + """ + self.__process.setReadChannel(QProcess.StandardError) + + while self.__process.canReadLine(): + stderr = str(self.__process.readLine(), + Preferences.getSystem("IOEncoding"), + 'replace') + self.__logError(stderr) + + def __logOutput(self, stdout): + """ + Private method to log some output. + + @param stdout output string to log + @type str + """ + self.contents.insertPlainText(stdout) + self.contents.ensureCursorVisible() + + def __logError(self, stderr): + """ + Private method to log an error. + + @param stderr error string to log + @type str + """ + self.errorGroup.show() + self.errors.insertPlainText(stderr) + self.errors.ensureCursorVisible()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaExecDialog.ui Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CondaExecDialog</class> + <widget class="QDialog" name="CondaExecDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>750</width> + <height>600</height> + </rect> + </property> + <property name="windowTitle"> + <string>Conda Execution</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QGroupBox" name="messagesGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>3</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Messages</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTextBrowser" name="contents"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>3</verstretch> + </sizepolicy> + </property> + <property name="whatsThis"> + <string><b>virtualenv Execution</b> +<p>This shows the output of the virtualenv command.</p></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="progressLabel"/> + </item> + <item> + <widget class="QProgressBar" name="progressBar"/> + </item> + <item> + <widget class="QGroupBox" name="errorGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Errors</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QTextBrowser" name="errors"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="whatsThis"> + <string><b>virtualenv Execution</b> +<p>This shows the errors of the virtualenv command.</p></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>contents</tabstop> + <tabstop>errors</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CondaExecDialog</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>CondaExecDialog</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>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaExportDialog.py Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to generate a requirements file for conda. +""" + +from __future__ import unicode_literals +try: + str = unicode # __IGNORE_EXCEPTION__ +except NameError: + pass + +import os + +from PyQt5.QtCore import pyqtSlot, Qt +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \ + QApplication + +from E5Gui import E5MessageBox, E5FileDialog +from E5Gui.E5PathPicker import E5PathPickerModes +from E5Gui.E5Application import e5App + +from .Ui_CondaExportDialog import Ui_CondaExportDialog + + +class CondaExportDialog(QDialog, Ui_CondaExportDialog): + """ + Class implementing a dialog to generate a requirements file for conda. + """ + def __init__(self, conda, envName, envPrefix, parent=None): + """ + Constructor + + @param conda reference to the master object + @type Conda + @param envName name of the environment to create the requirements + file from + @type str + @param envPrefix prefix of the environment to create the requirements + file from + @type str + @param parent reference to the parent widget + @type QWidget + """ + super(CondaExportDialog, self).__init__(parent) + self.setupUi(self) + self.setWindowFlags(Qt.Window) + + self.__refreshButton = self.buttonBox.addButton( + self.tr("&Refresh"), QDialogButtonBox.ActionRole) + + self.requirementsFilePicker.setMode(E5PathPickerModes.SaveFileMode) + self.requirementsFilePicker.setFilters( + self.tr("Text Files (*.txt);;All Files (*)")) + + self.__conda = conda + self.__prefix = envPrefix + + self.environmentLabel.setText("<b>{0}</b>".format(envName)) + + self.__requirementsEdited = False + self.__requirementsAvailable = False + + self.__updateButtons() + + def closeEvent(self, e): + """ + Protected slot implementing a close event handler. + + @param e close event + @type QCloseEvent + """ + QApplication.restoreOverrideCursor() + e.accept() + + @pyqtSlot(str) + def on_requirementsFilePicker_textChanged(self, txt): + """ + Private slot handling a change of the requirements file name. + + @param txt name of the requirements file + @type str + """ + self.__updateButtons() + + @pyqtSlot() + def on_requirementsEdit_textChanged(self): + """ + Private slot handling changes of the requirements text. + """ + self.__requirementsEdited = True + + @pyqtSlot(QAbstractButton) + def on_buttonBox_clicked(self, button): + """ + Private slot called by a button of the button box clicked. + + @param button button that was clicked + @type QAbstractButton + """ + if button == self.buttonBox.button(QDialogButtonBox.Close): + self.close() + elif button == self.__refreshButton: + self.__refresh() + + def __refresh(self): + """ + Private slot to refresh the displayed list. + """ + if self.__requirementsEdited: + ok = E5MessageBox.yesNo( + self, + self.tr("Generate Requirements"), + self.tr("""The requirements were changed. Do you want""" + """ to overwrite these changes?""")) + else: + ok = True + if ok: + self.start() + + def start(self): + """ + Public method to start the command. + """ + self.requirementsEdit.clear() + self.__requirementsAvailable = False + + args = [ + "list", + "--export", + "--prefix", + self.__prefix, + ] + + QApplication.setOverrideCursor(Qt.WaitCursor) + success, output = self.__conda.runProcess(args) + + if success: + self.requirementsEdit.setPlainText(output) + self.__requirementsAvailable = True + else: + self.requirementsEdit.setPlainText( + self.tr("No output generated by conda.")) + + QApplication.restoreOverrideCursor() + self.__updateButtons() + + self.__requirementsEdited = False + + def __updateButtons(self): + """ + Private method to set the state of the various buttons. + """ + self.saveButton.setEnabled( + self.__requirementsAvailable and + bool(self.requirementsFilePicker.text()) + ) + self.saveToButton.setEnabled(self.__requirementsAvailable) + self.copyButton.setEnabled(self.__requirementsAvailable) + + aw = e5App().getObject("ViewManager").activeWindow() + if aw and self.__requirementsAvailable: + self.insertButton.setEnabled(True) + self.replaceAllButton.setEnabled(True) + self.replaceSelectionButton.setEnabled( + aw.hasSelectedText()) + else: + self.insertButton.setEnabled(False) + self.replaceAllButton.setEnabled(False) + self.replaceSelectionButton.setEnabled(False) + + def __writeToFile(self, fileName): + """ + Private method to write the requirements text to a file. + + @param fileName name of the file to write to + @type str + """ + if os.path.exists(fileName): + ok = E5MessageBox.warning( + self, + self.tr("Generate Requirements"), + self.tr("""The file <b>{0}</b> already exists. Do you want""" + """ to overwrite it?""").format(fileName)) + if not ok: + return + + try: + f = open(fileName, "w") + f.write(self.requirementsEdit.toPlainText()) + f.close() + except (OSError, IOError) as err: + E5MessageBox.critical( + self, + self.tr("Generate Requirements"), + self.tr("""<p>The requirements could not be written""" + """ to <b>{0}</b>.</p><p>Reason: {1}</p>""") + .format(fileName, str(err))) + + @pyqtSlot() + def on_saveButton_clicked(self): + """ + Private slot to save the requirements text to the requirements file. + """ + fileName = self.requirementsFilePicker.text() + self.__writeToFile(fileName) + + @pyqtSlot() + def on_saveToButton_clicked(self): + """ + Private slot to write the requirements text to a new file. + """ + fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( + self, + self.tr("Generate Requirements"), + os.path.expanduser("~"), + self.tr("Text Files (*.txt);;All Files (*)"), + None, + E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite) + ) + if fileName: + ext = os.path.splitext(fileName)[1] + if not ext: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fileName += ex + self.__writeToFile(fileName) + + @pyqtSlot() + def on_copyButton_clicked(self): + """ + Private slot to copy the requirements text to the clipboard. + """ + txt = self.requirementsEdit.toPlainText() + cb = QApplication.clipboard() + cb.setText(txt) + + @pyqtSlot() + def on_insertButton_clicked(self): + """ + Private slot to insert the requirements text at the cursor position + of the current editor. + """ + aw = e5App().getObject("ViewManager").activeWindow() + if aw: + aw.beginUndoAction() + cline, cindex = aw.getCursorPosition() + aw.insertAt(self.requirementsEdit.toPlainText(), cline, cindex) + aw.endUndoAction() + + @pyqtSlot() + def on_replaceSelectionButton_clicked(self): + """ + Private slot to replace the selected text of the current editor + with the requirements text. + """ + aw = e5App().getObject("ViewManager").activeWindow() + if aw: + aw.beginUndoAction() + aw.replaceSelectedText(self.requirementsEdit.toPlainText()) + aw.endUndoAction() + + @pyqtSlot() + def on_replaceAllButton_clicked(self): + """ + Private slot to replace the text of the current editor with the + requirements text. + """ + aw = e5App().getObject("ViewManager").activeWindow() + if aw: + aw.setText(self.requirementsEdit.toPlainText())
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaExportDialog.ui Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CondaExportDialog</class> + <widget class="QDialog" name="CondaExportDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>550</height> + </rect> + </property> + <property name="windowTitle"> + <string>Generate Requirements</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Conda Environment:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="environmentLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Requirements File:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="E5PathPicker" name="requirementsFilePicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1"> + <widget class="QPushButton" name="saveButton"> + <property name="toolTip"> + <string>Press to save to the requirements file</string> + </property> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="saveToButton"> + <property name="toolTip"> + <string>Save to a new file</string> + </property> + <property name="text"> + <string>Save To</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QPushButton" name="copyButton"> + <property name="toolTip"> + <string>Copy the requirements text to the clipboard</string> + </property> + <property name="text"> + <string>Copy</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QPushButton" name="insertButton"> + <property name="toolTip"> + <string>Insert the requirements text at the cursor position</string> + </property> + <property name="text"> + <string>Insert</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QPushButton" name="replaceSelectionButton"> + <property name="toolTip"> + <string>Replace the current selection with the requirements text</string> + </property> + <property name="text"> + <string>Replace Selection</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QPushButton" name="replaceAllButton"> + <property name="toolTip"> + <string>Replace all text with the requirements text</string> + </property> + <property name="text"> + <string>Replace All</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" rowspan="7"> + <widget class="QPlainTextEdit" name="requirementsEdit"> + <property name="tabChangesFocus"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <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> + <customwidgets> + <customwidget> + <class>E5PathPicker</class> + <extends>QWidget</extends> + <header>E5Gui/E5PathPicker.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>requirementsFilePicker</tabstop> + <tabstop>requirementsEdit</tabstop> + <tabstop>saveButton</tabstop> + <tabstop>saveToButton</tabstop> + <tabstop>copyButton</tabstop> + <tabstop>insertButton</tabstop> + <tabstop>replaceSelectionButton</tabstop> + <tabstop>replaceAllButton</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CondaExportDialog</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>CondaExportDialog</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>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaInfoDialog.py Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the conda information dialog. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QDialog + +from .Ui_CondaInfoDialog import Ui_CondaInfoDialog + +import UI.PixmapCache + + +class CondaInfoDialog(QDialog, Ui_CondaInfoDialog): + """ + Class implementing the conda information dialog. + """ + def __init__(self, infoDict, parent=None): + """ + Constructor + + @param infoDict dictionary containing the information to be shown + @type dict + @param parent reference to the parent widget + @type QWidget + """ + super(CondaInfoDialog, self).__init__(parent) + self.setupUi(self) + self.setWindowFlags(Qt.Window) + + self.iconLabel.setPixmap( + UI.PixmapCache.getPixmap("miniconda48.png")) + + # version information + if "conda_version" in infoDict: + self.condaVersionLabel.setText( + infoDict["conda_version"]) + if "conda_build_version" in infoDict: + self.condaBuildVersionLabel.setText( + infoDict["conda_build_version"]) + if "conda_env_version" in infoDict: + self.condaEnvVersionLabel.setText( + infoDict["conda_env_version"]) + if "python_version" in infoDict: + self.pythonVersionLabel.setText( + infoDict["python_version"]) + + # prefixes + if "active_prefix" in infoDict or "active_prefix_name" in infoDict: + if infoDict["active_prefix_name"] and infoDict["active_prefix"]: + self.activeEnvironmentEdit.setText( + "{0} ({1})".format(infoDict["active_prefix_name"], + infoDict["active_prefix"])) + elif infoDict["active_prefix"]: + self.activeEnvironmentEdit.setText( + infoDict["active_prefix"]) + elif infoDict["active_prefix_name"]: + self.activeEnvironmentEdit.setText( + infoDict["active_prefix_name"]) + else: + self.activeEnvironmentEdit.setText( + self.tr("None")) + else: + self.activeEnvironmentLabel.hide() + self.activeEnvironmentEdit.hide() + if "root_prefix" in infoDict: + if "root_writable" in infoDict and infoDict["root_writable"]: + self.baseEnvironmentEdit.setText( + self.tr("{0} (writable)").format(infoDict["root_prefix"])) + else: + self.baseEnvironmentEdit.setText( + infoDict["root_prefix"]) + if "envs_dirs" in infoDict: + self.envDirsEdit.setPlainText( + "\n".join(infoDict["envs_dirs"])) + + # configurations + if "rc_path" in infoDict: + self.userConfigEdit.setText( + infoDict["rc_path"]) + if "user_rc_path" in infoDict: + # overwrite with more specific info + self.userConfigEdit.setText( + infoDict["user_rc_path"]) + if "sys_rc_path" in infoDict: + self.systemConfigEdit.setText( + infoDict["sys_rc_path"]) + if "config_files" in infoDict: + self.configurationsEdit.setPlainText( + "\n".join(infoDict["config_files"])) + else: + self.configurationsLabel.hide() + self.configurationsEdit.hide() + + # channels + if "channels" in infoDict: + self.channelsEdit.setPlainText( + "\n".join(infoDict["channels"])) + + # various + if "pkgs_dirs" in infoDict: + self.cachesEdit.setPlainText( + "\n".join(infoDict["pkgs_dirs"])) + if "platform" in infoDict: + self.platformLabel.setText( + infoDict["platform"]) + if "user_agent" in infoDict: + self.useragentEdit.setText( + infoDict["user_agent"]) + else: + self.useragentLabel.hide() + self.useragentEdit.hide() + if "UID" in infoDict and "GID" in infoDict: + self.uidGidDataLabel.setText( + "{0}:{1}".format(infoDict["UID"], infoDict["GID"])) + else: + self.uidGidLabel.hide() + self.uidGidDataLabel.hide() + if "netrc_file" in infoDict: + if infoDict["netrc_file"]: + self.netrcEdit.setText( + infoDict["netrc_file"]) + else: + self.netrcEdit.setText( + self.tr("None")) + else: + self.netrcLabel.hide() + self.netrcEdit.hide() + if "offline" in infoDict: + self.offlineCheckBox.setChecked( + infoDict["offline"]) + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height())
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaInfoDialog.ui Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,301 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CondaInfoDialog</class> + <widget class="QDialog" name="CondaInfoDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>650</width> + <height>669</height> + </rect> + </property> + <property name="windowTitle"> + <string>Conda Information</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="iconLabel"> + <property name="text"> + <string>icon</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string><h2>Conda Information</h2></string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>conda Version:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="condaVersionLabel"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>conda-build Version:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="condaBuildVersionLabel"/> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>python Version:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="pythonVersionLabel"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="activeEnvironmentLabel"> + <property name="text"> + <string>Active Environment:</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="activeEnvironmentEdit"/> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>User Configuration:</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLineEdit" name="userConfigEdit"/> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string>System Configuration:</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QLineEdit" name="systemConfigEdit"/> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="configurationsLabel"> + <property name="text"> + <string>Populated Configurations:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QPlainTextEdit" name="configurationsEdit"/> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Base Environment:</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QLineEdit" name="baseEnvironmentEdit"/> + </item> + <item row="9" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Channel URLs:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="9" column="1"> + <widget class="QPlainTextEdit" name="channelsEdit"/> + </item> + <item row="10" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Package Cache:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="10" column="1"> + <widget class="QPlainTextEdit" name="cachesEdit"/> + </item> + <item row="11" column="0"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Environment Directories:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="11" column="1"> + <widget class="QPlainTextEdit" name="envDirsEdit"/> + </item> + <item row="12" column="0"> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string>Platform:</string> + </property> + </widget> + </item> + <item row="12" column="1"> + <widget class="QLabel" name="platformLabel"/> + </item> + <item row="13" column="0"> + <widget class="QLabel" name="useragentLabel"> + <property name="text"> + <string>User-Agent:</string> + </property> + </widget> + </item> + <item row="13" column="1"> + <widget class="QLineEdit" name="useragentEdit"/> + </item> + <item row="14" column="0"> + <widget class="QLabel" name="uidGidLabel"> + <property name="text"> + <string>UID:GID:</string> + </property> + </widget> + </item> + <item row="14" column="1"> + <widget class="QLabel" name="uidGidDataLabel"/> + </item> + <item row="15" column="0"> + <widget class="QLabel" name="netrcLabel"> + <property name="text"> + <string>netrc File:</string> + </property> + </widget> + </item> + <item row="15" column="1"> + <widget class="QLineEdit" name="netrcEdit"/> + </item> + <item row="16" column="0"> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>Offline Mode:</string> + </property> + </widget> + </item> + <item row="16" column="1"> + <widget class="QCheckBox" name="offlineCheckBox"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_18"> + <property name="text"> + <string>conda-env Version:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="condaEnvVersionLabel"/> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CondaInfoDialog</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>CondaInfoDialog</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>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaNewEnvironmentDataDialog.py Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to enter data for a new conda environment. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSlot +from PyQt5.QtWidgets import QDialog, QDialogButtonBox + +from E5Gui.E5PathPicker import E5PathPickerModes + +from .Ui_CondaNewEnvironmentDataDialog import Ui_CondaNewEnvironmentDataDialog + + +class CondaNewEnvironmentDataDialog(QDialog, Ui_CondaNewEnvironmentDataDialog): + """ + Class implementing a dialog to enter data for a new conda environment. + """ + def __init__(self, title, showRequirements, parent=None): + """ + Constructor + + @param title tirle of the dialog + @type str + @param showRequirements flag indicating to show the requirements + file input widget + @type bool + @param parent reference to the parent widget + @type QWidget + """ + super(CondaNewEnvironmentDataDialog, self).__init__(parent) + self.setupUi(self) + + self.setWindowTitle(title) + + self.__requirementsMode = showRequirements + + self.requirementsFilePicker.setMode(E5PathPickerModes.OpenFileMode) + self.requirementsFilePicker.setFilters( + self.tr("Text Files (*.txt);;All Files (*)")) + + self.requirementsLabel.setVisible(showRequirements) + self.requirementsFilePicker.setVisible(showRequirements) + + self.__updateOK() + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + def __updateOK(self): + """ + Private method to update the enabled state of the OK button. + """ + enable = bool(self.nameEdit.text()) and bool(self.condaNameEdit.text()) + if self.__requirementsMode: + enable &= bool(self.requirementsFilePicker.text()) + + 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 text of the logical name entry + @type str + """ + self.__updateOK() + + @pyqtSlot(str) + def on_condaNameEdit_textChanged(self, txt): + """ + Private slot to handle changes of the conda name. + + @param txt current text of the conda name entry + @type str + """ + self.__updateOK() + + @pyqtSlot(str) + def on_requirementsFilePicker_textChanged(self, txt): + """ + Private slot to handle changes of the requirements file name. + + @param txt current text of the requirements file name entry + @type str + """ + self.__updateOK() + + def getData(self): + """ + Public method to get the entered data. + + @return tuple with the logical name of the new environment, the conda + name and the requirements file name + @rtype tuple of (str, str, str) + """ + if self.__requirementsMode: + requirementsFile = self.requirementsFilePicker.text() + else: + requirementsFile = "" + + return ( + self.nameEdit.text(), + self.condaNameEdit.text(), + requirementsFile + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaNewEnvironmentDataDialog.ui Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CondaNewEnvironmentDataDialog</class> + <widget class="QDialog" name="CondaNewEnvironmentDataDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>112</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Logical Name:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="E5ClearableLineEdit" name="nameEdit"> + <property name="toolTip"> + <string>Enter a unique name for the virtual environment to register it with the Virtual Environment Manager</string> + </property> + <property name="placeholderText"> + <string>Name for registration of the virtual environment</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Conda Name:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="E5ClearableLineEdit" name="condaNameEdit"> + <property name="toolTip"> + <string>Enter the name of the virtual environment in Conda</string> + </property> + <property name="placeholderText"> + <string>Name of the virtual environment in Conda</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="requirementsLabel"> + <property name="text"> + <string>Requirements File:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="E5PathPicker" name="requirementsFilePicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </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>E5ClearableLineEdit</class> + <extends>QLineEdit</extends> + <header>E5Gui/E5LineEdit.h</header> + </customwidget> + <customwidget> + <class>E5PathPicker</class> + <extends>QWidget</extends> + <header>E5Gui/E5PathPicker.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CondaNewEnvironmentDataDialog</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>CondaNewEnvironmentDataDialog</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>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaPackageDetailsWidget.py Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a widget and a dialog to show package details. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import Qt, QDateTime +from PyQt5.QtWidgets import QWidget, QDialog, QVBoxLayout, QDialogButtonBox + +from .Ui_CondaPackageDetailsWidget import Ui_CondaPackageDetailsWidget + +from Globals import dataString + + +class CondaPackageDetailsWidget(QWidget, Ui_CondaPackageDetailsWidget): + """ + Class implementing a widget to show package details. + """ + def __init__(self, details, parent=None): + """ + Constructor + + @param details dictionary containing the package details + @type dict + @param parent reference to the parent widget + @type QWidget + """ + super(CondaPackageDetailsWidget, self).__init__(parent) + self.setupUi(self) + + self.headerLabel.setText(self.tr("<b>{0} / {1} / {2}</b>").format( + details["name"], details["version"], details["build"])) + if "fn" in details: + self.filenameLabel.setText(details["fn"]) + if "size" in details: + self.sizeLabel.setText(dataString(details["size"])) + if "channel" in details: + self.channelLabel.setText(details["channel"]) + if "url" in details: + self.urlLabel.setText(details["url"]) + if "md5" in details: + self.md5Label.setText(details["md5"]) + if "license" in details: + self.licenseLabel.setText(details["license"]) + if "subdir" in details: + self.platformLabel.setText(details["subdir"]) + elif "platform" in details: + self.platformLabel.setText(details["platform"]) + else: + self.platformLabel.setText(self.tr("unknown")) + if "depends" in details: + self.dependenciesEdit.setPlainText("\n".join(details["depends"])) + + if "timestamp" in details: + dt = QDateTime.fromMSecsSinceEpoch(details["timestamp"], Qt.UTC) + self.timestampLabel.setText(dt.toString("yyyy-MM-dd hh:mm:ss t")) + + self.resize(600, 450) + + +class CondaPackageDetailsDialog(QDialog): + """ + Class implementing a dialog to show package details. + """ + def __init__(self, details, parent=None): + """ + Constructor + + @param details dictionary containing the package details + @type dict + @param parent reference to the parent widget + @type QWidget + """ + super(CondaPackageDetailsDialog, self).__init__(parent) + self.setSizeGripEnabled(True) + + self.__layout = QVBoxLayout(self) + self.setLayout(self.__layout) + + self.__cw = CondaPackageDetailsWidget(details, self) + size = self.__cw.size() + self.__layout.addWidget(self.__cw) + self.__buttonBox = QDialogButtonBox(self) + self.__buttonBox.setStandardButtons(QDialogButtonBox.Close) + self.__layout.addWidget(self.__buttonBox) + + self.resize(size) + self.setWindowTitle(self.tr("Package Details")) + + self.__buttonBox.accepted.connect(self.accept) + self.__buttonBox.rejected.connect(self.reject)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaPackageDetailsWidget.ui Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,129 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CondaPackageDetailsWidget</class> + <widget class="QWidget" name="CondaPackageDetailsWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>450</height> + </rect> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="headerLabel"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Filename:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="filenameLabel"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Size:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="sizeLabel"/> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Channel:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="channelLabel"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>URL:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="urlLabel"> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>MD5:</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="md5Label"/> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Timestamp:</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QLabel" name="timestampLabel"/> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>License:</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QLabel" name="licenseLabel"/> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Platform:</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QLabel" name="platformLabel"/> + </item> + <item row="9" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Dependencies:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="9" column="1"> + <widget class="QPlainTextEdit" name="dependenciesEdit"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaPackagesWidget.py Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,714 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the conda packages management widget. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import pyqtSlot, Qt +from PyQt5.QtGui import QCursor +from PyQt5.QtWidgets import QWidget, QToolButton, QMenu, QTreeWidgetItem, \ + QApplication, QLineEdit, QDialog + +from E5Gui import E5FileDialog, E5MessageBox, E5TextInputDialog +from E5Gui.E5Application import e5App + +from .Ui_CondaPackagesWidget import Ui_CondaPackagesWidget + +import UI.PixmapCache + +import CondaInterface + + +class CondaPackagesWidget(QWidget, Ui_CondaPackagesWidget): + """ + Class implementing the conda packages management widget. + """ + # Role definition of packages list + PackageVersionRole = Qt.UserRole + 1 + PackageBuildRole = Qt.UserRole + 2 + + # Role definitions of search results list + PackageDetailedDataRole = Qt.UserRole + 1 + + def __init__(self, conda, parent=None): + """ + Constructor + + @param conda reference to the conda interface + @type Conda + @param parent reference to the parent widget + @type QWidget + """ + super(CondaPackagesWidget, self).__init__(parent) + self.setupUi(self) + + self.__conda = conda + + if not CondaInterface.isCondaAvailable(): + self.baseWidget.hide() + self.searchWidget.hide() + + else: + self.notAvailableWidget.hide() + + self.__initCondaInterface() + + def __initCondaInterface(self): + """ + Private method to initialize the conda interface elements. + """ + self.statusLabel.hide() + + self.condaMenuButton.setObjectName( + "navigation_supermenu_button") + self.condaMenuButton.setIcon(UI.PixmapCache.getIcon("superMenu.png")) + self.condaMenuButton.setToolTip(self.tr("Conda Menu")) + self.condaMenuButton.setPopupMode(QToolButton.InstantPopup) + self.condaMenuButton.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.condaMenuButton.setFocusPolicy(Qt.NoFocus) + self.condaMenuButton.setAutoRaise(True) + self.condaMenuButton.setShowMenuInside(True) + + self.searchToggleButton.setIcon(UI.PixmapCache.getIcon("find.png")) + + if CondaInterface.condaVersion() >= (4, 4, 0): + self.searchOptionsWidget.hide() + else: + self.platformComboBox.addItems(sorted([ + "", "win-32", "win-64", "osx-64", "linux-32", "linux-64", + ])) + + self.__initCondaMenu() + self.__populateEnvironments() + self.__updateActionButtons() + + self.searchWidget.hide() + + self.__conda.condaEnvironmentCreated.connect( + self.on_refreshButton_clicked) + self.__conda.condaEnvironmentRemoved.connect( + self.on_refreshButton_clicked) + + def __populateEnvironments(self): + """ + Private method to get a list of environments and populate the selector. + """ + environments = [("", "")] + sorted( + self.__conda.getCondaEnvironmentsList()) + for environment in environments: + self.environmentsComboBox.addItem(environment[0], environment[1]) + + def __initCondaMenu(self): + """ + Private method to create the super menu and attach it to the super + menu button. + """ + self.__condaMenu = QMenu(self) + self.__envActs = [] + + self.__cleanMenu = QMenu(self.tr("Clean"), self) + self.__cleanMenu.addAction( + self.tr("All"), lambda: self.__conda.cleanConda("all")) + self.__cleanMenu.addAction( + self.tr("Cache"), lambda: self.__conda.cleanConda("index-cache")) + self.__cleanMenu.addAction( + self.tr("Lock Files"), + lambda: self.__conda.cleanConda("lock")) + self.__cleanMenu.addAction( + self.tr("Packages"), lambda: self.__conda.cleanConda("packages")) + self.__cleanMenu.addAction( + self.tr("Tarballs"), lambda: self.__conda.cleanConda("tarballs")) + + self.__condaMenu.addAction( + self.tr("About Conda..."), self.__aboutConda) + self.__condaMenu.addSeparator() + self.__condaMenu.addAction( + self.tr("Update Conda"), self.__conda.updateConda) + self.__condaMenu.addSeparator() + self.__envActs.append(self.__condaMenu.addAction( + self.tr("Install Packages"), self.__installPackages)) + self.__envActs.append(self.__condaMenu.addAction( + self.tr("Install Requirements"), self.__installRequirements)) + self.__condaMenu.addSeparator() + self.__envActs.append(self.__condaMenu.addAction( + self.tr("Generate Requirements"), self.__generateRequirements)) + self.__condaMenu.addSeparator() + self.__condaMenu.addAction( + self.tr("Create Environment from Requirements"), + self.__createEnvironment) + self.__envActs.append(self.__condaMenu.addAction( + self.tr("Clone Environment"), self.__cloneEnvironment)) + self.__deleteEnvAct = self.__condaMenu.addAction( + self.tr("Delete Environment"), self.__deleteEnvironment) + self.__condaMenu.addSeparator() + self.__condaMenu.addMenu(self.__cleanMenu) + self.__condaMenu.addSeparator() + self.__condaMenu.addAction( + self.tr("Edit User Configuration..."), + self.__editUserConfiguration) + self.__condaMenu.addSeparator() + self.__condaMenu.addAction( + self.tr("Configure..."), self.__condaConfigure) + + self.condaMenuButton.setMenu(self.__condaMenu) + + self.__condaMenu.aboutToShow.connect(self.__aboutToShowCondaMenu) + + def __selectedUpdateableItems(self): + """ + Private method to get a list of selected items that can be updated. + + @return list of selected items that can be updated + @rtype list of QTreeWidgetItem + """ + return [ + itm for itm in self.packagesList.selectedItems() + if bool(itm.text(2)) + ] + + def __allUpdateableItems(self): + """ + Private method to get a list of all items that can be updated. + + @return list of all items that can be updated + @rtype list of QTreeWidgetItem + """ + updateableItems = [] + for index in range(self.packagesList.topLevelItemCount()): + itm = self.packagesList.topLevelItem(index) + if itm.text(2): + updateableItems.append(itm) + + return updateableItems + + def __updateActionButtons(self): + """ + Private method to set the state of the action buttons. + """ + self.upgradeButton.setEnabled( + bool(self.__selectedUpdateableItems())) + self.uninstallButton.setEnabled( + bool(self.packagesList.selectedItems())) + self.upgradeAllButton.setEnabled( + bool(self.__allUpdateableItems())) + + @pyqtSlot(int) + def on_environmentsComboBox_currentIndexChanged(self, index): + """ + Private slot handling the selection of a conda environment. + + @param index index of the selected conda environment + @type int + """ + self.packagesList.clear() + prefix = self.environmentsComboBox.itemData(index) + if prefix: + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + self.statusLabel.show() + self.statusLabel.setText(self.tr("Getting installed packages...")) + QApplication.processEvents() + + # 1. populate with installed packages + self.packagesList.setUpdatesEnabled(False) + installedPackages = \ + self.__conda.getInstalledPackages(prefix=prefix) + for package, version, build in installedPackages: + itm = QTreeWidgetItem(self.packagesList, [package, version]) + itm.setData(1, self.PackageVersionRole, version) + itm.setData(1, self.PackageBuildRole, build) + self.packagesList.setUpdatesEnabled(True) + self.statusLabel.setText(self.tr("Getting outdated packages...")) + QApplication.processEvents() + + # 2. update with update information + self.packagesList.setUpdatesEnabled(False) + updateablePackages = \ + self.__conda.getUpdateablePackages(prefix=prefix) + for package, version, build in updateablePackages: + items = self.packagesList.findItems( + package, Qt.MatchExactly | Qt.MatchCaseSensitive) + if items: + itm = items[0] + itm.setText(2, version) + itm.setData(2, self.PackageVersionRole, version) + itm.setData(2, self.PackageBuildRole, build) + if itm.data(1, self.PackageVersionRole) == version: + # build must be different, show in version display + itm.setText(1, self.tr("{0} (Build: {1})").format( + itm.data(1, self.PackageVersionRole), + itm.data(1, self.PackageBuildRole), + )) + itm.setText(2, self.tr("{0} (Build: {1})").format( + itm.data(2, self.PackageVersionRole), + itm.data(2, self.PackageBuildRole), + )) + + self.packagesList.sortItems(0, Qt.AscendingOrder) + for col in range(self.packagesList.columnCount()): + self.packagesList.resizeColumnToContents(col) + self.packagesList.setUpdatesEnabled(True) + QApplication.restoreOverrideCursor() + self.statusLabel.hide() + + self.__updateActionButtons() + self.__updateSearchActionButtons() + + @pyqtSlot() + def on_packagesList_itemSelectionChanged(self): + """ + Private slot to handle the selection of some items.. + """ + self.__updateActionButtons() + + @pyqtSlot() + def on_refreshButton_clicked(self): + """ + Private slot to refresh the display. + """ + currentEnvironment = self.environmentsComboBox.currentText() + self.environmentsComboBox.clear() + self.packagesList.clear() + + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QApplication.processEvents() + + self.__populateEnvironments() + + index = self.environmentsComboBox.findText( + currentEnvironment, Qt.MatchExactly | Qt.MatchCaseSensitive) + if index != -1: + self.environmentsComboBox.setCurrentIndex(index) + + QApplication.restoreOverrideCursor() + self.__updateActionButtons() + + @pyqtSlot() + def on_upgradeButton_clicked(self): + """ + Private slot to upgrade selected packages of the selected environment. + """ + packages = [itm.text(0) for itm in self.__selectedUpdateableItems()] + if packages: + prefix = self.environmentsComboBox.itemData( + self.environmentsComboBox.currentIndex()) + ok = self.__conda.updatePackages(packages, prefix=prefix) + if ok: + self.on_refreshButton_clicked() + + @pyqtSlot() + def on_upgradeAllButton_clicked(self): + """ + Private slot to upgrade all packages of the selected environment. + """ + prefix = self.environmentsComboBox.itemData( + self.environmentsComboBox.currentIndex()) + ok = self.__conda.updateAllPackages(prefix=prefix) + if ok: + self.on_refreshButton_clicked() + + @pyqtSlot() + def on_uninstallButton_clicked(self): + """ + Private slot to remove selected packages of the selected environment. + """ + packages = [itm.text(0) for itm in self.packagesList.selectedItems()] + if packages: + prefix = self.environmentsComboBox.itemData( + self.environmentsComboBox.currentIndex()) + ok = self.__conda.uninstallPackages(packages, prefix=prefix) + if ok: + self.on_refreshButton_clicked() + + ####################################################################### + ## Search widget related methods below + ####################################################################### + + def __updateSearchActionButtons(self): + """ + Private method to update the action button states of the search widget. + """ + enable = len(self.searchResultList.selectedItems()) == 1 + self.installButton.setEnabled( + enable and self.environmentsComboBox.currentIndex() > 0) + self.showDetailsButton.setEnabled( + enable and bool(self.searchResultList.selectedItems()[0].parent())) + + def __doSearch(self): + """ + Private method to search for packages. + """ + self.searchResultList.clear() + pattern = self.searchEdit.text() + if pattern: + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QApplication.processEvents() + + if CondaInterface.condaVersion() >= (4, 4, 0): + prefix = "" + else: + prefix = self.environmentsComboBox.itemData( + self.environmentsComboBox.currentIndex()) + ok, result = self.__conda.searchPackages( + pattern, + fullNameOnly=self.fullNameButton.isChecked(), + packageSpec=self.packageSpecButton.isChecked(), + platform=self.platformComboBox.currentText(), + prefix=prefix, + ) + + if result: + if ok: + self.searchResultList.setUpdatesEnabled(False) + for package in result: + itm = QTreeWidgetItem(self.searchResultList, [package]) + itm.setExpanded(False) + for detail in result[package]: + version = detail["version"] + build = detail["build"] + if "subdir" in detail: + platform = detail["subdir"] + elif "platform" in detail: + platform = detail["platform"] + else: + platform = "" + citm = QTreeWidgetItem( + itm, ["", version, build, platform]) + citm.setData(0, self.PackageDetailedDataRole, + detail) + + self.searchResultList.sortItems(0, Qt.AscendingOrder) + self.searchResultList.resizeColumnToContents(0) + self.searchResultList.setUpdatesEnabled(True) + else: + QApplication.restoreOverrideCursor() + try: + message = result["message"] + except KeyError: + message = result["error"] + E5MessageBox.warning( + self, + self.tr("Conda Search Package Error"), + message) + QApplication.restoreOverrideCursor() + + def __showDetails(self, item): + """ + Private method to show a dialog with details about a package item. + + @param item reference to the package item + @type QTreeWidgetItem + """ + details = item.data(0, self.PackageDetailedDataRole) + if details: + from .CondaPackageDetailsWidget import CondaPackageDetailsDialog + dlg = CondaPackageDetailsDialog(details, self) + dlg.exec_() + + @pyqtSlot(str) + def on_searchEdit_textChanged(self, txt): + """ + Private slot handling changes of the entered search specification. + + @param txt current search entry + @type str + """ + self.searchButton.setEnabled(bool(txt)) + + @pyqtSlot() + def on_searchEdit_returnPressed(self): + """ + Private slot handling the user pressing the Return button in the + search edit. + """ + self.__doSearch() + + @pyqtSlot() + def on_searchButton_clicked(self): + """ + Private slot handling the press of the search button. + """ + self.__doSearch() + + @pyqtSlot() + def on_installButton_clicked(self): + """ + Private slot to install a selected package. + """ + if len(self.searchResultList.selectedItems()) == 1: + item = self.searchResultList.selectedItems()[0] + if item.parent() is None: + # it is just the package item + package = item.text(0) + else: + # item with version and build + package = "{0}={1}={2}".format( + item.parent().text(0), + item.text(1), + item.text(2), + ) + + prefix = self.environmentsComboBox.itemData( + self.environmentsComboBox.currentIndex()) + ok = self.__conda.installPackages([package], prefix=prefix) + if ok: + self.on_refreshButton_clicked() + + @pyqtSlot() + def on_showDetailsButton_clicked(self): + """ + Private slot handling the 'Show Details' button. + """ + item = self.searchResultList.selectedItems()[0] + self.__showDetails(item) + + @pyqtSlot() + def on_searchResultList_itemSelectionChanged(self): + """ + Private slot handling a change of selected search results. + """ + self.__updateSearchActionButtons() + + @pyqtSlot(QTreeWidgetItem) + def on_searchResultList_itemExpanded(self, item): + """ + Private slot handling the expansion of an item. + + @param item reference to the expanded item + @type QTreeWidgetItem + """ + for col in range(1, self.searchResultList.columnCount()): + self.searchResultList.resizeColumnToContents(col) + + @pyqtSlot(QTreeWidgetItem, int) + def on_searchResultList_itemDoubleClicked(self, item, column): + """ + Private slot handling a double click of an item. + + @param item reference to the item that was double clicked + @type QTreeWidgetItem + @param column column of the double click + @type int + """ + if item.parent() is not None: + self.__showDetails(item) + + @pyqtSlot(bool) + def on_searchToggleButton_toggled(self, checked): + """ + Private slot to togle the search widget. + + @param checked state of the search widget button + @type bool + """ + self.searchWidget.setVisible(checked) + + if checked: + self.searchEdit.setFocus(Qt.OtherFocusReason) + self.searchEdit.selectAll() + + self.__updateSearchActionButtons() + + ####################################################################### + ## Menu related methods below + ####################################################################### + + @pyqtSlot() + def __aboutToShowCondaMenu(self): + """ + Private slot to handle the conda menu about to be shown. + """ + selectedEnvironment = self.environmentsComboBox.currentText() + enable = selectedEnvironment not in [""] + for act in self.__envActs: + act.setEnabled(enable) + + self.__deleteEnvAct.setEnabled( + selectedEnvironment not in ["", self.__conda.RootName]) + + @pyqtSlot() + def __aboutConda(self): + """ + Private slot to show some information about the conda installation. + """ + infoDict = self.__conda.getCondaInformation() + + from .CondaInfoDialog import CondaInfoDialog + dlg = CondaInfoDialog(infoDict, self) + dlg.exec_() + + @pyqtSlot() + def __installPackages(self): + """ + Private slot to install packages. + """ + prefix = self.environmentsComboBox.itemData( + self.environmentsComboBox.currentIndex()) + if prefix: + ok, packageSpecs = E5TextInputDialog.getText( + self, + self.tr("Install Packages"), + self.tr("Package Specifications (separated by whitespace):"), + QLineEdit.Normal, + minimumWidth=600) + if ok and packageSpecs.strip(): + packages = [p.strip() for p in packageSpecs.split()] + ok = self.__conda.installPackages(packages, prefix=prefix) + if ok: + self.on_refreshButton_clicked() + + @pyqtSlot() + def __installRequirements(self): + """ + Private slot to install packages from requirements files. + """ + prefix = self.environmentsComboBox.itemData( + self.environmentsComboBox.currentIndex()) + if prefix: + requirements = E5FileDialog.getOpenFileNames( + self, + self.tr("Install Packages"), + "", + self.tr("Text Files (*.txt);;All Files (*)")) + if requirements: + args = [] + for requirement in requirements: + args.extend(["--file", requirement]) + ok = self.__conda.installPackages(args, prefix=prefix) + if ok: + self.on_refreshButton_clicked() + + @pyqtSlot() + def __generateRequirements(self): + """ + Private slot to generate a requirements file. + """ + prefix = self.environmentsComboBox.itemData( + self.environmentsComboBox.currentIndex()) + if prefix: + env = self.environmentsComboBox.currentText() + + from .CondaExportDialog import CondaExportDialog + + self.__requirementsDialog = CondaExportDialog( + self.__conda, env, prefix) + self.__requirementsDialog.show() + QApplication.processEvents() + self.__requirementsDialog.start() + + @pyqtSlot() + def __cloneEnvironment(self): + """ + Private slot to clone a conda environment. + """ + from .CondaNewEnvironmentDataDialog import \ + CondaNewEnvironmentDataDialog + + prefix = self.environmentsComboBox.itemData( + self.environmentsComboBox.currentIndex()) + if prefix: + dlg = CondaNewEnvironmentDataDialog(self.tr("Clone Environment"), + False, self) + if dlg.exec_() == QDialog.Accepted: + virtEnvName, envName, _ = dlg.getData() + args = [ + "--name", + envName.strip(), + "--clone", + prefix, + ] + ok, prefix, interpreter = \ + self.__conda.createCondaEnvironment(args) + if ok: + e5App().getObject("VirtualEnvManager").addVirtualEnv( + virtEnvName, prefix, interpreter, isConda=True) + + @pyqtSlot() + def __createEnvironment(self): + """ + Private slot to create a conda environment from a requirements file. + """ + from .CondaNewEnvironmentDataDialog import \ + CondaNewEnvironmentDataDialog + + dlg = CondaNewEnvironmentDataDialog(self.tr("Create Environment"), + True, self) + if dlg.exec_() == QDialog.Accepted: + virtEnvName, envName, requirements = dlg.getData() + args = [ + "--name", + envName.strip(), + "--file", + requirements, + ] + ok, prefix, interpreter = \ + self.__conda.createCondaEnvironment(args) + if ok: + e5App().getObject("VirtualEnvManager").addVirtualEnv( + virtEnvName, prefix, interpreter, isConda=True) + + @pyqtSlot() + def __deleteEnvironment(self): + """ + Private slot to delete a conda environment. + """ + envName = self.environmentsComboBox.currentText() + ok = E5MessageBox.yesNo( + self, + self.tr("Delete Environment"), + self.tr("""<p>Shal the environment <b>{0}</b> really be""" + """ deleted?</p>""").format(envName) + ) + if ok: + self.__conda.removeCondaEnvironment(name=envName) + + @pyqtSlot() + def __editUserConfiguration(self): + """ + Private slot to edit the user configuration. + """ + from QScintilla.MiniEditor import MiniEditor + + cfgFile = CondaInterface.userConfiguration() + if not cfgFile: + return + + if not os.path.exists(cfgFile): + self.__conda.writeDefaultConfiguration() + + # check, if the destination is writeable + if not os.access(cfgFile, os.W_OK): + E5MessageBox.critical( + None, + self.tr("Edit Configuration"), + self.tr("""The configuration file "{0}" does not exist""" + """ or is not writable.""")) + return + + self.__editor = MiniEditor(cfgFile, "YAML") + self.__editor.show() + + @pyqtSlot() + def __condaConfigure(self): + """ + Private slot to open the configuration page. + """ + e5App().getObject("UserInterface").showPreferences("condaPage") + + @pyqtSlot() + def on_recheckButton_clicked(self): + """ + Private slot to re-check the availability of conda and adjust the + interface if it became available. + """ + if CondaInterface.isCondaAvailable(): + self.__initCondaInterface() + + self.notAvailableWidget.hide() + self.baseWidget.show()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaPackagesWidget.ui Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,484 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CondaPackagesWidget</class> + <widget class="QWidget" name="CondaPackagesWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>600</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <widget class="QWidget" name="notAvailableWidget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="notAvailableLabel"> + <property name="text"> + <string><h2>conda is not available</h2></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="recheckButton"> + <property name="toolTip"> + <string>Press to re-check the availability of conda</string> + </property> + <property name="text"> + <string>Re-Check</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>2</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="baseWidget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QComboBox" name="environmentsComboBox"/> + </item> + <item> + <widget class="E5ToolButton" name="condaMenuButton"/> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="statusLabel"/> + </item> + <item> + <widget class="QTreeWidget" name="packagesList"> + <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> + <attribute name="headerDefaultSectionSize"> + <number>150</number> + </attribute> + <column> + <property name="text"> + <string>Package</string> + </property> + </column> + <column> + <property name="text"> + <string>Installed Version</string> + </property> + </column> + <column> + <property name="text"> + <string>Available Version</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="refreshButton"> + <property name="toolTip"> + <string>Press to refresh the lists</string> + </property> + <property name="text"> + <string>&Refresh</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="upgradeButton"> + <property name="toolTip"> + <string>Press to upgrade the selected packages</string> + </property> + <property name="text"> + <string>Up&grade</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="upgradeAllButton"> + <property name="toolTip"> + <string>Press to upgrade all listed packages</string> + </property> + <property name="text"> + <string>Upgrade &All</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="uninstallButton"> + <property name="toolTip"> + <string>Press to uninstall the selected package</string> + </property> + <property name="text"> + <string>&Uninstall</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="searchToggleButton"> + <property name="toolTip"> + <string>Toggle to show or hide the search window</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="searchWidget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLineEdit" name="searchEdit"> + <property name="placeholderText"> + <string>Enter search specification</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="searchButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>Press to start the search</string> + </property> + <property name="text"> + <string>&Search</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QWidget" name="searchOptionsWidget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QRadioButton" name="patternButton"> + <property name="toolTip"> + <string>Search string is a pattern (default)</string> + </property> + <property name="text"> + <string>Search Pattern</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="fullNameButton"> + <property name="toolTip"> + <string>Search string is a full name</string> + </property> + <property name="text"> + <string>Full Name</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="packageSpecButton"> + <property name="toolTip"> + <string>Search string is a package specification</string> + </property> + <property name="text"> + <string>Package Specification</string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Platform:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="platformComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the platform</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QTreeWidget" name="searchResultList"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <column> + <property name="text"> + <string>Package</string> + </property> + </column> + <column> + <property name="text"> + <string>Version</string> + </property> + </column> + <column> + <property name="text"> + <string>Build</string> + </property> + </column> + <column> + <property name="text"> + <string>Platform</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="installButton"> + <property name="toolTip"> + <string>Press to install the selected package (by name or package specification)</string> + </property> + <property name="text"> + <string>&Install</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="showDetailsButton"> + <property name="toolTip"> + <string>Press to show details for the selected entry</string> + </property> + <property name="text"> + <string>Show Details...</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>E5ToolButton</class> + <extends>QToolButton</extends> + <header>E5Gui/E5ToolButton.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>environmentsComboBox</tabstop> + <tabstop>condaMenuButton</tabstop> + <tabstop>packagesList</tabstop> + <tabstop>refreshButton</tabstop> + <tabstop>upgradeButton</tabstop> + <tabstop>upgradeAllButton</tabstop> + <tabstop>uninstallButton</tabstop> + <tabstop>searchToggleButton</tabstop> + <tabstop>searchEdit</tabstop> + <tabstop>searchButton</tabstop> + <tabstop>patternButton</tabstop> + <tabstop>fullNameButton</tabstop> + <tabstop>packageSpecButton</tabstop> + <tabstop>platformComboBox</tabstop> + <tabstop>searchResultList</tabstop> + <tabstop>installButton</tabstop> + <tabstop>showDetailsButton</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/__init__.py Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing the various conda related modules. +""" + +from __future__ import unicode_literals +try: + str = unicode +except NameError: + pass + +import json + +from PyQt5.QtCore import QCoreApplication, QProcess + +import Preferences + +__CondaVersion = tuple() +__CondaVersionStr = "" +__CondaRootPrefix = "" +__CondaUserConfig = "" + +__initialized = False + + +def __initializeCondaInterface(): + """ + Private module function to (re-)initialize the conda interface. + """ + global __CondaVersionStr, __CondaVersion, __CondaRootPrefix, \ + __CondaUserConfig, __initialized + + if not __initialized: + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + proc = QProcess() + proc.start(exe, ["info", "--json"]) + if not proc.waitForStarted(15000): + __CondaVersionStr = QCoreApplication.translate( + "CondaInterface", + '<conda not found or not configured.>') + else: + proc.waitForFinished(15000) + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + jsonDict = json.loads(output) + except Exception: + __CondaVersionStr = QCoreApplication.translate( + "CondaInterface", + '<conda returned invalid data.') + return + + __CondaVersionStr = jsonDict["conda_version"] + __CondaVersion = tuple( + int(i) for i in __CondaVersionStr.split(".") + ) + __CondaRootPrefix = jsonDict["root_prefix"] + if "user_rc_path" in jsonDict: + __CondaUserConfig = jsonDict["user_rc_path"] + elif "rc_path" in jsonDict: + __CondaUserConfig = jsonDict["rc_path"] + + __initialized = True + + +def condaVersion(): + """ + Module function to get the conda version. + + @return tuple containing the conda version + @rtype tuple of (int, int, int) + """ + __initializeCondaInterface() + return __CondaVersion + + +def condaVersionStr(): + """ + Module function to get the conda version as a string. + + @return conda version as a string + @rtype str + """ + __initializeCondaInterface() + return __CondaVersionStr + + +def rootPrefix(): + """ + Module function to get the root prefix. + + @return root prefix + @rtype str + """ + __initializeCondaInterface() + return __CondaRootPrefix + + +def userConfiguration(): + """ + Module function to get the path of the user configuration file. + + @return path of the user configuration file + @rtype str + """ + __initializeCondaInterface() + return __CondaUserConfig + + +def isCondaAvailable(): + """ + Module function to check the availability of conda. + + @return flag indicating conda availability + @rtype bool + """ + __initializeCondaInterface() + return bool(__CondaVersion) + +def resetInterface(): + """ + Module function to reset the conda interface. + """ + global __initialized + + __initialized = False
--- a/Globals/__init__.py Wed Feb 13 18:59:31 2019 +0100 +++ b/Globals/__init__.py Wed Feb 13 20:39:58 2019 +0100 @@ -412,6 +412,32 @@ return QByteArray(txt) +def dataString(size): + """ + Module function to generate a formatted size string. + + @param size size to be formatted + @type int + @return formatted data string + @rtype str + """ + if size < 1024: + return QCoreApplication.translate( + "Globals", "{0:.1f} Bytes").format(size) + elif size < 1024 * 1024: + size /= 1024 + return QCoreApplication.translate( + "Globals", "{0:.1f} KiB").format(size) + elif size < 1024 * 1024 * 1024: + size /= 1024 * 1024 + return QCoreApplication.translate( + "Globals", "{0:.2f} MiB").format(size) + else: + size /= 1024 * 1024 * 1024 + return QCoreApplication.translate( + "Globals", "{0:.2f} GiB").format(size) + + ############################################################################### ## functions for converting QSetting return types to valid types ###############################################################################
--- a/Preferences/ConfigurationDialog.py Wed Feb 13 18:59:31 2019 +0100 +++ b/Preferences/ConfigurationDialog.py Wed Feb 13 20:39:58 2019 +0100 @@ -142,6 +142,9 @@ "applicationPage": [self.tr("Application"), "preferences-application.png", "ApplicationPage", None, None], + "condaPage": + [self.tr("Conda"), "miniconda.png", + "CondaPage", None, None], "cooperationPage": [self.tr("Cooperation"), "preferences-cooperation.png", "CooperationPage", None, None],
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Preferences/ConfigurationPages/CondaPage.py Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the conda configuration page. +""" + +from __future__ import unicode_literals + +from E5Gui.E5PathPicker import E5PathPickerModes + +from .ConfigurationPageBase import ConfigurationPageBase +from .Ui_CondaPage import Ui_CondaPage + +import Preferences + + +class CondaPage(ConfigurationPageBase, Ui_CondaPage): + """ + Class implementing the conda configuration page. + """ + def __init__(self): + """ + Constructor + """ + super(CondaPage, self).__init__() + self.setupUi(self) + self.setObjectName("CondaPage") + + self.condaExePicker.setMode(E5PathPickerModes.OpenFileMode) + self.condaExePicker.setToolTip(self.tr( + "Press to select the conda executable via a file selection" + " dialog.")) + + # set initial values + self.__condaExecutable = Preferences.getConda("CondaExecutable") + self.condaExePicker.setText(self.__condaExecutable) + + def save(self): + """ + Public slot to save the conda configuration. + """ + condaExecutable = self.condaExePicker.text() + if condaExecutable != self.__condaExecutable: + Preferences.setConda("CondaExecutable", condaExecutable) + + import CondaInterface + CondaInterface.resetInterface() + + +def create(dlg): + """ + Module function to create the configuration page. + + @param dlg reference to the configuration dialog + @return reference to the instantiated page (ConfigurationPageBase) + """ + page = CondaPage() + return page
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Preferences/ConfigurationPages/CondaPage.ui Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CondaPage</class> + <widget class="QWidget" name="CondaPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>585</width> + <height>431</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="headerLabel"> + <property name="text"> + <string><b>Configure "conda" support</b></string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line13"> + <property name="frameShape"> + <enum>QFrame::HLine</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>conda Executable</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="E5PathPicker" name="condaExePicker" native="true"> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the path to the conda executable.</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="textLabel1_4"> + <property name="text"> + <string><b>Note:</b> Leave this entry empty to use the default value (conda or conda.exe).</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>292</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>E5PathPicker</class> + <extends>QWidget</extends> + <header>E5Gui/E5PathPicker.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui>
--- a/Preferences/ProgramsDialog.py Wed Feb 13 18:59:31 2019 +0100 +++ b/Preferences/ProgramsDialog.py Wed Feb 13 20:39:58 2019 +0100 @@ -215,8 +215,17 @@ Utilities.isWindowsPlatform() and "rbrcc.exe" or "rbrcc", '-version', 'Ruby Resource Compiler', -1) - # 5. do the CORBA and Protobuf programs - # 5a. omniORB + # 5. do the Conda program(s) + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + if Utilities.isWindowsPlatform(): + exe += ".exe" + self.__createProgramEntry( + self.tr("conda Manager"), exe, '--version', 'conda', -1) + + # 6. do the CORBA and Protobuf programs + # 6a. omniORB exe = Preferences.getCorba("omniidl") if not exe: exe = "omniidl" @@ -224,7 +233,7 @@ exe += ".exe" self.__createProgramEntry( self.tr("CORBA IDL Compiler"), exe, '-V', 'omniidl', -1) - # 5b. protobuf + # 6b. protobuf exe = Preferences.getProtobuf("protoc") if not exe: exe = "protoc" @@ -232,7 +241,7 @@ exe += ".exe" self.__createProgramEntry( self.tr("Protobuf Compiler"), exe, '--version', 'libprotoc', -1) - # 5c. grpc + # 6c. grpc exe = Preferences.getProtobuf("grpcPython") if not exe: exe = sys.executable @@ -240,7 +249,7 @@ self.tr("gRPC Compiler"), exe, '--version', 'libprotoc', -1, exeModule=['-m', 'grpc_tools.protoc']) - # 6. do the spell checking entry + # 7. do the spell checking entry try: import enchant try: @@ -257,7 +266,7 @@ self.__createEntry( self.tr("Spell Checker - PyEnchant"), text, version) - # 7. do the pygments entry + # 8. do the pygments entry try: import pygments try: @@ -274,7 +283,7 @@ self.__createEntry( self.tr("Source Highlighter - Pygments"), text, version) - # do the plugin related programs + # 9. do the plugin related programs pm = e5App().getObject("PluginManager") for info in pm.getPluginExeDisplayData(): if info["programEntry"]:
--- a/Preferences/__init__.py Wed Feb 13 18:59:31 2019 +0100 +++ b/Preferences/__init__.py Wed Feb 13 20:39:58 2019 +0100 @@ -1580,6 +1580,11 @@ "Provider": "disabled", "ShowInfoOnOpenParenthesis": True, } + + # defaults for conda + condaDefaults = { + "CondaExecutable": "", + } def readToolGroups(prefClass=Prefs): @@ -3720,7 +3725,7 @@ @param key the key of the value to get @param prefClass preferences class used as the storage area - @return the requested editor colour + @return the requested Code Documentation Viewer value """ if key in ["ShowInfoAsRichText", "ShowInfoOnOpenParenthesis"]: return toBool(prefClass.settings.value( @@ -3743,6 +3748,30 @@ prefClass.settings.setValue("CodeDocumentationViewer/" + key, value) +def getConda(key, prefClass=Prefs): + """ + Module function to retrieve the conda related settings. + + @param key the key of the value to get + @param prefClass preferences class used as the storage area + @return the requested conda value + """ + return prefClass.settings.value( + "Conda/" + key, + prefClass.condaDefaults[key]) + + +def setConda(key, value, prefClass=Prefs): + """ + Module function to store the conda related settings. + + @param key the key of the setting to be set + @param value the value to be set + @param prefClass preferences class used as the storage area + """ + prefClass.settings.setValue("Conda/" + key, value) + + def getGeometry(key, prefClass=Prefs): """ Module function to retrieve the display geometry.
--- a/UI/UserInterface.py Wed Feb 13 18:59:31 2019 +0100 +++ b/UI/UserInterface.py Wed Feb 13 20:39:58 2019 +0100 @@ -231,6 +231,11 @@ # load the view profiles self.profiles = Preferences.getUI("ViewProfiles2") + # Generate the conda interface + from CondaInterface.Conda import Conda + self.condaInterface = Conda(self) + e5App().registerObject("Conda", self.condaInterface) + # Generate the virtual environment manager from VirtualEnv.VirtualenvManager import VirtualenvManager self.virtualenvManager = VirtualenvManager(self) @@ -835,6 +840,14 @@ UI.PixmapCache.getIcon("debugViewer.png"), self.tr("Debug-Viewer")) + # Create the conda package manager + logging.debug("Creating Conda Package Manager...") + from CondaInterface.CondaPackagesWidget import CondaPackagesWidget + self.condaWidget = CondaPackagesWidget(self.condaInterface) + self.rToolbox.addItem(self.condaWidget, + UI.PixmapCache.getIcon("miniconda.png"), + self.tr("Conda")) + if Preferences.getUI("ShowCooperation"): # Create the chat part of the user interface logging.debug("Creating Chat Widget...") @@ -976,6 +989,14 @@ self.debugViewer, UI.PixmapCache.getIcon("debugViewer.png"), self.tr("Debug-Viewer")) + # Create the conda package manager + logging.debug("Creating Conda Package Manager...") + from CondaInterface.CondaPackagesWidget import CondaPackagesWidget + self.condaWidget = CondaPackagesWidget(self.condaInterface) + self.rightSidebar.addTab( + self.condaWidget, UI.PixmapCache.getIcon("miniconda.png"), + self.tr("Conda")) + if Preferences.getUI("ShowCooperation"): # Create the chat part of the user interface logging.debug("Creating Chat Widget...")
--- a/VirtualEnv/VirtualenvConfigurationDialog.py Wed Feb 13 18:59:31 2019 +0100 +++ b/VirtualEnv/VirtualenvConfigurationDialog.py Wed Feb 13 20:39:58 2019 +0100 @@ -28,6 +28,8 @@ import Preferences import Utilities +import CondaInterface + class VirtualenvConfigurationDialog(QDialog, Ui_VirtualenvConfigurationDialog): """ @@ -60,22 +62,55 @@ self.pythonExecPicker.setDefaultDirectory( sys.executable.replace("w.exe", ".exe")) + self.condaTargetDirectoryPicker.setMode( + E5PathPickerModes.DirectoryMode) + self.condaTargetDirectoryPicker.setWindowTitle( + self.tr("Conda Environment Location")) + self.condaTargetDirectoryPicker.setDefaultDirectory( + Utilities.getHomeDir()) + + self.condaCloneDirectoryPicker.setMode( + E5PathPickerModes.DirectoryMode) + self.condaCloneDirectoryPicker.setWindowTitle( + self.tr("Conda Environment Location")) + self.condaCloneDirectoryPicker.setDefaultDirectory( + Utilities.getHomeDir()) + + self.condaRequirementsFilePicker.setMode( + E5PathPickerModes.OpenFileMode) + self.condaRequirementsFilePicker.setWindowTitle( + self.tr("Conda Requirements File")) + self.condaRequirementsFilePicker.setDefaultDirectory( + Utilities.getHomeDir()) + self.condaRequirementsFilePicker.setFilters( + self.tr("Text Files (*.txt);;All Files (*)")) + self.__versionRe = re.compile(r""".*?(\d+\.\d+\.\d+).*""") self.__virtualenvFound = False self.__pyvenvFound = False + self.__condaFound = False self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self.__mandatoryStyleSheet = "QLineEdit {border: 2px solid;}" self.targetDirectoryPicker.setStyleSheet(self.__mandatoryStyleSheet) self.nameEdit.setStyleSheet(self.__mandatoryStyleSheet) + self.condaTargetDirectoryPicker.setStyleSheet( + self.__mandatoryStyleSheet) + self.condaNameEdit.setStyleSheet(self.__mandatoryStyleSheet) self.__setVirtualenvVersion() self.__setPyvenvVersion() - if self.__virtualenvFound: + self.__setCondaVersion() + if self.__pyvenvFound: + self.pyvenvButton.setChecked(True) + elif self.__virtualenvFound: self.virtualenvButton.setChecked(True) - elif self.__pyvenvFound: - self.pyvenvButton.setChecked(True) + elif self.__condaFound: + self.condaButton.setChecked(True) + + self.condaInsecureCheckBox.setEnabled( + CondaInterface.condaVersion() >= (4, 3, 18)) msh = self.minimumSizeHint() self.resize(max(self.width(), msh.width()), msh.height()) @@ -84,17 +119,31 @@ """ Private method to update the enabled status of the OK button. """ - self.buttonBox.button(QDialogButtonBox.Ok).setEnabled( - (self.__virtualenvFound or self.__pyvenvFound) and - bool(self.targetDirectoryPicker.text()) and - bool(self.nameEdit.text()) - ) + if self.virtualenvButton.isChecked() or self.pyvenvButton.isChecked(): + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled( + (self.__virtualenvFound or self.__pyvenvFound) and + bool(self.targetDirectoryPicker.text()) and + bool(self.nameEdit.text()) + ) + elif self.condaButton.isChecked(): + enable = bool(self.condaNameEdit.text()) or \ + bool(self.condaTargetDirectoryPicker.text()) + if self.condaSpecialsGroup.isChecked(): + if self.condaCloneButton.isChecked(): + enable &= bool(self.condaCloneNameEdit.text()) or \ + bool(self.condaCloneDirectoryPicker.text()) + elif self.condaRequirementsButton.isChecked(): + enable &= bool(self.condaRequirementsFilePicker.text()) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable) + else: + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) def __updateUi(self): """ Private method to update the UI depending on the selected virtual environment creator (virtualenv or pyvenv). """ + # venv page enable = self.virtualenvButton.isChecked() self.extraSearchPathLabel.setEnabled(enable) self.extraSearchPathPicker.setEnabled(enable) @@ -108,6 +157,20 @@ self.noSetuptoolsCheckBox.setEnabled(enable) self.symlinkCheckBox.setEnabled(not enable) self.upgradeCheckBox.setEnabled(not enable) + + # conda page + enable = not self.condaSpecialsGroup.isChecked() + self.condaPackagesEdit.setEnabled(enable) + self.condaPythonEdit.setEnabled(enable) + self.condaInsecureCheckBox.setEnabled( + enable and CondaInterface.condaVersion() >= (4, 3, 18)) + self.condaDryrunCheckBox.setEnabled(enable) + + # select page + if self.condaButton.isChecked(): + self.venvStack.setCurrentWidget(self.condaPage) + else: + self.venvStack.setCurrentWidget(self.venvPage) @pyqtSlot(str) def on_targetDirectoryPicker_textChanged(self, txt): @@ -151,6 +214,88 @@ """ self.__updateUi() + @pyqtSlot(bool) + def on_condaButton_toggled(self, checked): + """ + Private slot to react to the selection of 'conda'. + + @param checked state of the checkbox + @type bool + """ + self.__updateUi() + + @pyqtSlot(str) + def on_condaNameEdit_textChanged(self, txt): + """ + Private slot handling a change of the conda environment name. + + @param txt environment name + @type str + """ + self.__updateOK() + + @pyqtSlot(str) + def on_condaTargetDirectoryPicker_textChanged(self, txt): + """ + Private slot handling a change of the conda target directory. + + @param txt target directory + @type str + """ + self.__updateOK() + + @pyqtSlot() + def on_condaSpecialsGroup_clicked(self): + """ + Private slot handling the selection of the specials group. + """ + self.__updateOK() + self.__updateUi() + + @pyqtSlot(str) + def on_condaCloneNameEdit_textChanged(self, txt): + """ + Private slot handling a change of the conda source environment name. + + @param txt name of the environment to be cloned + @type str + """ + self.__updateOK() + + @pyqtSlot(str) + def on_condaCloneDirectoryPicker_textChanged(self, txt): + """ + Private slot handling a change of the cloned from directory. + + @param txt target directory + @type str + """ + self.__updateOK() + + @pyqtSlot() + def on_condaCloneButton_clicked(self): + """ + Private slot handling the selection of the clone button. + """ + self.__updateOK() + + @pyqtSlot() + def on_condaRequirementsButton_clicked(self): + """ + Private slot handling the selection of the requirements button. + """ + self.__updateOK() + + @pyqtSlot(str) + def on_condaRequirementsFilePicker_textChanged(self, txt): + """ + Private slot handling a change of the requirements file entry. + + @param txt current text of the requirements file entry + @type str + """ + self.__updateOK() + def __setVirtualenvVersion(self): """ Private method to determine the virtualenv version and set the @@ -259,6 +404,18 @@ if not self.__pyvenvFound: self.pyvenvButton.setChecked(False) + def __setCondaVersion(self): + """ + Private method to determine the conda version and set the respective + label. + """ + self.__condaFound = bool(CondaInterface.condaVersion()) + self.condaButton.setText(self.tr( + "conda Version: {0}".format(CondaInterface.condaVersionStr()))) + self.condaButton.setEnabled(self.__condaFound) + if not self.__condaFound: + self.condaButton.setChecked(False) + def __generateTargetDir(self): """ Private method to generate a valid target directory path. @@ -281,75 +438,123 @@ @rtype list of str """ args = [] - if self.virtualenvButton.isChecked(): - if self.extraSearchPathPicker.text(): - args.append("--extra-search-dir={0}".format( - Utilities.toNativeSeparators( - self.extraSearchPathPicker.text()))) - if self.promptPrefixEdit.text(): - args.append("--prompt={0}".format( - self.promptPrefixEdit.text().replace(" ", "_"))) - if self.pythonExecPicker.text(): - args.append("--python={0}".format( - Utilities.toNativeSeparators( - self.pythonExecPicker.text()))) - elif self.versionComboBox.currentText(): - args.append("--python=python{0}".format( - self.versionComboBox.currentText())) - if self.verbositySpinBox.value() == 1: - args.append("--verbose") - elif self.verbositySpinBox.value() == -1: - args.append("--quiet") - if self.clearCheckBox.isChecked(): - args.append("--clear") - if self.systemCheckBox.isChecked(): - args.append("--system-site-packages") - if self.unzipCheckBox.isChecked(): - args.append("--unzip-setuptools") - if self.noSetuptoolsCheckBox.isChecked(): - args.append("--no-setuptools") - if self.noPipCcheckBox.isChecked(): - args.append("--no-pip") - if self.copyCheckBox.isChecked(): - args.append("--always-copy") - elif self.pyvenvButton.isChecked(): - if self.clearCheckBox.isChecked(): - args.append("--clear") - if self.systemCheckBox.isChecked(): - args.append("--system-site-packages") - if self.noPipCcheckBox.isChecked(): - args.append("--without-pip") - if self.copyCheckBox.isChecked(): - args.append("--copies") - if self.symlinkCheckBox.isChecked(): - args.append("--symlinks") - if self.upgradeCheckBox.isChecked(): - args.append("--upgrade") - targetDirectory = self.__generateTargetDir() - args.append(targetDirectory) + if self.condaButton.isChecked(): + if bool(self.condaNameEdit.text()): + args.extend(["--name", self.condaNameEdit.text()]) + if bool(self.condaTargetDirectoryPicker.text()): + args.extend(["--prefix", + self.condaTargetDirectoryPicker.text()]) + if self.condaSpecialsGroup.isChecked(): + if self.condaCloneButton.isChecked(): + if bool(self.condaCloneNameEdit.text()): + args.extend( + ["--clone", self.condaCloneNameEdit.text()] + ) + elif bool(self.condaCloneDirectoryPicker.text()): + args.extend(["--clone", + self.condaCloneDirectoryPicker.text()]) + elif self.condaRequirementsButton.isChecked(): + args.extend( + ["--file", self.condaRequirementsFilePicker.text()] + ) + if self.condaInsecureCheckBox.isChecked(): + args.append("--insecure") + if self.condaDryrunCheckBox.isChecked(): + args.append("--dry-run") + if not self.condaSpecialsGroup.isChecked(): + if bool(self.condaPythonEdit.text()): + args.append("python={0}".format( + self.condaPythonEdit.text())) + if bool(self.condaPackagesEdit.text()): + args.extend(self.condaPackagesEdit.text().split()) + else: + if self.virtualenvButton.isChecked(): + if self.extraSearchPathPicker.text(): + args.append("--extra-search-dir={0}".format( + Utilities.toNativeSeparators( + self.extraSearchPathPicker.text()))) + if self.promptPrefixEdit.text(): + args.append("--prompt={0}".format( + self.promptPrefixEdit.text().replace(" ", "_"))) + if self.pythonExecPicker.text(): + args.append("--python={0}".format( + Utilities.toNativeSeparators( + self.pythonExecPicker.text()))) + elif self.versionComboBox.currentText(): + args.append("--python=python{0}".format( + self.versionComboBox.currentText())) + if self.verbositySpinBox.value() == 1: + args.append("--verbose") + elif self.verbositySpinBox.value() == -1: + args.append("--quiet") + if self.clearCheckBox.isChecked(): + args.append("--clear") + if self.systemCheckBox.isChecked(): + args.append("--system-site-packages") + if self.unzipCheckBox.isChecked(): + args.append("--unzip-setuptools") + if self.noSetuptoolsCheckBox.isChecked(): + args.append("--no-setuptools") + if self.noPipCcheckBox.isChecked(): + args.append("--no-pip") + if self.copyCheckBox.isChecked(): + args.append("--always-copy") + elif self.pyvenvButton.isChecked(): + if self.clearCheckBox.isChecked(): + args.append("--clear") + if self.systemCheckBox.isChecked(): + args.append("--system-site-packages") + if self.noPipCcheckBox.isChecked(): + args.append("--without-pip") + if self.copyCheckBox.isChecked(): + args.append("--copies") + if self.symlinkCheckBox.isChecked(): + args.append("--symlinks") + if self.upgradeCheckBox.isChecked(): + args.append("--upgrade") + targetDirectory = self.__generateTargetDir() + args.append(targetDirectory) + return args def getData(self): """ Public method to retrieve the dialog data. - @return tuple containing a flag indicating the pyvenv selection, the - process arguments, a name for the virtual environment, a flag - indicating to open the target directory after creation, a flag - indicating to write a log file, a flag indicating to write a - script, the name of the target directory and the name of the - Python interpreter to use - @rtype tuple of (bool, list of str, str, bool, bool, bool, str, str) + @return dictionary containing the data for the two environment + variants. The keys for both variants are 'arguments' containing the + command line arguments, 'logicalName' containing the environment + name to be used with the virtual env manager and 'envType' + containing the environment type (virtualenv, pyvenv or conda). The + virtualenv/pyvenv specific keys are 'openTarget' containg a flag to + open the target directory after creation, 'createLog' containing a + flag to write a log file, 'createScript' containing a flag to write + a script, 'targetDirectory' containing the target directory and + 'pythonExe' containing the Python interpreter to be used. The + conda specific key is 'command' giving the conda command to be + executed (always 'create'). + @rtype dict """ args = self.__generateArguments() - targetDirectory = self.__generateTargetDir() - return ( - self.pyvenvButton.isChecked(), - args, - self.nameEdit.text(), - self.openCheckBox.isChecked(), - self.logCheckBox.isChecked(), - self.scriptCheckBox.isChecked(), - targetDirectory, - Utilities.toNativeSeparators(self.pythonExecPicker.text()), - ) + resultDict = { + "arguments": args, + "logicalName": self.nameEdit.text(), + } + if self.condaButton.isChecked(): + resultDict.update({ + "envType": "conda", + "command": "create", + }) + else: + resultDict.update({ + "envType": ("pyvenv" if self.pyvenvButton.isChecked() else + "virtualenv"), + "openTarget": self.openCheckBox.isChecked(), + "createLog": self.logCheckBox.isChecked(), + "createScript": self.scriptCheckBox.isChecked(), + "targetDirectory": self.__generateTargetDir(), + "pythonExe": Utilities.toNativeSeparators( + self.pythonExecPicker.text()), + }) + + return resultDict
--- a/VirtualEnv/VirtualenvConfigurationDialog.ui Wed Feb 13 18:59:31 2019 +0100 +++ b/VirtualEnv/VirtualenvConfigurationDialog.ui Wed Feb 13 20:39:58 2019 +0100 @@ -7,11 +7,11 @@ <x>0</x> <y>0</y> <width>700</width> - <height>592</height> + <height>654</height> </rect> </property> <property name="windowTitle"> - <string>Virtualenv Configuration</string> + <string>Virtual Environment Configuration</string> </property> <property name="sizeGripEnabled"> <bool>true</bool> @@ -68,6 +68,22 @@ </property> </widget> </item> + <item row="2" column="0"> + <widget class="QRadioButton" name="condaButton"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="toolTip"> + <string>Select to use 'conda'</string> + </property> + <property name="text"> + <string notr="true">0.0</string> + </property> + </widget> + </item> </layout> </widget> </item> @@ -93,332 +109,611 @@ </layout> </item> <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>Paths</string> + <widget class="QStackedWidget" name="venvStack"> + <property name="currentIndex"> + <number>1</number> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Target Directory:</string> - </property> - </widget> - </item> - <item row="0" 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 target directory for the virtual environment</string> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="extraSearchPathLabel"> - <property name="text"> - <string>Extra Search Path:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="E5PathPicker" name="extraSearchPathPicker" 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 extra search path to look for setuptools/pip</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="promptPrefixLabel"> - <property name="text"> - <string>Prompt Prefix:</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="E5ClearableLineEdit" name="promptPrefixEdit"> - <property name="toolTip"> - <string>Enter the prompt prefix for the virtual environment</string> - </property> - <property name="placeholderText"> - <string>Prompt prefix for the virtual environment</string> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Python Executable:</string> - </property> - </widget> - </item> - <item row="3" 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 for the virtual environment</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Options</string> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="0"> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QLabel" name="verbosityLabel"> - <property name="text"> - <string>Verbosity:</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="verbositySpinBox"> - <property name="toolTip"> - <string>Select the verbosity (-1: quiet, 0: normal, 1: verbose)</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="minimum"> - <number>-1</number> - </property> - <property name="maximum"> - <number>1</number> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLabel" name="versionLabel"> - <property name="text"> - <string>Python Version:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="versionComboBox"> - <property name="toolTip"> - <string>Select the Python version (empty for current)</string> - </property> - <item> + <widget class="QWidget" name="venvPage"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Paths</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Target Directory:</string> + </property> + </widget> + </item> + <item row="0" 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 target directory for the virtual environment</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="extraSearchPathLabel"> + <property name="text"> + <string>Extra Search Path:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="E5PathPicker" name="extraSearchPathPicker" 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 extra search path to look for setuptools/pip</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="promptPrefixLabel"> + <property name="text"> + <string>Prompt Prefix:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="E5ClearableLineEdit" name="promptPrefixEdit"> + <property name="toolTip"> + <string>Enter the prompt prefix for the virtual environment</string> + </property> + <property name="placeholderText"> + <string>Prompt prefix for the virtual environment</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Python Executable:</string> + </property> + </widget> + </item> + <item row="3" 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 for the virtual environment</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Options</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="verbosityLabel"> + <property name="text"> + <string>Verbosity:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="verbositySpinBox"> + <property name="toolTip"> + <string>Select the verbosity (-1: quiet, 0: normal, 1: verbose)</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="minimum"> + <number>-1</number> + </property> + <property name="maximum"> + <number>1</number> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="versionLabel"> + <property name="text"> + <string>Python Version:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="versionComboBox"> + <property name="toolTip"> + <string>Select the Python version (empty for current)</string> + </property> + <item> + <property name="text"> + <string notr="true"/> + </property> + </item> + <item> + <property name="text"> + <string notr="true">2.6</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">2.7</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">3.4</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">3.5</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">3.6</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">3.7</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="systemCheckBox"> + <property name="toolTip"> + <string>Select to give the virtualenv access to the global site-packages</string> + </property> + <property name="text"> + <string>System-wide Python Packages</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="unzipCheckBox"> + <property name="toolTip"> + <string>Select to unzip setuptools when installing it</string> + </property> + <property name="text"> + <string>Unzip Setuptool to virtualenv</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="noSetuptoolsCheckBox"> + <property name="toolTip"> + <string>Select to not install setuptools (or pip) in the new virtualenv</string> + </property> + <property name="text"> + <string>Don't install 'setuptool' (or pip) in the virtualenv</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="noPipCcheckBox"> + <property name="toolTip"> + <string>Select to not install pip in the new virtualenv</string> + </property> + <property name="text"> + <string>Don't install 'pip' in the virtualenv</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="clearCheckBox"> + <property name="toolTip"> + <string>Select to clear the target first</string> + </property> + <property name="text"> + <string>Clear out the target directory</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QCheckBox" name="copyCheckBox"> + <property name="toolTip"> + <string>Select to always copy files rather than symlinking</string> + </property> + <property name="text"> + <string>Always copy files</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QCheckBox" name="symlinkCheckBox"> + <property name="toolTip"> + <string>Select to use symlinks instead of copies</string> + </property> + <property name="text"> + <string>Use Symbolic Links</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QCheckBox" name="upgradeCheckBox"> + <property name="toolTip"> + <string>Select to upgrade a virtual environment</string> + </property> + <property name="text"> + <string>Upgrade</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QCheckBox" name="logCheckBox"> + <property name="toolTip"> + <string>Select to generate a log file in the target directory</string> + </property> + <property name="text"> + <string>Save a log file in the target directory after creation</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="scriptCheckBox"> + <property name="toolTip"> + <string>Select to write a shell script/batch file to regenerate the virtualenv</string> + </property> + <property name="text"> + <string>Save virtualenv generation script</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QCheckBox" name="openCheckBox"> + <property name="toolTip"> + <string>Open the newly created virtualenv in a file manager window</string> + </property> + <property name="text"> + <string>Open target directory after creation</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="condaPage"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="title"> + <string>Target Environment Specification</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Name:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="E5ClearableLineEdit" name="condaNameEdit"> + <property name="toolTip"> + <string>Enter the name for the environment</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Path:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="E5PathPicker" name="condaTargetDirectoryPicker" 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 target directory for the conda environment</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string><b>Note:</b> Only one of the above entries is mandatory.</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="condaSpecialsGroup"> + <property name="title"> + <string>Special Operations</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="0" column="0"> + <widget class="QRadioButton" name="condaCloneButton"> + <property name="toolTip"> + <string>Select to clone an environment</string> + </property> + <property name="text"> + <string>Clone Environment</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QRadioButton" name="condaRequirementsButton"> + <property name="statusTip"> + <string>Select to create the environment from a requirements file</string> + </property> + <property name="text"> + <string>from Requirements</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QFrame" name="condaCloneFrame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QGridLayout" name="gridLayout_7"> + <item row="0" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Name:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="E5ClearableLineEdit" name="condaCloneNameEdit"> + <property name="toolTip"> + <string>Enter the name of the environment to be cloned</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Path:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="E5PathPicker" name="condaCloneDirectoryPicker" 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 environment to be cloned</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="1"> + <widget class="QFrame" name="condaRequirementsFrame"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="E5PathPicker" name="condaRequirementsFilePicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>37</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLabel" name="label_9"> <property name="text"> - <string notr="true"/> - </property> - </item> - <item> - <property name="text"> - <string notr="true">2.6</string> + <string>Package Specs:</string> </property> - </item> - <item> - <property name="text"> - <string notr="true">2.7</string> + </widget> + </item> + <item> + <widget class="E5ClearableLineEdit" name="condaPackagesEdit"> + <property name="toolTip"> + <string>Enter the package specifications for the environment</string> </property> - </item> - <item> - <property name="text"> - <string notr="true">3.4</string> - </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox_5"> + <property name="title"> + <string>Options</string> + </property> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="0" column="0"> + <widget class="QLabel" name="versionLabel_2"> + <property name="text"> + <string>Python Version:</string> + </property> + </widget> </item> - <item> - <property name="text"> - <string notr="true">3.5</string> - </property> - </item> - <item> - <property name="text"> - <string notr="true">3.6</string> - </property> - </item> - <item> - <property name="text"> - <string notr="true">3.7</string> - </property> + <item row="0" column="1"> + <widget class="E5ClearableLineEdit" name="condaPythonEdit"> + <property name="toolTip"> + <string>Enter the Python version for the environment</string> + </property> + </widget> </item> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QCheckBox" name="systemCheckBox"> - <property name="toolTip"> - <string>Select to give the virtualenv access to the global site-packages</string> - </property> - <property name="text"> - <string>System-wide Python Packages</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QCheckBox" name="unzipCheckBox"> - <property name="toolTip"> - <string>Select to unzip setuptools when installing it</string> - </property> - <property name="text"> - <string>Unzip Setuptool to virtualenv</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QCheckBox" name="noSetuptoolsCheckBox"> - <property name="toolTip"> - <string>Select to not install setuptools (or pip) in the new virtualenv</string> - </property> - <property name="text"> - <string>Don't install 'setuptool' (or pip) in the virtualenv</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QCheckBox" name="noPipCcheckBox"> - <property name="toolTip"> - <string>Select to not install pip in the new virtualenv</string> - </property> - <property name="text"> - <string>Don't install 'pip' in the virtualenv</string> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QCheckBox" name="clearCheckBox"> - <property name="toolTip"> - <string>Select to clear the target first</string> - </property> - <property name="text"> - <string>Clear out the target directory</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QCheckBox" name="copyCheckBox"> - <property name="toolTip"> - <string>Select to always copy files rather than symlinking</string> - </property> - <property name="text"> - <string>Always copy files</string> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QCheckBox" name="symlinkCheckBox"> - <property name="toolTip"> - <string>Select to use symlinks instead of copies</string> - </property> - <property name="text"> - <string>Use Symbolic Links</string> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QCheckBox" name="upgradeCheckBox"> - <property name="toolTip"> - <string>Select to upgrade a virtual environment</string> - </property> - <property name="text"> - <string>Upgrade</string> - </property> - </widget> - </item> - <item row="5" column="0"> - <widget class="QCheckBox" name="logCheckBox"> - <property name="toolTip"> - <string>Select to generate a log file in the target directory</string> - </property> - <property name="text"> - <string>Save a log file in the target directory after creation</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="5" column="1"> - <widget class="QCheckBox" name="scriptCheckBox"> - <property name="toolTip"> - <string>Select to write a shell script/batch file to regenerate the virtualenv</string> - </property> - <property name="text"> - <string>Save virtualenv generation script</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="6" column="0"> - <widget class="QCheckBox" name="openCheckBox"> - <property name="toolTip"> - <string>Open the newly created virtualenv in a file manager window</string> - </property> - <property name="text"> - <string>Open target directory after creation</string> - </property> - </widget> - </item> - </layout> + <item row="1" column="0" colspan="2"> + <widget class="QCheckBox" name="condaInsecureCheckBox"> + <property name="text"> + <string>Allow insecure SSL connections</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QCheckBox" name="condaDryrunCheckBox"> + <property name="toolTip"> + <string>Select to perform just a dry-run</string> + </property> + <property name="text"> + <string>Perform dry-run</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </widget> </widget> </item> <item> @@ -449,6 +744,7 @@ <tabstops> <tabstop>virtualenvButton</tabstop> <tabstop>pyvenvButton</tabstop> + <tabstop>condaButton</tabstop> <tabstop>nameEdit</tabstop> <tabstop>targetDirectoryPicker</tabstop> <tabstop>extraSearchPathPicker</tabstop> @@ -467,6 +763,18 @@ <tabstop>logCheckBox</tabstop> <tabstop>scriptCheckBox</tabstop> <tabstop>openCheckBox</tabstop> + <tabstop>condaNameEdit</tabstop> + <tabstop>condaTargetDirectoryPicker</tabstop> + <tabstop>condaSpecialsGroup</tabstop> + <tabstop>condaCloneButton</tabstop> + <tabstop>condaCloneNameEdit</tabstop> + <tabstop>condaCloneDirectoryPicker</tabstop> + <tabstop>condaRequirementsButton</tabstop> + <tabstop>condaRequirementsFilePicker</tabstop> + <tabstop>condaPackagesEdit</tabstop> + <tabstop>condaPythonEdit</tabstop> + <tabstop>condaInsecureCheckBox</tabstop> + <tabstop>condaDryrunCheckBox</tabstop> </tabstops> <resources/> <connections> @@ -477,8 +785,8 @@ <slot>accept()</slot> <hints> <hint type="sourcelabel"> - <x>248</x> - <y>254</y> + <x>257</x> + <y>644</y> </hint> <hint type="destinationlabel"> <x>157</x> @@ -493,8 +801,8 @@ <slot>reject()</slot> <hints> <hint type="sourcelabel"> - <x>316</x> - <y>260</y> + <x>325</x> + <y>644</y> </hint> <hint type="destinationlabel"> <x>286</x> @@ -502,5 +810,37 @@ </hint> </hints> </connection> + <connection> + <sender>condaCloneButton</sender> + <signal>toggled(bool)</signal> + <receiver>condaCloneFrame</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>108</x> + <y>361</y> + </hint> + <hint type="destinationlabel"> + <x>78</x> + <y>380</y> + </hint> + </hints> + </connection> + <connection> + <sender>condaRequirementsButton</sender> + <signal>toggled(bool)</signal> + <receiver>condaRequirementsFrame</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>431</x> + <y>359</y> + </hint> + <hint type="destinationlabel"> + <x>427</x> + <y>381</y> + </hint> + </hints> + </connection> </connections> </ui>
--- a/VirtualEnv/VirtualenvExecDialog.py Wed Feb 13 18:59:31 2019 +0100 +++ b/VirtualEnv/VirtualenvExecDialog.py Wed Feb 13 20:39:58 2019 +0100 @@ -33,28 +33,13 @@ This class starts a QProcess and displays a dialog that shows the output of the virtualenv or pyvenv process. """ - def __init__(self, pyvenv, targetDir, venvName, openTarget, createLog, - createScript, interpreter, venvManager, parent=None): + def __init__(self, configuration, venvManager, parent=None): """ Constructor - @param pyvenv flag indicating the use of 'pyvenv' - @type bool - @param targetDir name of the virtualenv directory - @type str - @param venvName logical name for the virtual environment - @type str - @param openTarget flag indicating to open the virtualenv directory - in a file manager - @type bool - @param createLog flag indicating to create a log file of the - creation process - @type bool - @param createScript flag indicating to create a script to recreate - the virtual environment - @type bool - @param interpreter name of the python interpreter to use - @type str + @param configuration dictionary containing the configuration parameters + as returned by the command configuration dialog + @type dict @param venvManager reference to the virtual environment manager @type VirtualenvManager @param parent reference to the parent widget @@ -66,21 +51,22 @@ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) - self.__pyvenv = pyvenv - self.__targetDir = targetDir - self.__openTarget = openTarget - self.__createLog = createLog - self.__createScript = createScript - self.__venvName = venvName + self.__pyvenv = configuration["envType"] == "pyvenv" + self.__targetDir = configuration["targetDirectory"] + self.__openTarget = configuration["openTarget"] + self.__createLog = configuration["createLog"] + self.__createScript = configuration["createScript"] + self.__venvName = configuration["logicalName"] self.__venvManager = venvManager self.__process = None self.__cmd = "" - if pyvenv: + if self.__pyvenv: self.__calls = [] - if interpreter: - self.__calls.append((interpreter, ["-m", "venv"])) + if configuration["pythonExe"]: + self.__calls.append((configuration["pythonExe"], + ["-m", "venv"])) self.__calls.extend([ (sys.executable.replace("w.exe", ".exe"), ["-m", "venv"]), @@ -250,7 +236,7 @@ """ Private method to log some output. - @param s output sstring to log (string) + @param s output string to log (string) """ self.contents.insertPlainText(s) self.contents.ensureCursorVisible()
--- a/VirtualEnv/VirtualenvManager.py Wed Feb 13 18:59:31 2019 +0100 +++ b/VirtualEnv/VirtualenvManager.py Wed Feb 13 20:39:58 2019 +0100 @@ -19,9 +19,9 @@ from PyQt5.QtWidgets import QDialog from E5Gui import E5MessageBox +from E5Gui.E5Application import e5App import Preferences -import Utilities class VirtualenvManager(QObject): @@ -152,17 +152,25 @@ dlg = VirtualenvConfigurationDialog() if dlg.exec_() == QDialog.Accepted: - (pyvenv, args, name, openTarget, createLog, createScript, - targetDir, interpreter) = dlg.getData() + resultDict = dlg.getData() - # now do the call - from .VirtualenvExecDialog import VirtualenvExecDialog - dia = VirtualenvExecDialog(pyvenv, targetDir, name, openTarget, - createLog, createScript, interpreter, - self) - dia.show() - dia.start(args) - dia.exec_() + if resultDict["envType"] == "conda": + # create the conda environment + conda = e5App().getObject("Conda") + ok, prefix, interpreter = conda.createCondaEnvironment( + resultDict["arguments"]) + if ok and "--dry-run" not in resultDict["arguments"]: + self.addVirtualEnv(resultDict["logicalName"], + prefix, + venvInterpreter=interpreter, + isConda=True) + else: + # now do the call + from .VirtualenvExecDialog import VirtualenvExecDialog + dia = VirtualenvExecDialog(resultDict, self) + dia.show() + dia.start(resultDict["arguments"]) + dia.exec_() def addVirtualEnv(self, venvName, venvDirectory, venvInterpreter="", venvVariant=3, isGlobal=False, isConda=False, @@ -197,7 +205,14 @@ .format(venvName), icon=E5MessageBox.Warning) if not ok: - return + from .VirtualenvNameDialog import VirtualenvNameDialog + dlg = VirtualenvNameDialog( + list(self.__virtualEnvironments.keys()), + venvName) + if dlg.exec_() != QDialog.Accepted: + return + + venvName = dlg.getName() if not venvInterpreter: from .VirtualenvInterpreterSelectionDialog import \ @@ -205,9 +220,6 @@ dlg = VirtualenvInterpreterSelectionDialog(venvName, venvDirectory) if dlg.exec_() == QDialog.Accepted: venvInterpreter, venvVariant = dlg.getData() - if not Utilities.startswithPath(venvInterpreter, - venvDirectory): - isGlobal = True if venvInterpreter: self.__virtualEnvironments[venvName] = { @@ -342,9 +354,17 @@ if dlg.exec_() == QDialog.Accepted: for venvName in venvNames: if self.__isEnvironmentDeleteable(venvName): - shutil.rmtree( - self.__virtualEnvironments[venvName]["path"], True) - del self.__virtualEnvironments[venvName] + if self.isCondaEnvironment(venvName): + conda = e5App().getObject("Conda") + path = self.__virtualEnvironments[venvName]["path"] + res = conda.removeCondaEnvironment(prefix=path) + if res: + del self.__virtualEnvironments[venvName] + else: + shutil.rmtree( + self.__virtualEnvironments[venvName]["path"], + True) + del self.__virtualEnvironments[venvName] self.__saveSettings()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualEnv/VirtualenvNameDialog.py Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to enter the logical name for a new virtual +environment. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSlot, Qt +from PyQt5.QtWidgets import QDialog, QDialogButtonBox + +from .Ui_VirtualenvNameDialog import Ui_VirtualenvNameDialog + + +class VirtualenvNameDialog(QDialog, Ui_VirtualenvNameDialog): + """ + Class implementing a dialog to enter the logical name for a new virtual + environment. + """ + def __init__(self, environments, currentName, parent=None): + """ + Constructor + + @param environments list of environment names to be shown + @type list of str + @param currentName name to be shown in the name edit + @type str + @param parent reference to the parent widget + @type QWidget + """ + super(VirtualenvNameDialog, self).__init__(parent) + self.setupUi(self) + + self.envsList.addItems(environments) + self.nameEdit.setText(currentName) + + self.nameEdit.setFocus(Qt.OtherFocusReason) + self.nameEdit.selectAll() + + @pyqtSlot(str) + def on_nameEdit_textChanged(self, txt): + """ + Private slot to handle a change of the environment name. + + @param txt contens of the name edit + @type str + """ + items = self.envsList.findItems(txt, Qt.MatchExactly) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled( + bool(txt) and len(items) == 0) + + def getName(self): + """ + Public method to get the entered name. + + @return name for the environment + @rtype str + """ + return self.nameEdit.text()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualEnv/VirtualenvNameDialog.ui Wed Feb 13 20:39:58 2019 +0100 @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>VirtualenvNameDialog</class> + <widget class="QDialog" name="VirtualenvNameDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>450</height> + </rect> + </property> + <property name="windowTitle"> + <string>Virtualenv Name</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QListWidget" name="envsList"> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::NoSelection</enum> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Enter a logical name for the virtual environment:</string> + </property> + </widget> + </item> + <item> + <widget class="E5ClearableLineEdit" name="nameEdit"> + <property name="toolTip"> + <string>Enter a unique name for the virtual environment</string> + </property> + <property name="placeholderText"> + <string>Name for the virtual environment</string> + </property> + </widget> + </item> + <item> + <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>E5ClearableLineEdit</class> + <extends>QLineEdit</extends> + <header>E5Gui/E5LineEdit.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>envsList</tabstop> + <tabstop>nameEdit</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>VirtualenvNameDialog</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>VirtualenvNameDialog</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/eric6.e4p Wed Feb 13 18:59:31 2019 +0100 +++ b/eric6.e4p Wed Feb 13 20:39:58 2019 +0100 @@ -16,6 +16,14 @@ <TranslationPattern>i18n/eric6_%language%.ts</TranslationPattern> <Eol index="1"/> <Sources> + <Source>CondaInterface/Conda.py</Source> + <Source>CondaInterface/CondaExecDialog.py</Source> + <Source>CondaInterface/CondaExportDialog.py</Source> + <Source>CondaInterface/CondaInfoDialog.py</Source> + <Source>CondaInterface/CondaNewEnvironmentDataDialog.py</Source> + <Source>CondaInterface/CondaPackageDetailsWidget.py</Source> + <Source>CondaInterface/CondaPackagesWidget.py</Source> + <Source>CondaInterface/__init__.py</Source> <Source>Cooperation/ChatWidget.py</Source> <Source>Cooperation/Connection.py</Source> <Source>Cooperation/CooperationClient.py</Source> @@ -838,6 +846,7 @@ <Source>Plugins/__init__.py</Source> <Source>Preferences/ConfigurationDialog.py</Source> <Source>Preferences/ConfigurationPages/ApplicationPage.py</Source> + <Source>Preferences/ConfigurationPages/CondaPage.py</Source> <Source>Preferences/ConfigurationPages/ConfigurationPageBase.py</Source> <Source>Preferences/ConfigurationPages/CooperationPage.py</Source> <Source>Preferences/ConfigurationPages/CorbaPage.py</Source> @@ -1419,6 +1428,7 @@ <Source>VirtualEnv/VirtualenvInterpreterSelectionDialog.py</Source> <Source>VirtualEnv/VirtualenvManager.py</Source> <Source>VirtualEnv/VirtualenvManagerDialog.py</Source> + <Source>VirtualEnv/VirtualenvNameDialog.py</Source> <Source>VirtualEnv/__init__.py</Source> <Source>WebBrowser/AdBlock/AdBlockDialog.py</Source> <Source>WebBrowser/AdBlock/AdBlockExceptionsDialog.py</Source> @@ -1712,6 +1722,12 @@ <Source>uninstall.py</Source> </Sources> <Forms> + <Form>CondaInterface/CondaNewEnvironmentDataDialog.ui</Form> + <Form>CondaInterface/CondaExecDialog.ui</Form> + <Form>CondaInterface/CondaExportDialog.ui</Form> + <Form>CondaInterface/CondaInfoDialog.ui</Form> + <Form>CondaInterface/CondaPackageDetailsWidget.ui</Form> + <Form>CondaInterface/CondaPackagesWidget.ui</Form> <Form>Cooperation/ChatWidget.ui</Form> <Form>DataViews/CodeMetricsDialog.ui</Form> <Form>DataViews/PyCoverageDialog.ui</Form> @@ -2011,6 +2027,7 @@ <Form>Plugins/WizardPlugins/QRegularExpressionWizard/QRegularExpressionWizardRepeatDialog.ui</Form> <Form>Plugins/WizardPlugins/SetupWizard/SetupWizardDialog.ui</Form> <Form>Preferences/ConfigurationPages/ApplicationPage.ui</Form> + <Form>Preferences/ConfigurationPages/CondaPage.ui</Form> <Form>Preferences/ConfigurationPages/CooperationPage.ui</Form> <Form>Preferences/ConfigurationPages/CorbaPage.ui</Form> <Form>Preferences/ConfigurationPages/DebuggerGeneralPage.ui</Form> @@ -2143,6 +2160,7 @@ <Form>VirtualEnv/VirtualenvExecDialog.ui</Form> <Form>VirtualEnv/VirtualenvInterpreterSelectionDialog.ui</Form> <Form>VirtualEnv/VirtualenvManagerDialog.ui</Form> + <Form>VirtualEnv/VirtualenvNameDialog.ui</Form> <Form>WebBrowser/AdBlock/AdBlockDialog.ui</Form> <Form>WebBrowser/AdBlock/AdBlockExceptionsDialog.ui</Form> <Form>WebBrowser/Bookmarks/AddBookmarkDialog.ui</Form> @@ -2243,14 +2261,14 @@ </Resources> <Others> <Other>.hgignore</Other> - <Other>APIs/Python/zope-2.10.7.api</Other> - <Other>APIs/Python/zope-2.11.2.api</Other> - <Other>APIs/Python/zope-3.3.1.api</Other> <Other>APIs/Python3/PyQt4.bas</Other> <Other>APIs/Python3/PyQt5.bas</Other> <Other>APIs/Python3/QScintilla2.bas</Other> <Other>APIs/Python3/eric6.api</Other> <Other>APIs/Python3/eric6.bas</Other> + <Other>APIs/Python/zope-2.10.7.api</Other> + <Other>APIs/Python/zope-2.11.2.api</Other> + <Other>APIs/Python/zope-3.3.1.api</Other> <Other>APIs/QSS/qss.api</Other> <Other>APIs/Ruby/Ruby-1.8.7.api</Other> <Other>APIs/Ruby/Ruby-1.8.7.bas</Other>