eric6/Project/Project.py

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

eric ide

mercurial