src/eric7/Preferences/ProgramsDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9016
6f079c524e99
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2006 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Programs page.
8 """
9
10 import os
11 import re
12
13 from PyQt6.QtCore import pyqtSlot, Qt, QProcess
14 from PyQt6.QtWidgets import (
15 QApplication, QTreeWidgetItem, QHeaderView, QDialog, QDialogButtonBox
16 )
17
18 from EricWidgets.EricApplication import ericApp
19 from EricGui.EricOverrideCursor import EricOverrideCursor
20
21 from .Ui_ProgramsDialog import Ui_ProgramsDialog
22
23 import Globals
24 import Preferences
25 import Utilities
26
27
28 class ProgramsDialog(QDialog, Ui_ProgramsDialog):
29 """
30 Class implementing the Programs page.
31 """
32 ToolAvailableRole = Qt.ItemDataRole.UserRole + 1
33
34 def __init__(self, parent=None):
35 """
36 Constructor
37
38 @param parent The parent widget of this dialog. (QWidget)
39 """
40 super().__init__(parent)
41 self.setupUi(self)
42 self.setObjectName("ProgramsDialog")
43 self.setWindowFlags(Qt.WindowType.Window)
44
45 self.__hasSearched = False
46
47 self.programsList.headerItem().setText(
48 self.programsList.columnCount(), "")
49
50 self.searchButton = self.buttonBox.addButton(
51 self.tr("Search"), QDialogButtonBox.ButtonRole.ActionRole)
52 self.searchButton.setToolTip(
53 self.tr("Press to search for programs"))
54
55 self.showComboBox.addItems([
56 self.tr("All Supported Tools"),
57 self.tr("Available Tools Only"),
58 self.tr("Unavailable Tools Only"),
59 ])
60
61 def show(self):
62 """
63 Public slot to show the dialog.
64 """
65 QDialog.show(self)
66 if not self.__hasSearched:
67 self.on_programsSearchButton_clicked()
68
69 def on_buttonBox_clicked(self, button):
70 """
71 Private slot called by a button of the button box clicked.
72
73 @param button button that was clicked (QAbstractButton)
74 """
75 if button == self.searchButton:
76 self.on_programsSearchButton_clicked()
77
78 @pyqtSlot()
79 def on_programsSearchButton_clicked(self):
80 """
81 Private slot to search for all supported/required programs.
82 """
83 self.programsList.clear()
84 header = self.programsList.header()
85 header.setSortIndicator(0, Qt.SortOrder.AscendingOrder)
86 header.setSortIndicatorShown(False)
87
88 with EricOverrideCursor():
89 # 1. do the Qt programs
90 # 1a. Translation Converter
91 exe = (
92 Utilities.isWindowsPlatform() and
93 "{0}.exe".format(Utilities.generateQtToolName("lrelease")) or
94 Utilities.generateQtToolName("lrelease")
95 )
96 exe = os.path.join(Utilities.getQtBinariesPath(), exe)
97 version = self.__createProgramEntry(
98 self.tr("Translation Converter (Qt)"), exe, '-version',
99 'lrelease', -1)
100 # 1b. Qt Designer
101 if Utilities.isWindowsPlatform():
102 exe = os.path.join(
103 Utilities.getQtBinariesPath(),
104 "{0}.exe".format(Utilities.generateQtToolName("designer")))
105 elif Utilities.isMacPlatform():
106 exe = Utilities.getQtMacBundle("designer")
107 else:
108 exe = os.path.join(
109 Utilities.getQtBinariesPath(),
110 Utilities.generateQtToolName("designer"))
111 self.__createProgramEntry(
112 self.tr("Qt Designer"), exe, version=version)
113 # 1c. Qt Linguist
114 if Utilities.isWindowsPlatform():
115 exe = os.path.join(
116 Utilities.getQtBinariesPath(),
117 "{0}.exe".format(Utilities.generateQtToolName("linguist")))
118 elif Utilities.isMacPlatform():
119 exe = Utilities.getQtMacBundle("linguist")
120 else:
121 exe = os.path.join(
122 Utilities.getQtBinariesPath(),
123 Utilities.generateQtToolName("linguist"))
124 self.__createProgramEntry(
125 self.tr("Qt Linguist"), exe, version=version)
126 # 1d. Qt Assistant
127 if Utilities.isWindowsPlatform():
128 exe = os.path.join(
129 Utilities.getQtBinariesPath(),
130 "{0}.exe".format(
131 Utilities.generateQtToolName("assistant")))
132 elif Utilities.isMacPlatform():
133 exe = Utilities.getQtMacBundle("assistant")
134 else:
135 exe = os.path.join(
136 Utilities.getQtBinariesPath(),
137 Utilities.generateQtToolName("assistant"))
138 self.__createProgramEntry(
139 self.tr("Qt Assistant"), exe, version=version)
140
141 # 2. do the PyQt programs
142 # 2.1 do the PyQt5 programs
143 # 2.1a. Translation Extractor PyQt5
144 self.__createProgramEntry(
145 self.tr("Translation Extractor (Python, PyQt5)"),
146 Utilities.generatePyQtToolPath("pylupdate5"),
147 '-version', 'pylupdate', -1)
148 # 2.1b. Forms Compiler PyQt5
149 self.__createProgramEntry(
150 self.tr("Forms Compiler (Python, PyQt5)"),
151 Utilities.generatePyQtToolPath("pyuic5", ["py3uic5"]),
152 '--version', 'Python User', 4)
153 # 2.1c. Resource Compiler PyQt5
154 self.__createProgramEntry(
155 self.tr("Resource Compiler (Python, PyQt5)"),
156 Utilities.generatePyQtToolPath("pyrcc5"),
157 '-version', '', -1, versionRe='Resource Compiler|pyrcc5')
158
159 # 2.2 do the PyQt6 programs
160 # 2.2a. Translation Extractor PyQt6
161 self.__createProgramEntry(
162 self.tr("Translation Extractor (Python, PyQt6)"),
163 Utilities.generatePyQtToolPath("pylupdate6"),
164 '--version', versionPosition=0)
165 # 2.2b. Forms Compiler PyQt6
166 self.__createProgramEntry(
167 self.tr("Forms Compiler (Python, PyQt6)"),
168 Utilities.generatePyQtToolPath("pyuic6"),
169 '--version', versionPosition=0)
170
171 # 3. do the PySide programs
172 # 3.1 do the PySide2 programs
173 # 3.1a. Translation Extractor PySide2
174 self.__createProgramEntry(
175 self.tr("Translation Extractor (Python, PySide2)"),
176 Utilities.generatePySideToolPath("pyside2-lupdate", variant=2),
177 '-version', '', -1, versionRe='lupdate')
178 # 3.1b. Forms Compiler PySide2
179 self.__createProgramEntry(
180 self.tr("Forms Compiler (Python, PySide2)"),
181 Utilities.generatePySideToolPath("pyside2-uic", variant=2),
182 '--version', '', -1, versionRe='uic')
183 # 3.1c Resource Compiler PySide2
184 self.__createProgramEntry(
185 self.tr("Resource Compiler (Python, PySide2)"),
186 Utilities.generatePySideToolPath("pyside2-rcc", variant=2),
187 '-version', '', -1, versionRe='rcc')
188 # 3.2 do the PySide5 programs
189 # 3.2a. Translation Extractor PySide6
190 self.__createProgramEntry(
191 self.tr("Translation Extractor (Python, PySide6)"),
192 Utilities.generatePySideToolPath("pyside6-lupdate", variant=6),
193 '-version', '', -1, versionRe='lupdate')
194 # 3.2b. Forms Compiler PySide6
195 self.__createProgramEntry(
196 self.tr("Forms Compiler (Python, PySide6)"),
197 Utilities.generatePySideToolPath("pyside6-uic", variant=6),
198 '--version', '', -1, versionRe='uic')
199 # 3.2c Resource Compiler PySide6
200 self.__createProgramEntry(
201 self.tr("Resource Compiler (Python, PySide6)"),
202 Utilities.generatePySideToolPath("pyside6-rcc", variant=6),
203 '--version', '', -1, versionRe='rcc')
204
205 # 4. do the Conda program(s)
206 exe = Preferences.getConda("CondaExecutable")
207 if not exe:
208 exe = "conda"
209 if Utilities.isWindowsPlatform():
210 exe += ".exe"
211 self.__createProgramEntry(
212 self.tr("conda Manager"), exe, '--version', 'conda', -1)
213
214 # 5. do the pip program(s)
215 virtualenvManager = ericApp().getObject("VirtualEnvManager")
216 for venvName in virtualenvManager.getVirtualenvNames():
217 interpreter = virtualenvManager.getVirtualenvInterpreter(
218 venvName)
219 self.__createProgramEntry(
220 self.tr("PyPI Package Management"), interpreter,
221 '--version', 'pip', 1, exeModule=["-m", "pip"])
222
223 # 6. do the CORBA and Protobuf programs
224 # 6a. omniORB
225 exe = Preferences.getCorba("omniidl")
226 if not exe:
227 exe = "omniidl"
228 if Utilities.isWindowsPlatform():
229 exe += ".exe"
230 self.__createProgramEntry(
231 self.tr("CORBA IDL Compiler"), exe, '-V', 'omniidl', -1)
232 # 6b. protobuf
233 exe = Preferences.getProtobuf("protoc")
234 if not exe:
235 exe = "protoc"
236 if Utilities.isWindowsPlatform():
237 exe += ".exe"
238 self.__createProgramEntry(
239 self.tr("Protobuf Compiler"), exe, '--version', 'libprotoc',
240 -1)
241 # 6c. grpc
242 exe = Preferences.getProtobuf("grpcPython")
243 if not exe:
244 exe = Globals.getPythonExecutable()
245 self.__createProgramEntry(
246 self.tr("gRPC Compiler"), exe, '--version', 'libprotoc', -1,
247 exeModule=['-m', 'grpc_tools.protoc'])
248
249 # 7. do the spell checking entry
250 try:
251 import enchant
252 try:
253 text = os.path.dirname(enchant.__file__)
254 except AttributeError:
255 text = "enchant"
256 try:
257 version = enchant.__version__
258 except AttributeError:
259 version = self.tr("(unknown)")
260 except (ImportError, AttributeError, OSError):
261 text = "enchant"
262 version = ""
263 self.__createEntry(
264 self.tr("Spell Checker - PyEnchant"), text, version)
265
266 # 8. do the pygments entry
267 try:
268 import pygments
269 try:
270 text = os.path.dirname(pygments.__file__)
271 except AttributeError:
272 text = "pygments"
273 try:
274 version = pygments.__version__
275 except AttributeError:
276 version = self.tr("(unknown)")
277 except (ImportError, AttributeError, OSError):
278 text = "pygments"
279 version = ""
280 self.__createEntry(
281 self.tr("Source Highlighter - Pygments"), text, version)
282
283 # 9. do the MicroPython related entries
284 exe = Preferences.getMicroPython("MpyCrossCompiler")
285 if not exe:
286 exe = "mpy-cross"
287 self.__createProgramEntry(
288 self.tr("MicroPython - MPY Cross Compiler"), exe, '--version',
289 'MicroPython', 1)
290 self.__createProgramEntry(
291 self.tr("MicroPython - ESP Tool"),
292 Globals.getPythonExecutable(), 'version',
293 'esptool', -1, exeModule=['-m', 'esptool'])
294 exe = Preferences.getMicroPython("DfuUtilPath")
295 if not exe:
296 exe = "dfu-util"
297 self.__createProgramEntry(
298 self.tr("MicroPython - PyBoard Flasher"), exe, '--version',
299 'dfu-util', -1)
300
301 # 10. do the jedi related entries
302 try:
303 import jedi
304 try:
305 text = os.path.dirname(jedi.__file__)
306 except AttributeError:
307 text = "jedi"
308 try:
309 version = jedi.__version__
310 except AttributeError:
311 version = self.tr("(unknown)")
312 except (ImportError, AttributeError, OSError):
313 text = "jedi"
314 version = ""
315 self.__createEntry(
316 self.tr("Code Assistant - Jedi"), text, version)
317
318 # 11. do the plugin related programs
319 pm = ericApp().getObject("PluginManager")
320 for info in pm.getPluginExeDisplayData():
321 if info["programEntry"]:
322 if "exeModule" not in info:
323 info["exeModule"] = None
324 if "versionRe" not in info:
325 info["versionRe"] = None
326 self.__createProgramEntry(
327 info["header"],
328 info["exe"],
329 versionCommand=info["versionCommand"],
330 versionStartsWith=info["versionStartsWith"],
331 versionPosition=info["versionPosition"],
332 version=info["version"],
333 versionCleanup=info["versionCleanup"],
334 versionRe=info["versionRe"],
335 exeModule=info["exeModule"],
336 )
337 else:
338 self.__createEntry(
339 info["header"],
340 info["text"],
341 info["version"]
342 )
343
344 self.programsList.sortByColumn(0, Qt.SortOrder.AscendingOrder)
345 self.on_showComboBox_currentIndexChanged(
346 self.showComboBox.currentIndex())
347
348 self.__hasSearched = True
349
350 def __createProgramEntry(self, description, exe,
351 versionCommand="", versionStartsWith="",
352 versionPosition=None, version="",
353 versionCleanup=None, versionRe=None,
354 exeModule=None):
355 """
356 Private method to generate a program entry.
357
358 @param description descriptive text (string)
359 @param exe name of the executable program (string)
360 @param versionCommand command line switch to get the version info
361 (str). If this is empty, the given version will be shown.
362 @param versionStartsWith start of line identifying version info
363 (string)
364 @param versionPosition index of part containing the version info
365 (integer)
366 @param version version string to show (string)
367 @param versionCleanup tuple of two integers giving string positions
368 start and stop for the version string (tuple of integers)
369 @param versionRe regexp to determine the line identifying version
370 info (string). Takes precedence over versionStartsWith.
371 @param exeModule list of command line parameters to execute a module
372 with the program given in exe (e.g. to execute a Python module)
373 (list of str)
374 @return version string of detected or given version (string)
375 """
376 itmList = self.programsList.findItems(
377 description, Qt.MatchFlag.MatchCaseSensitive)
378 itm = (
379 itmList[0]
380 if itmList else
381 QTreeWidgetItem(self.programsList, [description])
382 )
383 font = itm.font(0)
384 font.setBold(True)
385 itm.setFont(0, font)
386 rememberedExe = exe
387 if not exe:
388 itm.setText(1, self.tr("(not configured)"))
389 else:
390 if os.path.isabs(exe):
391 if not Utilities.isExecutable(exe):
392 exe = ""
393 else:
394 exe = Utilities.getExecutablePath(exe)
395 if exe:
396 available = True
397 if versionCommand and versionPosition is not None:
398 proc = QProcess()
399 proc.setProcessChannelMode(
400 QProcess.ProcessChannelMode.MergedChannels)
401 if exeModule:
402 args = exeModule[:] + [versionCommand]
403 else:
404 args = [versionCommand]
405 proc.start(exe, args)
406 finished = proc.waitForFinished(10000)
407 if finished:
408 output = str(proc.readAllStandardOutput(),
409 Preferences.getSystem("IOEncoding"),
410 'replace')
411 if (
412 exeModule and
413 exeModule[0] == "-m" and
414 ("ImportError:" in output or
415 "ModuleNotFoundError:" in output or
416 proc.exitCode() != 0)
417 ):
418 version = self.tr("(module not found)")
419 available = False
420 elif not versionStartsWith and not versionRe:
421 # assume output is just one line
422 try:
423 version = (
424 output.strip().split()[versionPosition]
425 )
426 if versionCleanup:
427 version = version[
428 versionCleanup[0]:
429 versionCleanup[1]
430 ]
431 except IndexError:
432 version = self.tr("(unknown)")
433 available = False
434 else:
435 if versionRe is None:
436 versionRe = "^{0}".format(
437 re.escape(versionStartsWith))
438 versionRe = re.compile(versionRe, re.UNICODE)
439 for line in output.splitlines():
440 if versionRe.search(line):
441 try:
442 version = line.split()[versionPosition]
443 if versionCleanup:
444 version = version[
445 versionCleanup[0]:
446 versionCleanup[1]
447 ]
448 break
449 except IndexError:
450 version = self.tr("(unknown)")
451 available = False
452 else:
453 version = self.tr("(unknown)")
454 available = False
455 else:
456 version = self.tr("(not executable)")
457 available = False
458 if exeModule:
459 citm = QTreeWidgetItem(itm, [
460 "{0} {1}".format(exe, " ".join(exeModule)),
461 version])
462 else:
463 citm = QTreeWidgetItem(itm, [exe, version])
464 citm.setData(0, self.ToolAvailableRole, available)
465 itm.setExpanded(True)
466 else:
467 if itm.childCount() == 0:
468 itm.setText(1, self.tr("(not found)"))
469 else:
470 citm = QTreeWidgetItem(
471 itm, [rememberedExe, self.tr("(not found)")])
472 citm.setData(0, self.ToolAvailableRole, False)
473 itm.setExpanded(True)
474 QApplication.processEvents()
475 self.programsList.header().resizeSections(
476 QHeaderView.ResizeMode.ResizeToContents)
477 self.programsList.header().setStretchLastSection(True)
478 return version
479
480 def __createEntry(self, description, entryText, entryVersion):
481 """
482 Private method to generate a program entry.
483
484 @param description descriptive text (string)
485 @param entryText text to show (string)
486 @param entryVersion version string to show (string).
487 """
488 itm = QTreeWidgetItem(self.programsList, [description])
489 font = itm.font(0)
490 font.setBold(True)
491 itm.setFont(0, font)
492
493 if len(entryVersion):
494 citm = QTreeWidgetItem(itm, [entryText, entryVersion])
495 itm.setExpanded(True)
496 citm.setData(0, self.ToolAvailableRole,
497 not entryVersion.startswith("("))
498 # assume version starting with '(' is an unavailability
499 else:
500 itm.setText(1, self.tr("(not found)"))
501 QApplication.processEvents()
502 self.programsList.header().resizeSections(
503 QHeaderView.ResizeMode.ResizeToContents)
504 self.programsList.header().setStretchLastSection(True)
505
506 @pyqtSlot(int)
507 def on_showComboBox_currentIndexChanged(self, index):
508 """
509 Private slot to apply the selected show criteria.
510
511 @param index index of the show criterium
512 @type int
513 """
514 if index == 0:
515 # All Supported Tools
516 for topIndex in range(self.programsList.topLevelItemCount()):
517 topItem = self.programsList.topLevelItem(topIndex)
518 for childIndex in range(topItem.childCount()):
519 topItem.child(childIndex).setHidden(False)
520 topItem.setHidden(False)
521 else:
522 # 1 = Available Tools Only
523 # 2 = Unavailable Tools Only
524 for topIndex in range(self.programsList.topLevelItemCount()):
525 topItem = self.programsList.topLevelItem(topIndex)
526 if topItem.childCount() == 0:
527 topItem.setHidden(index == 1)
528 else:
529 availabilityList = []
530 for childIndex in range(topItem.childCount()):
531 childItem = topItem.child(childIndex)
532 available = childItem.data(0, self.ToolAvailableRole)
533 if index == 1:
534 childItem.setHidden(not available)
535 else:
536 childItem.setHidden(available)
537 availabilityList.append(available)
538 if index == 1:
539 topItem.setHidden(not any(availabilityList))
540 else:
541 topItem.setHidden(all(availabilityList))

eric ide

mercurial