|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2006 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Programs page. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 try: |
|
12 str = unicode |
|
13 except NameError: |
|
14 pass |
|
15 |
|
16 import os |
|
17 import re |
|
18 import sys |
|
19 |
|
20 from PyQt5.QtCore import pyqtSlot, Qt, QProcess |
|
21 from PyQt5.QtGui import QCursor |
|
22 from PyQt5.QtWidgets import QApplication, QTreeWidgetItem, QHeaderView, \ |
|
23 QDialog, QDialogButtonBox |
|
24 |
|
25 from E5Gui.E5Application import e5App |
|
26 |
|
27 from .Ui_ProgramsDialog import Ui_ProgramsDialog |
|
28 |
|
29 import Preferences |
|
30 import Utilities |
|
31 |
|
32 |
|
33 class ProgramsDialog(QDialog, Ui_ProgramsDialog): |
|
34 """ |
|
35 Class implementing the Programs page. |
|
36 """ |
|
37 def __init__(self, parent=None): |
|
38 """ |
|
39 Constructor |
|
40 |
|
41 @param parent The parent widget of this dialog. (QWidget) |
|
42 """ |
|
43 super(ProgramsDialog, self).__init__(parent) |
|
44 self.setupUi(self) |
|
45 self.setObjectName("ProgramsDialog") |
|
46 self.setWindowFlags(Qt.Window) |
|
47 |
|
48 self.__hasSearched = False |
|
49 |
|
50 self.programsList.headerItem().setText( |
|
51 self.programsList.columnCount(), "") |
|
52 |
|
53 self.searchButton = self.buttonBox.addButton( |
|
54 self.tr("Search"), QDialogButtonBox.ActionRole) |
|
55 self.searchButton.setToolTip( |
|
56 self.tr("Press to search for programs")) |
|
57 |
|
58 def show(self): |
|
59 """ |
|
60 Public slot to show the dialog. |
|
61 """ |
|
62 QDialog.show(self) |
|
63 if not self.__hasSearched: |
|
64 self.on_programsSearchButton_clicked() |
|
65 |
|
66 def on_buttonBox_clicked(self, button): |
|
67 """ |
|
68 Private slot called by a button of the button box clicked. |
|
69 |
|
70 @param button button that was clicked (QAbstractButton) |
|
71 """ |
|
72 if button == self.searchButton: |
|
73 self.on_programsSearchButton_clicked() |
|
74 |
|
75 @pyqtSlot() |
|
76 def on_programsSearchButton_clicked(self): |
|
77 """ |
|
78 Private slot to search for all supported/required programs. |
|
79 """ |
|
80 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) |
|
81 QApplication.processEvents() |
|
82 |
|
83 self.programsList.clear() |
|
84 header = self.programsList.header() |
|
85 header.setSortIndicator(0, Qt.AscendingOrder) |
|
86 header.setSortIndicatorShown(False) |
|
87 |
|
88 # 1. do the Qt4/Qt5 programs |
|
89 # 1a. Translation Converter |
|
90 exe = Utilities.isWindowsPlatform() and \ |
|
91 "{0}.exe".format(Utilities.generateQtToolName("lrelease")) or \ |
|
92 Utilities.generateQtToolName("lrelease") |
|
93 exe = os.path.join(Utilities.getQtBinariesPath(), exe) |
|
94 version = self.__createProgramEntry( |
|
95 self.tr("Translation Converter (Qt)"), exe, '-version', |
|
96 'lrelease', -1) |
|
97 # 1b. Qt Designer |
|
98 if Utilities.isWindowsPlatform(): |
|
99 exe = os.path.join( |
|
100 Utilities.getQtBinariesPath(), |
|
101 "{0}.exe".format(Utilities.generateQtToolName("designer"))) |
|
102 elif Utilities.isMacPlatform(): |
|
103 exe = Utilities.getQtMacBundle("designer") |
|
104 else: |
|
105 exe = os.path.join( |
|
106 Utilities.getQtBinariesPath(), |
|
107 Utilities.generateQtToolName("designer")) |
|
108 self.__createProgramEntry( |
|
109 self.tr("Qt Designer"), exe, version=version) |
|
110 # 1c. Qt Linguist |
|
111 if Utilities.isWindowsPlatform(): |
|
112 exe = os.path.join( |
|
113 Utilities.getQtBinariesPath(), |
|
114 "{0}.exe".format(Utilities.generateQtToolName("linguist"))) |
|
115 elif Utilities.isMacPlatform(): |
|
116 exe = Utilities.getQtMacBundle("linguist") |
|
117 else: |
|
118 exe = os.path.join( |
|
119 Utilities.getQtBinariesPath(), |
|
120 Utilities.generateQtToolName("linguist")) |
|
121 self.__createProgramEntry( |
|
122 self.tr("Qt Linguist"), exe, version=version) |
|
123 # 1d. Qt Assistant |
|
124 if Utilities.isWindowsPlatform(): |
|
125 exe = os.path.join( |
|
126 Utilities.getQtBinariesPath(), |
|
127 "{0}.exe".format(Utilities.generateQtToolName("assistant"))) |
|
128 elif Utilities.isMacPlatform(): |
|
129 exe = Utilities.getQtMacBundle("assistant") |
|
130 else: |
|
131 exe = os.path.join( |
|
132 Utilities.getQtBinariesPath(), |
|
133 Utilities.generateQtToolName("assistant")) |
|
134 self.__createProgramEntry( |
|
135 self.tr("Qt Assistant"), exe, version=version) |
|
136 |
|
137 # 2. do the PyQt programs |
|
138 # 2.1 do the PyQt4 programs |
|
139 # 2.1a. Translation Extractor PyQt4 |
|
140 self.__createProgramEntry( |
|
141 self.tr("Translation Extractor (Python, PyQt4)"), |
|
142 Utilities.generatePyQtToolPath("pylupdate4"), |
|
143 '-version', 'pylupdate', -1) |
|
144 # 2.1b. Forms Compiler PyQt4 |
|
145 self.__createProgramEntry( |
|
146 self.tr("Forms Compiler (Python, PyQt4)"), |
|
147 Utilities.generatePyQtToolPath("pyuic4", ["py3uic4", "py2uic4"]), |
|
148 '--version', 'Python User', 4) |
|
149 # 2.1c. Resource Compiler PyQt4 |
|
150 self.__createProgramEntry( |
|
151 self.tr("Resource Compiler (Python, PyQt4)"), |
|
152 Utilities.generatePyQtToolPath("pyrcc4"), |
|
153 '-version', 'Resource Compiler', -1) |
|
154 |
|
155 # 2.2 do the PyQt5 programs |
|
156 # 2.2a. Translation Extractor PyQt5 |
|
157 self.__createProgramEntry( |
|
158 self.tr("Translation Extractor (Python, PyQt5)"), |
|
159 Utilities.generatePyQtToolPath("pylupdate5"), |
|
160 '-version', 'pylupdate', -1) |
|
161 # 2.2b. Forms Compiler PyQt5 |
|
162 self.__createProgramEntry( |
|
163 self.tr("Forms Compiler (Python, PyQt5)"), |
|
164 Utilities.generatePyQtToolPath("pyuic5", ["py3uic5", "py2uic5"]), |
|
165 '--version', 'Python User', 4) |
|
166 # 2.2c. Resource Compiler PyQt5 |
|
167 self.__createProgramEntry( |
|
168 self.tr("Resource Compiler (Python, PyQt5)"), |
|
169 Utilities.generatePyQtToolPath("pyrcc5"), |
|
170 '-version', '', -1, versionRe='Resource Compiler|pyrcc5') |
|
171 |
|
172 # 3.1 do the PySide programs |
|
173 # 3.1a. Translation Extractor PySide |
|
174 self.__createProgramEntry( |
|
175 self.tr("Translation Extractor (Python, PySide)"), |
|
176 Utilities.generatePySideToolPath("pyside-lupdate", "1"), |
|
177 '-version', '', -1, versionRe='lupdate') |
|
178 # 3.1b. Forms Compiler PySide |
|
179 self.__createProgramEntry( |
|
180 self.tr("Forms Compiler (Python, PySide)"), |
|
181 Utilities.generatePySideToolPath("pyside-uic", "1"), |
|
182 '--version', 'PySide User', 5, versionCleanup=(0, -1)) |
|
183 # 3.1c Resource Compiler PySide |
|
184 self.__createProgramEntry( |
|
185 self.tr("Resource Compiler (Python, PySide)"), |
|
186 Utilities.generatePySideToolPath("pyside-rcc", "1"), |
|
187 '-version', 'Resource Compiler', -1) |
|
188 |
|
189 # 3.2 do the PySide2 programs |
|
190 # 3.2a. Translation Extractor PySide2 |
|
191 self.__createProgramEntry( |
|
192 self.tr("Translation Extractor (Python, PySide2)"), |
|
193 Utilities.generatePySideToolPath("pyside2-lupdate", "2"), |
|
194 '-version', '', -1, versionRe='lupdate') |
|
195 # 3.2b. Forms Compiler PySide2 |
|
196 self.__createProgramEntry( |
|
197 self.tr("Forms Compiler (Python, PySide2)"), |
|
198 Utilities.generatePySideToolPath("pyside2-uic", "2"), |
|
199 '--version', 'PySide2 User', -1, versionCleanup=(0, -1)) |
|
200 # 3.2c Resource Compiler PySide2 |
|
201 self.__createProgramEntry( |
|
202 self.tr("Resource Compiler (Python, PySide2)"), |
|
203 Utilities.generatePySideToolPath("pyside2-rcc", "2"), |
|
204 '-version', 'Resource Compiler', -1) |
|
205 |
|
206 # 4. do the Ruby programs |
|
207 # 4a. Forms Compiler for Qt4 |
|
208 self.__createProgramEntry( |
|
209 self.tr("Forms Compiler (Ruby, Qt4)"), |
|
210 Utilities.isWindowsPlatform() and "rbuic4.exe" or "rbuic4", |
|
211 '-version', 'Qt', -1) |
|
212 # 4b. Resource Compiler for Qt4 |
|
213 self.__createProgramEntry( |
|
214 self.tr("Resource Compiler (Ruby, Qt4)"), |
|
215 Utilities.isWindowsPlatform() and "rbrcc.exe" or "rbrcc", |
|
216 '-version', 'Ruby Resource Compiler', -1) |
|
217 |
|
218 # 5. do the Conda program(s) |
|
219 exe = Preferences.getConda("CondaExecutable") |
|
220 if not exe: |
|
221 exe = "conda" |
|
222 if Utilities.isWindowsPlatform(): |
|
223 exe += ".exe" |
|
224 self.__createProgramEntry( |
|
225 self.tr("conda Manager"), exe, '--version', 'conda', -1) |
|
226 |
|
227 # 6. do the pip program(s) |
|
228 virtualenvManager = e5App().getObject("VirtualEnvManager") |
|
229 for venvName in virtualenvManager.getVirtualenvNames(): |
|
230 interpreter = virtualenvManager.getVirtualenvInterpreter(venvName) |
|
231 self.__createProgramEntry( |
|
232 self.tr("PyPI Package Management"), interpreter, '--version', |
|
233 'pip', 1, exeModule=["-m", "pip"]) |
|
234 |
|
235 # 7. do the CORBA and Protobuf programs |
|
236 # 7a. omniORB |
|
237 exe = Preferences.getCorba("omniidl") |
|
238 if not exe: |
|
239 exe = "omniidl" |
|
240 if Utilities.isWindowsPlatform(): |
|
241 exe += ".exe" |
|
242 self.__createProgramEntry( |
|
243 self.tr("CORBA IDL Compiler"), exe, '-V', 'omniidl', -1) |
|
244 # 7b. protobuf |
|
245 exe = Preferences.getProtobuf("protoc") |
|
246 if not exe: |
|
247 exe = "protoc" |
|
248 if Utilities.isWindowsPlatform(): |
|
249 exe += ".exe" |
|
250 self.__createProgramEntry( |
|
251 self.tr("Protobuf Compiler"), exe, '--version', 'libprotoc', -1) |
|
252 # 7c. grpc |
|
253 exe = Preferences.getProtobuf("grpcPython") |
|
254 if not exe: |
|
255 exe = sys.executable |
|
256 self.__createProgramEntry( |
|
257 self.tr("gRPC Compiler"), exe, '--version', 'libprotoc', -1, |
|
258 exeModule=['-m', 'grpc_tools.protoc']) |
|
259 |
|
260 # 8. do the spell checking entry |
|
261 try: |
|
262 import enchant |
|
263 try: |
|
264 text = os.path.dirname(enchant.__file__) |
|
265 except AttributeError: |
|
266 text = "enchant" |
|
267 try: |
|
268 version = enchant.__version__ |
|
269 except AttributeError: |
|
270 version = self.tr("(unknown)") |
|
271 except (ImportError, AttributeError, OSError): |
|
272 text = "enchant" |
|
273 version = "" |
|
274 self.__createEntry( |
|
275 self.tr("Spell Checker - PyEnchant"), text, version) |
|
276 |
|
277 # 9. do the pygments entry |
|
278 try: |
|
279 import pygments |
|
280 try: |
|
281 text = os.path.dirname(pygments.__file__) |
|
282 except AttributeError: |
|
283 text = "pygments" |
|
284 try: |
|
285 version = pygments.__version__ |
|
286 except AttributeError: |
|
287 version = self.tr("(unknown)") |
|
288 except (ImportError, AttributeError, OSError): |
|
289 text = "pygments" |
|
290 version = "" |
|
291 self.__createEntry( |
|
292 self.tr("Source Highlighter - Pygments"), text, version) |
|
293 |
|
294 # 10. do the plugin related programs |
|
295 pm = e5App().getObject("PluginManager") |
|
296 for info in pm.getPluginExeDisplayData(): |
|
297 if info["programEntry"]: |
|
298 if "exeModule" not in info: |
|
299 info["exeModule"] = None |
|
300 if "versionRe" not in info: |
|
301 info["versionRe"] = None |
|
302 self.__createProgramEntry( |
|
303 info["header"], |
|
304 info["exe"], |
|
305 versionCommand=info["versionCommand"], |
|
306 versionStartsWith=info["versionStartsWith"], |
|
307 versionPosition=info["versionPosition"], |
|
308 version=info["version"], |
|
309 versionCleanup=info["versionCleanup"], |
|
310 versionRe=info["versionRe"], |
|
311 exeModule=info["exeModule"], |
|
312 ) |
|
313 else: |
|
314 self.__createEntry( |
|
315 info["header"], |
|
316 info["text"], |
|
317 info["version"] |
|
318 ) |
|
319 |
|
320 self.programsList.sortByColumn(0, Qt.AscendingOrder) |
|
321 QApplication.restoreOverrideCursor() |
|
322 |
|
323 self.__hasSearched = True |
|
324 |
|
325 def __createProgramEntry(self, description, exe, |
|
326 versionCommand="", versionStartsWith="", |
|
327 versionPosition=0, version="", |
|
328 versionCleanup=None, versionRe=None, |
|
329 exeModule=None): |
|
330 """ |
|
331 Private method to generate a program entry. |
|
332 |
|
333 @param description descriptive text (string) |
|
334 @param exe name of the executable program (string) |
|
335 @param versionCommand command line switch to get the version info |
|
336 (str). If this is empty, the given version will be shown. |
|
337 @param versionStartsWith start of line identifying version info |
|
338 (string) |
|
339 @param versionPosition index of part containing the version info |
|
340 (integer) |
|
341 @keyparam version version string to show (string) |
|
342 @keyparam versionCleanup tuple of two integers giving string positions |
|
343 start and stop for the version string (tuple of integers) |
|
344 @keyparam versionRe regexp to determine the line identifying version |
|
345 info (string). Takes precedence over versionStartsWith. |
|
346 @keyparam exeModule list of command line parameters to execute a module |
|
347 with the program given in exe (e.g. to execute a Python module) |
|
348 (list of str) |
|
349 @return version string of detected or given version (string) |
|
350 """ |
|
351 itmList = self.programsList.findItems( |
|
352 description, Qt.MatchCaseSensitive) |
|
353 if itmList: |
|
354 itm = itmList[0] |
|
355 else: |
|
356 itm = QTreeWidgetItem(self.programsList, [description]) |
|
357 font = itm.font(0) |
|
358 font.setBold(True) |
|
359 itm.setFont(0, font) |
|
360 rememberedExe = exe |
|
361 if not exe: |
|
362 itm.setText(1, self.tr("(not configured)")) |
|
363 else: |
|
364 if os.path.isabs(exe): |
|
365 if not Utilities.isExecutable(exe): |
|
366 exe = "" |
|
367 else: |
|
368 exe = Utilities.getExecutablePath(exe) |
|
369 if exe: |
|
370 if versionCommand and \ |
|
371 (versionStartsWith != "" or |
|
372 (versionRe is not None and versionRe != "")) and \ |
|
373 versionPosition: |
|
374 proc = QProcess() |
|
375 proc.setProcessChannelMode(QProcess.MergedChannels) |
|
376 if exeModule: |
|
377 args = exeModule[:] + [versionCommand] |
|
378 else: |
|
379 args = [versionCommand] |
|
380 proc.start(exe, args) |
|
381 finished = proc.waitForFinished(10000) |
|
382 if finished: |
|
383 output = str(proc.readAllStandardOutput(), |
|
384 Preferences.getSystem("IOEncoding"), |
|
385 'replace') |
|
386 if exeModule and exeModule[0] == "-m" and \ |
|
387 ("ImportError:" in output or |
|
388 "ModuleNotFoundError:" in output or |
|
389 proc.exitCode() != 0): |
|
390 version = self.tr("(module not found)") |
|
391 else: |
|
392 if versionRe is None: |
|
393 versionRe = "^{0}".format( |
|
394 re.escape(versionStartsWith)) |
|
395 versionRe = re.compile(versionRe, re.UNICODE) |
|
396 for line in output.splitlines(): |
|
397 if versionRe.search(line): |
|
398 try: |
|
399 version = line.split()[versionPosition] |
|
400 if versionCleanup: |
|
401 version = version[ |
|
402 versionCleanup[0]: |
|
403 versionCleanup[1] |
|
404 ] |
|
405 break |
|
406 except IndexError: |
|
407 version = self.tr("(unknown)") |
|
408 else: |
|
409 version = self.tr("(unknown)") |
|
410 else: |
|
411 version = self.tr("(not executable)") |
|
412 if exeModule: |
|
413 QTreeWidgetItem(itm, [ |
|
414 "{0} {1}".format(exe, " ".join(exeModule)), |
|
415 version]) |
|
416 else: |
|
417 QTreeWidgetItem(itm, [exe, version]) |
|
418 itm.setExpanded(True) |
|
419 else: |
|
420 if itm.childCount() == 0: |
|
421 itm.setText(1, self.tr("(not found)")) |
|
422 else: |
|
423 QTreeWidgetItem(itm, [rememberedExe, |
|
424 self.tr("(not found)")]) |
|
425 itm.setExpanded(True) |
|
426 QApplication.processEvents() |
|
427 self.programsList.header().resizeSections(QHeaderView.ResizeToContents) |
|
428 self.programsList.header().setStretchLastSection(True) |
|
429 return version |
|
430 |
|
431 def __createEntry(self, description, entryText, entryVersion): |
|
432 """ |
|
433 Private method to generate a program entry. |
|
434 |
|
435 @param description descriptive text (string) |
|
436 @param entryText text to show (string) |
|
437 @param entryVersion version string to show (string). |
|
438 """ |
|
439 itm = QTreeWidgetItem(self.programsList, [description]) |
|
440 font = itm.font(0) |
|
441 font.setBold(True) |
|
442 itm.setFont(0, font) |
|
443 |
|
444 if len(entryVersion): |
|
445 QTreeWidgetItem(itm, [entryText, entryVersion]) |
|
446 itm.setExpanded(True) |
|
447 else: |
|
448 itm.setText(1, self.tr("(not found)")) |
|
449 QApplication.processEvents() |
|
450 self.programsList.header().resizeSections(QHeaderView.ResizeToContents) |
|
451 self.programsList.header().setStretchLastSection(True) |