src/eric7/Project/Project.py

branch
eric7-maintenance
changeset 9264
18a7312cfdb3
parent 9192
a763d57e23bc
parent 9235
b5fe898e171f
child 9305
3b7ef53c34c7
equal deleted inserted replaced
9241:d23e9854aea4 9264:18a7312cfdb3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the project management functionality.
8 """
9
10 import os
11 import time
12 import shutil
13 import glob
14 import fnmatch
15 import copy
16 import zipfile
17 import contextlib
18 import pathlib
19
20 from PyQt6.QtCore import (
21 pyqtSlot,
22 QFile,
23 pyqtSignal,
24 QCryptographicHash,
25 QIODevice,
26 QByteArray,
27 QObject,
28 QProcess,
29 )
30 from PyQt6.QtGui import QKeySequence, QAction
31 from PyQt6.QtWidgets import QLineEdit, QToolBar, QDialog, QInputDialog, QMenu
32 from PyQt6.Qsci import QsciScintilla
33
34 from EricWidgets.EricApplication import ericApp
35 from EricWidgets import EricFileDialog, EricMessageBox
36 from EricWidgets.EricListSelectionDialog import EricListSelectionDialog
37 from EricWidgets.EricProgressDialog import EricProgressDialog
38 from EricGui.EricOverrideCursor import EricOverrideCursor, EricOverridenCursor
39
40 from Globals import recentNameProject
41
42 import UI.PixmapCache
43 from UI.NotificationWidget import NotificationTypes
44
45 from EricGui.EricAction import EricAction, createActionGroup
46
47 import Globals
48 import Preferences
49 import Utilities
50
51 from .ProjectFile import ProjectFile
52 from .UserProjectFile import UserProjectFile
53 from .DebuggerPropertiesFile import DebuggerPropertiesFile
54
55 from Sessions.SessionFile import SessionFile
56
57 from Tasks.TasksFile import TasksFile
58
59 from CodeFormatting.BlackFormattingAction import BlackFormattingAction
60
61
62 class Project(QObject):
63 """
64 Class implementing the project management functionality.
65
66 @signal dirty(bool) emitted when the dirty state changes
67 @signal projectLanguageAdded(str) emitted after a new language was added
68 @signal projectLanguageAddedByCode(str) emitted after a new language was
69 added. The language code is sent by this signal.
70 @signal projectLanguageRemoved(str) emitted after a language was removed
71 @signal projectFormAdded(str) emitted after a new form was added
72 @signal projectFormRemoved(str) emitted after a form was removed
73 @signal projectFormCompiled(str) emitted after a form was compiled
74 @signal projectSourceAdded(str) emitted after a new source file was added
75 @signal projectSourceRemoved(str) emitted after a source was removed
76 @signal projectInterfaceAdded(str) emitted after a new IDL file was added
77 @signal projectInterfaceRemoved(str) emitted after a IDL file was removed
78 @signal projectProtocolAdded(str) emitted after a new proto file was added
79 @signal projectProtocolRemoved(str) emitted after a proto file was removed
80 @signal projectResourceAdded(str) emitted after a new resource file was
81 added
82 @signal projectResourceRemoved(str) emitted after a resource was removed
83 @signal projectOthersAdded(str) emitted after a file or directory was added
84 to the OTHERS project data area
85 @signal projectOthersRemoved(str) emitted after a file was removed from the
86 OTHERS project data area
87 @signal projectAboutToBeCreated() emitted just before the project will be
88 created
89 @signal newProjectHooks() emitted after a new project was generated but
90 before the newProject() signal is sent
91 @signal newProject() emitted after a new project was generated
92 @signal sourceFile(str) emitted after a project file was read to
93 open the main script
94 @signal designerFile(str) emitted to open a found designer file
95 @signal linguistFile(str) emitted to open a found translation file
96 @signal projectOpenedHooks() emitted after a project file was read but
97 before the projectOpened() signal is sent
98 @signal projectOpened() emitted after a project file was read
99 @signal projectClosedHooks() emitted after a project file was closed but
100 before the projectClosed() signal is sent
101 @signal projectClosed(shutdown) emitted after a project was closed sending
102 a flag indicating the IDE shutdown operation
103 @signal projectFileRenamed(str, str) emitted after a file of the project
104 has been renamed
105 @signal projectPropertiesChanged() emitted after the project properties
106 were changed
107 @signal directoryRemoved(str) emitted after a directory has been removed
108 from the project
109 @signal prepareRepopulateItem(str) emitted before an item of the model is
110 repopulated
111 @signal completeRepopulateItem(str) emitted after an item of the model was
112 repopulated
113 @signal vcsStatusMonitorData(list) emitted to signal the VCS status data
114 @signal vcsStatusMonitorAllData(dict) emitted to signal all VCS status
115 (key is project relative file name, value is status)
116 @signal vcsStatusMonitorStatus(str, str) emitted to signal the status of
117 the monitoring thread (ok, nok, op, off) and a status message
118 @signal vcsStatusMonitorInfo(str) emitted to signal some info of the
119 monitoring thread
120 @signal vcsCommitted() emitted to indicate a completed commit action
121 @signal reinitVCS() emitted after the VCS has been reinitialized
122 @signal showMenu(str, QMenu) emitted when a menu is about to be shown. The
123 name of the menu and a reference to the menu are given.
124 @signal lexerAssociationsChanged() emitted after the lexer associations
125 have been changed
126 @signal projectChanged() emitted to signal a change of the project
127 @signal appendStdout(str) emitted after something was received from
128 a QProcess on stdout
129 @signal appendStderr(str) emitted after something was received from
130 a QProcess on stderr
131 """
132
133 dirty = pyqtSignal(bool)
134 projectLanguageAdded = pyqtSignal(str)
135 projectLanguageAddedByCode = pyqtSignal(str)
136 projectLanguageRemoved = pyqtSignal(str)
137 projectFormAdded = pyqtSignal(str)
138 projectFormRemoved = pyqtSignal(str)
139 projectFormCompiled = pyqtSignal(str)
140 projectSourceAdded = pyqtSignal(str)
141 projectSourceRemoved = pyqtSignal(str)
142 projectInterfaceAdded = pyqtSignal(str)
143 projectInterfaceRemoved = pyqtSignal(str)
144 projectProtocolAdded = pyqtSignal(str)
145 projectProtocolRemoved = pyqtSignal(str)
146 projectResourceAdded = pyqtSignal(str)
147 projectResourceRemoved = pyqtSignal(str)
148 projectOthersAdded = pyqtSignal(str)
149 projectOthersRemoved = pyqtSignal(str)
150 projectAboutToBeCreated = pyqtSignal()
151 newProjectHooks = pyqtSignal()
152 newProject = pyqtSignal()
153 sourceFile = pyqtSignal(str)
154 designerFile = pyqtSignal(str)
155 linguistFile = pyqtSignal(str)
156 projectOpenedHooks = pyqtSignal()
157 projectOpened = pyqtSignal()
158 projectClosedHooks = pyqtSignal()
159 projectClosed = pyqtSignal(bool)
160 projectFileRenamed = pyqtSignal(str, str)
161 projectPropertiesChanged = pyqtSignal()
162 directoryRemoved = pyqtSignal(str)
163 prepareRepopulateItem = pyqtSignal(str)
164 completeRepopulateItem = pyqtSignal(str)
165 vcsStatusMonitorData = pyqtSignal(list)
166 vcsStatusMonitorAllData = pyqtSignal(dict)
167 vcsStatusMonitorStatus = pyqtSignal(str, str)
168 vcsStatusMonitorInfo = pyqtSignal(str)
169 vcsCommitted = pyqtSignal()
170 reinitVCS = pyqtSignal()
171 showMenu = pyqtSignal(str, QMenu)
172 lexerAssociationsChanged = pyqtSignal()
173 projectChanged = pyqtSignal()
174 appendStdout = pyqtSignal(str)
175 appendStderr = pyqtSignal(str)
176
177 eols = [os.linesep, "\n", "\r", "\r\n"]
178
179 DefaultMake = "make"
180 DefaultMakefile = "makefile"
181
182 def __init__(self, parent=None, filename=None):
183 """
184 Constructor
185
186 @param parent parent widget (usually the ui object) (QWidget)
187 @param filename optional filename of a project file to open (string)
188 """
189 super().__init__(parent)
190
191 self.ui = parent
192
193 self.__progLanguages = [
194 "Python3",
195 "MicroPython",
196 "Ruby",
197 "JavaScript",
198 ]
199
200 self.__dbgFilters = {
201 "Python3": self.tr(
202 "Python3 Files (*.py *.py3);;" "Python3 GUI Files (*.pyw *.pyw3);;"
203 ),
204 }
205
206 self.vcsMenu = None
207 self.__makeProcess = None
208
209 self.__initProjectTypes()
210
211 self.__initData()
212
213 self.__projectFile = ProjectFile(self)
214 self.__userProjectFile = UserProjectFile(self)
215 self.__debuggerPropertiesFile = DebuggerPropertiesFile(self)
216 self.__sessionFile = SessionFile(False)
217 self.__tasksFile = TasksFile(False)
218
219 self.recent = []
220 self.__loadRecent()
221
222 if filename is not None:
223 self.openProject(filename)
224 else:
225 self.vcs = self.initVCS()
226
227 from .ProjectBrowserModel import ProjectBrowserModel
228
229 self.__model = ProjectBrowserModel(self)
230
231 self.codemetrics = None
232 self.codecoverage = None
233 self.profiledata = None
234 self.applicationDiagram = None
235 self.loadedDiagram = None
236 self.__findProjectFileDialog = None
237
238 def __sourceExtensions(self, language):
239 """
240 Private method to get the source extensions of a programming language.
241
242 @param language programming language (string)
243 @return source extensions (list of string)
244 """
245 if language == "Python3":
246 extensions = Preferences.getPython("Python3Extensions")
247 # *.py and *.pyw should always be associated with source files
248 for ext in [".py", ".pyw"]:
249 if ext not in extensions:
250 extensions.append(ext)
251 return extensions
252 elif language == "MicroPython":
253 extensions = Preferences.getPython("Python3Extensions")
254 # *.py should always be associated with source files
255 for ext in [".py"]:
256 if ext not in extensions:
257 extensions.append(ext)
258 return extensions
259 else:
260 return {
261 "Ruby": [".rb"],
262 "JavaScript": [".js"],
263 "Mixed": (Preferences.getPython("Python3Extensions") + [".rb", ".js"]),
264 }.get(language, "")
265
266 def getProgrammingLanguages(self):
267 """
268 Public method to get the programming languages supported by project.
269
270 @return list of supported programming languages (list of string)
271 """
272 return self.__progLanguages[:]
273
274 def getDebuggerFilters(self, language):
275 """
276 Public method to get the debugger filters for a programming language.
277
278 @param language programming language
279 @type str
280 @return filter string
281 @rtype str
282 """
283 try:
284 return self.__dbgFilters[language]
285 except KeyError:
286 return ""
287
288 def __initProjectTypes(self):
289 """
290 Private method to initialize the list of supported project types.
291 """
292 self.__fileTypeCallbacks = {}
293 self.__lexerAssociationCallbacks = {}
294 self.__binaryTranslationsCallbacks = {}
295
296 self.__projectTypes = {
297 "PyQt5": self.tr("PyQt5 GUI"),
298 "PyQt5C": self.tr("PyQt5 Console"),
299 "PyQt6": self.tr("PyQt6 GUI"),
300 "PyQt6C": self.tr("PyQt6 Console"),
301 "E7Plugin": self.tr("Eric7 Plugin"),
302 "Console": self.tr("Console"),
303 "Other": self.tr("Other"),
304 }
305
306 self.__projectProgLanguages = {
307 "Python3": [
308 "PyQt5",
309 "PyQt5C",
310 "PyQt6",
311 "PyQt6C",
312 "E7Plugin",
313 "Console",
314 "Other",
315 ],
316 "MicroPython": ["Console", "Other"],
317 "Ruby": ["Console", "Other"],
318 "JavaScript": ["Other"],
319 }
320
321 if Utilities.checkPyside(variant=2):
322 self.__projectTypes["PySide2"] = self.tr("PySide2 GUI")
323 self.__projectTypes["PySide2C"] = self.tr("PySide2 Console")
324 self.__projectProgLanguages["Python3"].extend(["PySide2", "PySide2C"])
325
326 if Utilities.checkPyside(variant=6):
327 self.__projectTypes["PySide6"] = self.tr("PySide6 GUI")
328 self.__projectTypes["PySide6C"] = self.tr("PySide6 Console")
329 self.__projectProgLanguages["Python3"].extend(["PySide6", "PySide6C"])
330
331 def getProjectTypes(self, progLanguage=""):
332 """
333 Public method to get the list of supported project types.
334
335 @param progLanguage programming language to get project types for
336 (string)
337 @return reference to the dictionary of project types.
338 """
339 if progLanguage and progLanguage in self.__projectProgLanguages:
340 ptypes = {}
341 for ptype in self.__projectProgLanguages[progLanguage]:
342 ptypes[ptype] = self.__projectTypes[ptype]
343 return ptypes
344 else:
345 return self.__projectTypes
346
347 def hasProjectType(self, type_, progLanguage=""):
348 """
349 Public method to check, if a project type is already registered.
350
351 @param type_ internal type designator (string)
352 @param progLanguage programming language of the project type (string)
353 @return flag indicating presence of the project type (boolean)
354 """
355 if progLanguage:
356 return (
357 progLanguage in self.__projectProgLanguages
358 and type_ in self.__projectProgLanguages[progLanguage]
359 )
360 else:
361 return type_ in self.__projectTypes
362
363 def registerProjectType(
364 self,
365 type_,
366 description,
367 fileTypeCallback=None,
368 binaryTranslationsCallback=None,
369 lexerAssociationCallback=None,
370 progLanguages=None,
371 ):
372 """
373 Public method to register a project type.
374
375 @param type_ internal type designator to be registered (string)
376 @param description more verbose type name (display string) (string)
377 @param fileTypeCallback reference to a method returning a dictionary
378 of filetype associations.
379 @param binaryTranslationsCallback reference to a method returning
380 the name of the binary translation file given the name of the raw
381 translation file
382 @param lexerAssociationCallback reference to a method returning the
383 lexer type to be used for syntax highlighting given the name of
384 a file
385 @param progLanguages programming languages supported by the
386 project type (list of string)
387 """
388 if progLanguages:
389 for progLanguage in progLanguages:
390 if progLanguage not in self.__projectProgLanguages:
391 EricMessageBox.critical(
392 self.ui,
393 self.tr("Registering Project Type"),
394 self.tr(
395 """<p>The Programming Language <b>{0}</b> is not"""
396 """ supported (project type: {1}).</p>"""
397 ).format(progLanguage, type_),
398 )
399 return
400
401 if type_ in self.__projectProgLanguages[progLanguage]:
402 EricMessageBox.critical(
403 self.ui,
404 self.tr("Registering Project Type"),
405 self.tr(
406 """<p>The Project type <b>{0}</b> is already"""
407 """ registered with Programming Language"""
408 """ <b>{1}</b>.</p>"""
409 ).format(type_, progLanguage),
410 )
411 return
412
413 if type_ in self.__projectTypes:
414 EricMessageBox.critical(
415 self.ui,
416 self.tr("Registering Project Type"),
417 self.tr(
418 """<p>The Project type <b>{0}</b> is already"""
419 """ registered.</p>"""
420 ).format(type_),
421 )
422 else:
423 self.__projectTypes[type_] = description
424 self.__fileTypeCallbacks[type_] = fileTypeCallback
425 self.__lexerAssociationCallbacks[type_] = lexerAssociationCallback
426 self.__binaryTranslationsCallbacks[type_] = binaryTranslationsCallback
427 if progLanguages:
428 for progLanguage in progLanguages:
429 self.__projectProgLanguages[progLanguage].append(type_)
430 else:
431 # no specific programming languages given -> add to all
432 for progLanguage in self.__projectProgLanguages:
433 self.__projectProgLanguages[progLanguage].append(type_)
434
435 def unregisterProjectType(self, type_):
436 """
437 Public method to unregister a project type.
438
439 @param type_ internal type designator to be unregistered (string)
440 """
441 for progLanguage in self.__projectProgLanguages:
442 if type_ in self.__projectProgLanguages[progLanguage]:
443 self.__projectProgLanguages[progLanguage].remove(type_)
444 if type_ in self.__projectTypes:
445 del self.__projectTypes[type_]
446 if type_ in self.__fileTypeCallbacks:
447 del self.__fileTypeCallbacks[type_]
448 if type_ in self.__lexerAssociationCallbacks:
449 del self.__lexerAssociationCallbacks[type_]
450 if type_ in self.__binaryTranslationsCallbacks:
451 del self.__binaryTranslationsCallbacks[type_]
452
453 def __initData(self):
454 """
455 Private method to initialize the project data part.
456 """
457 self.loaded = False # flag for the loaded status
458 self.__dirty = False # dirty flag
459 self.pfile = "" # name of the project file
460 self.ppath = "" # name of the project directory
461 self.translationsRoot = "" # the translations prefix
462 self.name = ""
463 self.opened = False
464 self.subdirs = []
465 # record the project dir as a relative path (i.e. empty path)
466 self.otherssubdirs = []
467 self.vcs = None
468 self.vcsRequested = False
469 self.dbgVirtualEnv = ""
470 self.dbgCmdline = ""
471 self.dbgWd = ""
472 self.dbgEnv = ""
473 self.dbgReportExceptions = True
474 self.dbgExcList = []
475 self.dbgExcIgnoreList = []
476 self.dbgAutoClearShell = True
477 self.dbgTracePython = False
478 self.dbgAutoContinue = True
479 self.dbgEnableMultiprocess = True
480 self.dbgMultiprocessNoDebug = ""
481 self.dbgGlobalConfigOverride = {
482 "enable": False,
483 "redirect": True,
484 }
485
486 self.pdata = {
487 "DESCRIPTION": "",
488 "VERSION": "",
489 "SOURCES": [],
490 "FORMS": [],
491 "RESOURCES": [],
492 "INTERFACES": [],
493 "PROTOCOLS": [],
494 "OTHERS": [],
495 "TRANSLATIONS": [],
496 "TRANSLATIONEXCEPTIONS": [],
497 "TRANSLATIONPATTERN": "",
498 "TRANSLATIONSBINPATH": "",
499 "MAINSCRIPT": "",
500 "VCS": "None",
501 "VCSOPTIONS": {},
502 "VCSOTHERDATA": {},
503 "AUTHOR": "",
504 "EMAIL": "",
505 "HASH": "",
506 "PROGLANGUAGE": "Python3",
507 "MIXEDLANGUAGE": False,
508 "PROJECTTYPE": "PyQt5",
509 "SPELLLANGUAGE": Preferences.getEditor("SpellCheckingDefaultLanguage"),
510 "SPELLWORDS": "",
511 "SPELLEXCLUDES": "",
512 "FILETYPES": {},
513 "LEXERASSOCS": {},
514 "PROJECTTYPESPECIFICDATA": {},
515 "CHECKERSPARMS": {},
516 "PACKAGERSPARMS": {},
517 "DOCUMENTATIONPARMS": {},
518 "OTHERTOOLSPARMS": {},
519 "MAKEPARAMS": {
520 "MakeEnabled": False,
521 "MakeExecutable": "",
522 "MakeFile": "",
523 "MakeTarget": "",
524 "MakeParameters": "",
525 "MakeTestOnly": True,
526 },
527 "IDLPARAMS": {
528 "IncludeDirs": [],
529 "DefinedNames": [],
530 "UndefinedNames": [],
531 },
532 "UICPARAMS": {
533 "Package": "",
534 "RcSuffix": "",
535 "PackagesRoot": "",
536 },
537 "RCCPARAMS": {
538 "CompressionThreshold": 70, # default value
539 "CompressLevel": 0, # use zlib default
540 "CompressionDisable": False,
541 "PathPrefix": "",
542 },
543 "EOL": -1,
544 "DOCSTRING": "",
545 "TESTING_FRAMEWORK": "",
546 "LICENSE": "",
547 }
548
549 self.__initDebugProperties()
550
551 self.pudata = {
552 "VCSOVERRIDE": "",
553 "VCSSTATUSMONITORINTERVAL": 0,
554 }
555
556 self.vcs = self.initVCS()
557
558 def getData(self, category, key):
559 """
560 Public method to get data out of the project data store.
561
562 @param category category of the data to get (string, one of
563 PROJECTTYPESPECIFICDATA, CHECKERSPARMS, PACKAGERSPARMS,
564 DOCUMENTATIONPARMS or OTHERTOOLSPARMS)
565 @param key key of the data entry to get (string).
566 @return a copy of the requested data or None
567 """
568 # __IGNORE_WARNING_D202__
569 if (
570 category
571 in [
572 "PROJECTTYPESPECIFICDATA",
573 "CHECKERSPARMS",
574 "PACKAGERSPARMS",
575 "DOCUMENTATIONPARMS",
576 "OTHERTOOLSPARMS",
577 ]
578 and key in self.pdata[category]
579 ):
580 return copy.deepcopy(self.pdata[category][key])
581 else:
582 return None
583
584 def setData(self, category, key, data):
585 """
586 Public method to store data in the project data store.
587
588 @param category category of the data to get (string, one of
589 PROJECTTYPESPECIFICDATA, CHECKERSPARMS, PACKAGERSPARMS,
590 DOCUMENTATIONPARMS or OTHERTOOLSPARMS)
591 @param key key of the data entry to get (string).
592 @param data data to be stored
593 @return flag indicating success (boolean)
594 """
595 # __IGNORE_WARNING_D202__
596 if category not in [
597 "PROJECTTYPESPECIFICDATA",
598 "CHECKERSPARMS",
599 "PACKAGERSPARMS",
600 "DOCUMENTATIONPARMS",
601 "OTHERTOOLSPARMS",
602 ]:
603 return False
604
605 # test for changes of data and save them in the project
606 # 1. there were none, now there are
607 if key not in self.pdata[category] and len(data) > 0:
608 self.pdata[category][key] = copy.deepcopy(data)
609 self.setDirty(True)
610 # 2. there were some, now there aren't
611 elif key in self.pdata[category] and len(data) == 0:
612 del self.pdata[category][key]
613 self.setDirty(True)
614 # 3. there were some and still are
615 elif key in self.pdata[category] and len(data) > 0:
616 if data != self.pdata[category][key]:
617 self.pdata[category][key] = copy.deepcopy(data)
618 self.setDirty(True)
619 # 4. there were none and none are given
620 else:
621 return False
622 return True
623
624 def initFileTypes(self):
625 """
626 Public method to initialize the filetype associations with default
627 values.
628 """
629 self.pdata["FILETYPES"] = {
630 "*.txt": "OTHERS",
631 "*.md": "OTHERS",
632 "*.rst": "OTHERS",
633 "README": "OTHERS",
634 "README.*": "OTHERS",
635 "*.e4p": "OTHERS",
636 "*.epj": "OTHERS",
637 "GNUmakefile": "OTHERS",
638 "makefile": "OTHERS",
639 "Makefile": "OTHERS",
640 }
641
642 # Sources
643 sourceKey = (
644 "Mixed" if self.pdata["MIXEDLANGUAGE"] else self.pdata["PROGLANGUAGE"]
645 )
646 for ext in self.__sourceExtensions(sourceKey):
647 self.pdata["FILETYPES"]["*{0}".format(ext)] = "SOURCES"
648
649 # IDL interfaces
650 self.pdata["FILETYPES"]["*.idl"] = "INTERFACES"
651
652 # Protobuf Files
653 self.pdata["FILETYPES"]["*.proto"] = "PROTOCOLS"
654
655 # Forms
656 if self.pdata["PROJECTTYPE"] in [
657 "E7Plugin",
658 "PyQt5",
659 "PyQt6",
660 "PySide2",
661 "PySide6",
662 ]:
663 self.pdata["FILETYPES"]["*.ui"] = "FORMS"
664
665 # Resources
666 if self.pdata["PROJECTTYPE"] in [
667 "PyQt5",
668 "PyQt5C",
669 "PySide2",
670 "PySide2C",
671 "PySide6",
672 "PySide6C",
673 ]:
674 self.pdata["FILETYPES"]["*.qrc"] = "RESOURCES"
675
676 # Translations
677 if self.pdata["PROJECTTYPE"] in [
678 "E7Plugin",
679 "PyQt5",
680 "PyQt5C",
681 "PyQt6",
682 "PyQt6C",
683 "PySide2",
684 "PySide2C",
685 "PySide6",
686 "PySide6C",
687 ]:
688 self.pdata["FILETYPES"]["*.ts"] = "TRANSLATIONS"
689 self.pdata["FILETYPES"]["*.qm"] = "TRANSLATIONS"
690
691 # Project type specific ones
692 with contextlib.suppress(KeyError):
693 if self.__fileTypeCallbacks[self.pdata["PROJECTTYPE"]] is not None:
694 ftypes = self.__fileTypeCallbacks[self.pdata["PROJECTTYPE"]]()
695 self.pdata["FILETYPES"].update(ftypes)
696
697 self.setDirty(True)
698
699 def updateFileTypes(self):
700 """
701 Public method to update the filetype associations with new default
702 values.
703 """
704 if self.pdata["PROJECTTYPE"] in [
705 "E7Plugin",
706 "PyQt5",
707 "PyQt5C",
708 "PyQt6",
709 "PyQt6C",
710 "PySide2",
711 "PySide2C",
712 "PySide6",
713 "PySide6C",
714 ]:
715 if "*.ts" not in self.pdata["FILETYPES"]:
716 self.pdata["FILETYPES"]["*.ts"] = "TRANSLATIONS"
717 if "*.qm" not in self.pdata["FILETYPES"]:
718 self.pdata["FILETYPES"]["*.qm"] = "TRANSLATIONS"
719 with contextlib.suppress(KeyError):
720 if self.__fileTypeCallbacks[self.pdata["PROJECTTYPE"]] is not None:
721 ftypes = self.__fileTypeCallbacks[self.pdata["PROJECTTYPE"]]()
722 for pattern, ftype in list(ftypes.items()):
723 if pattern not in self.pdata["FILETYPES"]:
724 self.pdata["FILETYPES"][pattern] = ftype
725 self.setDirty(True)
726
727 def __loadRecent(self):
728 """
729 Private method to load the recently opened project filenames.
730 """
731 self.recent = []
732 Preferences.Prefs.rsettings.sync()
733 rp = Preferences.Prefs.rsettings.value(recentNameProject)
734 if rp is not None:
735 for f in rp:
736 if pathlib.Path(f).exists():
737 self.recent.append(f)
738
739 def __saveRecent(self):
740 """
741 Private method to save the list of recently opened filenames.
742 """
743 Preferences.Prefs.rsettings.setValue(recentNameProject, self.recent)
744 Preferences.Prefs.rsettings.sync()
745
746 def getMostRecent(self):
747 """
748 Public method to get the most recently opened project.
749
750 @return path of the most recently opened project (string)
751 """
752 if len(self.recent):
753 return self.recent[0]
754 else:
755 return None
756
757 def getModel(self):
758 """
759 Public method to get a reference to the project browser model.
760
761 @return reference to the project browser model (ProjectBrowserModel)
762 """
763 return self.__model
764
765 def startFileSystemMonitoring(self):
766 """
767 Public method to (re)start monitoring the project file system.
768 """
769 self.__model.startFileSystemMonitoring()
770
771 def stopFileSystemMonitoring(self):
772 """
773 Public method to stop monitoring the project file system.
774 """
775 self.__model.stopFileSystemMonitoring()
776
777 def getVcs(self):
778 """
779 Public method to get a reference to the VCS object.
780
781 @return reference to the VCS object
782 """
783 return self.vcs
784
785 def handlePreferencesChanged(self):
786 """
787 Public slot used to handle the preferencesChanged signal.
788 """
789 if self.pudata["VCSSTATUSMONITORINTERVAL"]:
790 self.setStatusMonitorInterval(self.pudata["VCSSTATUSMONITORINTERVAL"])
791 else:
792 self.setStatusMonitorInterval(Preferences.getVCS("StatusMonitorInterval"))
793
794 self.__model.preferencesChanged()
795
796 def setDirty(self, dirty):
797 """
798 Public method to set the dirty state.
799
800 It emits the signal dirty(bool).
801
802 @param dirty dirty state
803 @type bool
804 """
805 self.__dirty = dirty
806 self.saveAct.setEnabled(dirty)
807 self.dirty.emit(dirty)
808 if self.__dirty:
809 self.projectChanged.emit()
810
811 def isDirty(self):
812 """
813 Public method to return the dirty state.
814
815 @return dirty state (boolean)
816 """
817 return self.__dirty
818
819 def isOpen(self):
820 """
821 Public method to return the opened state.
822
823 @return open state (boolean)
824 """
825 return self.opened
826
827 def __checkFilesExist(self, index):
828 """
829 Private method to check, if the files in a list exist.
830
831 The files in the indicated list are checked for existance in the
832 filesystem. Non existant files are removed from the list and the
833 dirty state of the project is changed accordingly.
834
835 @param index key of the list to be checked (string)
836 """
837 removed = False
838 removelist = []
839 for file in self.pdata[index]:
840 if not os.path.exists(os.path.join(self.ppath, file)):
841 removelist.append(file)
842 removed = True
843
844 if removed:
845 for file in removelist:
846 self.pdata[index].remove(file)
847 self.setDirty(True)
848
849 def __readProject(self, fn):
850 """
851 Private method to read in a project (.epj or .e4p) file.
852
853 @param fn filename of the project file to be read (string)
854 @return flag indicating success
855 """
856 if os.path.splitext(fn)[1] == ".epj":
857 # new JSON based format
858 with EricOverrideCursor():
859 res = self.__projectFile.readFile(fn)
860 else:
861 # old XML based format
862 f = QFile(fn)
863 if f.open(QIODevice.OpenModeFlag.ReadOnly):
864 from EricXML.ProjectReader import ProjectReader
865
866 reader = ProjectReader(f, self)
867 reader.readXML()
868 res = not reader.hasError()
869 f.close()
870
871 # create hash value, if it doesn't have one
872 if reader.version.startswith("5.") and not self.pdata["HASH"]:
873 hashStr = str(
874 QCryptographicHash.hash(
875 QByteArray(self.ppath.encode("utf-8")),
876 QCryptographicHash.Algorithm.Sha1,
877 ).toHex(),
878 encoding="utf-8",
879 )
880 self.pdata["HASH"] = hashStr
881 self.setDirty(True)
882 else:
883 EricMessageBox.critical(
884 self.ui,
885 self.tr("Read Project File"),
886 self.tr(
887 "<p>The project file <b>{0}</b> could not be read." "</p>"
888 ).format(fn),
889 )
890 res = False
891
892 if res:
893 self.pfile = os.path.abspath(fn)
894 self.ppath = os.path.abspath(os.path.dirname(fn))
895
896 # insert filename into list of recently opened projects
897 self.__syncRecent()
898
899 if self.pdata["TRANSLATIONPATTERN"]:
900 self.translationsRoot = self.pdata["TRANSLATIONPATTERN"].split(
901 "%language%"
902 )[0]
903 elif self.pdata["MAINSCRIPT"]:
904 self.translationsRoot = os.path.splitext(self.pdata["MAINSCRIPT"])[0]
905 if os.path.isdir(os.path.join(self.ppath, self.translationsRoot)):
906 dn = self.translationsRoot
907 else:
908 dn = os.path.dirname(self.translationsRoot)
909 if dn not in self.subdirs:
910 self.subdirs.append(dn)
911
912 self.name = os.path.splitext(os.path.basename(fn))[0]
913
914 # check, if the files of the project still exist in the
915 # project directory
916 self.__checkFilesExist("SOURCES")
917 self.__checkFilesExist("FORMS")
918 self.__checkFilesExist("INTERFACES")
919 self.__checkFilesExist("PROTOCOLS")
920 self.__checkFilesExist("TRANSLATIONS")
921 self.__checkFilesExist("RESOURCES")
922 self.__checkFilesExist("OTHERS")
923
924 # get the names of subdirectories the files are stored in
925 for fn in (
926 self.pdata["SOURCES"]
927 + self.pdata["FORMS"]
928 + self.pdata["INTERFACES"]
929 + self.pdata["PROTOCOLS"]
930 + self.pdata["RESOURCES"]
931 + self.pdata["TRANSLATIONS"]
932 ):
933 dn = os.path.dirname(fn)
934 if dn not in self.subdirs:
935 self.subdirs.append(dn)
936
937 # get the names of other subdirectories
938 for fn in self.pdata["OTHERS"]:
939 dn = os.path.dirname(fn)
940 if dn not in self.otherssubdirs:
941 self.otherssubdirs.append(dn)
942
943 return res
944
945 def __writeProject(self, fn=None):
946 """
947 Private method to save the project infos to a project file.
948
949 @param fn optional filename of the project file to be written (string).
950 If fn is None, the filename stored in the project object
951 is used. This is the 'save' action. If fn is given, this filename
952 is used instead of the one in the project object. This is the
953 'save as' action.
954 @return flag indicating success
955 """
956 if self.vcs is not None:
957 self.pdata["VCSOPTIONS"] = copy.deepcopy(self.vcs.vcsGetOptions())
958 self.pdata["VCSOTHERDATA"] = copy.deepcopy(self.vcs.vcsGetOtherData())
959
960 if not self.pdata["HASH"]:
961 hashStr = str(
962 QCryptographicHash.hash(
963 QByteArray(self.ppath.encode("utf-8")),
964 QCryptographicHash.Algorithm.Sha1,
965 ).toHex(),
966 encoding="utf-8",
967 )
968 self.pdata["HASH"] = hashStr
969
970 if fn is None:
971 fn = self.pfile
972
973 with EricOverrideCursor():
974 res = self.__projectFile.writeFile(fn)
975
976 if res:
977 self.pfile = os.path.abspath(fn)
978 self.ppath = os.path.abspath(os.path.dirname(fn))
979 self.name = os.path.splitext(os.path.basename(fn))[0]
980 self.setDirty(False)
981
982 # insert filename into list of recently opened projects
983 self.__syncRecent()
984
985 return res
986
987 def __readUserProperties(self):
988 """
989 Private method to read in the user specific project file (.eqj or
990 .e4q).
991 """
992 if self.pfile is None:
993 return
994
995 fn1, ext = os.path.splitext(os.path.basename(self.pfile))
996 fn = os.path.join(self.getProjectManagementDir(), "{0}.eqj".format(fn1))
997 if os.path.exists(fn):
998 # try the new JSON based format first
999 self.__userProjectFile.readFile(fn)
1000 else:
1001 # try the old XML based format second
1002 fn = os.path.join(self.getProjectManagementDir(), "{0}.e4q".format(fn1))
1003 if os.path.exists(fn):
1004 f = QFile(fn)
1005 if f.open(QIODevice.OpenModeFlag.ReadOnly):
1006 from EricXML.UserProjectReader import UserProjectReader
1007
1008 reader = UserProjectReader(f, self)
1009 reader.readXML()
1010 f.close()
1011 else:
1012 EricMessageBox.critical(
1013 self.ui,
1014 self.tr("Read User Project Properties"),
1015 self.tr(
1016 "<p>The user specific project properties file"
1017 " <b>{0}</b> could not be read.</p>"
1018 ).format(fn),
1019 )
1020
1021 def __writeUserProperties(self):
1022 """
1023 Private method to write the user specific project data to a JSON file.
1024 """
1025 if self.pfile is None:
1026 return
1027
1028 fn, ext = os.path.splitext(os.path.basename(self.pfile))
1029 fn = os.path.join(self.getProjectManagementDir(), "{0}.eqj".format(fn))
1030
1031 with EricOverrideCursor():
1032 self.__userProjectFile.writeFile(fn)
1033
1034 def __showContextMenuSession(self):
1035 """
1036 Private slot called before the Session menu is shown.
1037 """
1038 enable = True
1039 if self.pfile is None:
1040 enable = False
1041 else:
1042 fn, ext = os.path.splitext(os.path.basename(self.pfile))
1043 fn_new = os.path.join(self.getProjectManagementDir(), "{0}.esj".format(fn))
1044 fn_old = os.path.join(self.getProjectManagementDir(), "{0}.e5s".format(fn))
1045 enable = os.path.exists(fn_new) or os.path.exists(fn_old)
1046 self.sessActGrp.findChild(QAction, "project_load_session").setEnabled(enable)
1047 self.sessActGrp.findChild(QAction, "project_delete_session").setEnabled(enable)
1048
1049 @pyqtSlot()
1050 def __readSession(self, quiet=False, indicator=""):
1051 """
1052 Private method to read in the project session file (.esj or .e5s).
1053
1054 @param quiet flag indicating quiet operations.
1055 If this flag is true, no errors are reported.
1056 @param indicator indicator string (string)
1057 """
1058 if self.pfile is None:
1059 if not quiet:
1060 EricMessageBox.critical(
1061 self.ui,
1062 self.tr("Read Project Session"),
1063 self.tr("Please save the project first."),
1064 )
1065 return
1066
1067 fn1, ext = os.path.splitext(os.path.basename(self.pfile))
1068 fn = os.path.join(
1069 self.getProjectManagementDir(), "{0}{1}.esj".format(fn1, indicator)
1070 )
1071 if os.path.exists(fn):
1072 # try the new JSON based format first
1073 self.__sessionFile.readFile(fn)
1074 else:
1075 # try the old XML based format second
1076 fn = os.path.join(
1077 self.getProjectManagementDir(), "{0}{1}.e5s".format(fn1, indicator)
1078 )
1079 if os.path.exists(fn):
1080 f = QFile(fn)
1081 if f.open(QIODevice.OpenModeFlag.ReadOnly):
1082 from EricXML.SessionReader import SessionReader
1083
1084 reader = SessionReader(f, False)
1085 reader.readXML(quiet=quiet)
1086 f.close()
1087 else:
1088 if not quiet:
1089 EricMessageBox.critical(
1090 self.ui,
1091 self.tr("Read project session"),
1092 self.tr(
1093 "<p>The project session file <b>{0}</b> could"
1094 " not be read.</p>"
1095 ).format(fn),
1096 )
1097
1098 @pyqtSlot()
1099 def __writeSession(self, quiet=False, indicator=""):
1100 """
1101 Private method to write the session data to an XML file (.esj).
1102
1103 @param quiet flag indicating quiet operations.
1104 If this flag is true, no errors are reported.
1105 @param indicator indicator string (string)
1106 """
1107 if self.pfile is None:
1108 if not quiet:
1109 EricMessageBox.critical(
1110 self.ui,
1111 self.tr("Save Project Session"),
1112 self.tr("Please save the project first."),
1113 )
1114 return
1115
1116 fn, ext = os.path.splitext(os.path.basename(self.pfile))
1117 fn = os.path.join(
1118 self.getProjectManagementDir(), "{0}{1}.esj".format(fn, indicator)
1119 )
1120
1121 self.__sessionFile.writeFile(fn)
1122
1123 def __deleteSession(self):
1124 """
1125 Private method to delete the session file.
1126 """
1127 if self.pfile is None:
1128 EricMessageBox.critical(
1129 self.ui,
1130 self.tr("Delete Project Session"),
1131 self.tr("Please save the project first."),
1132 )
1133 return
1134
1135 fname, ext = os.path.splitext(os.path.basename(self.pfile))
1136
1137 for ext in (".esj", ".e5s", ".e4s"):
1138 fn = os.path.join(
1139 self.getProjectManagementDir(), "{0}{1}".format(fname, ext)
1140 )
1141 if os.path.exists(fn):
1142 try:
1143 os.remove(fn)
1144 except OSError:
1145 EricMessageBox.critical(
1146 self.ui,
1147 self.tr("Delete Project Session"),
1148 self.tr(
1149 "<p>The project session file <b>{0}</b> could"
1150 " not be deleted.</p>"
1151 ).format(fn),
1152 )
1153
1154 def __readTasks(self):
1155 """
1156 Private method to read in the project tasks file (.etj or .e6t).
1157 """
1158 if self.pfile is None:
1159 EricMessageBox.critical(
1160 self.ui,
1161 self.tr("Read Tasks"),
1162 self.tr("Please save the project first."),
1163 )
1164 return
1165
1166 base, ext = os.path.splitext(os.path.basename(self.pfile))
1167 fn = os.path.join(self.getProjectManagementDir(), "{0}.etj".format(base))
1168 if os.path.exists(fn):
1169 # try new style JSON file first
1170 self.__tasksFile.readFile(fn)
1171 else:
1172 # try old style XML file second
1173 fn = os.path.join(self.getProjectManagementDir(), "{0}.e6t".format(base))
1174 if os.path.exists(fn):
1175 f = QFile(fn)
1176 if f.open(QIODevice.OpenModeFlag.ReadOnly):
1177 from EricXML.TasksReader import TasksReader
1178
1179 reader = TasksReader(f, True)
1180 reader.readXML()
1181 f.close()
1182 else:
1183 EricMessageBox.critical(
1184 self.ui,
1185 self.tr("Read Tasks"),
1186 self.tr(
1187 "<p>The tasks file <b>{0}</b> could not be read." "</p>"
1188 ).format(fn),
1189 )
1190
1191 def writeTasks(self):
1192 """
1193 Public method to write the tasks data to a JSON file (.etj).
1194 """
1195 if self.pfile is None:
1196 return
1197
1198 fn, ext = os.path.splitext(os.path.basename(self.pfile))
1199
1200 fn = os.path.join(self.getProjectManagementDir(), "{0}.etj".format(fn))
1201 self.__tasksFile.writeFile(fn)
1202
1203 def __showContextMenuDebugger(self):
1204 """
1205 Private slot called before the Debugger menu is shown.
1206 """
1207 enable = True
1208 if self.pfile is None:
1209 enable = False
1210 else:
1211 fn, ext = os.path.splitext(os.path.basename(self.pfile))
1212 # try new style file first
1213 fn = os.path.join(self.getProjectManagementDir(), "{0}.edj".format(fn))
1214 if not os.path.exists(fn):
1215 # try old style file second
1216 fn = os.path.join(self.getProjectManagementDir(), "{0}.e4d".format(fn))
1217 enable = os.path.exists(fn)
1218 self.dbgActGrp.findChild(
1219 QAction, "project_debugger_properties_load"
1220 ).setEnabled(enable)
1221 self.dbgActGrp.findChild(
1222 QAction, "project_debugger_properties_delete"
1223 ).setEnabled(enable)
1224
1225 @pyqtSlot()
1226 def __readDebugProperties(self, quiet=False):
1227 """
1228 Private method to read in the project debugger properties file
1229 (.edj or .e4d).
1230
1231 @param quiet flag indicating quiet operations.
1232 If this flag is true, no errors are reported.
1233 """
1234 if self.pfile is None:
1235 if not quiet:
1236 EricMessageBox.critical(
1237 self.ui,
1238 self.tr("Read Debugger Properties"),
1239 self.tr("Please save the project first."),
1240 )
1241 return
1242
1243 fn1, ext = os.path.splitext(os.path.basename(self.pfile))
1244 fn = os.path.join(self.getProjectManagementDir(), "{0}.edj".format(fn1))
1245 if os.path.exists(fn):
1246 # try the new JSON based format first
1247 if self.__debuggerPropertiesFile.readFile(fn):
1248 self.debugPropertiesLoaded = True
1249 self.debugPropertiesChanged = False
1250 else:
1251 # try the old XML based format second
1252 fn = os.path.join(self.getProjectManagementDir(), "{0}.e4d".format(fn1))
1253
1254 f = QFile(fn)
1255 if f.open(QIODevice.OpenModeFlag.ReadOnly):
1256 from EricXML.DebuggerPropertiesReader import DebuggerPropertiesReader
1257
1258 reader = DebuggerPropertiesReader(f, self)
1259 reader.readXML(quiet=quiet)
1260 f.close()
1261 self.debugPropertiesLoaded = True
1262 self.debugPropertiesChanged = False
1263 else:
1264 if not quiet:
1265 EricMessageBox.critical(
1266 self.ui,
1267 self.tr("Read Debugger Properties"),
1268 self.tr(
1269 "<p>The project debugger properties file"
1270 " <b>{0}</b> could not be read.</p>"
1271 ).format(fn),
1272 )
1273
1274 @pyqtSlot()
1275 def __writeDebugProperties(self, quiet=False):
1276 """
1277 Private method to write the project debugger properties file (.edj).
1278
1279 @param quiet flag indicating quiet operations.
1280 If this flag is true, no errors are reported.
1281 """
1282 if self.pfile is None:
1283 if not quiet:
1284 EricMessageBox.critical(
1285 self.ui,
1286 self.tr("Save Debugger Properties"),
1287 self.tr("Please save the project first."),
1288 )
1289 return
1290
1291 fn, ext = os.path.splitext(os.path.basename(self.pfile))
1292 fn = os.path.join(self.getProjectManagementDir(), "{0}.edj".format(fn))
1293
1294 with EricOverrideCursor():
1295 self.__debuggerPropertiesFile.writeFile(fn)
1296
1297 def __deleteDebugProperties(self):
1298 """
1299 Private method to delete the project debugger properties file
1300 (.edj or .e4d).
1301 """
1302 if self.pfile is None:
1303 EricMessageBox.critical(
1304 self.ui,
1305 self.tr("Delete Debugger Properties"),
1306 self.tr("Please save the project first."),
1307 )
1308 return
1309
1310 fname, ext = os.path.splitext(os.path.basename(self.pfile))
1311
1312 for ext in (".edj", ".e4d"):
1313 fn = os.path.join(
1314 self.getProjectManagementDir(), "{0}{1}".format(fname, ext)
1315 )
1316 if os.path.exists(fn):
1317 try:
1318 os.remove(fn)
1319 except OSError:
1320 EricMessageBox.critical(
1321 self.ui,
1322 self.tr("Delete Debugger Properties"),
1323 self.tr(
1324 "<p>The project debugger properties file"
1325 " <b>{0}</b> could not be deleted.</p>"
1326 ).format(fn),
1327 )
1328
1329 def __initDebugProperties(self):
1330 """
1331 Private method to initialize the debug properties.
1332 """
1333 self.debugPropertiesLoaded = False
1334 self.debugPropertiesChanged = False
1335 self.debugProperties = {
1336 "VIRTUALENV": "",
1337 "DEBUGCLIENT": "",
1338 "ENVIRONMENTOVERRIDE": False,
1339 "ENVIRONMENTSTRING": "",
1340 "REMOTEDEBUGGER": False,
1341 "REMOTEHOST": "",
1342 "REMOTECOMMAND": "",
1343 "PATHTRANSLATION": False,
1344 "REMOTEPATH": "",
1345 "LOCALPATH": "",
1346 "CONSOLEDEBUGGER": False,
1347 "CONSOLECOMMAND": "",
1348 "REDIRECT": False,
1349 "NOENCODING": False,
1350 }
1351
1352 def isDebugPropertiesLoaded(self):
1353 """
1354 Public method to return the status of the debug properties.
1355
1356 @return load status of debug properties (boolean)
1357 """
1358 return self.debugPropertiesLoaded
1359
1360 def __showDebugProperties(self):
1361 """
1362 Private slot to display the debugger properties dialog.
1363 """
1364 from .DebuggerPropertiesDialog import DebuggerPropertiesDialog
1365
1366 dlg = DebuggerPropertiesDialog(self)
1367 if dlg.exec() == QDialog.DialogCode.Accepted:
1368 dlg.storeData()
1369
1370 def getDebugProperty(self, key):
1371 """
1372 Public method to retrieve a debugger property.
1373
1374 @param key key of the property (string)
1375 @return value of the property
1376 """
1377 if key == "INTERPRETER":
1378 return (
1379 ericApp()
1380 .getObject("VirtualEnvManager")
1381 .getVirtualenvInterpreter(self.debugProperties["VIRTUALENV"])
1382 )
1383 else:
1384 return self.debugProperties[key]
1385
1386 def setDbgInfo(
1387 self,
1388 venvName,
1389 argv,
1390 wd,
1391 env,
1392 excReporting,
1393 excList,
1394 excIgnoreList,
1395 autoClearShell,
1396 tracePython=None,
1397 autoContinue=None,
1398 enableMultiprocess=None,
1399 multiprocessNoDebug=None,
1400 configOverride=None,
1401 ):
1402 """
1403 Public method to set the debugging information.
1404
1405 @param venvName name of the virtual environment used
1406 @type str
1407 @param argv command line arguments to be used
1408 @type str
1409 @param wd working directory
1410 @type str
1411 @param env environment setting
1412 @type str
1413 @param excReporting flag indicating the highlighting of exceptions
1414 @type bool
1415 @param excList list of exceptions to be highlighted
1416 @type list of str
1417 @param excIgnoreList list of exceptions to be ignored
1418 @type list of str
1419 @param autoClearShell flag indicating, that the interpreter window
1420 should be cleared
1421 @type bool
1422 @param tracePython flag to indicate if the Python library should be
1423 traced as well
1424 @type bool
1425 @param autoContinue flag indicating, that the debugger should not
1426 stop at the first executable line
1427 @type bool
1428 @param enableMultiprocess flag indicating, that the debugger should
1429 run in multi process mode
1430 @type bool
1431 @param multiprocessNoDebug list of programs not to be debugged in
1432 multi process mode
1433 @type str
1434 @param configOverride dictionary containing the global config override
1435 data
1436 @type dict
1437 """
1438 self.dbgVirtualEnv = venvName
1439 self.dbgCmdline = argv
1440 self.dbgWd = wd
1441 self.dbgEnv = env
1442 self.dbgReportExceptions = excReporting
1443 self.dbgExcList = excList[:] # keep a copy of the list
1444 self.dbgExcIgnoreList = excIgnoreList[:] # keep a copy of the list
1445 self.dbgAutoClearShell = autoClearShell
1446 if tracePython is not None:
1447 self.dbgTracePython = tracePython
1448 if autoContinue is not None:
1449 self.dbgAutoContinue = autoContinue
1450 if enableMultiprocess is not None:
1451 self.dbgEnableMultiprocess = enableMultiprocess
1452 if multiprocessNoDebug is not None:
1453 self.dbgMultiprocessNoDebug = multiprocessNoDebug
1454 if configOverride is not None:
1455 self.dbgGlobalConfigOverride = copy.deepcopy(configOverride)
1456
1457 def getTranslationPattern(self):
1458 """
1459 Public method to get the translation pattern.
1460
1461 @return translation pattern (string)
1462 """
1463 return self.pdata["TRANSLATIONPATTERN"]
1464
1465 def setTranslationPattern(self, pattern):
1466 """
1467 Public method to set the translation pattern.
1468
1469 @param pattern translation pattern
1470 @type str
1471 """
1472 self.pdata["TRANSLATIONPATTERN"] = pattern
1473
1474 def addLanguage(self):
1475 """
1476 Public slot used to add a language to the project.
1477 """
1478 if not self.pdata["TRANSLATIONPATTERN"]:
1479 EricMessageBox.critical(
1480 self.ui,
1481 self.tr("Add Language"),
1482 self.tr("You have to specify a translation pattern first."),
1483 )
1484 return
1485
1486 from .AddLanguageDialog import AddLanguageDialog
1487
1488 dlg = AddLanguageDialog(self.parent())
1489 if dlg.exec() == QDialog.DialogCode.Accepted:
1490 lang = dlg.getSelectedLanguage()
1491 if self.pdata["PROJECTTYPE"] in [
1492 "PyQt5",
1493 "PyQt5C",
1494 "PyQt6",
1495 "PyQt6C",
1496 "E7Plugin",
1497 "PySide2",
1498 "PySide2C",
1499 "PySide6",
1500 "PySide6C",
1501 ]:
1502 langFile = self.pdata["TRANSLATIONPATTERN"].replace("%language%", lang)
1503 self.appendFile(langFile)
1504 self.projectLanguageAddedByCode.emit(lang)
1505
1506 def __binaryTranslationFile(self, langFile):
1507 """
1508 Private method to calculate the filename of the binary translations
1509 file given the name of the raw translations file.
1510
1511 @param langFile name of the raw translations file (string)
1512 @return name of the binary translations file (string)
1513 """
1514 qmFile = ""
1515 try:
1516 if (
1517 self.__binaryTranslationsCallbacks[self.pdata["PROJECTTYPE"]]
1518 is not None
1519 ):
1520 qmFile = self.__binaryTranslationsCallbacks[self.pdata["PROJECTTYPE"]](
1521 langFile
1522 )
1523 except KeyError:
1524 qmFile = langFile.replace(".ts", ".qm")
1525 if qmFile == langFile:
1526 qmFile = ""
1527 return qmFile
1528
1529 def checkLanguageFiles(self):
1530 """
1531 Public slot to check the language files after a release process.
1532 """
1533 tbPath = self.pdata["TRANSLATIONSBINPATH"]
1534 for langFile in self.pdata["TRANSLATIONS"][:]:
1535 qmFile = self.__binaryTranslationFile(langFile)
1536 if qmFile:
1537 if qmFile not in self.pdata["TRANSLATIONS"] and os.path.exists(
1538 os.path.join(self.ppath, qmFile)
1539 ):
1540 self.appendFile(qmFile)
1541 if tbPath:
1542 qmFile = os.path.join(tbPath, os.path.basename(qmFile))
1543 if qmFile not in self.pdata["TRANSLATIONS"] and os.path.exists(
1544 os.path.join(self.ppath, qmFile)
1545 ):
1546 self.appendFile(qmFile)
1547
1548 def removeLanguageFile(self, langFile):
1549 """
1550 Public slot to remove a translation from the project.
1551
1552 The translation file is not deleted from the project directory.
1553
1554 @param langFile the translation file to be removed (string)
1555 """
1556 langFile = self.getRelativePath(langFile)
1557 qmFile = self.__binaryTranslationFile(langFile)
1558 self.pdata["TRANSLATIONS"].remove(langFile)
1559 self.__model.removeItem(langFile)
1560 if qmFile:
1561 with contextlib.suppress(ValueError):
1562 if self.pdata["TRANSLATIONSBINPATH"]:
1563 qmFile = self.getRelativePath(
1564 os.path.join(
1565 self.pdata["TRANSLATIONSBINPATH"], os.path.basename(qmFile)
1566 )
1567 )
1568 self.pdata["TRANSLATIONS"].remove(qmFile)
1569 self.__model.removeItem(qmFile)
1570 self.setDirty(True)
1571
1572 def deleteLanguageFile(self, langFile):
1573 """
1574 Public slot to delete a translation from the project directory.
1575
1576 @param langFile the translation file to be removed (string)
1577 """
1578 try:
1579 from send2trash import send2trash as s2t
1580 except ImportError:
1581 s2t = os.remove
1582
1583 langFile = self.getRelativePath(langFile)
1584 qmFile = self.__binaryTranslationFile(langFile)
1585
1586 try:
1587 fn = os.path.join(self.ppath, langFile)
1588 if os.path.exists(fn):
1589 s2t(fn)
1590 except OSError as err:
1591 EricMessageBox.critical(
1592 self.ui,
1593 self.tr("Delete translation"),
1594 self.tr(
1595 "<p>The selected translation file <b>{0}</b> could not be"
1596 " deleted.</p><p>Reason: {1}</p>"
1597 ).format(langFile, str(err)),
1598 )
1599 return
1600
1601 self.removeLanguageFile(langFile)
1602
1603 # now get rid of the .qm file
1604 if qmFile:
1605 try:
1606 if self.pdata["TRANSLATIONSBINPATH"]:
1607 qmFile = self.getRelativePath(
1608 os.path.join(
1609 self.pdata["TRANSLATIONSBINPATH"], os.path.basename(qmFile)
1610 )
1611 )
1612 fn = os.path.join(self.ppath, qmFile)
1613 if os.path.exists(fn):
1614 s2t(fn)
1615 except OSError as err:
1616 EricMessageBox.critical(
1617 self.ui,
1618 self.tr("Delete translation"),
1619 self.tr(
1620 "<p>The selected translation file <b>{0}</b> could"
1621 " not be deleted.</p><p>Reason: {1}</p>"
1622 ).format(qmFile, str(err)),
1623 )
1624 return
1625
1626 def appendFile(self, fn, isSourceFile=False, updateModel=True):
1627 """
1628 Public method to append a file to the project.
1629
1630 @param fn filename to be added to the project (string)
1631 @param isSourceFile flag indicating that this is a source file
1632 even if it doesn't have the source extension (boolean)
1633 @param updateModel flag indicating an update of the model is
1634 requested (boolean)
1635 """
1636 dirty = False
1637
1638 # make it relative to the project root, if it starts with that path
1639 # assume relative paths are relative to the project root
1640 newfn = self.getRelativePath(fn) if os.path.isabs(fn) else fn
1641 newdir = os.path.dirname(newfn)
1642
1643 if isSourceFile:
1644 filetype = "SOURCES"
1645 else:
1646 filetype = "OTHERS"
1647 bfn = os.path.basename(newfn)
1648 if fnmatch.fnmatch(bfn, "*.ts") or fnmatch.fnmatch(bfn, "*.qm"):
1649 filetype = "TRANSLATIONS"
1650 else:
1651 for pattern in sorted(self.pdata["FILETYPES"].keys(), reverse=True):
1652 if fnmatch.fnmatch(bfn, pattern):
1653 filetype = self.pdata["FILETYPES"][pattern]
1654 break
1655
1656 if filetype == "__IGNORE__":
1657 return
1658
1659 if filetype in ["SOURCES", "FORMS", "INTERFACES", "PROTOCOLS", "RESOURCES"]:
1660 if filetype == "SOURCES":
1661 if newfn not in self.pdata["SOURCES"]:
1662 self.pdata["SOURCES"].append(newfn)
1663 self.projectSourceAdded.emit(newfn)
1664 updateModel and self.__model.addNewItem("SOURCES", newfn)
1665 dirty = True
1666 else:
1667 updateModel and self.repopulateItem(newfn)
1668 elif filetype == "FORMS":
1669 if newfn not in self.pdata["FORMS"]:
1670 self.pdata["FORMS"].append(newfn)
1671 self.projectFormAdded.emit(newfn)
1672 updateModel and self.__model.addNewItem("FORMS", newfn)
1673 dirty = True
1674 else:
1675 updateModel and self.repopulateItem(newfn)
1676 elif filetype == "INTERFACES":
1677 if newfn not in self.pdata["INTERFACES"]:
1678 self.pdata["INTERFACES"].append(newfn)
1679 self.projectInterfaceAdded.emit(newfn)
1680 (updateModel and self.__model.addNewItem("INTERFACES", newfn))
1681 dirty = True
1682 else:
1683 updateModel and self.repopulateItem(newfn)
1684 elif filetype == "PROTOCOLS":
1685 if newfn not in self.pdata["PROTOCOLS"]:
1686 self.pdata["PROTOCOLS"].append(newfn)
1687 self.projectProtocolAdded.emit(newfn)
1688 (updateModel and self.__model.addNewItem("PROTOCOLS", newfn))
1689 dirty = True
1690 else:
1691 updateModel and self.repopulateItem(newfn)
1692 elif filetype == "RESOURCES":
1693 if newfn not in self.pdata["RESOURCES"]:
1694 self.pdata["RESOURCES"].append(newfn)
1695 self.projectResourceAdded.emit(newfn)
1696 updateModel and self.__model.addNewItem("RESOURCES", newfn)
1697 dirty = True
1698 else:
1699 updateModel and self.repopulateItem(newfn)
1700 if newdir not in self.subdirs:
1701 self.subdirs.append(newdir)
1702 elif filetype == "TRANSLATIONS":
1703 if newfn not in self.pdata["TRANSLATIONS"]:
1704 self.pdata["TRANSLATIONS"].append(newfn)
1705 updateModel and self.__model.addNewItem("TRANSLATIONS", newfn)
1706 self.projectLanguageAdded.emit(newfn)
1707 dirty = True
1708 else:
1709 updateModel and self.repopulateItem(newfn)
1710 else: # filetype == "OTHERS"
1711 if newfn not in self.pdata["OTHERS"]:
1712 self.pdata["OTHERS"].append(newfn)
1713 self.othersAdded(newfn, updateModel)
1714 dirty = True
1715 else:
1716 updateModel and self.repopulateItem(newfn)
1717 if newdir not in self.otherssubdirs:
1718 self.otherssubdirs.append(newdir)
1719
1720 if dirty:
1721 self.setDirty(True)
1722
1723 @pyqtSlot()
1724 def addFiles(self, fileTypeFilter=None, startdir=None):
1725 """
1726 Public slot used to add files to the project.
1727
1728 @param fileTypeFilter filter to be used by the add file dialog
1729 @type str out of source, form, resource, interface, protocol, others
1730 @param startdir start directory for the selection dialog
1731 @type str
1732 """
1733 if startdir is None:
1734 startdir = self.ppath
1735 from .AddFileDialog import AddFileDialog
1736
1737 dlg = AddFileDialog(self, self.parent(), fileTypeFilter, startdir=startdir)
1738 if dlg.exec() == QDialog.DialogCode.Accepted:
1739 fnames, target, isSource = dlg.getData()
1740 if target != "":
1741 for fn in fnames:
1742 targetfile = os.path.join(target, os.path.basename(fn))
1743 if not Utilities.samepath(os.path.dirname(fn), target):
1744 try:
1745 if not os.path.isdir(target):
1746 os.makedirs(target)
1747
1748 if os.path.exists(targetfile):
1749 res = EricMessageBox.yesNo(
1750 self.ui,
1751 self.tr("Add file"),
1752 self.tr(
1753 "<p>The file <b>{0}</b> already"
1754 " exists.</p><p>Overwrite it?</p>"
1755 ).format(targetfile),
1756 icon=EricMessageBox.Warning,
1757 )
1758 if not res:
1759 return # don't overwrite
1760
1761 shutil.copy(fn, target)
1762 except OSError as why:
1763 EricMessageBox.critical(
1764 self.ui,
1765 self.tr("Add file"),
1766 self.tr(
1767 "<p>The selected file <b>{0}</b> could"
1768 " not be added to <b>{1}</b>.</p>"
1769 "<p>Reason: {2}</p>"
1770 ).format(fn, target, str(why)),
1771 )
1772 continue
1773
1774 self.appendFile(targetfile, isSource or fileTypeFilter == "source")
1775 else:
1776 EricMessageBox.critical(
1777 self.ui,
1778 self.tr("Add file"),
1779 self.tr("The target directory must not be empty."),
1780 )
1781
1782 def __addSingleDirectory(self, filetype, source, target, quiet=False):
1783 """
1784 Private method used to add all files of a single directory to the
1785 project.
1786
1787 @param filetype type of files to add (string)
1788 @param source source directory (string)
1789 @param target target directory (string)
1790 @param quiet flag indicating quiet operations (boolean)
1791 """
1792 # get all relevant filename patterns
1793 patterns = []
1794 ignorePatterns = []
1795 for pattern, patterntype in list(self.pdata["FILETYPES"].items()):
1796 if patterntype == filetype:
1797 patterns.append(pattern)
1798 elif patterntype == "__IGNORE__":
1799 ignorePatterns.append(pattern)
1800
1801 files = []
1802 for pattern in patterns:
1803 sstring = "{0}{1}{2}".format(source, os.sep, pattern)
1804 files.extend(glob.glob(sstring))
1805
1806 if len(files) == 0:
1807 if not quiet:
1808 EricMessageBox.information(
1809 self.ui,
1810 self.tr("Add directory"),
1811 self.tr(
1812 "<p>The source directory doesn't contain"
1813 " any files belonging to the selected category.</p>"
1814 ),
1815 )
1816 return
1817
1818 if not Utilities.samepath(target, source) and not os.path.isdir(target):
1819 try:
1820 os.makedirs(target)
1821 except OSError as why:
1822 EricMessageBox.critical(
1823 self.ui,
1824 self.tr("Add directory"),
1825 self.tr(
1826 "<p>The target directory <b>{0}</b> could not be"
1827 " created.</p><p>Reason: {1}</p>"
1828 ).format(target, str(why)),
1829 )
1830 return
1831
1832 for file in files:
1833 for pattern in ignorePatterns:
1834 if fnmatch.fnmatch(file, pattern):
1835 continue
1836
1837 targetfile = os.path.join(target, os.path.basename(file))
1838 if not Utilities.samepath(target, source):
1839 try:
1840 if os.path.exists(targetfile):
1841 res = EricMessageBox.yesNo(
1842 self.ui,
1843 self.tr("Add directory"),
1844 self.tr(
1845 "<p>The file <b>{0}</b> already exists.</p>"
1846 "<p>Overwrite it?</p>"
1847 ).format(targetfile),
1848 icon=EricMessageBox.Warning,
1849 )
1850 if not res:
1851 continue
1852 # don't overwrite, carry on with next file
1853
1854 shutil.copy(file, target)
1855 except OSError:
1856 continue
1857 self.appendFile(targetfile)
1858
1859 def __addRecursiveDirectory(self, filetype, source, target):
1860 """
1861 Private method used to add all files of a directory tree.
1862
1863 The tree is rooted at source to another one rooted at target. This
1864 method decents down to the lowest subdirectory.
1865
1866 @param filetype type of files to add (string)
1867 @param source source directory (string)
1868 @param target target directory (string)
1869 """
1870 # first perform the addition of source
1871 self.__addSingleDirectory(filetype, source, target, True)
1872
1873 ignore_patterns = [
1874 pattern
1875 for pattern, filetype in self.pdata["FILETYPES"].items()
1876 if filetype == "__IGNORE__"
1877 ]
1878
1879 # now recurse into subdirectories
1880 for name in os.listdir(source):
1881 ns = os.path.join(source, name)
1882 if os.path.isdir(ns):
1883 skip = False
1884 for ignore_pattern in ignore_patterns:
1885 if fnmatch.fnmatch(name, ignore_pattern):
1886 skip = True
1887 break
1888 if skip:
1889 continue
1890
1891 nt = os.path.join(target, name)
1892 self.__addRecursiveDirectory(filetype, ns, nt)
1893
1894 @pyqtSlot()
1895 def addDirectory(self, fileTypeFilter=None, startdir=None):
1896 """
1897 Public method used to add all files of a directory to the project.
1898
1899 @param fileTypeFilter filter to be used by the add directory dialog
1900 @type str out of source, form, resource, interface, protocol, others
1901 @param startdir start directory for the selection dialog
1902 @type str
1903 """
1904 if startdir is None:
1905 startdir = self.ppath
1906 from .AddDirectoryDialog import AddDirectoryDialog
1907
1908 dlg = AddDirectoryDialog(self, fileTypeFilter, self.parent(), startdir=startdir)
1909 if dlg.exec() == QDialog.DialogCode.Accepted:
1910 filetype, source, target, recursive = dlg.getData()
1911 if target == "":
1912 EricMessageBox.critical(
1913 self.ui,
1914 self.tr("Add directory"),
1915 self.tr("The target directory must not be empty."),
1916 )
1917 return
1918
1919 if filetype == "OTHERS":
1920 self.addToOthers(source)
1921 return
1922
1923 if source == "":
1924 EricMessageBox.critical(
1925 self.ui,
1926 self.tr("Add directory"),
1927 self.tr("The source directory must not be empty."),
1928 )
1929 return
1930
1931 if recursive:
1932 self.__addRecursiveDirectory(filetype, source, target)
1933 else:
1934 self.__addSingleDirectory(filetype, source, target)
1935
1936 def addToOthers(self, fn):
1937 """
1938 Public method to add a file/directory to the OTHERS project data.
1939
1940 @param fn file name or directory name to add (string)
1941 """
1942 if fn:
1943 # if it is below the project directory, make it relative to that
1944 fn = self.getRelativePath(fn)
1945
1946 # if it ends with the directory separator character, remove it
1947 if fn.endswith(os.sep):
1948 fn = fn[:-1]
1949
1950 if fn not in self.pdata["OTHERS"]:
1951 self.pdata["OTHERS"].append(fn)
1952 self.othersAdded(fn)
1953 self.setDirty(True)
1954
1955 if os.path.isdir(fn) and fn not in self.otherssubdirs:
1956 self.otherssubdirs.append(fn)
1957
1958 def addSourceFiles(self):
1959 """
1960 Public slot to add source files to the current project.
1961 """
1962 self.addFiles("source")
1963
1964 def addUiFiles(self):
1965 """
1966 Public slot to add forms to the current project.
1967 """
1968 self.addFiles("form")
1969
1970 def addIdlFiles(self):
1971 """
1972 Public slot to add IDL interfaces to the current project.
1973 """
1974 self.addFiles("interface")
1975
1976 def addProtoFiles(self):
1977 """
1978 Public slot to add protocol files to the current project.
1979 """
1980 self.addFiles("protocol")
1981
1982 def addResourceFiles(self):
1983 """
1984 Public slot to add Qt resources to the current project.
1985 """
1986 self.addFiles("resource")
1987
1988 def addOthersFiles(self):
1989 """
1990 Public slot to add files to the OTHERS project data.
1991 """
1992 self.addFiles("others")
1993
1994 def addSourceDir(self):
1995 """
1996 Public slot to add all source files of a directory to the current
1997 project.
1998 """
1999 self.addDirectory("source")
2000
2001 def addUiDir(self):
2002 """
2003 Public slot to add all forms of a directory to the current project.
2004 """
2005 self.addDirectory("form")
2006
2007 def addIdlDir(self):
2008 """
2009 Public slot to add all IDL interfaces of a directory to the current
2010 project.
2011 """
2012 self.addDirectory("interface")
2013
2014 def addProtoDir(self):
2015 """
2016 Public slot to add all protocol files of a directory to the current
2017 project.
2018 """
2019 self.addDirectory("protocol")
2020
2021 def addResourceDir(self):
2022 """
2023 Public slot to add all Qt resource files of a directory to the current
2024 project.
2025 """
2026 self.addDirectory("resource")
2027
2028 def addOthersDir(self):
2029 """
2030 Public slot to add a directory to the OTHERS project data.
2031 """
2032 self.addDirectory("others")
2033
2034 def renameMainScript(self, oldfn, newfn):
2035 """
2036 Public method to rename the main script.
2037
2038 @param oldfn old filename (string)
2039 @param newfn new filename of the main script (string)
2040 """
2041 if self.pdata["MAINSCRIPT"]:
2042 ofn = self.getRelativePath(oldfn)
2043 if ofn != self.pdata["MAINSCRIPT"]:
2044 return
2045
2046 fn = self.getRelativePath(newfn)
2047 self.pdata["MAINSCRIPT"] = fn
2048 self.setDirty(True)
2049
2050 def renameFile(self, oldfn, newfn=None):
2051 """
2052 Public slot to rename a file of the project.
2053
2054 @param oldfn old filename of the file (string)
2055 @param newfn new filename of the file (string)
2056 @return flag indicating success
2057 """
2058 fn = self.getRelativePath(oldfn)
2059 isSourceFile = fn in self.pdata["SOURCES"]
2060
2061 if newfn is None:
2062 newfn = EricFileDialog.getSaveFileName(
2063 None,
2064 self.tr("Rename file"),
2065 oldfn,
2066 "",
2067 EricFileDialog.DontConfirmOverwrite,
2068 )
2069 if not newfn:
2070 return False
2071 newfn = Utilities.toNativeSeparators(newfn)
2072
2073 if os.path.exists(newfn):
2074 res = EricMessageBox.yesNo(
2075 self.ui,
2076 self.tr("Rename File"),
2077 self.tr(
2078 """<p>The file <b>{0}</b> already exists."""
2079 """ Overwrite it?</p>"""
2080 ).format(newfn),
2081 icon=EricMessageBox.Warning,
2082 )
2083 if not res:
2084 return False
2085
2086 try:
2087 os.rename(oldfn, newfn)
2088 except OSError as msg:
2089 EricMessageBox.critical(
2090 self.ui,
2091 self.tr("Rename File"),
2092 self.tr(
2093 """<p>The file <b>{0}</b> could not be renamed.<br />"""
2094 """Reason: {1}</p>"""
2095 ).format(oldfn, str(msg)),
2096 )
2097 return False
2098
2099 if (
2100 fn in self.pdata["SOURCES"]
2101 or fn in self.pdata["FORMS"]
2102 or fn in self.pdata["TRANSLATIONS"]
2103 or fn in self.pdata["INTERFACES"]
2104 or fn in self.pdata["PROTOCOLS"]
2105 or fn in self.pdata["RESOURCES"]
2106 or fn in self.pdata["OTHERS"]
2107 ):
2108 self.renameFileInPdata(oldfn, newfn, isSourceFile)
2109
2110 return True
2111
2112 def renameFileInPdata(self, oldname, newname, isSourceFile=False):
2113 """
2114 Public method to rename a file in the pdata structure.
2115
2116 @param oldname old filename (string)
2117 @param newname new filename (string)
2118 @param isSourceFile flag indicating that this is a source file
2119 even if it doesn't have the source extension (boolean)
2120 """
2121 fn = self.getRelativePath(oldname)
2122 if os.path.dirname(oldname) == os.path.dirname(newname):
2123 if self.__isInPdata(oldname):
2124 self.removeFile(oldname, False)
2125 self.appendFile(newname, isSourceFile, False)
2126 self.__model.renameItem(fn, newname)
2127 else:
2128 self.removeFile(oldname)
2129 self.appendFile(newname, isSourceFile)
2130 self.projectFileRenamed.emit(oldname, newname)
2131
2132 self.renameMainScript(fn, newname)
2133
2134 def getFiles(self, start):
2135 """
2136 Public method to get all files starting with a common prefix.
2137
2138 @param start prefix (string)
2139 @return list of files starting with a common prefix (list of strings)
2140 """
2141 filelist = []
2142 start = self.getRelativePath(start)
2143 for key in [
2144 "SOURCES",
2145 "FORMS",
2146 "INTERFACES",
2147 "PROTOCOLS",
2148 "RESOURCES",
2149 "OTHERS",
2150 ]:
2151 for entry in self.pdata[key][:]:
2152 if entry.startswith(start):
2153 filelist.append(os.path.join(self.ppath, entry))
2154 return filelist
2155
2156 def __reorganizeFiles(self):
2157 """
2158 Private method to reorganize files stored in the project.
2159 """
2160 reorganized = False
2161
2162 # init data store for the reorganization
2163 newPdata = {}
2164 for key in [
2165 "SOURCES",
2166 "FORMS",
2167 "INTERFACES",
2168 "PROTOCOLS",
2169 "RESOURCES",
2170 "OTHERS",
2171 "TRANSLATIONS",
2172 ]:
2173 newPdata[key] = []
2174
2175 # iterate over all files checking for a reassignment
2176 for key in [
2177 "SOURCES",
2178 "FORMS",
2179 "INTERFACES",
2180 "PROTOCOLS",
2181 "RESOURCES",
2182 "OTHERS",
2183 "TRANSLATIONS",
2184 ]:
2185 for fn in self.pdata[key][:]:
2186 filetype = key
2187 bfn = os.path.basename(fn)
2188 for pattern in sorted(self.pdata["FILETYPES"].keys(), reverse=True):
2189 if fnmatch.fnmatch(bfn, pattern):
2190 filetype = self.pdata["FILETYPES"][pattern]
2191 break
2192
2193 if filetype != "__IGNORE__":
2194 newPdata[filetype].append(fn)
2195 if filetype != key:
2196 reorganized = True
2197
2198 if reorganized:
2199 # copy the reorganized files back to the project
2200 for key in [
2201 "SOURCES",
2202 "FORMS",
2203 "INTERFACES",
2204 "PROTOCOLS",
2205 "RESOURCES",
2206 "OTHERS",
2207 "TRANSLATIONS",
2208 ]:
2209 self.pdata[key] = newPdata[key][:]
2210
2211 # repopulate the model
2212 self.__model.projectClosed(False)
2213 self.__model.projectOpened()
2214
2215 def copyDirectory(self, olddn, newdn):
2216 """
2217 Public slot to copy a directory.
2218
2219 @param olddn original directory name (string)
2220 @param newdn new directory name (string)
2221 """
2222 olddn = self.getRelativePath(olddn)
2223 newdn = self.getRelativePath(newdn)
2224 for key in [
2225 "SOURCES",
2226 "FORMS",
2227 "INTERFACES",
2228 "PROTOCOLS",
2229 "RESOURCES",
2230 "OTHERS",
2231 ]:
2232 for entry in self.pdata[key][:]:
2233 if entry.startswith(olddn):
2234 entry = entry.replace(olddn, newdn)
2235 self.appendFile(os.path.join(self.ppath, entry), key == "SOURCES")
2236 self.setDirty(True)
2237
2238 def moveDirectory(self, olddn, newdn):
2239 """
2240 Public slot to move a directory.
2241
2242 @param olddn old directory name (string)
2243 @param newdn new directory name (string)
2244 """
2245 olddn = self.getRelativePath(olddn)
2246 newdn = self.getRelativePath(newdn)
2247 typeStrings = []
2248 for key in [
2249 "SOURCES",
2250 "FORMS",
2251 "INTERFACES",
2252 "PROTOCOLS",
2253 "RESOURCES",
2254 "OTHERS",
2255 ]:
2256 for entry in self.pdata[key][:]:
2257 if entry.startswith(olddn):
2258 if key not in typeStrings:
2259 typeStrings.append(key)
2260 self.pdata[key].remove(entry)
2261 entry = entry.replace(olddn, newdn)
2262 self.pdata[key].append(entry)
2263 if key == "OTHERS":
2264 if newdn not in self.otherssubdirs:
2265 self.otherssubdirs.append(newdn)
2266 else:
2267 if newdn not in self.subdirs:
2268 self.subdirs.append(newdn)
2269 if typeStrings:
2270 # the directory is controlled by the project
2271 self.setDirty(True)
2272 self.__model.removeItem(olddn)
2273 typeString = typeStrings[0]
2274 del typeStrings[0]
2275 self.__model.addNewItem(typeString, newdn, typeStrings)
2276 else:
2277 self.__model.renameItem(olddn, self.getAbsolutePath(newdn))
2278 self.directoryRemoved.emit(olddn)
2279
2280 def removeFile(self, fn, updateModel=True):
2281 """
2282 Public slot to remove a file from the project.
2283
2284 The file is not deleted from the project directory.
2285
2286 @param fn filename to be removed from the project
2287 @param updateModel flag indicating an update of the model is
2288 requested (boolean)
2289 """
2290 fn = self.getRelativePath(fn)
2291 dirty = True
2292 if fn in self.pdata["SOURCES"]:
2293 self.pdata["SOURCES"].remove(fn)
2294 self.projectSourceRemoved.emit(fn)
2295 elif fn in self.pdata["FORMS"]:
2296 self.pdata["FORMS"].remove(fn)
2297 self.projectFormRemoved.emit(fn)
2298 elif fn in self.pdata["INTERFACES"]:
2299 self.pdata["INTERFACES"].remove(fn)
2300 self.projectInterfaceRemoved.emit(fn)
2301 elif fn in self.pdata["PROTOCOLS"]:
2302 self.pdata["PROTOCOLS"].remove(fn)
2303 self.projectProtocolRemoved.emit(fn)
2304 elif fn in self.pdata["RESOURCES"]:
2305 self.pdata["RESOURCES"].remove(fn)
2306 self.projectResourceRemoved.emit(fn)
2307 elif fn in self.pdata["OTHERS"]:
2308 self.pdata["OTHERS"].remove(fn)
2309 self.projectOthersRemoved.emit(fn)
2310 elif fn in self.pdata["TRANSLATIONS"]:
2311 self.pdata["TRANSLATIONS"].remove(fn)
2312 self.projectLanguageRemoved.emit(fn)
2313 else:
2314 dirty = False
2315 updateModel and self.__model.removeItem(fn)
2316 if dirty:
2317 self.setDirty(True)
2318
2319 def removeDirectory(self, dn):
2320 """
2321 Public method to remove a directory from the project.
2322
2323 The directory is not deleted from the project directory.
2324
2325 @param dn directory name to be removed from the project
2326 """
2327 dirty = False
2328 dn = self.getRelativePath(dn)
2329 for entry in self.pdata["OTHERS"][:]:
2330 if entry.startswith(dn):
2331 self.pdata["OTHERS"].remove(entry)
2332 dirty = True
2333 dn2 = dn if dn.endswith(os.sep) else dn + os.sep
2334 for key in [
2335 "SOURCES",
2336 "FORMS",
2337 "INTERFACES",
2338 "PROTOCOLS",
2339 "RESOURCES",
2340 "TRANSLATIONS",
2341 ]:
2342 for entry in self.pdata[key][:]:
2343 if entry.startswith(dn2):
2344 self.pdata[key].remove(entry)
2345 dirty = True
2346 self.__model.removeItem(dn)
2347 if dirty:
2348 self.setDirty(True)
2349 self.directoryRemoved.emit(dn)
2350
2351 def deleteFile(self, fn):
2352 """
2353 Public method to delete a file from the project directory.
2354
2355 @param fn filename to be deleted from the project
2356 @return flag indicating success (boolean)
2357 """
2358 try:
2359 from send2trash import send2trash as s2t
2360 except ImportError:
2361 s2t = os.remove
2362
2363 try:
2364 s2t(os.path.join(self.ppath, fn))
2365 path, ext = os.path.splitext(fn)
2366 if ext == ".ui":
2367 fn2 = os.path.join(self.ppath, "{0}.h".format(fn))
2368 if os.path.isfile(fn2):
2369 s2t(fn2)
2370 head, tail = os.path.split(path)
2371 for ext in [".pyc", ".pyo"]:
2372 fn2 = os.path.join(self.ppath, path + ext)
2373 if os.path.isfile(fn2):
2374 s2t(fn2)
2375 pat = os.path.join(
2376 self.ppath, head, "__pycache__", "{0}.*{1}".format(tail, ext)
2377 )
2378 for f in glob.glob(pat):
2379 s2t(f)
2380 except OSError as err:
2381 EricMessageBox.critical(
2382 self.ui,
2383 self.tr("Delete file"),
2384 self.tr(
2385 "<p>The selected file <b>{0}</b> could not be"
2386 " deleted.</p><p>Reason: {1}</p>"
2387 ).format(fn, str(err)),
2388 )
2389 return False
2390
2391 self.removeFile(fn)
2392 if ext == ".ui":
2393 self.removeFile(fn + ".h")
2394 return True
2395
2396 def deleteDirectory(self, dn):
2397 """
2398 Public method to delete a directory from the project directory.
2399
2400 @param dn directory name to be removed from the project
2401 @return flag indicating success (boolean)
2402 """
2403 if not os.path.isabs(dn):
2404 dn = os.path.join(self.ppath, dn)
2405 try:
2406 try:
2407 from send2trash import send2trash
2408
2409 send2trash(dn)
2410 except ImportError:
2411 shutil.rmtree(dn, True)
2412 except OSError as err:
2413 EricMessageBox.critical(
2414 self.ui,
2415 self.tr("Delete directory"),
2416 self.tr(
2417 "<p>The selected directory <b>{0}</b> could not be"
2418 " deleted.</p><p>Reason: {1}</p>"
2419 ).format(dn, str(err)),
2420 )
2421 return False
2422
2423 self.removeDirectory(dn)
2424 return True
2425
2426 def hasEntry(self, fn):
2427 """
2428 Public method to check the project for a file.
2429
2430 @param fn filename to be checked (string)
2431 @return flag indicating, if the project contains the file (boolean)
2432 """
2433 fn = self.getRelativePath(fn)
2434 return (
2435 fn in self.pdata["SOURCES"]
2436 or fn in self.pdata["FORMS"]
2437 or fn in self.pdata["INTERFACES"]
2438 or fn in self.pdata["PROTOCOLS"]
2439 or fn in self.pdata["RESOURCES"]
2440 or fn in self.pdata["OTHERS"]
2441 )
2442
2443 def createNewProject(self):
2444 """
2445 Public slot to built a new project.
2446
2447 This method displays the new project dialog and initializes
2448 the project object with the data entered.
2449 """
2450 if not self.checkDirty():
2451 return
2452
2453 from .PropertiesDialog import PropertiesDialog
2454
2455 dlg = PropertiesDialog(self, True)
2456 if dlg.exec() == QDialog.DialogCode.Accepted:
2457 self.closeProject()
2458 dlg.storeData()
2459 self.pdata["VCS"] = "None"
2460 self.opened = True
2461 if not self.pdata["FILETYPES"]:
2462 self.initFileTypes()
2463 self.setDirty(True)
2464 self.closeAct.setEnabled(True)
2465 self.saveasAct.setEnabled(True)
2466 self.actGrp2.setEnabled(True)
2467 self.propsAct.setEnabled(True)
2468 self.userPropsAct.setEnabled(True)
2469 self.filetypesAct.setEnabled(True)
2470 self.lexersAct.setEnabled(True)
2471 self.sessActGrp.setEnabled(False)
2472 self.dbgActGrp.setEnabled(True)
2473 self.menuDebuggerAct.setEnabled(True)
2474 self.menuSessionAct.setEnabled(False)
2475 self.menuCheckAct.setEnabled(True)
2476 self.menuShowAct.setEnabled(True)
2477 self.menuDiagramAct.setEnabled(True)
2478 self.menuApidocAct.setEnabled(True)
2479 self.menuPackagersAct.setEnabled(True)
2480 self.pluginGrp.setEnabled(self.pdata["PROJECTTYPE"] in ["E7Plugin"])
2481 self.addLanguageAct.setEnabled(bool(self.pdata["TRANSLATIONPATTERN"]))
2482 self.makeGrp.setEnabled(self.pdata["MAKEPARAMS"]["MakeEnabled"])
2483 self.menuMakeAct.setEnabled(self.pdata["MAKEPARAMS"]["MakeEnabled"])
2484 self.menuOtherToolsAct.setEnabled(True)
2485 self.menuFormattingAct.setEnabled(True)
2486
2487 self.projectAboutToBeCreated.emit()
2488
2489 hashStr = str(
2490 QCryptographicHash.hash(
2491 QByteArray(self.ppath.encode("utf-8")),
2492 QCryptographicHash.Algorithm.Sha1,
2493 ).toHex(),
2494 encoding="utf-8",
2495 )
2496 self.pdata["HASH"] = hashStr
2497
2498 if self.pdata["PROGLANGUAGE"] == "MicroPython":
2499 # change the lexer association for *.py files
2500 self.pdata["LEXERASSOCS"] = {
2501 "*.py": "MicroPython",
2502 }
2503
2504 # create the project directory if it doesn't exist already
2505 if not os.path.isdir(self.ppath):
2506 try:
2507 os.makedirs(self.ppath)
2508 except OSError:
2509 EricMessageBox.critical(
2510 self.ui,
2511 self.tr("Create project directory"),
2512 self.tr(
2513 "<p>The project directory <b>{0}</b> could not"
2514 " be created.</p>"
2515 ).format(self.ppath),
2516 )
2517 self.vcs = self.initVCS()
2518 return
2519
2520 # create an empty __init__.py file to make it a Python package
2521 # (only for Python and Python3)
2522 if self.pdata["PROGLANGUAGE"] in ["Python3", "MicroPython"]:
2523 fn = os.path.join(self.ppath, "__init__.py")
2524 with open(fn, "w", encoding="utf-8"):
2525 pass
2526 self.appendFile(fn, True)
2527
2528 # create an empty main script file, if a name was given
2529 if self.pdata["MAINSCRIPT"]:
2530 if not os.path.isabs(self.pdata["MAINSCRIPT"]):
2531 ms = os.path.join(self.ppath, self.pdata["MAINSCRIPT"])
2532 else:
2533 ms = self.pdata["MAINSCRIPT"]
2534 os.makedirs(os.path.dirname(ms), exist_ok=True)
2535 with open(ms, "w"):
2536 pass
2537 self.appendFile(ms, True)
2538
2539 if self.pdata["MAKEPARAMS"]["MakeEnabled"]:
2540 mf = self.pdata["MAKEPARAMS"]["MakeFile"]
2541 if mf:
2542 if not os.path.isabs(mf):
2543 mf = os.path.join(self.ppath, mf)
2544 else:
2545 mf = os.path.join(self.ppath, Project.DefaultMakefile)
2546 os.makedirs(os.path.dirname(mf), exist_ok=True)
2547 with open(mf, "w"):
2548 pass
2549 self.appendFile(mf)
2550
2551 tpd = os.path.join(self.ppath, self.translationsRoot)
2552 if not self.translationsRoot.endswith(os.sep):
2553 tpd = os.path.dirname(tpd)
2554 if not os.path.isdir(tpd):
2555 os.makedirs(tpd, exist_ok=True)
2556 if self.pdata["TRANSLATIONSBINPATH"]:
2557 tpd = os.path.join(self.ppath, self.pdata["TRANSLATIONSBINPATH"])
2558 if not os.path.isdir(tpd):
2559 os.makedirs(tpd, exist_ok=True)
2560
2561 # create management directory if not present
2562 self.createProjectManagementDir()
2563
2564 self.saveProject()
2565 addAllToVcs = True
2566 else:
2567 try:
2568 # create management directory if not present
2569 self.createProjectManagementDir()
2570 except OSError:
2571 EricMessageBox.critical(
2572 self.ui,
2573 self.tr("Create project management directory"),
2574 self.tr(
2575 "<p>The project directory <b>{0}</b> is not"
2576 " writable.</p>"
2577 ).format(self.ppath),
2578 )
2579 return
2580
2581 if self.pdata["MAINSCRIPT"]:
2582 if not os.path.isabs(self.pdata["MAINSCRIPT"]):
2583 ms = os.path.join(self.ppath, self.pdata["MAINSCRIPT"])
2584 else:
2585 ms = self.pdata["MAINSCRIPT"]
2586 if not os.path.exists(ms):
2587 try:
2588 os.makedirs(os.path.dirname(ms))
2589 with open(ms, "w"):
2590 pass
2591 except OSError as err:
2592 EricMessageBox.critical(
2593 self.ui,
2594 self.tr("Create main script"),
2595 self.tr(
2596 "<p>The mainscript <b>{0}</b> could not"
2597 " be created.<br/>Reason: {1}</p>"
2598 ).format(ms, str(err)),
2599 )
2600 self.appendFile(ms, True)
2601 else:
2602 ms = ""
2603
2604 if self.pdata["MAKEPARAMS"]["MakeEnabled"]:
2605 mf = self.pdata["MAKEPARAMS"]["MakeFile"]
2606 if mf:
2607 if not os.path.isabs(mf):
2608 mf = os.path.join(self.ppath, mf)
2609 else:
2610 mf = os.path.join(self.ppath, Project.DefaultMakefile)
2611 if not os.path.exists(mf):
2612 try:
2613 os.makedirs(os.path.dirname(mf))
2614 with open(mf, "w"):
2615 pass
2616 except OSError as err:
2617 EricMessageBox.critical(
2618 self.ui,
2619 self.tr("Create Makefile"),
2620 self.tr(
2621 "<p>The makefile <b>{0}</b> could not"
2622 " be created.<br/>Reason: {1}</p>"
2623 ).format(mf, str(err)),
2624 )
2625 self.appendFile(mf)
2626
2627 # add existing files to the project
2628 res = EricMessageBox.yesNo(
2629 self.ui,
2630 self.tr("New Project"),
2631 self.tr("""Add existing files to the project?"""),
2632 yesDefault=True,
2633 )
2634 if res:
2635 self.newProjectAddFiles(ms)
2636 addAllToVcs = res
2637 # create an empty __init__.py file to make it a Python package
2638 # if none exists (only for Python and Python3)
2639 if self.pdata["PROGLANGUAGE"] in ["Python3", "MicroPython"]:
2640 fn = os.path.join(self.ppath, "__init__.py")
2641 if not os.path.exists(fn):
2642 with open(fn, "w", encoding="utf-8"):
2643 pass
2644 self.appendFile(fn, True)
2645 self.saveProject()
2646
2647 # check, if the existing project directory is already under
2648 # VCS control
2649 pluginManager = ericApp().getObject("PluginManager")
2650 for indicator, vcsData in list(
2651 pluginManager.getVcsSystemIndicators().items()
2652 ):
2653 if os.path.exists(os.path.join(self.ppath, indicator)):
2654 if len(vcsData) > 1:
2655 vcsList = []
2656 for _vcsSystemStr, vcsSystemDisplay in vcsData:
2657 vcsList.append(vcsSystemDisplay)
2658 res, vcs_ok = QInputDialog.getItem(
2659 None,
2660 self.tr("New Project"),
2661 self.tr("Select Version Control System"),
2662 vcsList,
2663 0,
2664 False,
2665 )
2666 if vcs_ok:
2667 for vcsSystemStr, vcsSystemDisplay in vcsData:
2668 if res == vcsSystemDisplay:
2669 vcsSystem = vcsSystemStr
2670 break
2671 else:
2672 vcsSystem = "None"
2673 else:
2674 vcsSystem = "None"
2675 else:
2676 vcsSystem = vcsData[0][1]
2677 self.pdata["VCS"] = vcsSystem
2678 self.vcs = self.initVCS()
2679 self.setDirty(True)
2680 if self.vcs is not None:
2681 # edit VCS command options
2682 if self.vcs.vcsSupportCommandOptions():
2683 vcores = EricMessageBox.yesNo(
2684 self.ui,
2685 self.tr("New Project"),
2686 self.tr(
2687 """Would you like to edit the VCS"""
2688 """ command options?"""
2689 ),
2690 )
2691 else:
2692 vcores = False
2693 if vcores:
2694 from VCS.CommandOptionsDialog import (
2695 VcsCommandOptionsDialog,
2696 )
2697
2698 codlg = VcsCommandOptionsDialog(self.vcs)
2699 if codlg.exec() == QDialog.DialogCode.Accepted:
2700 self.vcs.vcsSetOptions(codlg.getOptions())
2701 # add project file to repository
2702 if res == 0:
2703 apres = EricMessageBox.yesNo(
2704 self.ui,
2705 self.tr("New project"),
2706 self.tr(
2707 "Shall the project file be added"
2708 " to the repository?"
2709 ),
2710 yesDefault=True,
2711 )
2712 if apres:
2713 self.saveProject()
2714 self.vcs.vcsAdd(self.pfile)
2715 else:
2716 self.pdata["VCS"] = "None"
2717 self.saveProject()
2718 break
2719
2720 # put the project under VCS control
2721 if self.vcs is None and self.vcsSoftwareAvailable() and self.vcsRequested:
2722 vcsSystemsDict = (
2723 ericApp()
2724 .getObject("PluginManager")
2725 .getPluginDisplayStrings("version_control")
2726 )
2727 vcsSystemsDisplay = [self.tr("None")]
2728 keys = sorted(vcsSystemsDict.keys())
2729 for key in keys:
2730 vcsSystemsDisplay.append(vcsSystemsDict[key])
2731 vcsSelected, ok = QInputDialog.getItem(
2732 None,
2733 self.tr("New Project"),
2734 self.tr("Select version control system for the project"),
2735 vcsSystemsDisplay,
2736 0,
2737 False,
2738 )
2739 if ok and vcsSelected != self.tr("None"):
2740 for vcsSystem, vcsSystemDisplay in vcsSystemsDict.items():
2741 if vcsSystemDisplay == vcsSelected:
2742 self.pdata["VCS"] = vcsSystem
2743 break
2744 else:
2745 self.pdata["VCS"] = "None"
2746 else:
2747 self.pdata["VCS"] = "None"
2748 self.vcs = self.initVCS()
2749 if self.vcs is not None:
2750 vcsdlg = self.vcs.vcsOptionsDialog(self, self.name)
2751 if vcsdlg.exec() == QDialog.DialogCode.Accepted:
2752 vcsDataDict = vcsdlg.getData()
2753 else:
2754 self.pdata["VCS"] = "None"
2755 self.vcs = self.initVCS()
2756 self.setDirty(True)
2757 if self.vcs is not None:
2758 # edit VCS command options
2759 if self.vcs.vcsSupportCommandOptions():
2760 vcores = EricMessageBox.yesNo(
2761 self.ui,
2762 self.tr("New Project"),
2763 self.tr(
2764 """Would you like to edit the VCS command"""
2765 """ options?"""
2766 ),
2767 )
2768 else:
2769 vcores = False
2770 if vcores:
2771 from VCS.CommandOptionsDialog import VcsCommandOptionsDialog
2772
2773 codlg = VcsCommandOptionsDialog(self.vcs)
2774 if codlg.exec() == QDialog.DialogCode.Accepted:
2775 self.vcs.vcsSetOptions(codlg.getOptions())
2776
2777 # create the project in the VCS
2778 self.vcs.vcsSetDataFromDict(vcsDataDict)
2779 self.saveProject()
2780 self.vcs.vcsConvertProject(vcsDataDict, self, addAll=addAllToVcs)
2781 else:
2782 self.newProjectHooks.emit()
2783 self.newProject.emit()
2784
2785 else:
2786 self.newProjectHooks.emit()
2787 self.newProject.emit()
2788
2789 def newProjectAddFiles(self, mainscript):
2790 """
2791 Public method to add files to a new project.
2792
2793 @param mainscript name of the mainscript (string)
2794 """
2795 # Show the file type associations for the user to change
2796 self.__showFiletypeAssociations()
2797
2798 with EricOverrideCursor():
2799 # search the project directory for files with known extensions
2800 filespecs = list(self.pdata["FILETYPES"].keys())
2801 for filespec in filespecs:
2802 files = Utilities.direntries(self.ppath, True, filespec)
2803 for file in files:
2804 self.appendFile(file)
2805
2806 # special handling for translation files
2807 if self.translationsRoot:
2808 tpd = os.path.join(self.ppath, self.translationsRoot)
2809 if not self.translationsRoot.endswith(os.sep):
2810 tpd = os.path.dirname(tpd)
2811 else:
2812 tpd = self.ppath
2813 tslist = []
2814 if self.pdata["TRANSLATIONPATTERN"]:
2815 pattern = os.path.basename(self.pdata["TRANSLATIONPATTERN"])
2816 if "%language%" in pattern:
2817 pattern = pattern.replace("%language%", "*")
2818 else:
2819 tpd = self.pdata["TRANSLATIONPATTERN"].split("%language%")[0]
2820 else:
2821 pattern = "*.ts"
2822 tslist.extend(Utilities.direntries(tpd, True, pattern))
2823 pattern = self.__binaryTranslationFile(pattern)
2824 if pattern:
2825 tslist.extend(Utilities.direntries(tpd, True, pattern))
2826 if tslist:
2827 if "_" in os.path.basename(tslist[0]):
2828 # the first entry determines the mainscript name
2829 mainscriptname = (
2830 os.path.splitext(mainscript)[0]
2831 or os.path.basename(tslist[0]).split("_")[0]
2832 )
2833 self.pdata["TRANSLATIONPATTERN"] = os.path.join(
2834 os.path.dirname(tslist[0]),
2835 "{0}_%language%{1}".format(
2836 os.path.basename(tslist[0]).split("_")[0],
2837 os.path.splitext(tslist[0])[1],
2838 ),
2839 )
2840 else:
2841 mainscriptname = ""
2842 pattern, ok = QInputDialog.getText(
2843 None,
2844 self.tr("Translation Pattern"),
2845 self.tr(
2846 "Enter the path pattern for translation files "
2847 "(use '%language%' in place of the language"
2848 " code):"
2849 ),
2850 QLineEdit.EchoMode.Normal,
2851 tslist[0],
2852 )
2853 if pattern:
2854 self.pdata["TRANSLATIONPATTERN"] = pattern
2855 if self.pdata["TRANSLATIONPATTERN"]:
2856 self.pdata["TRANSLATIONPATTERN"] = self.getRelativePath(
2857 self.pdata["TRANSLATIONPATTERN"]
2858 )
2859 pattern = self.pdata["TRANSLATIONPATTERN"].replace(
2860 "%language%", "*"
2861 )
2862 for ts in tslist:
2863 if fnmatch.fnmatch(ts, pattern):
2864 self.pdata["TRANSLATIONS"].append(ts)
2865 self.projectLanguageAdded.emit(ts)
2866 if self.pdata["TRANSLATIONSBINPATH"]:
2867 tpd = os.path.join(
2868 self.ppath, self.pdata["TRANSLATIONSBINPATH"]
2869 )
2870 pattern = os.path.basename(
2871 self.pdata["TRANSLATIONPATTERN"]
2872 ).replace("%language%", "*")
2873 pattern = self.__binaryTranslationFile(pattern)
2874 qmlist = Utilities.direntries(tpd, True, pattern)
2875 for qm in qmlist:
2876 self.pdata["TRANSLATIONS"].append(qm)
2877 self.projectLanguageAdded.emit(qm)
2878 if not self.pdata["MAINSCRIPT"] and bool(mainscriptname):
2879 if self.pdata["PROGLANGUAGE"] in ["Python3", "MicroPython"]:
2880 self.pdata["MAINSCRIPT"] = "{0}.py".format(mainscriptname)
2881 elif self.pdata["PROGLANGUAGE"] == "Ruby":
2882 self.pdata["MAINSCRIPT"] = "{0}.rb".format(mainscriptname)
2883 self.setDirty(True)
2884
2885 def __showProperties(self):
2886 """
2887 Private slot to display the properties dialog.
2888 """
2889 from .PropertiesDialog import PropertiesDialog
2890
2891 dlg = PropertiesDialog(self, False)
2892 if dlg.exec() == QDialog.DialogCode.Accepted:
2893 projectType = self.pdata["PROJECTTYPE"]
2894 dlg.storeData()
2895 self.setDirty(True)
2896 if self.pdata["MAINSCRIPT"]:
2897 if not os.path.isabs(self.pdata["MAINSCRIPT"]):
2898 ms = os.path.join(self.ppath, self.pdata["MAINSCRIPT"])
2899 else:
2900 ms = self.pdata["MAINSCRIPT"]
2901 if os.path.exists(ms):
2902 self.appendFile(ms)
2903
2904 if self.pdata["MAKEPARAMS"]["MakeEnabled"]:
2905 mf = self.pdata["MAKEPARAMS"]["MakeFile"]
2906 if mf:
2907 if not os.path.isabs(mf):
2908 mf = os.path.join(self.ppath, mf)
2909 else:
2910 mf = os.path.join(self.ppath, Project.DefaultMakefile)
2911 if not os.path.exists(mf):
2912 try:
2913 with open(mf, "w"):
2914 pass
2915 except OSError as err:
2916 EricMessageBox.critical(
2917 self.ui,
2918 self.tr("Create Makefile"),
2919 self.tr(
2920 "<p>The makefile <b>{0}</b> could not"
2921 " be created.<br/>Reason: {1}</p>"
2922 ).format(mf, str(err)),
2923 )
2924 self.appendFile(mf)
2925
2926 if self.pdata["PROJECTTYPE"] != projectType:
2927 # reinitialize filetype associations
2928 self.initFileTypes()
2929
2930 if self.translationsRoot:
2931 tp = os.path.join(self.ppath, self.translationsRoot)
2932 if not self.translationsRoot.endswith(os.sep):
2933 tp = os.path.dirname(tp)
2934 else:
2935 tp = self.ppath
2936 if not os.path.isdir(tp):
2937 os.makedirs(tp)
2938 if tp != self.ppath and tp not in self.subdirs:
2939 self.subdirs.append(tp)
2940
2941 if self.pdata["TRANSLATIONSBINPATH"]:
2942 tp = os.path.join(self.ppath, self.pdata["TRANSLATIONSBINPATH"])
2943 if not os.path.isdir(tp):
2944 os.makedirs(tp)
2945 if tp != self.ppath and tp not in self.subdirs:
2946 self.subdirs.append(tp)
2947
2948 self.pluginGrp.setEnabled(self.pdata["PROJECTTYPE"] in ["E7Plugin"])
2949
2950 self.__model.projectPropertiesChanged()
2951 self.projectPropertiesChanged.emit()
2952
2953 if self.pdata["PROJECTTYPE"] != projectType:
2954 self.__reorganizeFiles()
2955
2956 def __showUserProperties(self):
2957 """
2958 Private slot to display the user specific properties dialog.
2959 """
2960 vcsSystem = self.pdata["VCS"] or None
2961 vcsSystemOverride = self.pudata["VCSOVERRIDE"] or None
2962
2963 from .UserPropertiesDialog import UserPropertiesDialog
2964
2965 dlg = UserPropertiesDialog(self)
2966 if dlg.exec() == QDialog.DialogCode.Accepted:
2967 dlg.storeData()
2968
2969 if (
2970 (self.pdata["VCS"] and self.pdata["VCS"] != vcsSystem)
2971 or (
2972 self.pudata["VCSOVERRIDE"]
2973 and self.pudata["VCSOVERRIDE"] != vcsSystemOverride
2974 )
2975 or (vcsSystemOverride is not None and not self.pudata["VCSOVERRIDE"])
2976 ):
2977 # stop the VCS monitor thread and shutdown VCS
2978 if self.vcs is not None:
2979 self.vcs.stopStatusMonitor()
2980 self.vcs.vcsShutdown()
2981 self.vcs.deleteLater()
2982 self.vcs = None
2983 ericApp().getObject("PluginManager").deactivateVcsPlugins()
2984 # reinit VCS
2985 self.vcs = self.initVCS()
2986 # start the VCS monitor thread
2987 self.__vcsConnectStatusMonitor()
2988 self.reinitVCS.emit()
2989
2990 if self.pudata["VCSSTATUSMONITORINTERVAL"]:
2991 self.setStatusMonitorInterval(self.pudata["VCSSTATUSMONITORINTERVAL"])
2992 else:
2993 self.setStatusMonitorInterval(
2994 Preferences.getVCS("StatusMonitorInterval")
2995 )
2996
2997 def __showFiletypeAssociations(self):
2998 """
2999 Private slot to display the filetype association dialog.
3000 """
3001 from .FiletypeAssociationDialog import FiletypeAssociationDialog
3002
3003 dlg = FiletypeAssociationDialog(self)
3004 if dlg.exec() == QDialog.DialogCode.Accepted:
3005 dlg.transferData()
3006 self.setDirty(True)
3007 self.__reorganizeFiles()
3008
3009 def getFiletypeAssociations(self, associationType):
3010 """
3011 Public method to get the list of file type associations for
3012 the given association type.
3013
3014 @param associationType type of the association (one of FORMS,
3015 INTERFACES, OTHERS, PROTOCOLS, RESOURCES, SOURCES,
3016 TRANSLATIONS or __IGNORE__)
3017 @type str
3018 @return list of file patterns for the given type
3019 @rtype list of str
3020 """
3021 return [
3022 assoc
3023 for assoc in self.pdata["FILETYPES"]
3024 if self.pdata["FILETYPES"][assoc] == associationType
3025 ]
3026
3027 def __showLexerAssociations(self):
3028 """
3029 Private slot to display the lexer association dialog.
3030 """
3031 from .LexerAssociationDialog import LexerAssociationDialog
3032
3033 dlg = LexerAssociationDialog(self)
3034 if dlg.exec() == QDialog.DialogCode.Accepted:
3035 dlg.transferData()
3036 self.setDirty(True)
3037 self.lexerAssociationsChanged.emit()
3038
3039 def getEditorLexerAssoc(self, filename):
3040 """
3041 Public method to retrieve a lexer association.
3042
3043 @param filename filename used to determine the associated lexer
3044 language (string)
3045 @return the requested lexer language (string)
3046 """
3047 # try user settings first
3048 for pattern, language in list(self.pdata["LEXERASSOCS"].items()):
3049 if fnmatch.fnmatch(filename, pattern):
3050 return language
3051
3052 # try project type specific defaults next
3053 projectType = self.pdata["PROJECTTYPE"]
3054 with contextlib.suppress(KeyError):
3055 if self.__lexerAssociationCallbacks[projectType] is not None:
3056 return self.__lexerAssociationCallbacks[projectType](filename)
3057
3058 # return empty string to signal to use the global setting
3059 return ""
3060
3061 def getIgnorePatterns(self):
3062 """
3063 Public method to get the list of file name patterns for files to be
3064 ignored.
3065
3066 @return list of ignore file name patterns
3067 @rtype list of str
3068 """
3069 return self.getFiletypeAssociations("__IGNORE__")
3070
3071 @pyqtSlot()
3072 @pyqtSlot(str)
3073 def openProject(self, fn=None, restoreSession=True, reopen=False):
3074 """
3075 Public slot to open a project.
3076
3077 @param fn optional filename of the project file to be read
3078 @param restoreSession flag indicating to restore the project
3079 session (boolean)
3080 @param reopen flag indicating a reopening of the project (boolean)
3081 """
3082 if not self.checkDirty():
3083 return
3084
3085 if fn is None:
3086 fn = EricFileDialog.getOpenFileName(
3087 self.parent(),
3088 self.tr("Open project"),
3089 Preferences.getMultiProject("Workspace") or Utilities.getHomeDir(),
3090 self.tr("Project Files (*.epj);;XML Project Files (*.e4p)"),
3091 )
3092
3093 if fn and self.closeProject():
3094 with EricOverrideCursor():
3095 ok = self.__readProject(fn)
3096 if ok:
3097 self.opened = True
3098 if not self.pdata["FILETYPES"]:
3099 self.initFileTypes()
3100 else:
3101 self.updateFileTypes()
3102
3103 try:
3104 # create management directory if not present
3105 self.createProjectManagementDir()
3106 except OSError:
3107 EricMessageBox.critical(
3108 self.ui,
3109 self.tr("Create project management directory"),
3110 self.tr(
3111 "<p>The project directory <b>{0}</b> is not"
3112 " writable.</p>"
3113 ).format(self.ppath),
3114 )
3115 return
3116
3117 # read a user specific project file
3118 self.__readUserProperties()
3119
3120 with EricOverrideCursor():
3121 oldState = self.isDirty()
3122 self.vcs = self.initVCS()
3123 if self.vcs is None and self.isDirty() == oldState:
3124 # check, if project is version controlled
3125 pluginManager = ericApp().getObject("PluginManager")
3126 for (
3127 indicator,
3128 vcsData,
3129 ) in pluginManager.getVcsSystemIndicators().items():
3130 if os.path.exists(os.path.join(self.ppath, indicator)):
3131 if len(vcsData) > 1:
3132 vcsList = []
3133 for (_vcsSystemStr, vcsSystemDisplay) in vcsData:
3134 vcsList.append(vcsSystemDisplay)
3135 with EricOverridenCursor():
3136 res, vcs_ok = QInputDialog.getItem(
3137 None,
3138 self.tr("New Project"),
3139 self.tr("Select Version Control" " System"),
3140 vcsList,
3141 0,
3142 False,
3143 )
3144 if vcs_ok:
3145 for (vcsSystemStr, vcsSystemDisplay) in vcsData:
3146 if res == vcsSystemDisplay:
3147 vcsSystem = vcsSystemStr
3148 break
3149 else:
3150 vcsSystem = "None"
3151 else:
3152 vcsSystem = "None"
3153 else:
3154 vcsSystem = vcsData[0][0]
3155 self.pdata["VCS"] = vcsSystem
3156 self.vcs = self.initVCS()
3157 self.setDirty(True)
3158 if self.vcs is not None and (
3159 self.vcs.vcsRegisteredState(self.ppath)
3160 != self.vcs.canBeCommitted
3161 ):
3162 self.pdata["VCS"] = "None"
3163 self.vcs = self.initVCS()
3164 self.closeAct.setEnabled(True)
3165 self.saveasAct.setEnabled(True)
3166 self.actGrp2.setEnabled(True)
3167 self.propsAct.setEnabled(True)
3168 self.userPropsAct.setEnabled(True)
3169 self.filetypesAct.setEnabled(True)
3170 self.lexersAct.setEnabled(True)
3171 self.sessActGrp.setEnabled(True)
3172 self.dbgActGrp.setEnabled(True)
3173 self.menuDebuggerAct.setEnabled(True)
3174 self.menuSessionAct.setEnabled(True)
3175 self.menuCheckAct.setEnabled(True)
3176 self.menuShowAct.setEnabled(True)
3177 self.menuDiagramAct.setEnabled(True)
3178 self.menuApidocAct.setEnabled(True)
3179 self.menuPackagersAct.setEnabled(True)
3180 self.pluginGrp.setEnabled(self.pdata["PROJECTTYPE"] in ["E7Plugin"])
3181 self.addLanguageAct.setEnabled(
3182 bool(self.pdata["TRANSLATIONPATTERN"])
3183 )
3184 self.makeGrp.setEnabled(self.pdata["MAKEPARAMS"]["MakeEnabled"])
3185 self.menuMakeAct.setEnabled(self.pdata["MAKEPARAMS"]["MakeEnabled"])
3186 self.menuOtherToolsAct.setEnabled(True)
3187 self.menuFormattingAct.setEnabled(True)
3188
3189 # open a project debugger properties file being quiet
3190 # about errors
3191 if Preferences.getProject("AutoLoadDbgProperties"):
3192 self.__readDebugProperties(True)
3193
3194 self.__model.projectOpened()
3195 self.projectOpenedHooks.emit()
3196 self.projectOpened.emit()
3197
3198 if Preferences.getProject("SearchNewFiles"):
3199 self.__doSearchNewFiles()
3200
3201 # read a project tasks file
3202 self.__readTasks()
3203 self.ui.taskViewer.setProjectOpen(True)
3204 # rescan project tasks
3205 if Preferences.getProject("TasksProjectRescanOnOpen"):
3206 ericApp().getObject("TaskViewer").regenerateProjectTasks(quiet=True)
3207
3208 if restoreSession:
3209 # open the main script
3210 if self.pdata["MAINSCRIPT"]:
3211 if not os.path.isabs(self.pdata["MAINSCRIPT"]):
3212 ms = os.path.join(self.ppath, self.pdata["MAINSCRIPT"])
3213 else:
3214 ms = self.pdata["MAINSCRIPT"]
3215 self.sourceFile.emit(ms)
3216
3217 # open a project session file being quiet about errors
3218 if reopen:
3219 self.__readSession(quiet=True, indicator="_tmp")
3220 elif Preferences.getProject("AutoLoadSession"):
3221 self.__readSession(quiet=True)
3222
3223 # start the VCS monitor thread
3224 self.__vcsConnectStatusMonitor()
3225
3226 def reopenProject(self):
3227 """
3228 Public slot to reopen the current project.
3229 """
3230 projectFile = self.pfile
3231 res = self.closeProject(reopen=True)
3232 if res:
3233 self.openProject(projectFile, reopen=True)
3234
3235 def saveProject(self):
3236 """
3237 Public slot to save the current project.
3238
3239 @return flag indicating success
3240 """
3241 if self.isDirty():
3242 if len(self.pfile) > 0:
3243 if self.pfile.endswith(".e4p"):
3244 self.pfile = self.pfile.replace(".e4p", ".epj")
3245 self.__syncRecent()
3246 ok = self.__writeProject()
3247 else:
3248 ok = self.saveProjectAs()
3249 else:
3250 ok = True
3251 self.sessActGrp.setEnabled(ok)
3252 self.menuSessionAct.setEnabled(ok)
3253 return ok
3254
3255 def saveProjectAs(self):
3256 """
3257 Public slot to save the current project to a different file.
3258
3259 @return flag indicating success (boolean)
3260 """
3261 defaultFilter = self.tr("Project Files (*.epj)")
3262 defaultPath = (
3263 self.ppath
3264 if self.ppath
3265 else (Preferences.getMultiProject("Workspace") or Utilities.getHomeDir())
3266 )
3267 fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
3268 self.parent(),
3269 self.tr("Save Project"),
3270 defaultPath,
3271 self.tr("Project Files (*.epj)"),
3272 defaultFilter,
3273 EricFileDialog.DontConfirmOverwrite,
3274 )
3275
3276 if fn:
3277 fpath = pathlib.Path(fn)
3278 if not fpath.suffix:
3279 ex = selectedFilter.split("(*")[1].split(")")[0]
3280 if ex:
3281 fpath = fpath.with_suffix(ex)
3282 if fpath.exists():
3283 res = EricMessageBox.yesNo(
3284 self.ui,
3285 self.tr("Save File"),
3286 self.tr(
3287 """<p>The file <b>{0}</b> already exists."""
3288 """ Overwrite it?</p>"""
3289 ).format(fpath),
3290 icon=EricMessageBox.Warning,
3291 )
3292 if not res:
3293 return False
3294
3295 self.name = fpath.stem
3296 ok = self.__writeProject(str(fpath))
3297
3298 if ok:
3299 # create management directory if not present
3300 self.createProjectManagementDir()
3301
3302 # now save the tasks
3303 self.writeTasks()
3304
3305 self.sessActGrp.setEnabled(ok)
3306 self.menuSessionAct.setEnabled(ok)
3307 self.projectClosedHooks.emit()
3308 self.projectClosed.emit(False)
3309 self.projectOpenedHooks.emit()
3310 self.projectOpened.emit()
3311 return ok
3312 else:
3313 return False
3314
3315 def checkDirty(self):
3316 """
3317 Public method to check dirty status and open a message window.
3318
3319 @return flag indicating whether this operation was successful (boolean)
3320 """
3321 if self.isDirty():
3322 res = EricMessageBox.okToClearData(
3323 self.parent(),
3324 self.tr("Close Project"),
3325 self.tr("The current project has unsaved changes."),
3326 self.saveProject,
3327 )
3328 if res:
3329 self.setDirty(False)
3330 return res
3331
3332 return True
3333
3334 def __closeAllWindows(self):
3335 """
3336 Private method to close all project related windows.
3337 """
3338 self.codemetrics and self.codemetrics.close()
3339 self.codecoverage and self.codecoverage.close()
3340 self.profiledata and self.profiledata.close()
3341 self.applicationDiagram and self.applicationDiagram.close()
3342 self.loadedDiagram and self.loadedDiagram.close()
3343
3344 @pyqtSlot()
3345 def closeProject(self, reopen=False, noSave=False, shutdown=False):
3346 """
3347 Public slot to close the current project.
3348
3349 @param reopen flag indicating a reopening of the project
3350 @type bool
3351 @param noSave flag indicating to not perform save actions
3352 @type bool
3353 @param shutdown flag indicating the IDE shutdown
3354 @type bool
3355 @return flag indicating success
3356 @rtype bool
3357 """
3358 # save the list of recently opened projects
3359 self.__saveRecent()
3360
3361 if not self.isOpen():
3362 return True
3363
3364 if not self.checkDirty():
3365 return False
3366
3367 ericApp().getObject("TaskViewer").stopProjectTaskExtraction()
3368
3369 # save the user project properties
3370 if not noSave:
3371 self.__writeUserProperties()
3372
3373 # save the project session file being quiet about error
3374 if reopen:
3375 self.__writeSession(quiet=True, indicator="_tmp")
3376 elif Preferences.getProject("AutoSaveSession") and not noSave:
3377 self.__writeSession(quiet=True)
3378
3379 # save the project debugger properties file being quiet about error
3380 if (
3381 Preferences.getProject("AutoSaveDbgProperties")
3382 and self.isDebugPropertiesLoaded()
3383 and not noSave
3384 and self.debugPropertiesChanged
3385 ):
3386 self.__writeDebugProperties(True)
3387
3388 vm = ericApp().getObject("ViewManager")
3389
3390 # check dirty status of all project files first
3391 for fn in vm.getOpenFilenames():
3392 if self.isProjectFile(fn):
3393 reset = vm.checkFileDirty(fn)
3394 if not reset:
3395 # abort shutting down
3396 return False
3397
3398 # close all project related editors
3399 success = True
3400 for fn in vm.getOpenFilenames():
3401 if self.isProjectFile(fn):
3402 success &= vm.closeWindow(fn, ignoreDirty=True)
3403 if not success:
3404 return False
3405
3406 # stop the VCS monitor thread
3407 if self.vcs is not None:
3408 self.vcs.stopStatusMonitor()
3409
3410 # now save the tasks
3411 if not noSave:
3412 self.writeTasks()
3413 self.ui.taskViewer.clearProjectTasks()
3414 self.ui.taskViewer.setProjectOpen(False)
3415
3416 # now shutdown the vcs interface
3417 if self.vcs:
3418 self.vcs.vcsShutdown()
3419 self.vcs.deleteLater()
3420 self.vcs = None
3421 ericApp().getObject("PluginManager").deactivateVcsPlugins()
3422
3423 # now close all project related tool windows
3424 self.__closeAllWindows()
3425
3426 self.__initData()
3427 self.closeAct.setEnabled(False)
3428 self.saveasAct.setEnabled(False)
3429 self.saveAct.setEnabled(False)
3430 self.actGrp2.setEnabled(False)
3431 self.propsAct.setEnabled(False)
3432 self.userPropsAct.setEnabled(False)
3433 self.filetypesAct.setEnabled(False)
3434 self.lexersAct.setEnabled(False)
3435 self.sessActGrp.setEnabled(False)
3436 self.dbgActGrp.setEnabled(False)
3437 self.menuDebuggerAct.setEnabled(False)
3438 self.menuSessionAct.setEnabled(False)
3439 self.menuCheckAct.setEnabled(False)
3440 self.menuShowAct.setEnabled(False)
3441 self.menuDiagramAct.setEnabled(False)
3442 self.menuApidocAct.setEnabled(False)
3443 self.menuPackagersAct.setEnabled(False)
3444 self.pluginGrp.setEnabled(False)
3445 self.makeGrp.setEnabled(False)
3446 self.menuMakeAct.setEnabled(False)
3447 self.menuOtherToolsAct.setEnabled(False)
3448 self.menuFormattingAct.setEnabled(False)
3449
3450 self.__model.projectClosed()
3451 self.projectClosedHooks.emit()
3452 self.projectClosed.emit(shutdown)
3453
3454 return True
3455
3456 def saveAllScripts(self, reportSyntaxErrors=False):
3457 """
3458 Public method to save all scripts belonging to the project.
3459
3460 @param reportSyntaxErrors flag indicating special reporting
3461 for syntax errors (boolean)
3462 @return flag indicating success (boolean)
3463 """
3464 vm = ericApp().getObject("ViewManager")
3465 success = True
3466 filesWithSyntaxErrors = 0
3467 for fn in vm.getOpenFilenames():
3468 rfn = self.getRelativePath(fn)
3469 if rfn in self.pdata["SOURCES"] or rfn in self.pdata["OTHERS"]:
3470 editor = vm.getOpenEditor(fn)
3471 success &= vm.saveEditorEd(editor)
3472 if reportSyntaxErrors and editor.hasSyntaxErrors():
3473 filesWithSyntaxErrors += 1
3474
3475 if reportSyntaxErrors and filesWithSyntaxErrors > 0:
3476 EricMessageBox.critical(
3477 self.ui,
3478 self.tr("Syntax errors detected"),
3479 self.tr(
3480 """The project contains %n file(s) with syntax errors.""",
3481 "",
3482 filesWithSyntaxErrors,
3483 ),
3484 )
3485 return False
3486 else:
3487 return success
3488
3489 def checkAllScriptsDirty(self, reportSyntaxErrors=False):
3490 """
3491 Public method to check all scripts belonging to the project for
3492 their dirty status.
3493
3494 @param reportSyntaxErrors flag indicating special reporting
3495 for syntax errors (boolean)
3496 @return flag indicating success (boolean)
3497 """
3498 vm = ericApp().getObject("ViewManager")
3499 success = True
3500 filesWithSyntaxErrors = 0
3501 for fn in vm.getOpenFilenames():
3502 rfn = self.getRelativePath(fn)
3503 if rfn in self.pdata["SOURCES"] or rfn in self.pdata["OTHERS"]:
3504 editor = vm.getOpenEditor(fn)
3505 success &= editor.checkDirty()
3506 if reportSyntaxErrors and editor.hasSyntaxErrors():
3507 filesWithSyntaxErrors += 1
3508
3509 if reportSyntaxErrors and filesWithSyntaxErrors > 0:
3510 EricMessageBox.critical(
3511 self.ui,
3512 self.tr("Syntax errors detected"),
3513 self.tr(
3514 """The project contains %n file(s) with syntax errors.""",
3515 "",
3516 filesWithSyntaxErrors,
3517 ),
3518 )
3519 return False
3520 else:
3521 return success
3522
3523 def getMainScript(self, normalized=False):
3524 """
3525 Public method to return the main script filename.
3526
3527 The normalized name is the name of the main script prepended with
3528 the project path.
3529
3530 @param normalized flag indicating a normalized filename is wanted
3531 @type bool
3532 @return filename of the projects main script
3533 @rtype str
3534 """
3535 if self.pdata["MAINSCRIPT"]:
3536 if normalized:
3537 return os.path.join(self.ppath, self.pdata["MAINSCRIPT"])
3538 else:
3539 return self.pdata["MAINSCRIPT"]
3540 else:
3541 return ""
3542
3543 def getSources(self, normalized=False):
3544 """
3545 Public method to return the source script files.
3546
3547 @param normalized flag indicating a normalized filename is wanted
3548 @type bool
3549 @return list of the projects scripts
3550 @rtype list of str
3551 """
3552 return self.getProjectFiles("SOURCES", normalized=normalized)
3553
3554 def getProjectFiles(self, fileType, normalized=False):
3555 """
3556 Public method to get the file entries of the given type.
3557
3558 @param fileType project file type (one of SOURCES, FORMS, RESOURCES,
3559 INTERFACES, PROTOCOLS, OTHERS, TRANSLATIONS)
3560 @type str
3561 @param normalized flag indicating normalized file names are wanted
3562 @type boolean
3563 @return list of file names
3564 @rtype list of str
3565 @exception ValueError raised when an unsupported file type is given
3566 """
3567 if fileType not in [
3568 "SOURCES",
3569 "FORMS",
3570 "RESOURCES",
3571 "INTERFACES",
3572 "PROTOCOLS",
3573 "OTHERS",
3574 "TRANSLATIONS",
3575 ]:
3576 raise ValueError("Given file type has incorrect value.")
3577
3578 if normalized:
3579 return [os.path.join(self.ppath, fn) for fn in self.pdata[fileType]]
3580 else:
3581 return self.pdata[fileType]
3582
3583 def getProjectType(self):
3584 """
3585 Public method to get the type of the project.
3586
3587 @return UI type of the project (string)
3588 """
3589 return self.pdata["PROJECTTYPE"]
3590
3591 def getProjectLanguage(self):
3592 """
3593 Public method to get the project's programming language.
3594
3595 @return programming language (string)
3596 """
3597 return self.pdata["PROGLANGUAGE"]
3598
3599 def isMixedLanguageProject(self):
3600 """
3601 Public method to check, if this is a mixed language project.
3602
3603 @return flag indicating a mixed language project
3604 @rtype bool
3605 """
3606 return self.pdata["MIXEDLANGUAGE"]
3607
3608 def isPythonProject(self):
3609 """
3610 Public method to check, if this project is a Python3 or MicroPython
3611 project.
3612
3613 @return flag indicating a Python project (boolean)
3614 """
3615 return self.pdata["PROGLANGUAGE"] in ["Python3", "MicroPython"]
3616
3617 def isPy3Project(self):
3618 """
3619 Public method to check, if this project is a Python3 project.
3620
3621 @return flag indicating a Python3 project (boolean)
3622 """
3623 return self.pdata["PROGLANGUAGE"] == "Python3"
3624
3625 def isMicroPythonProject(self):
3626 """
3627 Public method to check, if this project is a MicroPython project.
3628
3629 @return flag indicating a MicroPython project
3630 @rtype bool
3631 """
3632 return self.pdata["PROGLANGUAGE"] == "MicroPython"
3633
3634 def isRubyProject(self):
3635 """
3636 Public method to check, if this project is a Ruby project.
3637
3638 @return flag indicating a Ruby project (boolean)
3639 """
3640 return self.pdata["PROGLANGUAGE"] == "Ruby"
3641
3642 def isJavaScriptProject(self):
3643 """
3644 Public method to check, if this project is a JavaScript project.
3645
3646 @return flag indicating a JavaScript project (boolean)
3647 """
3648 return self.pdata["PROGLANGUAGE"] == "JavaScript"
3649
3650 def getProjectSpellLanguage(self):
3651 """
3652 Public method to get the project's programming language.
3653
3654 @return programming language (string)
3655 """
3656 return self.pdata["SPELLLANGUAGE"]
3657
3658 def getProjectDictionaries(self):
3659 """
3660 Public method to get the names of the project specific dictionaries.
3661
3662 @return tuple of two strings giving the absolute path names of the
3663 project specific word and exclude list
3664 """
3665 pwl = ""
3666 if self.pdata["SPELLWORDS"]:
3667 pwl = os.path.join(self.ppath, self.pdata["SPELLWORDS"])
3668 if not os.path.isfile(pwl):
3669 pwl = ""
3670
3671 pel = ""
3672 if self.pdata["SPELLEXCLUDES"]:
3673 pel = os.path.join(self.ppath, self.pdata["SPELLEXCLUDES"])
3674 if not os.path.isfile(pel):
3675 pel = ""
3676
3677 return (pwl, pel)
3678
3679 def getDefaultSourceExtension(self):
3680 """
3681 Public method to get the default extension for the project's
3682 programming language.
3683
3684 @return default extension (including the dot) (string)
3685 """
3686 lang = self.pdata["PROGLANGUAGE"]
3687 if lang in ("", "Python"):
3688 lang = "Python3"
3689 return self.__sourceExtensions(lang)[0]
3690
3691 def getProjectPath(self):
3692 """
3693 Public method to get the project path.
3694
3695 @return project path (string)
3696 """
3697 return self.ppath
3698
3699 def startswithProjectPath(self, path):
3700 """
3701 Public method to check, if a path starts with the project path.
3702
3703 @param path path to be checked
3704 @type str
3705 @return flag indicating that the path starts with the project path
3706 @rtype bool
3707 """
3708 return bool(self.ppath) and (
3709 path == self.ppath
3710 or Utilities.normcasepath(Utilities.toNativeSeparators(path)).startswith(
3711 Utilities.normcasepath(Utilities.toNativeSeparators(self.ppath + "/"))
3712 )
3713 )
3714
3715 def getProjectFile(self):
3716 """
3717 Public method to get the path of the project file.
3718
3719 @return path of the project file (string)
3720 """
3721 return self.pfile
3722
3723 def getProjectName(self):
3724 """
3725 Public method to get the name of the project.
3726
3727 The project name is determined from the name of the project file.
3728
3729 @return name of the project (string)
3730 """
3731 if self.pfile:
3732 name = os.path.splitext(self.pfile)[0]
3733 return os.path.basename(name)
3734 else:
3735 return ""
3736
3737 def getProjectManagementDir(self):
3738 """
3739 Public method to get the path of the management directory.
3740
3741 @return path of the management directory (string)
3742 """
3743 return os.path.join(self.ppath, ".eric7project")
3744
3745 def createProjectManagementDir(self):
3746 """
3747 Public method to create the project management directory.
3748
3749 It does nothing, if it already exists.
3750 """
3751 # create management directory if not present
3752 mgmtDir = self.getProjectManagementDir()
3753 if not os.path.exists(mgmtDir):
3754 os.makedirs(mgmtDir)
3755
3756 def getHash(self):
3757 """
3758 Public method to get the project hash.
3759
3760 @return project hash as a hex string (string)
3761 """
3762 return self.pdata["HASH"]
3763
3764 def getRelativePath(self, path):
3765 """
3766 Public method to convert a file path to a project relative
3767 file path.
3768
3769 @param path file or directory name to convert (string)
3770 @return project relative path or unchanged path, if path doesn't
3771 belong to the project (string)
3772 """
3773 try:
3774 return str(pathlib.Path(path).relative_to(self.ppath))
3775 except ValueError:
3776 return path
3777
3778 def getRelativeUniversalPath(self, path):
3779 """
3780 Public method to convert a file path to a project relative
3781 file path with universal separators.
3782
3783 @param path file or directory name to convert (string)
3784 @return project relative path or unchanged path, if path doesn't
3785 belong to the project (string)
3786 """
3787 return Utilities.fromNativeSeparators(self.getRelativePath(path))
3788
3789 def getAbsolutePath(self, fn):
3790 """
3791 Public method to convert a project relative file path to an absolute
3792 file path.
3793
3794 @param fn file or directory name to convert (string)
3795 @return absolute path (string)
3796 """
3797 if not os.path.isabs(fn):
3798 fn = os.path.join(self.ppath, fn)
3799 return fn
3800
3801 def getAbsoluteUniversalPath(self, fn):
3802 """
3803 Public method to convert a project relative file path with universal
3804 separators to an absolute file path.
3805
3806 @param fn file or directory name to convert (string)
3807 @return absolute path (string)
3808 """
3809 if not os.path.isabs(fn):
3810 fn = os.path.join(self.ppath, Utilities.toNativeSeparators(fn))
3811 return fn
3812
3813 def getEolString(self):
3814 """
3815 Public method to get the EOL-string to be used by the project.
3816
3817 @return eol string (string)
3818 """
3819 if self.pdata["EOL"] >= 0:
3820 return self.eols[self.pdata["EOL"]]
3821 else:
3822 eolMode = Preferences.getEditor("EOLMode")
3823 if eolMode == QsciScintilla.EolMode.EolWindows:
3824 eol = "\r\n"
3825 elif eolMode == QsciScintilla.EolMode.EolUnix:
3826 eol = "\n"
3827 elif eolMode == QsciScintilla.EolMode.EolMac:
3828 eol = "\r"
3829 else:
3830 eol = os.linesep
3831 return eol
3832
3833 def useSystemEol(self):
3834 """
3835 Public method to check, if the project uses the system eol setting.
3836
3837 @return flag indicating the usage of system eol (boolean)
3838 """
3839 return self.pdata["EOL"] == 0
3840
3841 def getProjectVersion(self):
3842 """
3843 Public mehod to get the version number of the project.
3844
3845 @return version number
3846 @rtype str
3847 """
3848 return self.pdata["VERSION"]
3849
3850 def getProjectAuthor(self):
3851 """
3852 Public method to get the author of the project.
3853
3854 @return author name
3855 @rtype str
3856 """
3857 return self.pdata["AUTHOR"]
3858
3859 def getProjectAuthorEmail(self):
3860 """
3861 Public method to get the email address of the project author.
3862
3863 @return project author email
3864 @rtype str
3865 """
3866 return self.pdata["EMAIL"]
3867
3868 def getProjectDescription(self):
3869 """
3870 Public method to get the description of the project.
3871
3872 @return project description
3873 @rtype str
3874 """
3875 return self.pdata["DESCRIPTION"]
3876
3877 def getProjectVenv(self, resolveDebugger=True):
3878 """
3879 Public method to get the name of the virtual environment used by the
3880 project.
3881
3882 @param resolveDebugger flag indicating to resolve the virtual
3883 environment name via the debugger settings if none was configured
3884 @type bool
3885 @return name of the project's virtual environment
3886 @rtype str
3887 """
3888 venvName = self.getDebugProperty("VIRTUALENV")
3889 if (
3890 not venvName
3891 and resolveDebugger
3892 and self.getProjectLanguage() in ("Python3", "MicroPython", "Cython")
3893 ):
3894 venvName = Preferences.getDebugger("Python3VirtualEnv")
3895
3896 return venvName
3897
3898 def getProjectInterpreter(self, resolveGlobal=True):
3899 """
3900 Public method to get the path of the interpreter used by the project.
3901
3902 @param resolveGlobal flag indicating to resolve the interpreter using
3903 the global interpreter if no project of debugger specific
3904 environment was configured
3905 @type bool
3906 @return path of the project's interpreter
3907 @rtype str
3908 """
3909 interpreter = ""
3910 venvName = self.getProjectVenv()
3911 if venvName:
3912 interpreter = (
3913 ericApp()
3914 .getObject("VirtualEnvManager")
3915 .getVirtualenvInterpreter(venvName)
3916 )
3917 if not interpreter and resolveGlobal:
3918 interpreter = Globals.getPythonExecutable()
3919
3920 return interpreter
3921
3922 def getProjectExecPath(self):
3923 """
3924 Public method to get the executable search path prefix of the project.
3925
3926 @return executable search path prefix
3927 @rtype str
3928 """
3929 execPath = ""
3930 venvName = self.getProjectVenv()
3931 if venvName:
3932 execPath = (
3933 ericApp().getObject("VirtualEnvManager").getVirtualenvExecPath(venvName)
3934 )
3935
3936 return execPath
3937
3938 def getProjectTestingFramework(self):
3939 """
3940 Public method to get the testing framework name of the project.
3941
3942 @return testing framework name of the project
3943 @rtype str
3944 """
3945 try:
3946 return self.pdata["TESTING_FRAMEWORK"]
3947 except KeyError:
3948 return ""
3949
3950 def getProjectLicense(self):
3951 """
3952 Public method to get the license type used by the project.
3953
3954 @return license type of the project
3955 @rtype str
3956 """
3957 try:
3958 return self.pdata["LICENSE"]
3959 except KeyError:
3960 return ""
3961
3962 def __isInPdata(self, fn):
3963 """
3964 Private method used to check, if the passed in filename is project
3965 controlled..
3966
3967 @param fn filename to be checked
3968 @type str
3969 @return flag indicating membership
3970 @rtype bool
3971 """
3972 newfn = os.path.abspath(fn)
3973 newfn = self.getRelativePath(newfn)
3974 return any(
3975 newfn in self.pdata[group]
3976 for group in [
3977 "SOURCES",
3978 "FORMS",
3979 "INTERFACES",
3980 "PROTOCOLS",
3981 "RESOURCES",
3982 "TRANSLATIONS",
3983 "OTHERS",
3984 ]
3985 )
3986
3987 def isProjectFile(self, fn):
3988 """
3989 Public method used to check, if the passed in filename belongs to the
3990 project.
3991
3992 @param fn filename to be checked (string)
3993 @return flag indicating membership (boolean)
3994 """
3995 return any(
3996 self.__checkProjectFileGroup(fn, group)
3997 for group in [
3998 "SOURCES",
3999 "FORMS",
4000 "INTERFACES",
4001 "PROTOCOLS",
4002 "RESOURCES",
4003 "TRANSLATIONS",
4004 "OTHERS",
4005 ]
4006 )
4007
4008 def __checkProjectFileGroup(self, fn, group):
4009 """
4010 Private method to check, if a file is in a specific file group of the
4011 project.
4012
4013 @param fn filename to be checked (string)
4014 @param group group to check (string)
4015 @return flag indicating membership (boolean)
4016 """
4017 newfn = os.path.abspath(fn)
4018 newfn = self.getRelativePath(newfn)
4019 if newfn in self.pdata[group] or (
4020 group == "OTHERS"
4021 and any(newfn.startswith(entry) for entry in self.pdata[group])
4022 ):
4023 return True
4024
4025 if Utilities.isWindowsPlatform():
4026 # try the above case-insensitive
4027 newfn = newfn.lower()
4028 if any(entry.lower() == newfn for entry in self.pdata[group]):
4029 return True
4030 elif group == "OTHERS" and any(
4031 newfn.startswith(entry.lower()) for entry in self.pdata[group]
4032 ):
4033 return True
4034
4035 return False
4036
4037 def isProjectSource(self, fn):
4038 """
4039 Public method used to check, if the passed in filename belongs to the
4040 project sources.
4041
4042 @param fn filename to be checked (string)
4043 @return flag indicating membership (boolean)
4044 """
4045 return self.__checkProjectFileGroup(fn, "SOURCES")
4046
4047 def isProjectForm(self, fn):
4048 """
4049 Public method used to check, if the passed in filename belongs to the
4050 project forms.
4051
4052 @param fn filename to be checked (string)
4053 @return flag indicating membership (boolean)
4054 """
4055 return self.__checkProjectFileGroup(fn, "FORMS")
4056
4057 def isProjectInterface(self, fn):
4058 """
4059 Public method used to check, if the passed in filename belongs to the
4060 project interfaces.
4061
4062 @param fn filename to be checked (string)
4063 @return flag indicating membership (boolean)
4064 """
4065 return self.__checkProjectFileGroup(fn, "INTERFACES")
4066
4067 def isProjectProtocol(self, fn):
4068 """
4069 Public method used to check, if the passed in filename belongs to the
4070 project protocols.
4071
4072 @param fn filename to be checked
4073 @type str
4074 @return flag indicating membership
4075 @rtype bool
4076 """
4077 return self.__checkProjectFileGroup(fn, "PROTOCOLS")
4078
4079 def isProjectResource(self, fn):
4080 """
4081 Public method used to check, if the passed in filename belongs to the
4082 project resources.
4083
4084 @param fn filename to be checked (string)
4085 @return flag indicating membership (boolean)
4086 """
4087 return self.__checkProjectFileGroup(fn, "RESOURCES")
4088
4089 def initActions(self):
4090 """
4091 Public slot to initialize the project related actions.
4092 """
4093 self.actions = []
4094
4095 ###################################################################
4096 ## Project actions
4097 ###################################################################
4098
4099 self.actGrp1 = createActionGroup(self)
4100
4101 act = EricAction(
4102 self.tr("New project"),
4103 UI.PixmapCache.getIcon("projectNew"),
4104 self.tr("&New..."),
4105 0,
4106 0,
4107 self.actGrp1,
4108 "project_new",
4109 )
4110 act.setStatusTip(self.tr("Generate a new project"))
4111 act.setWhatsThis(
4112 self.tr(
4113 """<b>New...</b>"""
4114 """<p>This opens a dialog for entering the info for a"""
4115 """ new project.</p>"""
4116 )
4117 )
4118 act.triggered.connect(self.createNewProject)
4119 self.actions.append(act)
4120
4121 act = EricAction(
4122 self.tr("Open project"),
4123 UI.PixmapCache.getIcon("projectOpen"),
4124 self.tr("&Open..."),
4125 0,
4126 0,
4127 self.actGrp1,
4128 "project_open",
4129 )
4130 act.setStatusTip(self.tr("Open an existing project"))
4131 act.setWhatsThis(
4132 self.tr("""<b>Open...</b>""" """<p>This opens an existing project.</p>""")
4133 )
4134 act.triggered.connect(self.openProject)
4135 self.actions.append(act)
4136
4137 self.closeAct = EricAction(
4138 self.tr("Close project"),
4139 UI.PixmapCache.getIcon("projectClose"),
4140 self.tr("&Close"),
4141 0,
4142 0,
4143 self,
4144 "project_close",
4145 )
4146 self.closeAct.setStatusTip(self.tr("Close the current project"))
4147 self.closeAct.setWhatsThis(
4148 self.tr("""<b>Close</b>""" """<p>This closes the current project.</p>""")
4149 )
4150 self.closeAct.triggered.connect(self.closeProject)
4151 self.actions.append(self.closeAct)
4152
4153 self.saveAct = EricAction(
4154 self.tr("Save project"),
4155 UI.PixmapCache.getIcon("projectSave"),
4156 self.tr("&Save"),
4157 0,
4158 0,
4159 self,
4160 "project_save",
4161 )
4162 self.saveAct.setStatusTip(self.tr("Save the current project"))
4163 self.saveAct.setWhatsThis(
4164 self.tr("""<b>Save</b>""" """<p>This saves the current project.</p>""")
4165 )
4166 self.saveAct.triggered.connect(self.saveProject)
4167 self.actions.append(self.saveAct)
4168
4169 self.saveasAct = EricAction(
4170 self.tr("Save project as"),
4171 UI.PixmapCache.getIcon("projectSaveAs"),
4172 self.tr("Save &as..."),
4173 0,
4174 0,
4175 self,
4176 "project_save_as",
4177 )
4178 self.saveasAct.setStatusTip(self.tr("Save the current project to a new file"))
4179 self.saveasAct.setWhatsThis(
4180 self.tr(
4181 """<b>Save as</b>"""
4182 """<p>This saves the current project to a new file.</p>"""
4183 )
4184 )
4185 self.saveasAct.triggered.connect(self.saveProjectAs)
4186 self.actions.append(self.saveasAct)
4187
4188 ###################################################################
4189 ## Project management actions
4190 ###################################################################
4191
4192 self.actGrp2 = createActionGroup(self)
4193
4194 self.addFilesAct = EricAction(
4195 self.tr("Add files to project"),
4196 UI.PixmapCache.getIcon("fileMisc"),
4197 self.tr("Add &files..."),
4198 0,
4199 0,
4200 self.actGrp2,
4201 "project_add_file",
4202 )
4203 self.addFilesAct.setStatusTip(self.tr("Add files to the current project"))
4204 self.addFilesAct.setWhatsThis(
4205 self.tr(
4206 """<b>Add files...</b>"""
4207 """<p>This opens a dialog for adding files"""
4208 """ to the current project. The place to add is"""
4209 """ determined by the file extension.</p>"""
4210 )
4211 )
4212 self.addFilesAct.triggered.connect(self.addFiles)
4213 self.actions.append(self.addFilesAct)
4214
4215 self.addDirectoryAct = EricAction(
4216 self.tr("Add directory to project"),
4217 UI.PixmapCache.getIcon("dirOpen"),
4218 self.tr("Add directory..."),
4219 0,
4220 0,
4221 self.actGrp2,
4222 "project_add_directory",
4223 )
4224 self.addDirectoryAct.setStatusTip(
4225 self.tr("Add a directory to the current project")
4226 )
4227 self.addDirectoryAct.setWhatsThis(
4228 self.tr(
4229 """<b>Add directory...</b>"""
4230 """<p>This opens a dialog for adding a directory"""
4231 """ to the current project.</p>"""
4232 )
4233 )
4234 self.addDirectoryAct.triggered.connect(self.addDirectory)
4235 self.actions.append(self.addDirectoryAct)
4236
4237 self.addLanguageAct = EricAction(
4238 self.tr("Add translation to project"),
4239 UI.PixmapCache.getIcon("linguist4"),
4240 self.tr("Add &translation..."),
4241 0,
4242 0,
4243 self.actGrp2,
4244 "project_add_translation",
4245 )
4246 self.addLanguageAct.setStatusTip(
4247 self.tr("Add a translation to the current project")
4248 )
4249 self.addLanguageAct.setWhatsThis(
4250 self.tr(
4251 """<b>Add translation...</b>"""
4252 """<p>This opens a dialog for add a translation"""
4253 """ to the current project.</p>"""
4254 )
4255 )
4256 self.addLanguageAct.triggered.connect(self.addLanguage)
4257 self.actions.append(self.addLanguageAct)
4258
4259 act = EricAction(
4260 self.tr("Search new files"),
4261 self.tr("Searc&h new files..."),
4262 0,
4263 0,
4264 self.actGrp2,
4265 "project_search_new_files",
4266 )
4267 act.setStatusTip(self.tr("Search new files in the project directory."))
4268 act.setWhatsThis(
4269 self.tr(
4270 """<b>Search new files...</b>"""
4271 """<p>This searches for new files (sources, *.ui, *.idl,"""
4272 """ *.proto) in the project directory and registered"""
4273 """ subdirectories.</p>"""
4274 )
4275 )
4276 act.triggered.connect(self.__searchNewFiles)
4277 self.actions.append(act)
4278
4279 act = EricAction(
4280 self.tr("Search Project File"),
4281 self.tr("Search Project File..."),
4282 QKeySequence(self.tr("Alt+Ctrl+P", "Project|Search Project File")),
4283 0,
4284 self.actGrp2,
4285 "project_search_project_file",
4286 )
4287 act.setStatusTip(self.tr("Search for a file in the project list of files."))
4288 act.setWhatsThis(
4289 self.tr(
4290 """<b>Search Project File</b>"""
4291 """<p>This searches for a file in the project list of files.</p>"""
4292 )
4293 )
4294 act.triggered.connect(self.__searchProjectFile)
4295 self.actions.append(act)
4296
4297 self.propsAct = EricAction(
4298 self.tr("Project properties"),
4299 UI.PixmapCache.getIcon("projectProps"),
4300 self.tr("&Properties..."),
4301 0,
4302 0,
4303 self,
4304 "project_properties",
4305 )
4306 self.propsAct.setStatusTip(self.tr("Show the project properties"))
4307 self.propsAct.setWhatsThis(
4308 self.tr(
4309 """<b>Properties...</b>"""
4310 """<p>This shows a dialog to edit the project properties.</p>"""
4311 )
4312 )
4313 self.propsAct.triggered.connect(self.__showProperties)
4314 self.actions.append(self.propsAct)
4315
4316 self.userPropsAct = EricAction(
4317 self.tr("User project properties"),
4318 UI.PixmapCache.getIcon("projectUserProps"),
4319 self.tr("&User Properties..."),
4320 0,
4321 0,
4322 self,
4323 "project_user_properties",
4324 )
4325 self.userPropsAct.setStatusTip(
4326 self.tr("Show the user specific project properties")
4327 )
4328 self.userPropsAct.setWhatsThis(
4329 self.tr(
4330 """<b>User Properties...</b>"""
4331 """<p>This shows a dialog to edit the user specific project"""
4332 """ properties.</p>"""
4333 )
4334 )
4335 self.userPropsAct.triggered.connect(self.__showUserProperties)
4336 self.actions.append(self.userPropsAct)
4337
4338 self.filetypesAct = EricAction(
4339 self.tr("Filetype Associations"),
4340 self.tr("Filetype Associations..."),
4341 0,
4342 0,
4343 self,
4344 "project_filetype_associations",
4345 )
4346 self.filetypesAct.setStatusTip(
4347 self.tr("Show the project file type associations")
4348 )
4349 self.filetypesAct.setWhatsThis(
4350 self.tr(
4351 """<b>Filetype Associations...</b>"""
4352 """<p>This shows a dialog to edit the file type associations of"""
4353 """ the project. These associations determine the type"""
4354 """ (source, form, interface, protocol or others) with a"""
4355 """ filename pattern. They are used when adding a file to the"""
4356 """ project and when performing a search for new files.</p>"""
4357 )
4358 )
4359 self.filetypesAct.triggered.connect(self.__showFiletypeAssociations)
4360 self.actions.append(self.filetypesAct)
4361
4362 self.lexersAct = EricAction(
4363 self.tr("Lexer Associations"),
4364 self.tr("Lexer Associations..."),
4365 0,
4366 0,
4367 self,
4368 "project_lexer_associations",
4369 )
4370 self.lexersAct.setStatusTip(
4371 self.tr("Show the project lexer associations (overriding defaults)")
4372 )
4373 self.lexersAct.setWhatsThis(
4374 self.tr(
4375 """<b>Lexer Associations...</b>"""
4376 """<p>This shows a dialog to edit the lexer associations of"""
4377 """ the project. These associations override the global lexer"""
4378 """ associations. Lexers are used to highlight the editor"""
4379 """ text.</p>"""
4380 )
4381 )
4382 self.lexersAct.triggered.connect(self.__showLexerAssociations)
4383 self.actions.append(self.lexersAct)
4384
4385 ###################################################################
4386 ## Project debug actions
4387 ###################################################################
4388
4389 self.dbgActGrp = createActionGroup(self)
4390
4391 act = EricAction(
4392 self.tr("Debugger Properties"),
4393 self.tr("Debugger &Properties..."),
4394 0,
4395 0,
4396 self.dbgActGrp,
4397 "project_debugger_properties",
4398 )
4399 act.setStatusTip(self.tr("Show the debugger properties"))
4400 act.setWhatsThis(
4401 self.tr(
4402 """<b>Debugger Properties...</b>"""
4403 """<p>This shows a dialog to edit project specific debugger"""
4404 """ settings.</p>"""
4405 )
4406 )
4407 act.triggered.connect(self.__showDebugProperties)
4408 self.actions.append(act)
4409
4410 act = EricAction(
4411 self.tr("Load"),
4412 self.tr("&Load"),
4413 0,
4414 0,
4415 self.dbgActGrp,
4416 "project_debugger_properties_load",
4417 )
4418 act.setStatusTip(self.tr("Load the debugger properties"))
4419 act.setWhatsThis(
4420 self.tr(
4421 """<b>Load Debugger Properties</b>"""
4422 """<p>This loads the project specific debugger settings.</p>"""
4423 )
4424 )
4425 act.triggered.connect(self.__readDebugProperties)
4426 self.actions.append(act)
4427
4428 act = EricAction(
4429 self.tr("Save"),
4430 self.tr("&Save"),
4431 0,
4432 0,
4433 self.dbgActGrp,
4434 "project_debugger_properties_save",
4435 )
4436 act.setStatusTip(self.tr("Save the debugger properties"))
4437 act.setWhatsThis(
4438 self.tr(
4439 """<b>Save Debugger Properties</b>"""
4440 """<p>This saves the project specific debugger settings.</p>"""
4441 )
4442 )
4443 act.triggered.connect(self.__writeDebugProperties)
4444 self.actions.append(act)
4445
4446 act = EricAction(
4447 self.tr("Delete"),
4448 self.tr("&Delete"),
4449 0,
4450 0,
4451 self.dbgActGrp,
4452 "project_debugger_properties_delete",
4453 )
4454 act.setStatusTip(self.tr("Delete the debugger properties"))
4455 act.setWhatsThis(
4456 self.tr(
4457 """<b>Delete Debugger Properties</b>"""
4458 """<p>This deletes the file containing the project specific"""
4459 """ debugger settings.</p>"""
4460 )
4461 )
4462 act.triggered.connect(self.__deleteDebugProperties)
4463 self.actions.append(act)
4464
4465 act = EricAction(
4466 self.tr("Reset"),
4467 self.tr("&Reset"),
4468 0,
4469 0,
4470 self.dbgActGrp,
4471 "project_debugger_properties_resets",
4472 )
4473 act.setStatusTip(self.tr("Reset the debugger properties"))
4474 act.setWhatsThis(
4475 self.tr(
4476 """<b>Reset Debugger Properties</b>"""
4477 """<p>This resets the project specific debugger settings.</p>"""
4478 )
4479 )
4480 act.triggered.connect(self.__initDebugProperties)
4481 self.actions.append(act)
4482
4483 ###################################################################
4484 ## Project session actions
4485 ###################################################################
4486
4487 self.sessActGrp = createActionGroup(self)
4488
4489 act = EricAction(
4490 self.tr("Load session"),
4491 self.tr("Load session"),
4492 0,
4493 0,
4494 self.sessActGrp,
4495 "project_load_session",
4496 )
4497 act.setStatusTip(self.tr("Load the projects session file."))
4498 act.setWhatsThis(
4499 self.tr(
4500 """<b>Load session</b>"""
4501 """<p>This loads the projects session file. The session consists"""
4502 """ of the following data.<br>"""
4503 """- all open source files<br>"""
4504 """- all breakpoint<br>"""
4505 """- the commandline arguments<br>"""
4506 """- the working directory<br>"""
4507 """- the exception reporting flag</p>"""
4508 )
4509 )
4510 act.triggered.connect(self.__readSession)
4511 self.actions.append(act)
4512
4513 act = EricAction(
4514 self.tr("Save session"),
4515 self.tr("Save session"),
4516 0,
4517 0,
4518 self.sessActGrp,
4519 "project_save_session",
4520 )
4521 act.setStatusTip(self.tr("Save the projects session file."))
4522 act.setWhatsThis(
4523 self.tr(
4524 """<b>Save session</b>"""
4525 """<p>This saves the projects session file. The session consists"""
4526 """ of the following data.<br>"""
4527 """- all open source files<br>"""
4528 """- all breakpoint<br>"""
4529 """- the commandline arguments<br>"""
4530 """- the working directory<br>"""
4531 """- the exception reporting flag</p>"""
4532 )
4533 )
4534 act.triggered.connect(self.__writeSession)
4535 self.actions.append(act)
4536
4537 act = EricAction(
4538 self.tr("Delete session"),
4539 self.tr("Delete session"),
4540 0,
4541 0,
4542 self.sessActGrp,
4543 "project_delete_session",
4544 )
4545 act.setStatusTip(self.tr("Delete the projects session file."))
4546 act.setWhatsThis(
4547 self.tr(
4548 """<b>Delete session</b>"""
4549 """<p>This deletes the projects session file</p>"""
4550 )
4551 )
4552 act.triggered.connect(self.__deleteSession)
4553 self.actions.append(act)
4554
4555 ###################################################################
4556 ## Project Tools - check actions
4557 ###################################################################
4558
4559 self.chkGrp = createActionGroup(self)
4560
4561 self.codeMetricsAct = EricAction(
4562 self.tr("Code Metrics"),
4563 self.tr("&Code Metrics..."),
4564 0,
4565 0,
4566 self.chkGrp,
4567 "project_code_metrics",
4568 )
4569 self.codeMetricsAct.setStatusTip(
4570 self.tr("Show some code metrics for the project.")
4571 )
4572 self.codeMetricsAct.setWhatsThis(
4573 self.tr(
4574 """<b>Code Metrics...</b>"""
4575 """<p>This shows some code metrics for all Python files in"""
4576 """ the project.</p>"""
4577 )
4578 )
4579 self.codeMetricsAct.triggered.connect(self.__showCodeMetrics)
4580 self.actions.append(self.codeMetricsAct)
4581
4582 self.codeCoverageAct = EricAction(
4583 self.tr("Python Code Coverage"),
4584 self.tr("Code Co&verage..."),
4585 0,
4586 0,
4587 self.chkGrp,
4588 "project_code_coverage",
4589 )
4590 self.codeCoverageAct.setStatusTip(
4591 self.tr("Show code coverage information for the project.")
4592 )
4593 self.codeCoverageAct.setWhatsThis(
4594 self.tr(
4595 """<b>Code Coverage...</b>"""
4596 """<p>This shows the code coverage information for all Python"""
4597 """ files in the project.</p>"""
4598 )
4599 )
4600 self.codeCoverageAct.triggered.connect(self.__showCodeCoverage)
4601 self.actions.append(self.codeCoverageAct)
4602
4603 self.codeProfileAct = EricAction(
4604 self.tr("Profile Data"),
4605 self.tr("&Profile Data..."),
4606 0,
4607 0,
4608 self.chkGrp,
4609 "project_profile_data",
4610 )
4611 self.codeProfileAct.setStatusTip(
4612 self.tr("Show profiling data for the project.")
4613 )
4614 self.codeProfileAct.setWhatsThis(
4615 self.tr(
4616 """<b>Profile Data...</b>"""
4617 """<p>This shows the profiling data for the project.</p>"""
4618 )
4619 )
4620 self.codeProfileAct.triggered.connect(self.__showProfileData)
4621 self.actions.append(self.codeProfileAct)
4622
4623 ###################################################################
4624 ## Project Tools - graphics actions
4625 ###################################################################
4626
4627 self.graphicsGrp = createActionGroup(self)
4628
4629 self.applicationDiagramAct = EricAction(
4630 self.tr("Application Diagram"),
4631 self.tr("&Application Diagram..."),
4632 0,
4633 0,
4634 self.graphicsGrp,
4635 "project_application_diagram",
4636 )
4637 self.applicationDiagramAct.setStatusTip(
4638 self.tr("Show a diagram of the project.")
4639 )
4640 self.applicationDiagramAct.setWhatsThis(
4641 self.tr(
4642 """<b>Application Diagram...</b>"""
4643 """<p>This shows a diagram of the project.</p>"""
4644 )
4645 )
4646 self.applicationDiagramAct.triggered.connect(self.handleApplicationDiagram)
4647 self.actions.append(self.applicationDiagramAct)
4648
4649 self.loadDiagramAct = EricAction(
4650 self.tr("Load Diagram"),
4651 self.tr("&Load Diagram..."),
4652 0,
4653 0,
4654 self.graphicsGrp,
4655 "project_load_diagram",
4656 )
4657 self.loadDiagramAct.setStatusTip(self.tr("Load a diagram from file."))
4658 self.loadDiagramAct.setWhatsThis(
4659 self.tr(
4660 """<b>Load Diagram...</b>"""
4661 """<p>This loads a diagram from file.</p>"""
4662 )
4663 )
4664 self.loadDiagramAct.triggered.connect(self.__loadDiagram)
4665 self.actions.append(self.loadDiagramAct)
4666
4667 ###################################################################
4668 ## Project Tools - plugin packaging actions
4669 ###################################################################
4670
4671 self.pluginGrp = createActionGroup(self)
4672
4673 self.pluginPkgListAct = EricAction(
4674 self.tr("Create Package List"),
4675 UI.PixmapCache.getIcon("pluginArchiveList"),
4676 self.tr("Create &Package List"),
4677 0,
4678 0,
4679 self.pluginGrp,
4680 "project_plugin_pkglist",
4681 )
4682 self.pluginPkgListAct.setStatusTip(
4683 self.tr("Create an initial PKGLIST file for an eric plugin.")
4684 )
4685 self.pluginPkgListAct.setWhatsThis(
4686 self.tr(
4687 """<b>Create Package List</b>"""
4688 """<p>This creates an initial list of files to include in an"""
4689 """ eric plugin archive. The list is created from the project"""
4690 """ file.</p>"""
4691 )
4692 )
4693 self.pluginPkgListAct.triggered.connect(self.__pluginCreatePkgList)
4694 self.actions.append(self.pluginPkgListAct)
4695
4696 self.pluginArchiveAct = EricAction(
4697 self.tr("Create Plugin Archives"),
4698 UI.PixmapCache.getIcon("pluginArchive"),
4699 self.tr("Create Plugin &Archives"),
4700 0,
4701 0,
4702 self.pluginGrp,
4703 "project_plugin_archive",
4704 )
4705 self.pluginArchiveAct.setStatusTip(self.tr("Create eric plugin archive files."))
4706 self.pluginArchiveAct.setWhatsThis(
4707 self.tr(
4708 """<b>Create Plugin Archives</b>"""
4709 """<p>This creates eric plugin archive files using the list"""
4710 """ of files given in a PKGLIST* file. The archive name is"""
4711 """ built from the main script name if not designated in"""
4712 """ the package list file.</p>"""
4713 )
4714 )
4715 self.pluginArchiveAct.triggered.connect(self.__pluginCreateArchives)
4716 self.actions.append(self.pluginArchiveAct)
4717
4718 self.pluginSArchiveAct = EricAction(
4719 self.tr("Create Plugin Archives (Snapshot)"),
4720 UI.PixmapCache.getIcon("pluginArchiveSnapshot"),
4721 self.tr("Create Plugin Archives (&Snapshot)"),
4722 0,
4723 0,
4724 self.pluginGrp,
4725 "project_plugin_sarchive",
4726 )
4727 self.pluginSArchiveAct.setStatusTip(
4728 self.tr("Create eric plugin archive files (snapshot releases).")
4729 )
4730 self.pluginSArchiveAct.setWhatsThis(
4731 self.tr(
4732 """<b>Create Plugin Archives (Snapshot)</b>"""
4733 """<p>This creates eric plugin archive files using the list"""
4734 """ of files given in the PKGLIST* file. The archive name is"""
4735 """ built from the main script name if not designated in"""
4736 """ the package list file. The version entry of the main script"""
4737 """ is modified to reflect a snapshot release.</p>"""
4738 )
4739 )
4740 self.pluginSArchiveAct.triggered.connect(self.__pluginCreateSnapshotArchives)
4741 self.actions.append(self.pluginSArchiveAct)
4742
4743 ###################################################################
4744 ## Project Tools - make actions
4745 ###################################################################
4746
4747 self.makeGrp = createActionGroup(self)
4748
4749 self.makeExecuteAct = EricAction(
4750 self.tr("Execute Make"),
4751 self.tr("&Execute Make"),
4752 0,
4753 0,
4754 self.makeGrp,
4755 "project_make_execute",
4756 )
4757 self.makeExecuteAct.setStatusTip(self.tr("Perform a 'make' run."))
4758 self.makeExecuteAct.setWhatsThis(
4759 self.tr(
4760 """<b>Execute Make</b>"""
4761 """<p>This performs a 'make' run to rebuild the configured"""
4762 """ target.</p>"""
4763 )
4764 )
4765 self.makeExecuteAct.triggered.connect(self.__executeMake)
4766 self.actions.append(self.makeExecuteAct)
4767
4768 self.makeTestAct = EricAction(
4769 self.tr("Test for Changes"),
4770 self.tr("&Test for Changes"),
4771 0,
4772 0,
4773 self.makeGrp,
4774 "project_make_test",
4775 )
4776 self.makeTestAct.setStatusTip(
4777 self.tr("Question 'make', if a rebuild is needed.")
4778 )
4779 self.makeTestAct.setWhatsThis(
4780 self.tr(
4781 """<b>Test for Changes</b>"""
4782 """<p>This questions 'make', if a rebuild of the configured"""
4783 """ target is necessary.</p>"""
4784 )
4785 )
4786 self.makeTestAct.triggered.connect(
4787 lambda: self.__executeMake(questionOnly=True)
4788 )
4789 self.actions.append(self.makeTestAct)
4790
4791 ###################################################################
4792 ## Project Tools - other tools actions
4793 ###################################################################
4794
4795 self.othersGrp = createActionGroup(self)
4796
4797 self.createSBOMAct = EricAction(
4798 self.tr("Create SBOM File"),
4799 self.tr("Create &SBOM File"),
4800 0,
4801 0,
4802 self.othersGrp,
4803 "project_create_sbom",
4804 )
4805 self.createSBOMAct.setStatusTip(
4806 self.tr("Create a SBOM file of the project dependencies.")
4807 )
4808 self.createSBOMAct.setWhatsThis(
4809 self.tr(
4810 """<b>Create SBOM File</b>"""
4811 """<p>This allows the creation of a SBOM file of the project"""
4812 """ dependencies. This may be based on various input sources"""
4813 """ and will be saved as a CycloneDX SBOM file.</p>"""
4814 )
4815 )
4816 self.createSBOMAct.triggered.connect(self.__createSBOMFile)
4817 self.actions.append(self.createSBOMAct)
4818
4819 ###################################################################
4820 ## Project Tools - code formatting actions
4821 ###################################################################
4822
4823 self.blackFormattingGrp = createActionGroup(self)
4824
4825 self.blackAboutAct = EricAction(
4826 self.tr("About Black"),
4827 self.tr("&Black"),
4828 0,
4829 0,
4830 self.blackFormattingGrp,
4831 "project_black_about",
4832 )
4833 self.blackAboutAct.setStatusTip(self.tr("Show some information about 'Black'."))
4834 self.blackAboutAct.setWhatsThis(
4835 self.tr(
4836 "<b>Black</b>"
4837 "<p>This shows some information about the installed 'Black' tool.</p>"
4838 )
4839 )
4840 self.blackAboutAct.triggered.connect(self.__aboutBlack)
4841 self.actions.append(self.blackAboutAct)
4842 font = self.blackAboutAct.font()
4843 font.setBold(True)
4844 self.blackAboutAct.setFont(font)
4845
4846 self.blackFormatAct = EricAction(
4847 self.tr("Format Code"),
4848 self.tr("&Format Code"),
4849 0,
4850 0,
4851 self.blackFormattingGrp,
4852 "project_black_format_code",
4853 )
4854 self.blackFormatAct.setStatusTip(
4855 self.tr("Format the project sources with 'Black'.")
4856 )
4857 self.blackFormatAct.setWhatsThis(
4858 self.tr(
4859 "<b>Format Code</b>"
4860 "<p>This shows a dialog to enter parameters for the formatting run and"
4861 " reformats the project sources using 'Black'.</p>"
4862 )
4863 )
4864 self.blackFormatAct.triggered.connect(
4865 lambda: self.__performFormatWithBlack(BlackFormattingAction.Format)
4866 )
4867 self.actions.append(self.blackFormatAct)
4868
4869 self.blackCheckFormattingAct = EricAction(
4870 self.tr("Check Code Formatting"),
4871 self.tr("&Check Code Formatting"),
4872 0,
4873 0,
4874 self.blackFormattingGrp,
4875 "project_black_check_code",
4876 )
4877 self.blackCheckFormattingAct.setStatusTip(
4878 self.tr(
4879 "Check, if the project sources need to be reformatted with 'Black'."
4880 )
4881 )
4882 self.blackCheckFormattingAct.setWhatsThis(
4883 self.tr(
4884 "<b>Check Code Formatting</b>"
4885 "<p>This shows a dialog to enter parameters for the format check run"
4886 " and performs a check, if the project sources need to be reformatted"
4887 " using 'Black'.</p>"
4888 )
4889 )
4890 self.blackCheckFormattingAct.triggered.connect(
4891 lambda: self.__performFormatWithBlack(BlackFormattingAction.Check)
4892 )
4893 self.actions.append(self.blackCheckFormattingAct)
4894
4895 self.blackDiffFormattingAct = EricAction(
4896 self.tr("Code Formatting Diff"),
4897 self.tr("Code Formatting &Diff"),
4898 0,
4899 0,
4900 self.blackFormattingGrp,
4901 "project_black_diff_code",
4902 )
4903 self.blackDiffFormattingAct.setStatusTip(
4904 self.tr(
4905 "Generate a unified diff of potential project source reformatting"
4906 " with 'Black'."
4907 )
4908 )
4909 self.blackDiffFormattingAct.setWhatsThis(
4910 self.tr(
4911 "<b>Diff Code Formatting</b>"
4912 "<p>This shows a dialog to enter parameters for the format diff run and"
4913 " generates a unified diff of potential project source reformatting"
4914 " using 'Black'.</p>"
4915 )
4916 )
4917 self.blackDiffFormattingAct.triggered.connect(
4918 lambda: self.__performFormatWithBlack(BlackFormattingAction.Diff)
4919 )
4920 self.actions.append(self.blackDiffFormattingAct)
4921
4922 self.closeAct.setEnabled(False)
4923 self.saveAct.setEnabled(False)
4924 self.saveasAct.setEnabled(False)
4925 self.actGrp2.setEnabled(False)
4926 self.propsAct.setEnabled(False)
4927 self.userPropsAct.setEnabled(False)
4928 self.filetypesAct.setEnabled(False)
4929 self.lexersAct.setEnabled(False)
4930 self.sessActGrp.setEnabled(False)
4931 self.dbgActGrp.setEnabled(False)
4932 self.pluginGrp.setEnabled(False)
4933
4934 def initMenus(self):
4935 """
4936 Public slot to initialize the project menus.
4937
4938 @return tuple of generated menus
4939 @rtype tuple of (QMenu, QMenu)
4940 """
4941 menu = QMenu(self.tr("&Project"), self.parent())
4942 self.recentMenu = QMenu(self.tr("Open &Recent Projects"), menu)
4943 self.sessionMenu = QMenu(self.tr("Session"), menu)
4944 self.debuggerMenu = QMenu(self.tr("Debugger"), menu)
4945
4946 toolsMenu = QMenu(self.tr("Project-T&ools"), self.parent())
4947 self.vcsMenu = QMenu(self.tr("&Version Control"), toolsMenu)
4948 self.vcsMenu.setTearOffEnabled(True)
4949 self.vcsProjectHelper.initMenu(self.vcsMenu)
4950 self.vcsMenu.setEnabled(self.vcsSoftwareAvailable())
4951 self.checksMenu = QMenu(self.tr("Chec&k"), toolsMenu)
4952 self.checksMenu.setTearOffEnabled(True)
4953 self.formattingMenu = QMenu(self.tr("Code &Formatting"), toolsMenu)
4954 self.formattingMenu.setTearOffEnabled(True)
4955 self.menuShow = QMenu(self.tr("Sho&w"), toolsMenu)
4956 self.graphicsMenu = QMenu(self.tr("&Diagrams"), toolsMenu)
4957 self.packagersMenu = QMenu(self.tr("Pac&kagers"), toolsMenu)
4958 self.apidocMenu = QMenu(self.tr("Source &Documentation"), toolsMenu)
4959 self.apidocMenu.setTearOffEnabled(True)
4960 self.makeMenu = QMenu(self.tr("Make"), toolsMenu)
4961 self.othersMenu = QMenu(self.tr("Other Tools"), toolsMenu)
4962
4963 self.__menus = {
4964 "Main": menu,
4965 "Recent": self.recentMenu,
4966 "VCS": self.vcsMenu,
4967 "Checks": self.checksMenu,
4968 "Show": self.menuShow,
4969 "Graphics": self.graphicsMenu,
4970 "Session": self.sessionMenu,
4971 "Apidoc": self.apidocMenu,
4972 "Debugger": self.debuggerMenu,
4973 "Packagers": self.packagersMenu,
4974 "Make": self.makeMenu,
4975 "OtherTools": self.othersMenu,
4976 "Formatting": self.formattingMenu,
4977 }
4978
4979 # connect the aboutToShow signals
4980 self.recentMenu.aboutToShow.connect(self.__showContextMenuRecent)
4981 self.recentMenu.triggered.connect(self.__openRecent)
4982 self.vcsMenu.aboutToShow.connect(self.__showContextMenuVCS)
4983 self.checksMenu.aboutToShow.connect(self.__showContextMenuChecks)
4984 self.menuShow.aboutToShow.connect(self.__showContextMenuShow)
4985 self.graphicsMenu.aboutToShow.connect(self.__showContextMenuGraphics)
4986 self.apidocMenu.aboutToShow.connect(self.__showContextMenuApiDoc)
4987 self.packagersMenu.aboutToShow.connect(self.__showContextMenuPackagers)
4988 self.sessionMenu.aboutToShow.connect(self.__showContextMenuSession)
4989 self.debuggerMenu.aboutToShow.connect(self.__showContextMenuDebugger)
4990 self.makeMenu.aboutToShow.connect(self.__showContextMenuMake)
4991 self.othersMenu.aboutToShow.connect(self.__showContextMenuOthers)
4992 self.formattingMenu.aboutToShow.connect(self.__showContextMenuFormat)
4993 menu.aboutToShow.connect(self.__showMenu)
4994
4995 # build the show menu
4996 self.menuShow.setTearOffEnabled(True)
4997 self.menuShow.addAction(self.codeMetricsAct)
4998 self.menuShow.addAction(self.codeCoverageAct)
4999 self.menuShow.addAction(self.codeProfileAct)
5000
5001 # build the diagrams menu
5002 self.graphicsMenu.setTearOffEnabled(True)
5003 self.graphicsMenu.addAction(self.applicationDiagramAct)
5004 self.graphicsMenu.addSeparator()
5005 self.graphicsMenu.addAction(self.loadDiagramAct)
5006
5007 # build the session menu
5008 self.sessionMenu.setTearOffEnabled(True)
5009 self.sessionMenu.addActions(self.sessActGrp.actions())
5010
5011 # build the debugger menu
5012 self.debuggerMenu.setTearOffEnabled(True)
5013 self.debuggerMenu.addActions(self.dbgActGrp.actions())
5014
5015 # build the packagers menu
5016 self.packagersMenu.setTearOffEnabled(True)
5017 self.packagersMenu.addActions(self.pluginGrp.actions())
5018 self.packagersMenu.addSeparator()
5019
5020 # build the make menu
5021 self.makeMenu.setTearOffEnabled(True)
5022 self.makeMenu.addActions(self.makeGrp.actions())
5023 self.makeMenu.addSeparator()
5024
5025 # build the 'Other Tools' menu
5026 self.othersMenu.setTearOffEnabled(True)
5027 self.othersMenu.addActions(self.othersGrp.actions())
5028 self.othersMenu.addSeparator()
5029
5030 # build the 'Code Formatting' menu
5031 self.formattingMenu.setTearOffEnabled(True)
5032 self.formattingMenu.addActions(self.blackFormattingGrp.actions())
5033 self.formattingMenu.addSeparator()
5034
5035 # build the project main menu
5036 menu.setTearOffEnabled(True)
5037 menu.addActions(self.actGrp1.actions())
5038 self.menuRecentAct = menu.addMenu(self.recentMenu)
5039 menu.addSeparator()
5040 menu.addAction(self.closeAct)
5041 menu.addSeparator()
5042 menu.addAction(self.saveAct)
5043 menu.addAction(self.saveasAct)
5044 menu.addSeparator()
5045 menu.addActions(self.actGrp2.actions())
5046 menu.addSeparator()
5047 menu.addAction(self.propsAct)
5048 menu.addAction(self.userPropsAct)
5049 menu.addAction(self.filetypesAct)
5050 menu.addAction(self.lexersAct)
5051 menu.addSeparator()
5052 self.menuDebuggerAct = menu.addMenu(self.debuggerMenu)
5053 self.menuSessionAct = menu.addMenu(self.sessionMenu)
5054
5055 # build the project tools menu
5056 toolsMenu.setTearOffEnabled(True)
5057 toolsMenu.addSeparator()
5058 toolsMenu.addMenu(self.vcsMenu)
5059 toolsMenu.addSeparator()
5060 self.menuCheckAct = toolsMenu.addMenu(self.checksMenu)
5061 toolsMenu.addSeparator()
5062 self.menuFormattingAct = toolsMenu.addMenu(self.formattingMenu)
5063 toolsMenu.addSeparator()
5064 self.menuMakeAct = toolsMenu.addMenu(self.makeMenu)
5065 toolsMenu.addSeparator()
5066 self.menuDiagramAct = toolsMenu.addMenu(self.graphicsMenu)
5067 toolsMenu.addSeparator()
5068 self.menuShowAct = toolsMenu.addMenu(self.menuShow)
5069 toolsMenu.addSeparator()
5070 self.menuApidocAct = toolsMenu.addMenu(self.apidocMenu)
5071 toolsMenu.addSeparator()
5072 self.menuPackagersAct = toolsMenu.addMenu(self.packagersMenu)
5073 toolsMenu.addSeparator()
5074 self.menuOtherToolsAct = toolsMenu.addMenu(self.othersMenu)
5075
5076 self.menuCheckAct.setEnabled(False)
5077 self.menuShowAct.setEnabled(False)
5078 self.menuDiagramAct.setEnabled(False)
5079 self.menuSessionAct.setEnabled(False)
5080 self.menuDebuggerAct.setEnabled(False)
5081 self.menuApidocAct.setEnabled(False)
5082 self.menuPackagersAct.setEnabled(False)
5083 self.menuMakeAct.setEnabled(False)
5084 self.menuOtherToolsAct.setEnabled(False)
5085 self.menuFormattingAct.setEnabled(False)
5086
5087 self.__menu = menu
5088 self.__toolsMenu = toolsMenu
5089
5090 return menu, toolsMenu
5091
5092 def initToolbars(self, toolbarManager):
5093 """
5094 Public slot to initialize the project toolbar and the basic VCS
5095 toolbar.
5096
5097 @param toolbarManager reference to a toolbar manager object
5098 (EricToolBarManager)
5099 @return tuple of the generated toolbars (tuple of two QToolBar)
5100 """
5101 tb = QToolBar(self.tr("Project"), self.ui)
5102 tb.setIconSize(UI.Config.ToolBarIconSize)
5103 tb.setObjectName("ProjectToolbar")
5104 tb.setToolTip(self.tr("Project"))
5105
5106 tb.addActions(self.actGrp1.actions())
5107 tb.addAction(self.closeAct)
5108 tb.addSeparator()
5109 tb.addAction(self.saveAct)
5110 tb.addAction(self.saveasAct)
5111
5112 toolbarManager.addToolBar(tb, tb.windowTitle())
5113 toolbarManager.addAction(self.addFilesAct, tb.windowTitle())
5114 toolbarManager.addAction(self.addDirectoryAct, tb.windowTitle())
5115 toolbarManager.addAction(self.addLanguageAct, tb.windowTitle())
5116 toolbarManager.addAction(self.propsAct, tb.windowTitle())
5117 toolbarManager.addAction(self.userPropsAct, tb.windowTitle())
5118
5119 import VCS
5120
5121 vcstb = VCS.getBasicHelper(self).initBasicToolbar(self.ui, toolbarManager)
5122
5123 return tb, vcstb
5124
5125 def __showMenu(self):
5126 """
5127 Private method to set up the project menu.
5128 """
5129 self.menuRecentAct.setEnabled(len(self.recent) > 0)
5130
5131 self.showMenu.emit("Main", self.__menus["Main"])
5132
5133 def __syncRecent(self):
5134 """
5135 Private method to synchronize the list of recently opened projects
5136 with the central store.
5137 """
5138 for recent in self.recent[:]:
5139 if Utilities.samepath(self.pfile, recent):
5140 self.recent.remove(recent)
5141 self.recent.insert(0, self.pfile)
5142 maxRecent = Preferences.getProject("RecentNumber")
5143 if len(self.recent) > maxRecent:
5144 self.recent = self.recent[:maxRecent]
5145 self.__saveRecent()
5146
5147 def __showContextMenuRecent(self):
5148 """
5149 Private method to set up the recent projects menu.
5150 """
5151 self.__loadRecent()
5152
5153 self.recentMenu.clear()
5154
5155 for idx, rp in enumerate(self.recent, start=1):
5156 formatStr = "&{0:d}. {1}" if idx < 10 else "{0:d}. {1}"
5157 act = self.recentMenu.addAction(
5158 formatStr.format(
5159 idx, Utilities.compactPath(rp, self.ui.maxMenuFilePathLen)
5160 )
5161 )
5162 act.setData(rp)
5163 act.setEnabled(pathlib.Path(rp).exists())
5164
5165 self.recentMenu.addSeparator()
5166 self.recentMenu.addAction(self.tr("&Clear"), self.clearRecent)
5167
5168 def __openRecent(self, act):
5169 """
5170 Private method to open a project from the list of rencently opened
5171 projects.
5172
5173 @param act reference to the action that triggered (QAction)
5174 """
5175 file = act.data()
5176 if file:
5177 self.openProject(file)
5178
5179 def clearRecent(self):
5180 """
5181 Public method to clear the recent projects menu.
5182 """
5183 self.recent = []
5184 self.__saveRecent()
5185
5186 def clearHistories(self):
5187 """
5188 Public method to clear the project related histories.
5189 """
5190 self.clearRecent()
5191
5192 for key in ["DebugClientsHistory", "DebuggerInterpreterHistory"]:
5193 Preferences.setProject(key, [])
5194 Preferences.syncPreferences()
5195
5196 def __searchNewFiles(self):
5197 """
5198 Private slot used to handle the search new files action.
5199 """
5200 self.__doSearchNewFiles(False, True)
5201
5202 def __searchProjectFile(self):
5203 """
5204 Private slot to show the Find Project File dialog.
5205 """
5206 if self.__findProjectFileDialog is None:
5207 from .QuickFindFileDialog import QuickFindFileDialog
5208
5209 self.__findProjectFileDialog = QuickFindFileDialog(self)
5210 self.__findProjectFileDialog.sourceFile.connect(self.sourceFile)
5211 self.__findProjectFileDialog.designerFile.connect(self.designerFile)
5212 self.__findProjectFileDialog.linguistFile.connect(self.linguistFile)
5213 self.__findProjectFileDialog.show()
5214 self.__findProjectFileDialog.raise_()
5215 self.__findProjectFileDialog.activateWindow()
5216
5217 def __doSearchNewFiles(self, AI=True, onUserDemand=False):
5218 """
5219 Private method to search for new files in the project directory.
5220
5221 If new files were found, it shows a dialog listing these files and
5222 gives the user the opportunity to select the ones he wants to
5223 include. If 'Automatic Inclusion' is enabled, the new files are
5224 automatically added to the project.
5225
5226 @param AI flag indicating whether the automatic inclusion should
5227 be honoured (boolean)
5228 @param onUserDemand flag indicating whether this method was
5229 requested by the user via a menu action (boolean)
5230 """
5231 autoInclude = Preferences.getProject("AutoIncludeNewFiles")
5232 recursiveSearch = Preferences.getProject("SearchNewFilesRecursively")
5233 newFiles = []
5234
5235 ignore_patterns = [
5236 pattern
5237 for pattern, filetype in self.pdata["FILETYPES"].items()
5238 if filetype == "__IGNORE__"
5239 ]
5240
5241 dirs = self.subdirs[:]
5242 for directory in dirs:
5243 skip = False
5244 for ignore_pattern in ignore_patterns:
5245 if fnmatch.fnmatch(directory, ignore_pattern):
5246 skip = True
5247 break
5248 if skip:
5249 continue
5250
5251 curpath = os.path.join(self.ppath, directory)
5252 try:
5253 newSources = os.listdir(curpath)
5254 except OSError:
5255 newSources = []
5256 pattern = (
5257 self.pdata["TRANSLATIONPATTERN"].replace("%language%", "*")
5258 if self.pdata["TRANSLATIONPATTERN"]
5259 else "*.ts"
5260 )
5261 binpattern = self.__binaryTranslationFile(pattern)
5262 for ns in newSources:
5263 # ignore hidden files and directories
5264 if ns.startswith("."):
5265 continue
5266 if (
5267 Utilities.isWindowsPlatform()
5268 and os.path.isdir(os.path.join(curpath, ns))
5269 and ns.startswith("_")
5270 ):
5271 # dot net hack
5272 continue
5273
5274 # set fn to project relative name
5275 # then reset ns to fully qualified name for insertion,
5276 # possibly.
5277 fn = os.path.join(directory, ns) if directory else ns
5278 ns = os.path.abspath(os.path.join(curpath, ns))
5279
5280 # do not bother with dirs here...
5281 if os.path.isdir(ns):
5282 if recursiveSearch:
5283 d = self.getRelativePath(ns)
5284 if d not in dirs:
5285 dirs.append(d)
5286 continue
5287
5288 filetype = ""
5289 bfn = os.path.basename(fn)
5290 for pattern in sorted(self.pdata["FILETYPES"].keys(), reverse=True):
5291 if fnmatch.fnmatch(bfn, pattern):
5292 filetype = self.pdata["FILETYPES"][pattern]
5293 break
5294
5295 if (
5296 (filetype == "SOURCES" and fn not in self.pdata["SOURCES"])
5297 or (filetype == "FORMS" and fn not in self.pdata["FORMS"])
5298 or (filetype == "INTERFACES" and fn not in self.pdata["INTERFACES"])
5299 or (filetype == "PROTOCOLS" and fn not in self.pdata["PROTOCOLS"])
5300 or (filetype == "RESOURCES" and fn not in self.pdata["RESOURCES"])
5301 or (filetype == "OTHERS" and fn not in self.pdata["OTHERS"])
5302 or (
5303 filetype == "TRANSLATIONS"
5304 and fn not in self.pdata["TRANSLATIONS"]
5305 and (
5306 fnmatch.fnmatch(ns, pattern)
5307 or fnmatch.fnmatch(ns, binpattern)
5308 )
5309 )
5310 ):
5311 if autoInclude and AI:
5312 self.appendFile(ns)
5313 else:
5314 newFiles.append(ns)
5315
5316 # if autoInclude is set there is no more work left
5317 if autoInclude and AI:
5318 return
5319
5320 # if newfiles is empty, put up message box informing user nothing found
5321 if not newFiles:
5322 if onUserDemand:
5323 EricMessageBox.information(
5324 self.ui,
5325 self.tr("Search New Files"),
5326 self.tr("There were no new files found to be added."),
5327 )
5328 return
5329
5330 # autoInclude is not set, show a dialog
5331 from .AddFoundFilesDialog import AddFoundFilesDialog
5332
5333 dlg = AddFoundFilesDialog(newFiles, self.parent(), None)
5334 res = dlg.exec()
5335
5336 # the 'Add All' button was pressed
5337 if res == 1:
5338 for file in newFiles:
5339 self.appendFile(file)
5340
5341 # the 'Add Selected' button was pressed
5342 elif res == 2:
5343 files = dlg.getSelection()
5344 for file in files:
5345 self.appendFile(file)
5346
5347 def othersAdded(self, fn, updateModel=True):
5348 """
5349 Public slot to be called, if something was added to the OTHERS project
5350 data area.
5351
5352 @param fn filename or directory name added (string)
5353 @param updateModel flag indicating an update of the model is requested
5354 (boolean)
5355 """
5356 self.projectOthersAdded.emit(fn)
5357 updateModel and self.__model.addNewItem("OTHERS", fn)
5358
5359 def getActions(self):
5360 """
5361 Public method to get a list of all actions.
5362
5363 @return list of all actions (list of EricAction)
5364 """
5365 return self.actions[:]
5366
5367 def addEricActions(self, actions):
5368 """
5369 Public method to add actions to the list of actions.
5370
5371 @param actions list of actions (list of EricAction)
5372 """
5373 self.actions.extend(actions)
5374
5375 def removeEricActions(self, actions):
5376 """
5377 Public method to remove actions from the list of actions.
5378
5379 @param actions list of actions (list of EricAction)
5380 """
5381 for act in actions:
5382 with contextlib.suppress(ValueError):
5383 self.actions.remove(act)
5384
5385 def getMenu(self, menuName):
5386 """
5387 Public method to get a reference to the main menu or a submenu.
5388
5389 @param menuName name of the menu (string)
5390 @return reference to the requested menu (QMenu) or None
5391 """
5392 try:
5393 return self.__menus[menuName]
5394 except KeyError:
5395 return None
5396
5397 def repopulateItem(self, fullname):
5398 """
5399 Public slot to repopulate a named item.
5400
5401 @param fullname full name of the item to repopulate (string)
5402 """
5403 if not self.isOpen():
5404 return
5405
5406 with EricOverrideCursor():
5407 name = self.getRelativePath(fullname)
5408 self.prepareRepopulateItem.emit(name)
5409 self.__model.repopulateItem(name)
5410 self.completeRepopulateItem.emit(name)
5411
5412 ##############################################################
5413 ## Below is the VCS interface
5414 ##############################################################
5415
5416 def initVCS(self, vcsSystem=None, nooverride=False):
5417 """
5418 Public method used to instantiate a vcs system.
5419
5420 @param vcsSystem type of VCS to be used (string)
5421 @param nooverride flag indicating to ignore an override request
5422 (boolean)
5423 @return a reference to the vcs object
5424 """
5425 vcs = None
5426 forProject = True
5427 override = False
5428
5429 if vcsSystem is None:
5430 if self.pdata["VCS"] and self.pdata["VCS"] != "None":
5431 vcsSystem = self.pdata["VCS"]
5432 else:
5433 forProject = False
5434
5435 if (
5436 forProject
5437 and self.pdata["VCS"]
5438 and self.pdata["VCS"] != "None"
5439 and self.pudata["VCSOVERRIDE"]
5440 and not nooverride
5441 ):
5442 vcsSystem = self.pudata["VCSOVERRIDE"]
5443 override = True
5444
5445 if vcsSystem is not None:
5446 import VCS
5447
5448 try:
5449 vcs = VCS.factory(vcsSystem)
5450 except ImportError:
5451 if override:
5452 # override failed, revert to original
5453 self.pudata["VCSOVERRIDE"] = ""
5454 return self.initVCS(nooverride=True)
5455
5456 if vcs:
5457 vcsExists, msg = vcs.vcsExists()
5458 if not vcsExists:
5459 if override:
5460 # override failed, revert to original
5461 with EricOverridenCursor():
5462 EricMessageBox.critical(
5463 self.ui,
5464 self.tr("Version Control System"),
5465 self.tr(
5466 "<p>The selected VCS <b>{0}</b> could not be"
5467 " found. <br/>Reverting override.</p><p>{1}"
5468 "</p>"
5469 ).format(vcsSystem, msg),
5470 )
5471 self.pudata["VCSOVERRIDE"] = ""
5472 return self.initVCS(nooverride=True)
5473
5474 with EricOverridenCursor():
5475 EricMessageBox.critical(
5476 self.ui,
5477 self.tr("Version Control System"),
5478 self.tr(
5479 "<p>The selected VCS <b>{0}</b> could not be"
5480 " found.<br/>Disabling version control.</p>"
5481 "<p>{1}</p>"
5482 ).format(vcsSystem, msg),
5483 )
5484 vcs = None
5485 if forProject:
5486 self.pdata["VCS"] = "None"
5487 self.setDirty(True)
5488 else:
5489 vcs.vcsInitConfig(self)
5490
5491 if vcs and forProject:
5492 # set the vcs options
5493 if vcs.vcsSupportCommandOptions():
5494 with contextlib.suppress(LookupError):
5495 vcsopt = copy.deepcopy(self.pdata["VCSOPTIONS"])
5496 vcs.vcsSetOptions(vcsopt)
5497 # set vcs specific data
5498 with contextlib.suppress(LookupError):
5499 vcsother = copy.deepcopy(self.pdata["VCSOTHERDATA"])
5500 vcs.vcsSetOtherData(vcsother)
5501
5502 if forProject:
5503 if vcs is None:
5504 import VCS
5505
5506 self.vcsProjectHelper = VCS.getBasicHelper(self)
5507 self.vcsBasicHelper = True
5508 else:
5509 self.vcsProjectHelper = vcs.vcsGetProjectHelper(self)
5510 self.vcsBasicHelper = False
5511 if self.vcsMenu is not None:
5512 self.vcsProjectHelper.initMenu(self.vcsMenu)
5513 self.vcsMenu.setEnabled(self.vcsSoftwareAvailable())
5514
5515 return vcs
5516
5517 def resetVCS(self):
5518 """
5519 Public method to reset the VCS.
5520 """
5521 self.pdata["VCS"] = "None"
5522 self.vcs = self.initVCS()
5523 ericApp().getObject("PluginManager").deactivateVcsPlugins()
5524
5525 def __showContextMenuVCS(self):
5526 """
5527 Private slot called before the vcs menu is shown.
5528 """
5529 self.vcsProjectHelper.showMenu()
5530 if self.vcsBasicHelper:
5531 self.showMenu.emit("VCS", self.vcsMenu)
5532
5533 def vcsSoftwareAvailable(self):
5534 """
5535 Public method to check, if some supported VCS software is available
5536 to the IDE.
5537
5538 @return flag indicating availability of VCS software (boolean)
5539 """
5540 vcsSystemsDict = (
5541 ericApp()
5542 .getObject("PluginManager")
5543 .getPluginDisplayStrings("version_control")
5544 )
5545 return len(vcsSystemsDict) != 0
5546
5547 def __vcsStatusChanged(self):
5548 """
5549 Private slot to handle a change of the overall VCS status.
5550 """
5551 self.projectChanged.emit()
5552
5553 def __vcsConnectStatusMonitor(self):
5554 """
5555 Private method to start the VCS monitor and connect its signals.
5556 """
5557 if self.vcs is not None:
5558 self.vcs.committed.connect(self.vcsCommitted)
5559
5560 self.vcs.startStatusMonitor(self)
5561 self.vcs.vcsStatusMonitorData.connect(self.__model.changeVCSStates)
5562 self.vcs.vcsStatusMonitorData.connect(self.vcsStatusMonitorData)
5563 self.vcs.vcsStatusMonitorAllData.connect(self.vcsStatusMonitorAllData)
5564 self.vcs.vcsStatusMonitorStatus.connect(self.vcsStatusMonitorStatus)
5565 self.vcs.vcsStatusMonitorInfo.connect(self.vcsStatusMonitorInfo)
5566 self.vcs.vcsStatusChanged.connect(self.__vcsStatusChanged)
5567
5568 #########################################################################
5569 ## Below is the interface to the checker tools
5570 #########################################################################
5571
5572 def __showContextMenuChecks(self):
5573 """
5574 Private slot called before the checks menu is shown.
5575 """
5576 self.showMenu.emit("Checks", self.checksMenu)
5577
5578 #########################################################################
5579 ## Below is the interface to the packagers tools
5580 #########################################################################
5581
5582 def __showContextMenuPackagers(self):
5583 """
5584 Private slot called before the packagers menu is shown.
5585 """
5586 self.showMenu.emit("Packagers", self.packagersMenu)
5587
5588 #########################################################################
5589 ## Below is the interface to the apidoc tools
5590 #########################################################################
5591
5592 def __showContextMenuApiDoc(self):
5593 """
5594 Private slot called before the apidoc menu is shown.
5595 """
5596 self.showMenu.emit("Apidoc", self.apidocMenu)
5597
5598 #########################################################################
5599 ## Below is the interface to the show tools
5600 #########################################################################
5601
5602 def __showCodeMetrics(self):
5603 """
5604 Private slot used to calculate some code metrics for the project files.
5605 """
5606 files = [
5607 os.path.join(self.ppath, file)
5608 for file in self.pdata["SOURCES"]
5609 if file.endswith(".py")
5610 ]
5611 from DataViews.CodeMetricsDialog import CodeMetricsDialog
5612
5613 self.codemetrics = CodeMetricsDialog()
5614 self.codemetrics.show()
5615 self.codemetrics.prepare(files)
5616
5617 def __showCodeCoverage(self):
5618 """
5619 Private slot used to show the code coverage information for the
5620 project files.
5621 """
5622 fn = self.getMainScript(True)
5623 if fn is None:
5624 EricMessageBox.critical(
5625 self.ui,
5626 self.tr("Coverage Data"),
5627 self.tr(
5628 "There is no main script defined for the"
5629 " current project. Aborting"
5630 ),
5631 )
5632 return
5633
5634 files = Utilities.getCoverageFileNames(fn)
5635 if files:
5636 if len(files) > 1:
5637 fn, ok = QInputDialog.getItem(
5638 None,
5639 self.tr("Code Coverage"),
5640 self.tr("Please select a coverage file"),
5641 files,
5642 0,
5643 False,
5644 )
5645 if not ok:
5646 return
5647 else:
5648 fn = files[0]
5649 else:
5650 return
5651
5652 files = [
5653 os.path.join(self.ppath, file)
5654 for file in self.pdata["SOURCES"]
5655 if os.path.splitext(file)[1].startswith(".py")
5656 ]
5657 from DataViews.PyCoverageDialog import PyCoverageDialog
5658
5659 self.codecoverage = PyCoverageDialog()
5660 self.codecoverage.show()
5661 self.codecoverage.start(fn, files)
5662
5663 def __showProfileData(self):
5664 """
5665 Private slot used to show the profiling information for the project.
5666 """
5667 fn = self.getMainScript(True)
5668 if fn is None:
5669 EricMessageBox.critical(
5670 self.ui,
5671 self.tr("Profile Data"),
5672 self.tr(
5673 "There is no main script defined for the"
5674 " current project. Aborting"
5675 ),
5676 )
5677 return
5678
5679 files = Utilities.getProfileFileNames(fn)
5680 if files:
5681 if len(files) > 1:
5682 fn, ok = QInputDialog.getItem(
5683 None,
5684 self.tr("Profile Data"),
5685 self.tr("Please select a profile file"),
5686 files,
5687 0,
5688 False,
5689 )
5690 if not ok:
5691 return
5692 else:
5693 fn = files[0]
5694 else:
5695 return
5696
5697 from DataViews.PyProfileDialog import PyProfileDialog
5698
5699 self.profiledata = PyProfileDialog()
5700 self.profiledata.show()
5701 self.profiledata.start(fn)
5702
5703 def __showContextMenuShow(self):
5704 """
5705 Private slot called before the show menu is shown.
5706 """
5707 fn = self.getMainScript(True)
5708 if not fn:
5709 fn = self.getProjectPath()
5710
5711 self.codeProfileAct.setEnabled(
5712 self.isPy3Project() and bool(Utilities.getProfileFileName(fn))
5713 )
5714 self.codeCoverageAct.setEnabled(
5715 self.isPy3Project() and bool(Utilities.getCoverageFileNames(fn))
5716 )
5717
5718 self.showMenu.emit("Show", self.menuShow)
5719
5720 #########################################################################
5721 ## Below is the interface to the diagrams
5722 #########################################################################
5723
5724 def __showContextMenuGraphics(self):
5725 """
5726 Private slot called before the graphics menu is shown.
5727 """
5728 self.showMenu.emit("Graphics", self.graphicsMenu)
5729
5730 def handleApplicationDiagram(self):
5731 """
5732 Public method to handle the application diagram context menu action.
5733 """
5734 res = EricMessageBox.yesNo(
5735 self.ui,
5736 self.tr("Application Diagram"),
5737 self.tr("""Include module names?"""),
5738 yesDefault=True,
5739 )
5740
5741 from Graphics.UMLDialog import UMLDialog, UMLDialogType
5742
5743 self.applicationDiagram = UMLDialog(
5744 UMLDialogType.APPLICATION_DIAGRAM, self, self.parent(), noModules=not res
5745 )
5746 self.applicationDiagram.show()
5747
5748 def __loadDiagram(self):
5749 """
5750 Private slot to load a diagram from file.
5751 """
5752 from Graphics.UMLDialog import UMLDialog, UMLDialogType
5753
5754 self.loadedDiagram = None
5755 loadedDiagram = UMLDialog(UMLDialogType.NO_DIAGRAM, self, parent=self.parent())
5756 if loadedDiagram.load():
5757 self.loadedDiagram = loadedDiagram
5758 self.loadedDiagram.show(fromFile=True)
5759
5760 #########################################################################
5761 ## Below is the interface to the VCS monitor thread
5762 #########################################################################
5763
5764 def setStatusMonitorInterval(self, interval):
5765 """
5766 Public method to se the interval of the VCS status monitor thread.
5767
5768 @param interval status monitor interval in seconds (integer)
5769 """
5770 if self.vcs is not None:
5771 self.vcs.setStatusMonitorInterval(interval, self)
5772
5773 def getStatusMonitorInterval(self):
5774 """
5775 Public method to get the monitor interval.
5776
5777 @return interval in seconds (integer)
5778 """
5779 if self.vcs is not None:
5780 return self.vcs.getStatusMonitorInterval()
5781 else:
5782 return 0
5783
5784 def setStatusMonitorAutoUpdate(self, auto):
5785 """
5786 Public method to enable the auto update function.
5787
5788 @param auto status of the auto update function (boolean)
5789 """
5790 if self.vcs is not None:
5791 self.vcs.setStatusMonitorAutoUpdate(auto)
5792
5793 def getStatusMonitorAutoUpdate(self):
5794 """
5795 Public method to retrieve the status of the auto update function.
5796
5797 @return status of the auto update function (boolean)
5798 """
5799 if self.vcs is not None:
5800 return self.vcs.getStatusMonitorAutoUpdate()
5801 else:
5802 return False
5803
5804 def checkVCSStatus(self):
5805 """
5806 Public method to wake up the VCS status monitor thread.
5807 """
5808 if self.vcs is not None:
5809 self.vcs.checkVCSStatus()
5810
5811 def clearStatusMonitorCachedState(self, name):
5812 """
5813 Public method to clear the cached VCS state of a file/directory.
5814
5815 @param name name of the entry to be cleared (string)
5816 """
5817 if self.vcs is not None:
5818 self.vcs.clearStatusMonitorCachedState(name)
5819
5820 def startStatusMonitor(self):
5821 """
5822 Public method to start the VCS status monitor thread.
5823 """
5824 if self.vcs is not None:
5825 self.vcs.startStatusMonitor(self)
5826
5827 def stopStatusMonitor(self):
5828 """
5829 Public method to stop the VCS status monitor thread.
5830 """
5831 if self.vcs is not None:
5832 self.vcs.stopStatusMonitor()
5833
5834 #########################################################################
5835 ## Below are the plugin development related methods
5836 #########################################################################
5837
5838 def __pluginVersionToTuple(self, versionStr):
5839 """
5840 Private method to convert a plug-in version string into a version
5841 tuple.
5842
5843 @param versionStr version string to be converted
5844 @type str
5845 @return version info as a tuple
5846 @rtype tuple of int and str
5847 """
5848 vParts = []
5849 if "-" in versionStr:
5850 versionStr, additional = versionStr.split("-", 1)
5851 else:
5852 additional = ""
5853 for part in versionStr.split("."):
5854 try:
5855 vParts.append(int(part))
5856 except ValueError:
5857 vParts.append(part)
5858
5859 if additional:
5860 vParts.append(additional)
5861
5862 return tuple(vParts)
5863
5864 def __pluginCreatePkgList(self):
5865 """
5866 Private slot to create a PKGLIST file needed for archive file creation.
5867 """
5868 pkglist = os.path.join(self.ppath, "PKGLIST")
5869 if os.path.exists(pkglist):
5870 res = EricMessageBox.yesNo(
5871 self.ui,
5872 self.tr("Create Package List"),
5873 self.tr(
5874 "<p>The file <b>PKGLIST</b> already"
5875 " exists.</p><p>Overwrite it?</p>"
5876 ),
5877 icon=EricMessageBox.Warning,
5878 )
5879 if not res:
5880 return # don't overwrite
5881
5882 # build the list of entries
5883 lst_ = []
5884 for key in [
5885 "SOURCES",
5886 "FORMS",
5887 "RESOURCES",
5888 "TRANSLATIONS",
5889 "INTERFACES",
5890 "PROTOCOLS",
5891 "OTHERS",
5892 ]:
5893 lst_.extend(self.pdata[key])
5894 lst = []
5895 for entry in lst_:
5896 if os.path.isdir(self.getAbsolutePath(entry)):
5897 lst.extend(
5898 [
5899 self.getRelativePath(p)
5900 for p in Utilities.direntries(self.getAbsolutePath(entry), True)
5901 ]
5902 )
5903 continue
5904 else:
5905 lst.append(entry)
5906 lst.sort()
5907 if "PKGLIST" in lst:
5908 lst.remove("PKGLIST")
5909
5910 # build the header to indicate a freshly generated list
5911 header = [
5912 ";",
5913 "; initial_list (REMOVE THIS LINE WHEN DONE)",
5914 ";",
5915 " ",
5916 ]
5917
5918 # write the file
5919 try:
5920 newline = None if self.pdata["EOL"] == 0 else self.getEolString()
5921 with open(pkglist, "w", encoding="utf-8", newline=newline) as pkglistFile:
5922 pkglistFile.write("\n".join(header) + "\n")
5923 pkglistFile.write(
5924 "\n".join([Utilities.fromNativeSeparators(f) for f in lst])
5925 )
5926 pkglistFile.write("\n")
5927 # ensure the file ends with an empty line
5928 except OSError as why:
5929 EricMessageBox.critical(
5930 self.ui,
5931 self.tr("Create Package List"),
5932 self.tr(
5933 """<p>The file <b>PKGLIST</b> could not be created.</p>"""
5934 """<p>Reason: {0}</p>"""
5935 ).format(str(why)),
5936 )
5937 return
5938
5939 if "PKGLIST" not in self.pdata["OTHERS"]:
5940 self.appendFile("PKGLIST")
5941
5942 @pyqtSlot()
5943 def __pluginCreateArchives(self, snapshot=False):
5944 """
5945 Private slot to create eric plugin archives.
5946
5947 @param snapshot flag indicating snapshot archives (boolean)
5948 """
5949 if not self.pdata["MAINSCRIPT"]:
5950 EricMessageBox.critical(
5951 self.ui,
5952 self.tr("Create Plugin Archive"),
5953 self.tr(
5954 """The project does not have a main script defined. """
5955 """Aborting..."""
5956 ),
5957 )
5958 return
5959
5960 selectedLists = []
5961 pkglists = [
5962 os.path.basename(f) for f in glob.glob(os.path.join(self.ppath, "PKGLIST*"))
5963 ]
5964 if len(pkglists) == 1:
5965 selectedLists = [os.path.join(self.ppath, pkglists[0])]
5966 elif len(pkglists) > 1:
5967 dlg = EricListSelectionDialog(
5968 sorted(pkglists),
5969 title=self.tr("Create Plugin Archive"),
5970 message=self.tr("Select package lists:"),
5971 checkBoxSelection=True,
5972 )
5973 if dlg.exec() == QDialog.DialogCode.Accepted:
5974 selectedLists = [
5975 os.path.join(self.ppath, s) for s in dlg.getSelection()
5976 ]
5977 else:
5978 return
5979
5980 if not selectedLists:
5981 EricMessageBox.critical(
5982 self.ui,
5983 self.tr("Create Plugin Archive"),
5984 self.tr(
5985 """<p>No package list files (PKGLIST*) available or"""
5986 """ selected. Aborting...</p>"""
5987 ),
5988 )
5989 return
5990
5991 progress = EricProgressDialog(
5992 self.tr("Creating plugin archives..."),
5993 self.tr("Abort"),
5994 0,
5995 len(selectedLists),
5996 self.tr("%v/%m Archives"),
5997 self.ui,
5998 )
5999 progress.setMinimumDuration(0)
6000 progress.setWindowTitle(self.tr("Create Plugin Archives"))
6001 errors = 0
6002 for count, pkglist in enumerate(selectedLists):
6003 progress.setValue(count)
6004 if progress.wasCanceled():
6005 break
6006
6007 try:
6008 with open(pkglist, "r", encoding="utf-8") as pkglistFile:
6009 names = pkglistFile.read()
6010 except OSError as why:
6011 EricMessageBox.critical(
6012 self.ui,
6013 self.tr("Create Plugin Archive"),
6014 self.tr(
6015 """<p>The file <b>{0}</b> could not be read.</p>"""
6016 """<p>Reason: {1}</p>"""
6017 ).format(os.path.basename(pkglist), str(why)),
6018 )
6019 errors += 1
6020 continue
6021
6022 lines = names.splitlines()
6023 archiveName = ""
6024 archiveVersion = ""
6025 names = []
6026 listOK = True
6027 for line in lines:
6028 if line.startswith(";"):
6029 line = line[1:].strip()
6030 # it's a comment possibly containing a directive
6031 # supported directives are:
6032 # - archive_name= defines the name of the archive
6033 # - archive_version= defines the version of the archive
6034 if line.startswith("archive_name="):
6035 archiveName = line.split("=")[1]
6036 elif line.startswith("archive_version="):
6037 archiveVersion = line.split("=")[1]
6038 elif line.startswith("initial_list "):
6039 EricMessageBox.critical(
6040 self.ui,
6041 self.tr("Create Plugin Archive"),
6042 self.tr(
6043 """<p>The file <b>{0}</b> is not ready yet."""
6044 """</p><p>Please rework it and delete the"""
6045 """'; initial_list' line of the header."""
6046 """</p>"""
6047 ).format(os.path.basename(pkglist)),
6048 )
6049 errors += 1
6050 listOK = False
6051 break
6052 elif line.strip():
6053 names.append(line.strip())
6054
6055 if not listOK:
6056 continue
6057
6058 names = sorted(names)
6059 archive = (
6060 os.path.join(self.ppath, archiveName)
6061 if archiveName
6062 else os.path.join(
6063 self.ppath, self.pdata["MAINSCRIPT"].replace(".py", ".zip")
6064 )
6065 )
6066 try:
6067 archiveFile = zipfile.ZipFile(archive, "w")
6068 except OSError as why:
6069 EricMessageBox.critical(
6070 self.ui,
6071 self.tr("Create Plugin Archive"),
6072 self.tr(
6073 """<p>The eric plugin archive file <b>{0}</b>"""
6074 """ could not be created.</p>"""
6075 """<p>Reason: {1}</p>"""
6076 ).format(archive, str(why)),
6077 )
6078 errors += 1
6079 continue
6080
6081 for name in names:
6082 if name:
6083 try:
6084 self.__createZipDirEntries(os.path.split(name)[0], archiveFile)
6085 if snapshot and name == self.pdata["MAINSCRIPT"]:
6086 snapshotSource, version = self.__createSnapshotSource(
6087 os.path.join(self.ppath, self.pdata["MAINSCRIPT"])
6088 )
6089 archiveFile.writestr(name, snapshotSource)
6090 else:
6091 archiveFile.write(os.path.join(self.ppath, name), name)
6092 if name == self.pdata["MAINSCRIPT"]:
6093 version = self.__pluginExtractVersion(
6094 os.path.join(self.ppath, self.pdata["MAINSCRIPT"])
6095 )
6096 if archiveVersion and (
6097 self.__pluginVersionToTuple(version)
6098 < self.__pluginVersionToTuple(archiveVersion)
6099 ):
6100 version = archiveVersion
6101 except OSError as why:
6102 EricMessageBox.critical(
6103 self.ui,
6104 self.tr("Create Plugin Archive"),
6105 self.tr(
6106 """<p>The file <b>{0}</b> could not be"""
6107 """ stored in the archive. Ignoring it.</p>"""
6108 """<p>Reason: {1}</p>"""
6109 ).format(os.path.join(self.ppath, name), str(why)),
6110 )
6111 archiveFile.writestr("VERSION", version.encode("utf-8"))
6112 archiveFile.close()
6113
6114 if archive not in self.pdata["OTHERS"]:
6115 self.appendFile(archive)
6116
6117 progress.setValue(len(selectedLists))
6118
6119 if errors:
6120 self.ui.showNotification(
6121 UI.PixmapCache.getPixmap("pluginArchive48"),
6122 self.tr("Create Plugin Archive"),
6123 self.tr(
6124 "<p>The eric plugin archive files were "
6125 "created with some errors.</p>"
6126 ),
6127 kind=NotificationTypes.CRITICAL,
6128 timeout=0,
6129 )
6130 else:
6131 self.ui.showNotification(
6132 UI.PixmapCache.getPixmap("pluginArchive48"),
6133 self.tr("Create Plugin Archive"),
6134 self.tr(
6135 "<p>The eric plugin archive files were " "created successfully.</p>"
6136 ),
6137 )
6138
6139 def __pluginCreateSnapshotArchives(self):
6140 """
6141 Private slot to create eric plugin archive snapshot releases.
6142 """
6143 self.__pluginCreateArchives(True)
6144
6145 def __createZipDirEntries(self, path, zipFile):
6146 """
6147 Private method to create dir entries in the zip file.
6148
6149 @param path name of the directory entry to create (string)
6150 @param zipFile open ZipFile object (zipfile.ZipFile)
6151 """
6152 if path in ("", "/", "\\"):
6153 return
6154
6155 if not path.endswith("/") and not path.endswith("\\"):
6156 path = "{0}/".format(path)
6157
6158 if path not in zipFile.namelist():
6159 self.__createZipDirEntries(os.path.split(path[:-1])[0], zipFile)
6160 zipFile.writestr(path, b"")
6161
6162 def __createSnapshotSource(self, filename):
6163 """
6164 Private method to create a snapshot plugin version.
6165
6166 The version entry in the plugin module is modified to signify
6167 a snapshot version. This method appends the string "-snapshot-"
6168 and date indicator to the version string.
6169
6170 @param filename name of the plugin file to modify (string)
6171 @return modified source (bytes), snapshot version string (string)
6172 """
6173 try:
6174 sourcelines, encoding = Utilities.readEncodedFile(filename)
6175 sourcelines = sourcelines.splitlines(True)
6176 except (OSError, UnicodeError) as why:
6177 EricMessageBox.critical(
6178 self.ui,
6179 self.tr("Create Plugin Archive"),
6180 self.tr(
6181 """<p>The plugin file <b>{0}</b> could """
6182 """not be read.</p>"""
6183 """<p>Reason: {1}</p>"""
6184 ).format(filename, str(why)),
6185 )
6186 return b"", ""
6187
6188 lineno = 0
6189 while lineno < len(sourcelines):
6190 if sourcelines[lineno].startswith("version = "):
6191 # found the line to modify
6192 datestr = time.strftime("%Y%m%d")
6193 lineend = sourcelines[lineno].replace(sourcelines[lineno].rstrip(), "")
6194 sversion = "{0}-snapshot-{1}".format(
6195 sourcelines[lineno].replace("version = ", "").strip()[1:-1], datestr
6196 )
6197 sourcelines[lineno] = '{0} + "-snapshot-{1}"{2}'.format(
6198 sourcelines[lineno].rstrip(), datestr, lineend
6199 )
6200 break
6201
6202 lineno += 1
6203
6204 source = Utilities.encode("".join(sourcelines), encoding)[0]
6205 return source, sversion
6206
6207 def __pluginExtractVersion(self, filename):
6208 """
6209 Private method to extract the version number entry.
6210
6211 @param filename name of the plugin file (string)
6212 @return version string (string)
6213 """
6214 version = "0.0.0"
6215 try:
6216 sourcelines = Utilities.readEncodedFile(filename)[0]
6217 sourcelines = sourcelines.splitlines(True)
6218 except (OSError, UnicodeError) as why:
6219 EricMessageBox.critical(
6220 self.ui,
6221 self.tr("Create Plugin Archive"),
6222 self.tr(
6223 """<p>The plugin file <b>{0}</b> could """
6224 """not be read.</p> <p>Reason: {1}</p>"""
6225 ).format(filename, str(why)),
6226 )
6227 return ""
6228
6229 for sourceline in sourcelines:
6230 if sourceline.startswith("version = "):
6231 version = (
6232 sourceline.replace("version = ", "")
6233 .strip()
6234 .replace('"', "")
6235 .replace("'", "")
6236 )
6237 break
6238
6239 return version
6240
6241 #########################################################################
6242 ## Below are methods implementing the 'make' support
6243 #########################################################################
6244
6245 def __showContextMenuMake(self):
6246 """
6247 Private slot called before the make menu is shown.
6248 """
6249 self.showMenu.emit("Make", self.makeMenu)
6250
6251 def hasDefaultMakeParameters(self):
6252 """
6253 Public method to test, if the project contains the default make
6254 parameters.
6255
6256 @return flag indicating default parameter set
6257 @rtype bool
6258 """
6259 return self.pdata["MAKEPARAMS"] == {
6260 "MakeEnabled": False,
6261 "MakeExecutable": "",
6262 "MakeFile": "",
6263 "MakeTarget": "",
6264 "MakeParameters": "",
6265 "MakeTestOnly": True,
6266 }
6267
6268 def isMakeEnabled(self):
6269 """
6270 Public method to test, if make is enabled for the project.
6271
6272 @return flag indicating enabled make support
6273 @rtype bool
6274 """
6275 return self.pdata["MAKEPARAMS"]["MakeEnabled"]
6276
6277 @pyqtSlot()
6278 def executeMake(self):
6279 """
6280 Public slot to execute a project specific make run (auto-run)
6281 (execute or question).
6282 """
6283 self.__executeMake(
6284 questionOnly=self.pdata["MAKEPARAMS"]["MakeTestOnly"], interactive=False
6285 )
6286
6287 @pyqtSlot()
6288 def __executeMake(self, questionOnly=False, interactive=True):
6289 """
6290 Private method to execute a project specific make run.
6291
6292 @param questionOnly flag indicating to ask make for changes only
6293 @type bool
6294 @param interactive flag indicating an interactive invocation (i.e.
6295 through a menu action)
6296 @type bool
6297 """
6298 if (
6299 not self.pdata["MAKEPARAMS"]["MakeEnabled"]
6300 or self.__makeProcess is not None
6301 ):
6302 return
6303
6304 prog = (
6305 self.pdata["MAKEPARAMS"]["MakeExecutable"]
6306 if self.pdata["MAKEPARAMS"]["MakeExecutable"]
6307 else Project.DefaultMake
6308 )
6309
6310 args = []
6311 if self.pdata["MAKEPARAMS"]["MakeParameters"]:
6312 args.extend(
6313 Utilities.parseOptionString(self.pdata["MAKEPARAMS"]["MakeParameters"])
6314 )
6315
6316 if self.pdata["MAKEPARAMS"]["MakeFile"]:
6317 args.append("--makefile={0}".format(self.pdata["MAKEPARAMS"]["MakeFile"]))
6318
6319 if questionOnly:
6320 args.append("--question")
6321
6322 if self.pdata["MAKEPARAMS"]["MakeTarget"]:
6323 args.append(self.pdata["MAKEPARAMS"]["MakeTarget"])
6324
6325 self.__makeProcess = QProcess(self)
6326 self.__makeProcess.readyReadStandardOutput.connect(self.__makeReadStdOut)
6327 self.__makeProcess.readyReadStandardError.connect(self.__makeReadStdErr)
6328 self.__makeProcess.finished.connect(
6329 lambda exitCode, exitStatus: self.__makeFinished(
6330 exitCode, exitStatus, questionOnly, interactive
6331 )
6332 )
6333 self.__makeProcess.setWorkingDirectory(self.getProjectPath())
6334 self.__makeProcess.start(prog, args)
6335
6336 if not self.__makeProcess.waitForStarted():
6337 EricMessageBox.critical(
6338 self.ui,
6339 self.tr("Execute Make"),
6340 self.tr("""The make process did not start."""),
6341 )
6342
6343 self.__cleanupMake()
6344
6345 @pyqtSlot()
6346 def __makeReadStdOut(self):
6347 """
6348 Private slot to process process output received via stdout.
6349 """
6350 if self.__makeProcess is not None:
6351 output = str(
6352 self.__makeProcess.readAllStandardOutput(),
6353 Preferences.getSystem("IOEncoding"),
6354 "replace",
6355 )
6356 self.appendStdout.emit(output)
6357
6358 @pyqtSlot()
6359 def __makeReadStdErr(self):
6360 """
6361 Private slot to process process output received via stderr.
6362 """
6363 if self.__makeProcess is not None:
6364 error = str(
6365 self.__makeProcess.readAllStandardError(),
6366 Preferences.getSystem("IOEncoding"),
6367 "replace",
6368 )
6369 self.appendStderr.emit(error)
6370
6371 def __makeFinished(self, exitCode, exitStatus, questionOnly, interactive=True):
6372 """
6373 Private slot handling the make process finished signal.
6374
6375 @param exitCode exit code of the make process
6376 @type int
6377 @param exitStatus exit status of the make process
6378 @type QProcess.ExitStatus
6379 @param questionOnly flag indicating a test only run
6380 @type bool
6381 @param interactive flag indicating an interactive invocation (i.e.
6382 through a menu action)
6383 @type bool
6384 """
6385 if exitStatus == QProcess.ExitStatus.CrashExit:
6386 EricMessageBox.critical(
6387 self.ui,
6388 self.tr("Execute Make"),
6389 self.tr("""The make process crashed."""),
6390 )
6391 else:
6392 if questionOnly and exitCode == 1:
6393 # a rebuild is needed
6394 title = self.tr("Test for Changes")
6395
6396 if self.pdata["MAKEPARAMS"]["MakeTarget"]:
6397 message = self.tr(
6398 """<p>There are changes that require the configured"""
6399 """ make target <b>{0}</b> to be rebuilt.</p>"""
6400 ).format(self.pdata["MAKEPARAMS"]["MakeTarget"])
6401 else:
6402 message = self.tr(
6403 """<p>There are changes that require the default"""
6404 """ make target to be rebuilt.</p>"""
6405 )
6406
6407 self.ui.showNotification(
6408 UI.PixmapCache.getPixmap("makefile48"),
6409 title,
6410 message,
6411 kind=NotificationTypes.WARNING,
6412 timeout=0,
6413 )
6414 elif exitCode > 1:
6415 EricMessageBox.critical(
6416 self.ui,
6417 self.tr("Execute Make"),
6418 self.tr("""The makefile contains errors."""),
6419 )
6420
6421 self.__cleanupMake()
6422
6423 def __cleanupMake(self):
6424 """
6425 Private method to clean up make related stuff.
6426 """
6427 self.__makeProcess.readyReadStandardOutput.disconnect()
6428 self.__makeProcess.readyReadStandardError.disconnect()
6429 self.__makeProcess.finished.disconnect()
6430 self.__makeProcess.deleteLater()
6431 self.__makeProcess = None
6432
6433 #########################################################################
6434 ## Below are methods implementing some 'IDL' support functions
6435 #########################################################################
6436
6437 def hasDefaultIdlCompilerParameters(self):
6438 """
6439 Public method to test, if the project contains the default IDL compiler
6440 parameters.
6441
6442 @return flag indicating default parameter set
6443 @rtype bool
6444 """
6445 return self.pdata["IDLPARAMS"] == {
6446 "IncludeDirs": [],
6447 "DefinedNames": [],
6448 "UndefinedNames": [],
6449 }
6450
6451 #########################################################################
6452 ## Below are methods implementing some 'UIC' support functions
6453 #########################################################################
6454
6455 def hasDefaultUicCompilerParameters(self):
6456 """
6457 Public method to test, if the project contains the default uic compiler
6458 parameters.
6459
6460 @return flag indicating default parameter set
6461 @rtype bool
6462 """
6463 return self.pdata["UICPARAMS"] == {
6464 "Package": "",
6465 "RcSuffix": "",
6466 "PackagesRoot": "",
6467 }
6468
6469 def getUicParameter(self, name):
6470 """
6471 Public method to get a named uic related parameter.
6472
6473 @param name name of the parameter
6474 @type str
6475 @return value of the given parameter
6476 @rtype any, None in case on non-existence
6477 """
6478 if name in self.pdata["UICPARAMS"]:
6479 return self.pdata["UICPARAMS"][name]
6480 else:
6481 return None
6482
6483 #########################################################################
6484 ## Below are methods implementing some 'RCC' support functions
6485 #########################################################################
6486
6487 def hasDefaultRccCompilerParameters(self):
6488 """
6489 Public method to test, if the project contains the default rcc compiler
6490 parameters.
6491
6492 @return flag indicating default parameter set
6493 @rtype bool
6494 """
6495 return self.pdata["RCCPARAMS"] == self.getDefaultRccCompilerParameters()
6496
6497 def getDefaultRccCompilerParameters(self):
6498 """
6499 Public method to get the default rcc compiler parameters.
6500
6501 @return dictionary containing the default rcc compiler parameters
6502 @rtype dict
6503 """
6504 return {
6505 "CompressionThreshold": 70, # default value
6506 "CompressLevel": 0, # use zlib default
6507 "CompressionDisable": False,
6508 "PathPrefix": "",
6509 }
6510
6511 #########################################################################
6512 ## Below are methods implementing some 'docstring' support functions
6513 #########################################################################
6514
6515 def hasDefaultDocstringParameter(self):
6516 """
6517 Public method to test, if the project contains the default docstring
6518 parameter.
6519
6520 @return flag indicating default parameter
6521 @rtype bool
6522 """
6523 return self.pdata["DOCSTRING"] == ""
6524
6525 def getDocstringType(self):
6526 """
6527 Public method to get the configured docstring style.
6528
6529 @return configured docstring style
6530 @rtype str
6531 """
6532 return self.pdata["DOCSTRING"]
6533
6534 #########################################################################
6535 ## Below are methods implementing the 'SBOM' support
6536 #########################################################################
6537
6538 def __showContextMenuOthers(self):
6539 """
6540 Private slot called before the 'Other Tools' menu is shown.
6541 """
6542 self.showMenu.emit("OtherTools", self.othersMenu)
6543
6544 @pyqtSlot()
6545 def __createSBOMFile(self):
6546 """
6547 Private slot to create a SBOM file of the project dependencies.
6548 """
6549 import CycloneDXInterface
6550
6551 CycloneDXInterface.createCycloneDXFile("<project>")
6552
6553 #########################################################################
6554 ## Below are methods implementing the 'Code Formatting' support
6555 #########################################################################
6556
6557 def __showContextMenuFormat(self):
6558 """
6559 Private slot called before the 'Code Formatting' menu is shown.
6560 """
6561 self.showMenu.emit("Formatting", self.othersMenu)
6562
6563 @pyqtSlot()
6564 def __aboutBlack(self):
6565 """
6566 Private slot to show some information about the installed 'Black' tool.
6567 """
6568 import black
6569
6570 EricMessageBox.information(
6571 None,
6572 self.tr("About Black"),
6573 self.tr(
6574 """<p><b>Black Version {0}</b></p>"""
6575 """<p><i>Black</i> is the uncompromising Python code"""
6576 """ formatter.</p>"""
6577 ).format(black.__version__),
6578 )
6579
6580 def __performFormatWithBlack(self, action):
6581 """
6582 Private method to format the project sources using the 'Black' tool.
6583
6584 Following actions are supported.
6585 <ul>
6586 <li>BlackFormattingAction.Format - the code reformatting is performed</li>
6587 <li>BlackFormattingAction.Check - a check is performed, if code formatting
6588 is necessary</li>
6589 <li>BlackFormattingAction.Diff - a unified diff of potential code formatting
6590 changes is generated</li>
6591 </ul>
6592
6593 @param action formatting operation to be performed
6594 @type BlackFormattingAction
6595 """
6596 from CodeFormatting.BlackConfigurationDialog import BlackConfigurationDialog
6597 from CodeFormatting.BlackFormattingDialog import BlackFormattingDialog
6598
6599 if ericApp().getObject("ViewManager").checkAllDirty():
6600 dlg = BlackConfigurationDialog(withProject=True)
6601 if dlg.exec() == QDialog.DialogCode.Accepted:
6602 config = dlg.getConfiguration()
6603
6604 formattingDialog = BlackFormattingDialog(
6605 config,
6606 self.getProjectFiles("SOURCES", normalized=True),
6607 project=self,
6608 action=action,
6609 )
6610 formattingDialog.exec()
6611
6612
6613 #
6614 # eflag: noqa = M601

eric ide

mercurial