Tue, 19 Feb 2019 19:56:24 +0100
PipInterface: continued with the pip interface widget.
# -*- coding: utf-8 -*- # Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the pip packages management widget. """ from __future__ import unicode_literals from PyQt5.QtCore import pyqtSlot, Qt from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import QWidget, QToolButton, QApplication, QHeaderView, \ QTreeWidgetItem from E5Gui.E5Application import e5App from .Ui_PipPackagesWidget import Ui_PipPackagesWidget import UI.PixmapCache from .Pip import Pip class PipPackagesWidget(QWidget, Ui_PipPackagesWidget): """ Class implementing the pip packages management widget. """ ShowProcessGeneralMode = 0 ShowProcessClassifiersMode = 1 ShowProcessEntryPointsMode = 2 ShowProcessFilesListMode = 3 def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(PipPackagesWidget, self).__init__(parent) self.setupUi(self) self.pipMenuButton.setObjectName( "navigation_supermenu_button") self.pipMenuButton.setIcon(UI.PixmapCache.getIcon("superMenu")) self.pipMenuButton.setToolTip(self.tr("pip Menu")) self.pipMenuButton.setPopupMode(QToolButton.InstantPopup) self.pipMenuButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.pipMenuButton.setFocusPolicy(Qt.NoFocus) self.pipMenuButton.setAutoRaise(True) self.pipMenuButton.setShowMenuInside(True) self.searchToggleButton.setIcon(UI.PixmapCache.getIcon("find")) self.__pip = Pip(self) self.packagesList.header().setSortIndicator(0, Qt.AscendingOrder) self.__infoLabels = { "name": self.tr("Name:"), "version": self.tr("Version:"), "location": self.tr("Location:"), "requires": self.tr("Requires:"), "summary": self.tr("Summary:"), "home-page": self.tr("Homepage:"), "author": self.tr("Author:"), "author-email": self.tr("Author Email:"), "license": self.tr("License:"), "metadata-version": self.tr("Metadata Version:"), "installer": self.tr("Installer:"), "classifiers": self.tr("Classifiers:"), "entry-points": self.tr("Entry Points:"), "files": self.tr("Files:"), } self.infoWidget.setHeaderLabels(["Key", "Value"]) venvManager = e5App().getObject("VirtualEnvManager") venvManager.virtualEnvironmentAdded.connect( self.on_refreshButton_clicked) venvManager.virtualEnvironmentRemoved.connect( self.on_refreshButton_clicked) project = e5App().getObject("Project") project.projectOpened.connect( self.on_refreshButton_clicked) project.projectClosed.connect( self.on_refreshButton_clicked) self.__initPipMenu() self.__populateEnvironments() self.__updateActionButtons() self.statusLabel.hide() self.searchWidget.hide() def __populateEnvironments(self): """ Private method to get a list of environments and populate the selector. """ self.environmentsComboBox.addItem("") projectVenv = self.__pip.getProjectEnvironmentString() if projectVenv: self.environmentsComboBox.addItem(projectVenv) self.environmentsComboBox.addItems(self.__pip.getVirtualenvNames()) ####################################################################### ## Slots handling widget signals below ####################################################################### 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())) def __refreshPackagesList(self): """ Private method to referesh the packages list. """ self.packagesList.clear() venvName = self.environmentsComboBox.currentText() if venvName: interpreter = self.__pip.getVirtualenvInterpreter(venvName) if interpreter: 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.__pip.getInstalledPackages( venvName, localPackages=self.localCheckBox.isChecked(), notRequired=self.notRequiredCheckBox.isChecked(), usersite=self.userCheckBox.isChecked(), ) for package, version in installedPackages: QTreeWidgetItem(self.packagesList, [package, version]) self.packagesList.setUpdatesEnabled(True) self.statusLabel.setText( self.tr("Getting outdated packages...")) QApplication.processEvents() # 2. update with update information self.packagesList.setUpdatesEnabled(False) outdatedPackages = self.__pip.getOutdatedPackages( venvName, localPackages=self.localCheckBox.isChecked(), notRequired=self.notRequiredCheckBox.isChecked(), usersite=self.userCheckBox.isChecked(), ) for package, _version, latest in outdatedPackages: items = self.packagesList.findItems( package, Qt.MatchExactly | Qt.MatchCaseSensitive) if items: itm = items[0] itm.setText(2, latest) 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(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.__refreshPackagesList() @pyqtSlot(bool) def on_localCheckBox_clicked(self, checked): """ Private slot handling the switching of the local mode. @param checked state of the local check box @type bool """ self.__refreshPackagesList() @pyqtSlot(bool) def on_notRequiredCheckBox_clicked(self, checked): """ Private slot handling the switching of the 'not required' mode. @param checked state of the 'not required' check box @type bool """ self.__refreshPackagesList() @pyqtSlot(bool) def on_userCheckBox_clicked(self, checked): """ Private slot handling the switching of the 'user-site' mode. @param checked state of the 'user-site' check box @type bool """ self.__refreshPackagesList() @pyqtSlot() def on_packagesList_itemSelectionChanged(self): """ Private slot handling the selection of a package. """ self.infoWidget.clear() if len(self.packagesList.selectedItems()) == 1: itm = self.packagesList.selectedItems()[0] environment = self.environmentsComboBox.currentText() interpreter = self.__pip.getVirtualenvInterpreter(environment) if not interpreter: return QApplication.setOverrideCursor(Qt.WaitCursor) args = ["-m", "pip", "show"] if self.verboseCheckBox.isChecked(): args.append("--verbose") if self.installedFilesCheckBox.isChecked(): args.append("--files") args.append(itm.text(0)) success, output = self.__pip.runProcess(args, interpreter) if success and output: mode = self.ShowProcessGeneralMode for line in output.splitlines(): line = line.rstrip() if line != "---": if mode != self.ShowProcessGeneralMode: if line[0] == " ": QTreeWidgetItem( self.infoWidget, [" ", line.strip()]) else: mode = self.ShowProcessGeneralMode if mode == self.ShowProcessGeneralMode: try: label, info = line.split(": ", 1) except ValueError: label = line[:-1] info = "" label = label.lower() if label in self.__infoLabels: QTreeWidgetItem( self.infoWidget, [self.__infoLabels[label], info]) if label == "files": mode = self.ShowProcessFilesListMode elif label == "classifiers": mode = self.ShowProcessClassifiersMode elif label == "entry-points": mode = self.ShowProcessEntryPointsMode self.infoWidget.scrollToTop() header = self.infoWidget.header() header.setStretchLastSection(False) header.resizeSections(QHeaderView.ResizeToContents) if header.sectionSize(0) + header.sectionSize(1) < header.width(): header.setStretchLastSection(True) QApplication.restoreOverrideCursor() self.__updateActionButtons() @pyqtSlot(bool) def on_verboseCheckBox_clicked(self, checked): """ Private slot to handle a change of the verbose package information checkbox. @param checked state of the checkbox @type bool """ self.on_packagesList_itemSelectionChanged() @pyqtSlot(bool) def on_installedFilesCheckBox_clicked(self, checked): """ Private slot to handle a change of the installed files information checkbox. @param checked state of the checkbox @type bool """ self.on_packagesList_itemSelectionChanged() @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: ok = self.__executeUpgradePackages(packages) if ok: self.on_refreshButton_clicked() @pyqtSlot() def on_upgradeAllButton_clicked(self): """ Private slot to upgrade all packages of the selected environment. """ packages = [itm.text(0) for itm in self.__allUpdateableItems()] if packages: ok = self.__executeUpgradePackages(packages) 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: ok = self.__pip.uninstallPackages( packages, venvName=self.environmentsComboBox.currentText()) if ok: self.on_refreshButton_clicked() def __executeUpgradePackages(self, packages): """ Private method to execute the pip upgrade command. @param packages list of package names to be upgraded @type list of str @return flag indicating success @rtype bool """ ok = self.__pip.upgradePackages( packages, venvName=self.environmentsComboBox.currentText(), userSite=self.userCheckBox.isChecked()) return ok ####################################################################### ## Search widget related methods below ####################################################################### def __updateSearchActionButtons(self): """ Private method to update the action button states of the search widget. """ # TODO: adjust this like search dialog 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())) @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 ####################################################################### def __initPipMenu(self): """ Private method to create the super menu and attach it to the super menu button. """ self.__pip.initActions() self.__pipMenu = self.__pip.initMenu() self.pipMenuButton.setMenu(self.__pipMenu)