|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2018 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog to enter the data of a virtual environment. |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 from PyQt6.QtCore import pyqtSlot, Qt |
|
13 from PyQt6.QtWidgets import QDialog, QDialogButtonBox |
|
14 |
|
15 from EricWidgets.EricPathPicker import EricPathPickerModes |
|
16 |
|
17 from .Ui_VirtualenvAddEditDialog import Ui_VirtualenvAddEditDialog |
|
18 |
|
19 import Globals |
|
20 import Utilities |
|
21 |
|
22 |
|
23 class VirtualenvAddEditDialog(QDialog, Ui_VirtualenvAddEditDialog): |
|
24 """ |
|
25 Class implementing a dialog to enter the data of a virtual environment. |
|
26 """ |
|
27 def __init__(self, manager, venvName="", venvDirectory="", |
|
28 venvInterpreter="", isGlobal=False, isConda=False, |
|
29 isRemote=False, execPath="", baseDir="", parent=None): |
|
30 """ |
|
31 Constructor |
|
32 |
|
33 @param manager reference to the virtual environment manager |
|
34 @type VirtualenvManager |
|
35 @param venvName logical name of a virtual environment for editing |
|
36 @type str |
|
37 @param venvDirectory directory of the virtual environment |
|
38 @type str |
|
39 @param venvInterpreter Python interpreter of the virtual environment |
|
40 @type str |
|
41 @param isGlobal flag indicating a global environment |
|
42 @type bool |
|
43 @param isConda flag indicating an Anaconda virtual environment |
|
44 @type bool |
|
45 @param isRemote flag indicating a remotely accessed environment |
|
46 @type bool |
|
47 @param execPath search path string to be prepended to the PATH |
|
48 environment variable |
|
49 @type str |
|
50 @param baseDir base directory for the virtual environments |
|
51 @type str |
|
52 @param parent reference to the parent widget |
|
53 @type QWidget |
|
54 """ |
|
55 super().__init__(parent) |
|
56 self.setupUi(self) |
|
57 |
|
58 self.__venvName = venvName |
|
59 self.__manager = manager |
|
60 self.__editMode = bool(venvName) |
|
61 |
|
62 if self.__editMode: |
|
63 self.setWindowTitle(self.tr("Edit Virtual Environment")) |
|
64 else: |
|
65 self.setWindowTitle(self.tr("Add Virtual Environment")) |
|
66 |
|
67 self.__envBaseDir = baseDir |
|
68 if not self.__envBaseDir: |
|
69 self.__envBaseDir = Utilities.getHomeDir() |
|
70 |
|
71 self.targetDirectoryPicker.setMode(EricPathPickerModes.DIRECTORY_MODE) |
|
72 self.targetDirectoryPicker.setWindowTitle( |
|
73 self.tr("Virtualenv Target Directory")) |
|
74 self.targetDirectoryPicker.setDefaultDirectory(self.__envBaseDir) |
|
75 |
|
76 self.pythonExecPicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) |
|
77 self.pythonExecPicker.setWindowTitle( |
|
78 self.tr("Python Interpreter")) |
|
79 self.pythonExecPicker.setDefaultDirectory( |
|
80 Globals.getPythonExecutable()) |
|
81 |
|
82 self.execPathEdit.setToolTip(self.tr( |
|
83 "Enter the executable search path to be prepended to the PATH" |
|
84 " environment variable. Use '{0}' as the separator.").format( |
|
85 os.pathsep) |
|
86 ) |
|
87 |
|
88 self.nameEdit.setText(venvName) |
|
89 if venvDirectory: |
|
90 self.targetDirectoryPicker.setText(venvDirectory, |
|
91 toNative=not isRemote) |
|
92 else: |
|
93 self.targetDirectoryPicker.setText(self.__envBaseDir, |
|
94 toNative=not isRemote) |
|
95 if not venvInterpreter and venvDirectory and not isRemote: |
|
96 py = self.__detectPythonInterpreter(venvDirectory) |
|
97 self.pythonExecPicker.setText(py) |
|
98 else: |
|
99 self.pythonExecPicker.setText(venvInterpreter, |
|
100 toNative=not isRemote) |
|
101 |
|
102 self.globalCheckBox.setChecked(isGlobal) |
|
103 self.anacondaCheckBox.setChecked(isConda) |
|
104 self.remoteCheckBox.setChecked(isRemote) |
|
105 self.execPathEdit.setText(execPath) |
|
106 |
|
107 self.__updateOk() |
|
108 |
|
109 self.nameEdit.setFocus(Qt.FocusReason.OtherFocusReason) |
|
110 |
|
111 def __updateOk(self): |
|
112 """ |
|
113 Private slot to update the state of the OK button. |
|
114 """ |
|
115 enable = ( |
|
116 (bool(self.nameEdit.text()) and |
|
117 (self.nameEdit.text() == self.__venvName or |
|
118 self.__manager.isUnique(self.nameEdit.text()))) |
|
119 if self.__editMode else |
|
120 (bool(self.nameEdit.text()) and |
|
121 self.__manager.isUnique(self.nameEdit.text())) |
|
122 ) |
|
123 |
|
124 if not self.globalCheckBox.isChecked(): |
|
125 enable &= ( |
|
126 self.remoteCheckBox.isChecked() or ( |
|
127 bool(self.targetDirectoryPicker.text()) and |
|
128 self.targetDirectoryPicker.text() != self.__envBaseDir and |
|
129 os.path.exists(self.targetDirectoryPicker.text()) |
|
130 ) |
|
131 ) |
|
132 |
|
133 enable = ( |
|
134 enable and |
|
135 bool(self.pythonExecPicker.text()) and ( |
|
136 self.remoteCheckBox.isChecked() or |
|
137 os.access(self.pythonExecPicker.text(), os.X_OK) |
|
138 ) |
|
139 ) |
|
140 |
|
141 self.buttonBox.button( |
|
142 QDialogButtonBox.StandardButton.Ok).setEnabled(enable) |
|
143 |
|
144 def __detectPythonInterpreter(self, venvDirectory): |
|
145 """ |
|
146 Private method to search for a suitable Python interpreter inside an |
|
147 environment. |
|
148 |
|
149 @param venvDirectory directory for the virtual environment |
|
150 @type str |
|
151 @return detected Python interpreter or empty string |
|
152 @rtype str |
|
153 """ |
|
154 if venvDirectory: |
|
155 # try to determine a Python interpreter name |
|
156 if Utilities.isWindowsPlatform(): |
|
157 candidates = ( |
|
158 os.path.join(venvDirectory, "Scripts", "python.exe"), |
|
159 os.path.join(venvDirectory, "python.exe"), |
|
160 ) |
|
161 else: |
|
162 candidates = ( |
|
163 os.path.join(venvDirectory, "bin", "python3"), |
|
164 ) |
|
165 for py in candidates: |
|
166 if os.path.exists(py): |
|
167 return py |
|
168 |
|
169 return "" |
|
170 |
|
171 @pyqtSlot(str) |
|
172 def on_nameEdit_textChanged(self, txt): |
|
173 """ |
|
174 Private slot to handle changes of the logical name. |
|
175 |
|
176 @param txt current logical name |
|
177 @type str |
|
178 """ |
|
179 self.__updateOk() |
|
180 |
|
181 @pyqtSlot(str) |
|
182 def on_targetDirectoryPicker_textChanged(self, txt): |
|
183 """ |
|
184 Private slot to handle changes of the virtual environment directory. |
|
185 |
|
186 @param txt virtual environment directory |
|
187 @type str |
|
188 """ |
|
189 self.__updateOk() |
|
190 |
|
191 if txt: |
|
192 self.pythonExecPicker.setDefaultDirectory(txt) |
|
193 else: |
|
194 self.pythonExecPicker.setDefaultDirectory( |
|
195 Globals.getPythonExecutable()) |
|
196 py = self.__detectPythonInterpreter(txt) |
|
197 if py: |
|
198 self.pythonExecPicker.setText(py) |
|
199 |
|
200 @pyqtSlot(str) |
|
201 def on_pythonExecPicker_textChanged(self, txt): |
|
202 """ |
|
203 Private slot to handle changes of the virtual environment interpreter. |
|
204 |
|
205 @param txt virtual environment interpreter |
|
206 @type str |
|
207 """ |
|
208 self.__updateOk() |
|
209 |
|
210 @pyqtSlot(bool) |
|
211 def on_globalCheckBox_toggled(self, checked): |
|
212 """ |
|
213 Private slot handling a change of the global check box state. |
|
214 |
|
215 @param checked state of the check box |
|
216 @type bool |
|
217 """ |
|
218 self.__updateOk() |
|
219 |
|
220 @pyqtSlot(bool) |
|
221 def on_remoteCheckBox_toggled(self, checked): |
|
222 """ |
|
223 Private slot handling a change of the remote check box state. |
|
224 |
|
225 @param checked state of the check box |
|
226 @type bool |
|
227 """ |
|
228 self.__updateOk() |
|
229 |
|
230 @pyqtSlot(bool) |
|
231 def on_anacondaCheckBox_clicked(self, checked): |
|
232 """ |
|
233 Private slot handling a user click on this check box. |
|
234 |
|
235 @param checked state of the check box |
|
236 @type bool |
|
237 """ |
|
238 if checked and not bool(self.execPathEdit.text()): |
|
239 # prepopulate the execPathEdit widget |
|
240 if Utilities.isWindowsPlatform(): |
|
241 self.execPathEdit.setText(os.pathsep.join([ |
|
242 self.targetDirectoryPicker.text(), |
|
243 os.path.join(self.targetDirectoryPicker.text(), |
|
244 "Scripts"), |
|
245 os.path.join(self.targetDirectoryPicker.text(), |
|
246 "Library", "bin"), |
|
247 ])) |
|
248 else: |
|
249 self.execPathEdit.setText( |
|
250 os.path.join(self.targetDirectoryPicker.text(), |
|
251 "bin"), |
|
252 ) |
|
253 |
|
254 def getData(self): |
|
255 """ |
|
256 Public method to retrieve the entered data. |
|
257 |
|
258 @return tuple containing the logical name, the directory, the |
|
259 interpreter of the virtual environment, a flag indicating a |
|
260 global environment, a flag indicating an Anaconda environment, |
|
261 a flag indicating a remotely accessed environment and a string |
|
262 to be prepended to the PATH environment variable |
|
263 @rtype tuple of (str, str, str, bool, bool, bool, str) |
|
264 """ |
|
265 nativePaths = not self.remoteCheckBox.isChecked() |
|
266 return ( |
|
267 self.nameEdit.text(), |
|
268 self.targetDirectoryPicker.text(toNative=nativePaths), |
|
269 self.pythonExecPicker.text(toNative=nativePaths), |
|
270 self.globalCheckBox.isChecked(), |
|
271 self.anacondaCheckBox.isChecked(), |
|
272 self.remoteCheckBox.isChecked(), |
|
273 self.execPathEdit.text(), |
|
274 ) |