Mon, 11 Feb 2019 19:57:53 +0100
Conda: continued implementing the conda menu functionality
- Install ...
- Generate Requirements
- Clone Environment
- Delete Environment
- Clean ...
--- a/CondaInterface/Conda.py Mon Feb 11 19:33:12 2019 +0100 +++ b/CondaInterface/Conda.py Mon Feb 11 19:57:53 2019 +0100 @@ -24,7 +24,7 @@ import Globals import Preferences -from . import rootPrefix +from . import rootPrefix, condaVersion from .CondaExecDialog import CondaExecDialog @@ -456,8 +456,6 @@ if not name and not prefix: raise RuntimeError("One of 'name' or 'prefix' must be given.") - # TODO: not implemented yet - if packages: args = [ "install", @@ -517,8 +515,9 @@ "remove", "--json", "--yes", - "--prune", ] + if condaVersion() >= (4, 4, 0): + args.append("--prune",) if name: args.extend(["--name", name]) elif prefix: @@ -610,6 +609,9 @@ def updateConda(self): """ Public method to update conda itself. + + @return flag indicating success + @rtype bool """ args = [ "update", @@ -630,7 +632,7 @@ Public method to create a conda configuration with default values. """ args = [ - "config", + "config", "--write-default", "--quiet" ] @@ -660,7 +662,7 @@ proc = QProcess() proc.start(exe, ["info", "--json"]) if proc.waitForStarted(15000): - if proc.waitForFinished(15000): + if proc.waitForFinished(30000): output = str(proc.readAllStandardOutput(), Preferences.getSystem("IOEncoding"), 'replace').strip() @@ -670,3 +672,63 @@ 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/CondaExportDialog.py Mon Feb 11 19:57:53 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 Mon Feb 11 19:57:53 2019 +0100 @@ -0,0 +1,214 @@ +<?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="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Conda Environment:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="environmentLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Requirements File:</string> + </property> + </widget> + </item> + <item> + <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>
--- a/CondaInterface/CondaInfoDialog.ui Mon Feb 11 19:33:12 2019 +0100 +++ b/CondaInterface/CondaInfoDialog.ui Mon Feb 11 19:57:53 2019 +0100 @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>650</width> - <height>893</height> + <height>650</height> </rect> </property> <property name="windowTitle">
--- a/CondaInterface/CondaPackageDetailsWidget.py Mon Feb 11 19:33:12 2019 +0100 +++ b/CondaInterface/CondaPackageDetailsWidget.py Mon Feb 11 19:57:53 2019 +0100 @@ -60,7 +60,7 @@ dt = QDateTime.fromMSecsSinceEpoch(details["timestamp"], Qt.UTC) self.timestampLabel.setText(dt.toString("yyyy-MM-dd hh:mm:ss t")) - self.resize(600, 500) + self.resize(600, 450) class CondaPackageDetailsDialog(QDialog):
--- a/CondaInterface/CondaPackageDetailsWidget.ui Mon Feb 11 19:33:12 2019 +0100 +++ b/CondaInterface/CondaPackageDetailsWidget.ui Mon Feb 11 19:57:53 2019 +0100 @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>612</width> - <height>492</height> + <width>600</width> + <height>450</height> </rect> </property> <layout class="QGridLayout" name="gridLayout">
--- a/CondaInterface/CondaPackagesWidget.py Mon Feb 11 19:33:12 2019 +0100 +++ b/CondaInterface/CondaPackagesWidget.py Mon Feb 11 19:57:53 2019 +0100 @@ -14,9 +14,9 @@ from PyQt5.QtCore import pyqtSlot, Qt from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import QWidget, QToolButton, QMenu, QTreeWidgetItem, \ - QApplication + QApplication, QLineEdit -from E5Gui import E5MessageBox +from E5Gui import E5FileDialog, E5MessageBox, E5TextInputDialog from E5Gui.E5Application import e5App from .Ui_CondaPackagesWidget import Ui_CondaPackagesWidget @@ -100,19 +100,16 @@ self.__cleanMenu = QMenu(self.tr("Clean"), self) self.__cleanMenu.addAction( - self.tr("All"), lambda: self.__cleanMenuAction("all")) + self.tr("All"), lambda: self.__conda.cleanConda("all")) self.__cleanMenu.addAction( - self.tr("Cache"), lambda: self.__cleanMenuAction("index-cache")) + self.tr("Cache"), lambda: self.__conda.cleanConda("index-cache")) self.__cleanMenu.addAction( self.tr("Lock Files"), - lambda: self.__cleanMenuAction("lock")) - self.__cleanMenu.addAction( - self.tr("Packages"), lambda: self.__cleanMenuAction("packages")) + lambda: self.__conda.cleanConda("lock")) self.__cleanMenu.addAction( - self.tr("Tarballs"), lambda: self.__cleanMenuAction("tarballs")) + self.tr("Packages"), lambda: self.__conda.cleanConda("packages")) self.__cleanMenu.addAction( - self.tr("Temporary Files"), - lambda: self.__cleanMenuAction("temp_files")) + self.tr("Tarballs"), lambda: self.__conda.cleanConda("tarballs")) self.__condaMenu.addAction( self.tr("About Conda..."), self.__aboutConda) @@ -123,17 +120,20 @@ self.__envActs.append(self.__condaMenu.addAction( self.tr("Install Packages"), self.__installPackages)) self.__envActs.append(self.__condaMenu.addAction( - self.tr("Install Local Packages"), self.__installLocalPackage)) - 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.__envActs.append(self.__condaMenu.addMenu(self.__cleanMenu)) + self.__condaMenu.addMenu(self.__cleanMenu) self.__condaMenu.addSeparator() self.__condaMenu.addAction( self.tr("Edit User Configuration..."), @@ -509,14 +509,15 @@ 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 + Private slot to show some information about the conda installation. """ - # TODO: implement this menu function - # conda info --all infoDict = self.__conda.getCondaInformation() from .CondaInfoDialog import CondaInfoDialog @@ -526,55 +527,106 @@ @pyqtSlot() def __installPackages(self): """ - Private slot to + Private slot to install packages. """ - # TODO: implement this menu function - # conda install ... - - @pyqtSlot() - def __installLocalPackage(self): - """ - Private slot to - """ - # TODO: implement this menu function - # conda install --use-local + 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 + Private slot to install packages from requirements files. """ - # TODO: implement this menu function - # conda install --file FILE1 --file FILE2 ... + 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 + Private slot to generate a requirements file. """ - # TODO: implement this menu function - # conda list --export + 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 - """ - # TODO: implement this menu function - # conda create --clone ENV - - def __cleanMenuAction(self, cleanAction): - """ - Private method to + Private slot to clone a conda environment. """ - # TODO: implement this menu function - # conda clean - # --all (act = 'all') - # --index-cache (act = 'index-cache') - # --lock (act = 'lock') - # --packages (act = 'packages') - # --tarballs (act = 'tarballs') - # -c prefix1 prefix2 ... (act = 'temp_files') + prefix = self.environmentsComboBox.itemData( + self.environmentsComboBox.currentIndex()) + if prefix: + ok, envName = E5TextInputDialog.getText( + self, + self.tr("Clone Environment"), + self.tr("Enter name for the cloned environment:"), + QLineEdit.Normal) + if ok and envName.strip(): + args = [ + "--name", + envName.strip(), + "--clone", + prefix, + ] + self.__conda.createCondaEnvironment(args) + # TODO: add code to register the cloned env with the virt env manager + + @pyqtSlot() + def __createEnvironment(self): + """ + Private slot to create a conda environment from a requirements file. + """ + # TODO: implement this + + @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):
--- a/eric6.e4p Mon Feb 11 19:33:12 2019 +0100 +++ b/eric6.e4p Mon Feb 11 19:57:53 2019 +0100 @@ -18,6 +18,7 @@ <Sources> <Source>CondaInterface/Conda.py</Source> <Source>CondaInterface/CondaExecDialog.py</Source> + <Source>CondaInterface/CondaExportDialog.py</Source> <Source>CondaInterface/CondaInfoDialog.py</Source> <Source>CondaInterface/CondaPackageDetailsWidget.py</Source> <Source>CondaInterface/CondaPackagesWidget.py</Source> @@ -1721,6 +1722,7 @@ </Sources> <Forms> <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> @@ -2257,14 +2259,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>