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