20 import fnmatch |
20 import fnmatch |
21 import copy |
21 import copy |
22 import zipfile |
22 import zipfile |
23 |
23 |
24 from PyQt5.QtCore import pyqtSlot, QFile, QFileInfo, pyqtSignal, \ |
24 from PyQt5.QtCore import pyqtSlot, QFile, QFileInfo, pyqtSignal, \ |
25 QCryptographicHash, QIODevice, QByteArray, QObject, Qt |
25 QCryptographicHash, QIODevice, QByteArray, QObject, Qt, QProcess |
26 from PyQt5.QtGui import QCursor, QKeySequence |
26 from PyQt5.QtGui import QCursor, QKeySequence |
27 from PyQt5.QtWidgets import QLineEdit, QToolBar, QDialog, QInputDialog, \ |
27 from PyQt5.QtWidgets import QLineEdit, QToolBar, QDialog, QInputDialog, \ |
28 QApplication, QMenu, QAction |
28 QApplication, QMenu, QAction |
29 from PyQt5.Qsci import QsciScintilla |
29 from PyQt5.Qsci import QsciScintilla |
30 |
30 |
1420 os.path.join(self.pdata["TRANSLATIONSBINPATH"], |
1442 os.path.join(self.pdata["TRANSLATIONSBINPATH"], |
1421 os.path.basename(qmFile))) |
1443 os.path.basename(qmFile))) |
1422 fn = os.path.join(self.ppath, qmFile) |
1444 fn = os.path.join(self.ppath, qmFile) |
1423 if os.path.exists(fn): |
1445 if os.path.exists(fn): |
1424 s2t(fn) |
1446 s2t(fn) |
1425 except EnvironmentError: |
1447 except EnvironmentError as err: |
1426 E5MessageBox.critical( |
1448 E5MessageBox.critical( |
1427 self.ui, |
1449 self.ui, |
1428 self.tr("Delete translation"), |
1450 self.tr("Delete translation"), |
1429 self.tr( |
1451 self.tr( |
1430 "<p>The selected translation file <b>{0}</b> could" |
1452 "<p>The selected translation file <b>{0}</b> could" |
1431 " not be deleted.</p>").format(qmFile)) |
1453 " not be deleted.</p><p>Reason: {1}</p>").format( |
|
1454 qmFile, str(err))) |
1432 return |
1455 return |
1433 |
1456 |
1434 def appendFile(self, fn, isSourceFile=False, updateModel=True): |
1457 def appendFile(self, fn, isSourceFile=False, updateModel=True): |
1435 """ |
1458 """ |
1436 Public method to append a file to the project. |
1459 Public method to append a file to the project. |
2239 "<p>The project directory <b>{0}</b> could not" |
2267 "<p>The project directory <b>{0}</b> could not" |
2240 " be created.</p>") |
2268 " be created.</p>") |
2241 .format(self.ppath)) |
2269 .format(self.ppath)) |
2242 self.vcs = self.initVCS() |
2270 self.vcs = self.initVCS() |
2243 return |
2271 return |
|
2272 |
2244 # create an empty __init__.py file to make it a Python package |
2273 # create an empty __init__.py file to make it a Python package |
2245 # (only for Python and Python3) |
2274 # (only for Python and Python3) |
2246 if self.pdata["PROGLANGUAGE"] in \ |
2275 if self.pdata["PROGLANGUAGE"] in \ |
2247 ["Python", "Python2", "Python3"]: |
2276 ["Python", "Python2", "Python3"]: |
2248 fn = os.path.join(self.ppath, "__init__.py") |
2277 fn = os.path.join(self.ppath, "__init__.py") |
2249 f = open(fn, "w", encoding="utf-8") |
2278 f = open(fn, "w", encoding="utf-8") |
2250 f.close() |
2279 f.close() |
2251 self.appendFile(fn, True) |
2280 self.appendFile(fn, True) |
|
2281 |
2252 # create an empty main script file, if a name was given |
2282 # create an empty main script file, if a name was given |
2253 if self.pdata["MAINSCRIPT"]: |
2283 if self.pdata["MAINSCRIPT"]: |
2254 if not os.path.isabs(self.pdata["MAINSCRIPT"]): |
2284 if not os.path.isabs(self.pdata["MAINSCRIPT"]): |
2255 ms = os.path.join( |
2285 ms = os.path.join( |
2256 self.ppath, self.pdata["MAINSCRIPT"]) |
2286 self.ppath, self.pdata["MAINSCRIPT"]) |
2257 else: |
2287 else: |
2258 ms = self.pdata["MAINSCRIPT"] |
2288 ms = self.pdata["MAINSCRIPT"] |
2259 f = open(ms, "w") |
2289 f = open(ms, "w") |
2260 f.close() |
2290 f.close() |
2261 self.appendFile(ms, True) |
2291 self.appendFile(ms, True) |
|
2292 |
|
2293 if self.pdata["MAKEPARAMS"]["MakeEnabled"]: |
|
2294 mf = self.pdata["MAKEPARAMS"]["MakeFile"] |
|
2295 if mf: |
|
2296 if not os.path.isabs(mf): |
|
2297 mf = os.path.join(self.ppath, mf) |
|
2298 else: |
|
2299 mf = os.path.join(self.ppath, Project.DefaultMakefile) |
|
2300 f = open(mf, "w") |
|
2301 f.close() |
|
2302 self.appendFile(mf) |
|
2303 |
2262 tpd = os.path.join(self.ppath, self.translationsRoot) |
2304 tpd = os.path.join(self.ppath, self.translationsRoot) |
2263 if not self.translationsRoot.endswith(os.sep): |
2305 if not self.translationsRoot.endswith(os.sep): |
2264 tpd = os.path.dirname(tpd) |
2306 tpd = os.path.dirname(tpd) |
2265 if not os.path.isdir(tpd): |
2307 if not os.path.isdir(tpd): |
2266 os.makedirs(tpd) |
2308 os.makedirs(tpd) |
2303 self.ui, |
2345 self.ui, |
2304 self.tr("Create main script"), |
2346 self.tr("Create main script"), |
2305 self.tr( |
2347 self.tr( |
2306 "<p>The mainscript <b>{0}</b> could not" |
2348 "<p>The mainscript <b>{0}</b> could not" |
2307 " be created.<br/>Reason: {1}</p>") |
2349 " be created.<br/>Reason: {1}</p>") |
2308 .format(self.ppath, str(err))) |
2350 .format(ms, str(err))) |
2309 self.appendFile(ms) |
2351 self.appendFile(ms, True) |
2310 else: |
2352 else: |
2311 ms = "" |
2353 ms = "" |
|
2354 |
|
2355 if self.pdata["MAKEPARAMS"]["MakeEnabled"]: |
|
2356 mf = self.pdata["MAKEPARAMS"]["MakeFile"] |
|
2357 if mf: |
|
2358 if not os.path.isabs(mf): |
|
2359 mf = os.path.join(self.ppath, mf) |
|
2360 else: |
|
2361 mf = os.path.join(self.ppath, Project.DefaultMakefile) |
|
2362 if not os.path.exists(mf): |
|
2363 try: |
|
2364 f = open(mf, "w") |
|
2365 f.close() |
|
2366 except EnvironmentError as err: |
|
2367 E5MessageBox.critical( |
|
2368 self.ui, |
|
2369 self.tr("Create Makefile"), |
|
2370 self.tr( |
|
2371 "<p>The makefile <b>{0}</b> could not" |
|
2372 " be created.<br/>Reason: {1}</p>") |
|
2373 .format(mf, str(err))) |
|
2374 self.appendFile(mf) |
2312 |
2375 |
2313 # add existing files to the project |
2376 # add existing files to the project |
2314 res = E5MessageBox.yesNo( |
2377 res = E5MessageBox.yesNo( |
2315 self.ui, |
2378 self.ui, |
2316 self.tr("New Project"), |
2379 self.tr("New Project"), |
3006 if Preferences.getProject("AutoSaveDbgProperties") and \ |
3094 if Preferences.getProject("AutoSaveDbgProperties") and \ |
3007 self.isDebugPropertiesLoaded() and \ |
3095 self.isDebugPropertiesLoaded() and \ |
3008 not noSave: |
3096 not noSave: |
3009 self.__writeDebugProperties(True) |
3097 self.__writeDebugProperties(True) |
3010 |
3098 |
3011 # now save all open modified files of the project |
|
3012 vm = e5App().getObject("ViewManager") |
3099 vm = e5App().getObject("ViewManager") |
|
3100 |
|
3101 # check dirty status of all project files first |
|
3102 for fn in vm.getOpenFilenames(): |
|
3103 if self.isProjectFile(fn): |
|
3104 reset = vm.checkFileDirty(fn) |
|
3105 if not reset: |
|
3106 # abort shutting down |
|
3107 return False |
|
3108 |
|
3109 # close all project related editors |
3013 success = True |
3110 success = True |
3014 for fn in vm.getOpenFilenames(): |
3111 for fn in vm.getOpenFilenames(): |
3015 if self.isProjectFile(fn): |
3112 if self.isProjectFile(fn): |
3016 success &= vm.closeWindow(fn) |
3113 success &= vm.closeWindow(fn, ignoreDirty=True) |
3017 |
|
3018 if not success: |
3114 if not success: |
3019 return False |
3115 return False |
3020 |
3116 |
3021 # stop the VCS monitor thread |
3117 # stop the VCS monitor thread |
3022 if self.vcs is not None: |
3118 if self.vcs is not None: |
4029 )) |
4127 )) |
4030 self.pluginSArchiveAct.triggered.connect( |
4128 self.pluginSArchiveAct.triggered.connect( |
4031 self.__pluginCreateSnapshotArchives) |
4129 self.__pluginCreateSnapshotArchives) |
4032 self.actions.append(self.pluginSArchiveAct) |
4130 self.actions.append(self.pluginSArchiveAct) |
4033 |
4131 |
|
4132 self.makeGrp = createActionGroup(self) |
|
4133 |
|
4134 self.makeExecuteAct = E5Action( |
|
4135 self.tr('Execute Make'), |
|
4136 self.tr('&Execute Make'), 0, 0, |
|
4137 self.makeGrp, 'project_make_execute') |
|
4138 self.makeExecuteAct.setStatusTip( |
|
4139 self.tr("Perform a 'make' run.")) |
|
4140 self.makeExecuteAct.setWhatsThis(self.tr( |
|
4141 """<b>Execute Make</b>""" |
|
4142 """<p>This performs a 'make' run to rebuild the configured""" |
|
4143 """ target.</p>""" |
|
4144 )) |
|
4145 self.makeExecuteAct.triggered.connect(self.__executeMake) |
|
4146 self.actions.append(self.makeExecuteAct) |
|
4147 |
|
4148 self.makeTestAct = E5Action( |
|
4149 self.tr('Test for Changes'), |
|
4150 self.tr('&Test for Changes'), 0, 0, |
|
4151 self.makeGrp, 'project_make_test') |
|
4152 self.makeTestAct.setStatusTip( |
|
4153 self.tr("Question 'make', if a rebuild is needed.")) |
|
4154 self.makeTestAct.setWhatsThis(self.tr( |
|
4155 """<b>Test for Changes</b>""" |
|
4156 """<p>This questions 'make', if a rebuild of the configured""" |
|
4157 """ target is necessary.</p>""" |
|
4158 )) |
|
4159 self.makeTestAct.triggered.connect( |
|
4160 lambda: self.__executeMake(questionOnly=True)) |
|
4161 self.actions.append(self.makeTestAct) |
|
4162 |
4034 self.closeAct.setEnabled(False) |
4163 self.closeAct.setEnabled(False) |
4035 self.saveAct.setEnabled(False) |
4164 self.saveAct.setEnabled(False) |
4036 self.saveasAct.setEnabled(False) |
4165 self.saveasAct.setEnabled(False) |
4037 self.actGrp2.setEnabled(False) |
4166 self.actGrp2.setEnabled(False) |
4038 self.propsAct.setEnabled(False) |
4167 self.propsAct.setEnabled(False) |
4111 # build the debugger menu |
4242 # build the debugger menu |
4112 self.debuggerMenu.setTearOffEnabled(True) |
4243 self.debuggerMenu.setTearOffEnabled(True) |
4113 self.debuggerMenu.addActions(self.dbgActGrp.actions()) |
4244 self.debuggerMenu.addActions(self.dbgActGrp.actions()) |
4114 |
4245 |
4115 # build the packagers menu |
4246 # build the packagers menu |
|
4247 self.packagersMenu.setTearOffEnabled(True) |
4116 self.packagersMenu.addActions(self.pluginGrp.actions()) |
4248 self.packagersMenu.addActions(self.pluginGrp.actions()) |
4117 self.packagersMenu.addSeparator() |
4249 self.packagersMenu.addSeparator() |
|
4250 |
|
4251 # build the make menu |
|
4252 self.makeMenu.setTearOffEnabled(True) |
|
4253 self.makeMenu.addActions(self.makeGrp.actions()) |
|
4254 self.makeMenu.addSeparator() |
4118 |
4255 |
4119 # build the main menu |
4256 # build the main menu |
4120 menu.setTearOffEnabled(True) |
4257 menu.setTearOffEnabled(True) |
4121 menu.addActions(self.actGrp1.actions()) |
4258 menu.addActions(self.actGrp1.actions()) |
4122 self.menuRecentAct = menu.addMenu(self.recentMenu) |
4259 self.menuRecentAct = menu.addMenu(self.recentMenu) |
5256 version = sourceline.replace("version = ", "").strip()\ |
5396 version = sourceline.replace("version = ", "").strip()\ |
5257 .replace('"', "").replace("'", "") |
5397 .replace('"', "").replace("'", "") |
5258 break |
5398 break |
5259 |
5399 |
5260 return version |
5400 return version |
|
5401 |
|
5402 ######################################################################### |
|
5403 ## Below are methods implementing the 'make' support |
|
5404 ######################################################################### |
|
5405 |
|
5406 def __showContextMenuMake(self): |
|
5407 """ |
|
5408 Private slot called before the make menu is shown. |
|
5409 """ |
|
5410 self.showMenu.emit("Make", self.makeMenu) |
|
5411 |
|
5412 def hasDefaultMakeParameters(self): |
|
5413 """ |
|
5414 Public method to test, if the project contains the default make |
|
5415 parameters. |
|
5416 |
|
5417 @return flag indicating default parameter set |
|
5418 @rtype bool |
|
5419 """ |
|
5420 return self.pdata["MAKEPARAMS"] == { |
|
5421 "MakeEnabled": False, |
|
5422 "MakeExecutable": "", |
|
5423 "MakeFile": "", |
|
5424 "MakeTarget": "", |
|
5425 "MakeParameters": "", |
|
5426 "MakeTestOnly": True, |
|
5427 } |
|
5428 |
|
5429 def isMakeEnabled(self): |
|
5430 """ |
|
5431 Public method to test, if make is enabled for the project. |
|
5432 |
|
5433 @return flag indicating enabled make support |
|
5434 @rtype bool |
|
5435 """ |
|
5436 return self.pdata["MAKEPARAMS"]["MakeEnabled"] |
|
5437 |
|
5438 @pyqtSlot() |
|
5439 def executeMake(self): |
|
5440 """ |
|
5441 Public slot to execute a project specific make run (auto-run) |
|
5442 (execute or question). |
|
5443 """ |
|
5444 self.__executeMake( |
|
5445 questionOnly=self.pdata["MAKEPARAMS"]["MakeTestOnly"], |
|
5446 interactive=False) |
|
5447 |
|
5448 @pyqtSlot() |
|
5449 def __executeMake(self, questionOnly=False, interactive=True): |
|
5450 """ |
|
5451 Private method to execute a project specific make run. |
|
5452 |
|
5453 @param questionOnly flag indicating to ask make for changes only |
|
5454 @type bool |
|
5455 @param interactive flag indicating an interactive invocation (i.e. |
|
5456 through a menu action) |
|
5457 @type bool |
|
5458 """ |
|
5459 if not self.pdata["MAKEPARAMS"]["MakeEnabled"] or \ |
|
5460 self.__makeProcess is not None: |
|
5461 return |
|
5462 |
|
5463 if self.pdata["MAKEPARAMS"]["MakeExecutable"]: |
|
5464 prog = self.pdata["MAKEPARAMS"]["MakeExecutable"] |
|
5465 else: |
|
5466 prog = Project.DefaultMake |
|
5467 |
|
5468 args = [] |
|
5469 if self.pdata["MAKEPARAMS"]["MakeParameters"]: |
|
5470 args.extend(Utilities.parseOptionString( |
|
5471 self.pdata["MAKEPARAMS"]["MakeParameters"])) |
|
5472 |
|
5473 if self.pdata["MAKEPARAMS"]["MakeFile"]: |
|
5474 args.append("--makefile={0}".format( |
|
5475 self.pdata["MAKEPARAMS"]["MakeFile"])) |
|
5476 |
|
5477 if questionOnly: |
|
5478 args.append("--question") |
|
5479 |
|
5480 if self.pdata["MAKEPARAMS"]["MakeTarget"]: |
|
5481 args.append(self.pdata["MAKEPARAMS"]["MakeTarget"]) |
|
5482 |
|
5483 self.__makeProcess = QProcess(self) |
|
5484 self.__makeProcess.readyReadStandardOutput.connect( |
|
5485 self.__makeReadStdOut) |
|
5486 self.__makeProcess.readyReadStandardError.connect( |
|
5487 self.__makeReadStdErr) |
|
5488 self.__makeProcess.finished.connect( |
|
5489 lambda exitCode, exitStatus: self.__makeFinished( |
|
5490 exitCode, exitStatus, questionOnly, interactive)) |
|
5491 self.__makeProcess.setWorkingDirectory(self.getProjectPath()) |
|
5492 self.__makeProcess.start(prog, args) |
|
5493 |
|
5494 if not self.__makeProcess.waitForStarted(): |
|
5495 E5MessageBox.critical( |
|
5496 self.ui, |
|
5497 self.tr("Execute Make"), |
|
5498 self.tr("""The make process did not start.""")) |
|
5499 |
|
5500 self.__cleanupMake() |
|
5501 |
|
5502 @pyqtSlot() |
|
5503 def __makeReadStdOut(self): |
|
5504 """ |
|
5505 Private slot to process process output received via stdout. |
|
5506 """ |
|
5507 if self.__makeProcess is not None: |
|
5508 output = str(self.__makeProcess.readAllStandardOutput(), |
|
5509 Preferences.getSystem("IOEncoding"), |
|
5510 'replace') |
|
5511 self.appendStdout.emit(output) |
|
5512 |
|
5513 @pyqtSlot() |
|
5514 def __makeReadStdErr(self): |
|
5515 """ |
|
5516 Private slot to process process output received via stderr. |
|
5517 """ |
|
5518 if self.__makeProcess is not None: |
|
5519 error = str(self.__makeProcess.readAllStandardError(), |
|
5520 Preferences.getSystem("IOEncoding"), |
|
5521 'replace') |
|
5522 self.appendStderr.emit(error) |
|
5523 |
|
5524 def __makeFinished(self, exitCode, exitStatus, questionOnly, |
|
5525 interactive=True): |
|
5526 """ |
|
5527 Private slot handling the make process finished signal. |
|
5528 |
|
5529 @param exitCode exit code of the make process |
|
5530 @type int |
|
5531 @param exitStatus exit status of the make process |
|
5532 @type QProcess.ExitStatus |
|
5533 @param questionOnly flag indicating a test only run |
|
5534 @type bool |
|
5535 @param interactive flag indicating an interactive invocation (i.e. |
|
5536 through a menu action) |
|
5537 @type bool |
|
5538 """ |
|
5539 if exitStatus == QProcess.CrashExit: |
|
5540 E5MessageBox.critical( |
|
5541 self.ui, |
|
5542 self.tr("Execute Make"), |
|
5543 self.tr("""The make process crashed.""")) |
|
5544 else: |
|
5545 if questionOnly and exitCode == 1: |
|
5546 # a rebuild is needed |
|
5547 title = self.tr("Test for Changes") |
|
5548 |
|
5549 if self.pdata["MAKEPARAMS"]["MakeTarget"]: |
|
5550 message = self.tr( |
|
5551 """<p>There are changes that require the configured""" |
|
5552 """ make target <b>{0}</b> to be rebuilt.</p>""")\ |
|
5553 .format(self.pdata["MAKEPARAMS"]["MakeTarget"]) |
|
5554 else: |
|
5555 message = self.tr( |
|
5556 """<p>There are changes that require the default""" |
|
5557 """ make target to be rebuilt.</p>""") |
|
5558 |
|
5559 if self.ui.notificationsEnabled() and not interactive: |
|
5560 self.ui.showNotification( |
|
5561 UI.PixmapCache.getPixmap("makefile48.png"), |
|
5562 title, |
|
5563 message) |
|
5564 else: |
|
5565 E5MessageBox.information(self.ui, title, message) |
|
5566 elif exitCode > 1: |
|
5567 E5MessageBox.critical( |
|
5568 self.ui, |
|
5569 self.tr("Execute Make"), |
|
5570 self.tr("""The makefile contains errors.""")) |
|
5571 |
|
5572 self.__cleanupMake() |
|
5573 |
|
5574 def __cleanupMake(self): |
|
5575 """ |
|
5576 Private method to clean up make related stuff. |
|
5577 """ |
|
5578 self.__makeProcess.readyReadStandardOutput.disconnect() |
|
5579 self.__makeProcess.readyReadStandardError.disconnect() |
|
5580 self.__makeProcess.finished.disconnect() |
|
5581 self.__makeProcess.deleteLater() |
|
5582 self.__makeProcess = None |