--- a/PipInterface/PipPackagesWidget.py Wed Feb 20 19:44:13 2019 +0100 +++ b/PipInterface/PipPackagesWidget.py Thu Feb 21 19:55:35 2019 +0100 @@ -10,11 +10,12 @@ from __future__ import unicode_literals import textwrap +import os from PyQt5.QtCore import pyqtSlot, Qt, QEventLoop, QRegExp from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import QWidget, QToolButton, QApplication, QHeaderView, \ - QTreeWidgetItem + QTreeWidgetItem, QInputDialog, QMenu, QDialog from E5Gui.E5Application import e5App from E5Gui import E5MessageBox @@ -109,6 +110,11 @@ self.statusLabel.hide() self.searchWidget.hide() + + self.__detailsData = {} + self.__query = [] + + self.__packageDetailsDialog = None def __populateEnvironments(self): """ @@ -497,6 +503,13 @@ self.__updateSearchButton() @pyqtSlot() + def on_searchEdit_returnPressed(self): + """ + Private slot initiating a search via a press of the Return key. + """ + self.__search() + + @pyqtSlot() def on_searchButton_clicked(self): """ Private slot handling a press of the search button. @@ -685,6 +698,165 @@ return score + @pyqtSlot() + def on_installButton_clicked(self): + """ + Private slot to handle pressing the Install button.. + """ + self.__install() + + @pyqtSlot() + def on_installUserSiteButton_clicked(self): + """ + Private slot to handle pressing the Install to User-Site button.. + """ + self.__install(userSite=True) + + def __install(self, userSite=False): + """ + Private slot to install the selected packages. + + @param userSite flag indicating to install to the user directory + @type bool + """ + venvName = self.environmentsComboBox.currentText() + if venvName: + packages = [] + for itm in self.searchResultList.selectedItems(): + packages.append(itm.text(0).strip()) + if packages: + self.__pip.installPackages(packages, venvName=venvName, + userSite=userSite) + + @pyqtSlot() + def on_showDetailsButton_clicked(self): + """ + Private slot to handle pressing the Show Details button. + """ + self.__showDetails() + + @pyqtSlot(QTreeWidgetItem, int) + def on_searchResultList_itemActivated(self, item, column): + """ + Private slot reacting on an search result item activation. + + @param item reference to the activated item + @type QTreeWidgetItem + @param column activated column + @type int + """ + self.__showDetails(item) + + def __showDetails(self, item=None): + """ + Private slot to show details about the selected package. + + @param item reference to the search result item to show details for + @type QTreeWidgetItem + """ + self.showDetailsButton.setEnabled(False) + QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.processEvents(QEventLoop.ExcludeUserInputEvents) + + self.__detailsData = {} + + if not item: + item = self.searchResultList.selectedItems()[0] + + packageVersions = item.data(0, self.SearchVersionRole) + if len(packageVersions) == 1: + packageVersion = packageVersions[0] + elif len(packageVersions) == 0: + packageVersion = "" + else: + packageVersion, ok = QInputDialog.getItem( + self, + self.tr("Show Package Details"), + self.tr("Select the package version:"), + packageVersions, + 0, False) + if not ok: + return + + packageName = item.text(0) + self.__client.call( + "release_data", + (packageName, packageVersion), + lambda d: self.__getPackageDownloadsData(packageName, + packageVersion, + d), + lambda c, s: self.__detailsError(packageName, c, s) + ) + + def __getPackageDownloadsData(self, packageName, packageVersion, data): + """ + Private method to store the details data and get downloads + information. + + @param packageName name of the package + @type str + @param packageVersion version info + @type str + @param data result data with package details in the first + element + @type tuple + """ + if data and data[0]: + self.__detailsData = data[0] + self.__client.call( + "release_urls", + (packageName, packageVersion), + self.__displayPackageDetails, + lambda c, s: self.__detailsError(packageName, c, s) + ) + else: + QApplication.restoreOverrideCursor() + E5MessageBox.warning( + self, + self.tr("Search PyPI"), + self.tr("""<p>No package details info for <b>{0}</b>""" + """ available.</p>""").format(packageName)) + + def __displayPackageDetails(self, data): + """ + Private method to display the returned package details. + + @param data result data with downloads information in the first element + @type tuple + """ + from .PipPackageDetailsDialog import PipPackageDetailsDialog + + QApplication.restoreOverrideCursor() + self.showDetailsButton.setEnabled(True) + + if self.__packageDetailsDialog is not None: + self.__packageDetailsDialog.close() + + self.__packageDetailsDialog = \ + PipPackageDetailsDialog(self.__detailsData, data[0], self) + self.__packageDetailsDialog.show() + + def __detailsError(self, packageName, errorCode, errorString): + """ + Private method handling a details error. + + @param packageName name of the package + @type str + @param errorCode code of the error + @type int + @param errorString error message + @type str + """ + QApplication.restoreOverrideCursor() + self.showDetailsButton.setEnabled(True) + E5MessageBox.warning( + self, + self.tr("Search PyPI"), + self.tr("""<p>Package details info for <b>{0}</b> could not be""" + """ retrieved.</p><p>Reason: {1}</p>""") + .format(packageName, errorString) + ) + ####################################################################### ## Menu related methods below ####################################################################### @@ -694,8 +866,229 @@ Private method to create the super menu and attach it to the super menu button. """ - self.__pip.initActions() - - self.__pipMenu = self.__pip.initMenu() + self.__pipMenu = QMenu() + self.__installPipAct = self.__pipMenu.addAction( + self.tr("Install Pip"), + self.__installPip) + self.__installPipUserAct = self.__pipMenu.addAction( + self.tr("Install Pip to User-Site"), + self.__installPipUser) + self.__repairPipAct = self.__pipMenu.addAction( + self.tr("Repair Pip"), + self.__repairPip) + self.__pipMenu.addSeparator() + self.__installPackagesAct = self.__pipMenu.addAction( + self.tr("Install Packages"), + self.__installPackages) + self.__installLocalPackageAct = self.__pipMenu.addAction( + self.tr("Install Local Package"), + self.__installLocalPackage) + self.__pipMenu.addSeparator() + self.__installRequirementsAct = self.__pipMenu.addAction( + self.tr("Install Requirements"), + self.__installRequirements) + self.__uninstallRequirementsAct = self.__pipMenu.addAction( + self.tr("Uninstall Requirements"), + self.__uninstallRequirements) + self.__generateRequirementsAct = self.__pipMenu.addAction( + self.tr("Generate Requirements..."), + self.__generateRequirements) + self.__pipMenu.addSeparator() + # editUserConfigAct + self.__pipMenu.addAction( + self.tr("Edit User Configuration..."), + self.__editUserConfiguration) + self.__editVirtualenvConfigAct = self.__pipMenu.addAction( + self.tr("Edit Current Virtualenv Configuration..."), + self.__editVirtualenvConfiguration) + self.__pipMenu.addSeparator() + # pipConfigAct + self.__pipMenu.addAction( + self.tr("Configure..."), + self.__pipConfigure) + + self.__pipMenu.aboutToShow.connect(self.__aboutToShowPipMenu) self.pipMenuButton.setMenu(self.__pipMenu) + + def __aboutToShowPipMenu(self): + """ + Private slot to set the action enabled status. + """ + enable = bool(self.environmentsComboBox.currentText()) + enablePip = self.__isPipAvailable() + + self.__installPipAct.setEnabled(not enablePip) + self.__installPipUserAct.setEnabled(not enablePip) + self.__repairPipAct.setEnabled(enablePip) + + self.__installPackagesAct.setEnabled(enablePip) + self.__installLocalPackageAct.setEnabled(enablePip) + + self.__installRequirementsAct.setEnabled(enablePip) + self.__uninstallRequirementsAct.setEnabled(enablePip) + self.__generateRequirementsAct.setEnabled(enablePip) + + self.__editVirtualenvConfigAct.setEnabled(enable) + + @pyqtSlot() + def __installPip(self): + """ + Private slot to install pip into the selected environment. + """ + venvName = self.environmentsComboBox.currentText() + if venvName: + self.__pip.installPip(venvName) + + @pyqtSlot() + def __installPipUser(self): + """ + Private slot to install pip into the user site for the selected + environment. + """ + venvName = self.environmentsComboBox.currentText() + if venvName: + self.__pip.installPip(venvName, userSite=True) + + @pyqtSlot() + def __repairPip(self): + """ + Private slot to repair the pip installation of the selected + environment. + """ + venvName = self.environmentsComboBox.currentText() + if venvName: + self.__pip.repairPip(venvName) + + @pyqtSlot() + def __installPackages(self): + """ + Private slot to install packages to be given by the user. + """ + venvName = self.environmentsComboBox.currentText() + if venvName: + from .PipPackagesInputDialog import PipPackagesInputDialog + dlg = PipPackagesInputDialog(self, self.tr("Install Packages")) + if dlg.exec_() == QDialog.Accepted: + packages, user = dlg.getData() + if packages: + self.__pip.installPackages(packages, venvName=venvName, + userSite=user) + + @pyqtSlot() + def __installLocalPackage(self): + """ + Private slot to install a package available on local storage. + """ + venvName = self.environmentsComboBox.currentText() + if venvName: + from .PipFileSelectionDialog import PipFileSelectionDialog + dlg = PipFileSelectionDialog(self, "package") + if dlg.exec_() == QDialog.Accepted: + package, user = dlg.getData() + if package and os.path.exists(package): + self.__pip.installPackages([package], venvName=venvName, + userSite=user) + + @pyqtSlot() + def __installRequirements(self): + """ + + """ + # TODO: call pip.installRequirements() + + @pyqtSlot() + def __uninstallRequirements(self): + """ + + """ + # TODO: call pip.uninstallRequirements() + + @pyqtSlot() + def __generateRequirements(self): + """ + Private slot to generate the contents for a requirements file. + """ + # TODO: modify to get selected environment + from .PipFreezeDialog import PipFreezeDialog + self.__freezeDialog = PipFreezeDialog(self) + self.__freezeDialog.show() + self.__freezeDialog.start() + + @pyqtSlot() + def __editUserConfiguration(self): + """ + Private slot to edit the user configuration. + """ + self.__editConfiguration() + + @pyqtSlot() + def __editVirtualenvConfiguration(self): + """ + Private slot to edit the current virtualenv configuration. + """ + # TODO: modify to get selected environment + self.__editConfiguration(virtualenv=True) + + def __editConfiguration(self, virtualenv=False): + """ + Private method to edit a configuration. + + @param virtualenv flag indicating to edit the current virtualenv + configuration file + @type bool + """ + # TODO: modify to use venvName + from QScintilla.MiniEditor import MiniEditor + if virtualenv: + cfgFile = self.__getVirtualenvConfig() + if not cfgFile: + return + else: + cfgFile = self.__getUserConfig() + cfgDir = os.path.dirname(cfgFile) + if not cfgDir: + E5MessageBox.critical( + None, + self.tr("Edit Configuration"), + self.tr("""No valid configuration path determined.""" + """ Is a virtual environment selected? Aborting""")) + return + + try: + if not os.path.isdir(cfgDir): + os.makedirs(cfgDir) + except OSError: + E5MessageBox.critical( + None, + self.tr("Edit Configuration"), + self.tr("""No valid configuration path determined.""" + """ Is a virtual environment selected? Aborting""")) + return + + if not os.path.exists(cfgFile): + try: + f = open(cfgFile, "w") + f.write("[global]\n") + f.close() + except (IOError, OSError): + # ignore these + pass + + # check, if the destination is writeable + if not os.access(cfgFile, os.W_OK): + E5MessageBox.critical( + None, + self.tr("Edit Configuration"), + self.tr("""No valid configuartion path determined.""" + """ Is a virtual environment selected? Aborting""")) + return + + self.__editor = MiniEditor(cfgFile, "Properties") + self.__editor.show() + + def __pipConfigure(self): + """ + Private slot to open the configuration page. + """ + e5App().getObject("UserInterface").showPreferences("pipPage")