src/eric7/Project/Project.py

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

eric ide

mercurial