|
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 |