eric7/Project/ProjectResourcesBrowser.py

branch
eric7
changeset 8312
800c432b34c8
parent 8265
0090cfa83159
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a class used to display the resources part of the project.
8 """
9
10 import os
11 import contextlib
12
13 from PyQt5.QtCore import QThread, QFileInfo, pyqtSignal, QProcess
14 from PyQt5.QtWidgets import QDialog, QApplication, QMenu
15
16 from E5Gui.E5Application import e5App
17 from E5Gui import E5MessageBox, E5FileDialog
18 from E5Gui.E5ProgressDialog import E5ProgressDialog
19
20 from .ProjectBrowserModel import (
21 ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem,
22 ProjectBrowserDirectoryItem, ProjectBrowserResourceType
23 )
24 from .ProjectBaseBrowser import ProjectBaseBrowser
25
26 import UI.PixmapCache
27 from UI.NotificationWidget import NotificationTypes
28
29 import Preferences
30 import Utilities
31
32
33 class ProjectResourcesBrowser(ProjectBaseBrowser):
34 """
35 A class used to display the resources part of the project.
36
37 @signal appendStderr(str) emitted after something was received from
38 a QProcess on stderr
39 @signal showMenu(str, QMenu) emitted when a menu is about to be shown.
40 The name of the menu and a reference to the menu are given.
41 """
42 appendStderr = pyqtSignal(str)
43 showMenu = pyqtSignal(str, QMenu)
44
45 RCFilenameFormatPython = "{0}_rc.py"
46 RCFilenameFormatRuby = "{0}_rc.rb"
47
48 def __init__(self, project, parent=None):
49 """
50 Constructor
51
52 @param project reference to the project object
53 @param parent parent widget of this browser (QWidget)
54 """
55 ProjectBaseBrowser.__init__(self, project, ProjectBrowserResourceType,
56 parent)
57
58 self.selectedItemsFilter = [ProjectBrowserFileItem,
59 ProjectBrowserSimpleDirectoryItem]
60
61 self.setWindowTitle(self.tr('Resources'))
62
63 self.setWhatsThis(self.tr(
64 """<b>Project Resources Browser</b>"""
65 """<p>This allows to easily see all resources contained in the"""
66 """ current project. Several actions can be executed via the"""
67 """ context menu.</p>"""
68 ))
69
70 self.compileProc = None
71
72 def _createPopupMenus(self):
73 """
74 Protected overloaded method to generate the popup menu.
75 """
76 self.menuActions = []
77 self.multiMenuActions = []
78 self.dirMenuActions = []
79 self.dirMultiMenuActions = []
80
81 self.menu = QMenu(self)
82 if self.project.getProjectType() in [
83 "PyQt5", "PyQt5C", "E6Plugin",
84 "PySide2", "PySide2C", "PySide6", "PySide6C"
85 ]:
86 self.menu.addAction(
87 self.tr('Compile resource'),
88 self.__compileResource)
89 self.menu.addAction(
90 self.tr('Compile all resources'),
91 self.__compileAllResources)
92 self.menu.addSeparator()
93 self.menu.addAction(
94 self.tr('Configure rcc Compiler'),
95 self.__configureRccCompiler)
96 self.menu.addSeparator()
97 else:
98 if self.hooks["compileResource"] is not None:
99 self.menu.addAction(
100 self.hooksMenuEntries.get(
101 "compileResource",
102 self.tr('Compile resource')),
103 self.__compileResource)
104 if self.hooks["compileAllResources"] is not None:
105 self.menu.addAction(
106 self.hooksMenuEntries.get(
107 "compileAllResources",
108 self.tr('Compile all resources')),
109 self.__compileAllResources)
110 if (
111 self.hooks["compileResource"] is not None or
112 self.hooks["compileAllResources"] is not None
113 ):
114 self.menu.addSeparator()
115 self.menu.addAction(self.tr('Open'), self.__openFile)
116 self.menu.addSeparator()
117 act = self.menu.addAction(self.tr('Rename file'), self._renameFile)
118 self.menuActions.append(act)
119 act = self.menu.addAction(
120 self.tr('Remove from project'), self._removeFile)
121 self.menuActions.append(act)
122 act = self.menu.addAction(self.tr('Delete'), self.__deleteFile)
123 self.menuActions.append(act)
124 self.menu.addSeparator()
125 if self.project.getProjectType() in [
126 "PyQt5", "PyQt5C", "E6Plugin",
127 "PySide2", "PySide2C", "PySide6", "PySide6C"
128 ]:
129 self.menu.addAction(
130 self.tr('New resource...'), self.__newResource)
131 else:
132 if self.hooks["newResource"] is not None:
133 self.menu.addAction(
134 self.hooksMenuEntries.get(
135 "newResource",
136 self.tr('New resource...')), self.__newResource)
137 self.menu.addAction(
138 self.tr('Add resources...'), self.__addResourceFiles)
139 self.menu.addAction(
140 self.tr('Add resources directory...'),
141 self.__addResourcesDirectory)
142 self.menu.addSeparator()
143 self.menu.addAction(
144 self.tr('Copy Path to Clipboard'), self._copyToClipboard)
145 self.menu.addSeparator()
146 self.menu.addAction(
147 self.tr('Expand all directories'), self._expandAllDirs)
148 self.menu.addAction(
149 self.tr('Collapse all directories'), self._collapseAllDirs)
150 self.menu.addSeparator()
151 self.menu.addAction(self.tr('Configure...'), self._configure)
152
153 self.backMenu = QMenu(self)
154 if self.project.getProjectType() in [
155 "PyQt5", "PyQt5C", "E6Plugin",
156 "PySide2", "PySide2C", "PySide6", "PySide6C"
157 ]:
158 self.backMenu.addAction(
159 self.tr('Compile all resources'),
160 self.__compileAllResources)
161 self.backMenu.addSeparator()
162 self.backMenu.addAction(
163 self.tr('Configure rcc Compiler'),
164 self.__configureRccCompiler)
165 self.backMenu.addSeparator()
166 self.backMenu.addAction(
167 self.tr('New resource...'), self.__newResource)
168 else:
169 if self.hooks["compileAllResources"] is not None:
170 self.backMenu.addAction(
171 self.hooksMenuEntries.get(
172 "compileAllResources",
173 self.tr('Compile all resources')),
174 self.__compileAllResources)
175 self.backMenu.addSeparator()
176 if self.hooks["newResource"] is not None:
177 self.backMenu.addAction(
178 self.hooksMenuEntries.get(
179 "newResource",
180 self.tr('New resource...')), self.__newResource)
181 self.backMenu.addAction(
182 self.tr('Add resources...'), self.project.addResourceFiles)
183 self.backMenu.addAction(
184 self.tr('Add resources directory...'),
185 self.project.addResourceDir)
186 self.backMenu.addSeparator()
187 self.backMenu.addAction(
188 self.tr('Expand all directories'), self._expandAllDirs)
189 self.backMenu.addAction(
190 self.tr('Collapse all directories'), self._collapseAllDirs)
191 self.backMenu.addSeparator()
192 self.backMenu.addAction(self.tr('Configure...'), self._configure)
193 self.backMenu.setEnabled(False)
194
195 # create the menu for multiple selected files
196 self.multiMenu = QMenu(self)
197 if self.project.getProjectType() in [
198 "PyQt5", "PyQt5C", "E6Plugin",
199 "PySide2", "PySide2C", "PySide6", "PySide6C"
200 ]:
201 act = self.multiMenu.addAction(
202 self.tr('Compile resources'),
203 self.__compileSelectedResources)
204 self.multiMenu.addSeparator()
205 self.multiMenu.addAction(
206 self.tr('Configure rcc Compiler'),
207 self.__configureRccCompiler)
208 self.multiMenu.addSeparator()
209 else:
210 if self.hooks["compileSelectedResources"] is not None:
211 act = self.multiMenu.addAction(
212 self.hooksMenuEntries.get(
213 "compileSelectedResources",
214 self.tr('Compile resources')),
215 self.__compileSelectedResources)
216 self.multiMenu.addSeparator()
217 self.multiMenu.addAction(self.tr('Open'), self.__openFile)
218 self.multiMenu.addSeparator()
219 act = self.multiMenu.addAction(
220 self.tr('Remove from project'), self._removeFile)
221 self.multiMenuActions.append(act)
222 act = self.multiMenu.addAction(
223 self.tr('Delete'), self.__deleteFile)
224 self.multiMenuActions.append(act)
225 self.multiMenu.addSeparator()
226 self.multiMenu.addAction(
227 self.tr('Expand all directories'), self._expandAllDirs)
228 self.multiMenu.addAction(
229 self.tr('Collapse all directories'), self._collapseAllDirs)
230 self.multiMenu.addSeparator()
231 self.multiMenu.addAction(self.tr('Configure...'), self._configure)
232
233 self.dirMenu = QMenu(self)
234 if self.project.getProjectType() in [
235 "PyQt5", "PyQt5C", "E6Plugin",
236 "PySide2", "PySide2C", "PySide6", "PySide6C"
237 ]:
238 self.dirMenu.addAction(
239 self.tr('Compile all resources'),
240 self.__compileAllResources)
241 self.dirMenu.addSeparator()
242 self.dirMenu.addAction(
243 self.tr('Configure rcc Compiler'),
244 self.__configureRccCompiler)
245 self.dirMenu.addSeparator()
246 else:
247 if self.hooks["compileAllResources"] is not None:
248 self.dirMenu.addAction(
249 self.hooksMenuEntries.get(
250 "compileAllResources",
251 self.tr('Compile all resources')),
252 self.__compileAllResources)
253 self.dirMenu.addSeparator()
254 act = self.dirMenu.addAction(
255 self.tr('Remove from project'), self._removeDir)
256 self.dirMenuActions.append(act)
257 act = self.dirMenu.addAction(
258 self.tr('Delete'), self._deleteDirectory)
259 self.dirMenuActions.append(act)
260 self.dirMenu.addSeparator()
261 self.dirMenu.addAction(
262 self.tr('New resource...'), self.__newResource)
263 self.dirMenu.addAction(
264 self.tr('Add resources...'), self.__addResourceFiles)
265 self.dirMenu.addAction(
266 self.tr('Add resources directory...'),
267 self.__addResourcesDirectory)
268 self.dirMenu.addSeparator()
269 self.dirMenu.addAction(
270 self.tr('Copy Path to Clipboard'), self._copyToClipboard)
271 self.dirMenu.addSeparator()
272 self.dirMenu.addAction(
273 self.tr('Expand all directories'), self._expandAllDirs)
274 self.dirMenu.addAction(
275 self.tr('Collapse all directories'), self._collapseAllDirs)
276 self.dirMenu.addSeparator()
277 self.dirMenu.addAction(self.tr('Configure...'), self._configure)
278
279 self.dirMultiMenu = QMenu(self)
280 if self.project.getProjectType() in [
281 "PyQt5", "PyQt5C", "E6Plugin",
282 "PySide2", "PySide2C", "PySide6", "PySide6C"
283 ]:
284 self.dirMultiMenu.addAction(
285 self.tr('Compile all resources'),
286 self.__compileAllResources)
287 self.dirMultiMenu.addSeparator()
288 self.dirMultiMenu.addAction(
289 self.tr('Configure rcc Compiler'),
290 self.__configureRccCompiler)
291 self.dirMultiMenu.addSeparator()
292 else:
293 if self.hooks["compileAllResources"] is not None:
294 self.dirMultiMenu.addAction(
295 self.hooksMenuEntries.get(
296 "compileAllResources",
297 self.tr('Compile all resources')),
298 self.__compileAllResources)
299 self.dirMultiMenu.addSeparator()
300 self.dirMultiMenu.addAction(
301 self.tr('Add resources...'),
302 self.project.addResourceFiles)
303 self.dirMultiMenu.addAction(
304 self.tr('Add resources directory...'),
305 self.project.addResourceDir)
306 self.dirMultiMenu.addSeparator()
307 self.dirMultiMenu.addAction(
308 self.tr('Expand all directories'), self._expandAllDirs)
309 self.dirMultiMenu.addAction(
310 self.tr('Collapse all directories'), self._collapseAllDirs)
311 self.dirMultiMenu.addSeparator()
312 self.dirMultiMenu.addAction(
313 self.tr('Configure...'), self._configure)
314
315 self.menu.aboutToShow.connect(self.__showContextMenu)
316 self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
317 self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
318 self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti)
319 self.backMenu.aboutToShow.connect(self.__showContextMenuBack)
320 self.mainMenu = self.menu
321
322 def _contextMenuRequested(self, coord):
323 """
324 Protected slot to show the context menu.
325
326 @param coord the position of the mouse pointer (QPoint)
327 """
328 if not self.project.isOpen():
329 return
330
331 with contextlib.suppress(Exception):
332 categories = self.getSelectedItemsCountCategorized(
333 [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem])
334 cnt = categories["sum"]
335 if cnt <= 1:
336 index = self.indexAt(coord)
337 if index.isValid():
338 self._selectSingleItem(index)
339 categories = self.getSelectedItemsCountCategorized(
340 [ProjectBrowserFileItem,
341 ProjectBrowserSimpleDirectoryItem])
342 cnt = categories["sum"]
343
344 bfcnt = categories[str(ProjectBrowserFileItem)]
345 sdcnt = categories[str(ProjectBrowserSimpleDirectoryItem)]
346 if cnt > 1 and cnt == bfcnt:
347 self.multiMenu.popup(self.mapToGlobal(coord))
348 elif cnt > 1 and cnt == sdcnt:
349 self.dirMultiMenu.popup(self.mapToGlobal(coord))
350 else:
351 index = self.indexAt(coord)
352 if cnt == 1 and index.isValid():
353 if bfcnt == 1:
354 self.menu.popup(self.mapToGlobal(coord))
355 elif sdcnt == 1:
356 self.dirMenu.popup(self.mapToGlobal(coord))
357 else:
358 self.backMenu.popup(self.mapToGlobal(coord))
359 else:
360 self.backMenu.popup(self.mapToGlobal(coord))
361
362 def __showContextMenu(self):
363 """
364 Private slot called by the menu aboutToShow signal.
365 """
366 ProjectBaseBrowser._showContextMenu(self, self.menu)
367
368 self.showMenu.emit("Main", self.menu)
369
370 def __showContextMenuMulti(self):
371 """
372 Private slot called by the multiMenu aboutToShow signal.
373 """
374 ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu)
375
376 self.showMenu.emit("MainMulti", self.multiMenu)
377
378 def __showContextMenuDir(self):
379 """
380 Private slot called by the dirMenu aboutToShow signal.
381 """
382 ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu)
383
384 self.showMenu.emit("MainDir", self.dirMenu)
385
386 def __showContextMenuDirMulti(self):
387 """
388 Private slot called by the dirMultiMenu aboutToShow signal.
389 """
390 ProjectBaseBrowser._showContextMenuDirMulti(self, self.dirMultiMenu)
391
392 self.showMenu.emit("MainDirMulti", self.dirMultiMenu)
393
394 def __showContextMenuBack(self):
395 """
396 Private slot called by the backMenu aboutToShow signal.
397 """
398 ProjectBaseBrowser._showContextMenuBack(self, self.backMenu)
399
400 self.showMenu.emit("MainBack", self.backMenu)
401
402 def __addResourceFiles(self):
403 """
404 Private method to add resource files to the project.
405 """
406 itm = self.model().item(self.currentIndex())
407 if isinstance(itm, ProjectBrowserFileItem):
408 dn = os.path.dirname(itm.fileName())
409 elif isinstance(
410 itm,
411 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem)
412 ):
413 dn = itm.dirName()
414 else:
415 dn = None
416 self.project.addFiles('resource', dn)
417
418 def __addResourcesDirectory(self):
419 """
420 Private method to add resource files of a directory to the project.
421 """
422 itm = self.model().item(self.currentIndex())
423 if isinstance(itm, ProjectBrowserFileItem):
424 dn = os.path.dirname(itm.fileName())
425 elif isinstance(
426 itm,
427 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem)
428 ):
429 dn = itm.dirName()
430 else:
431 dn = None
432 self.project.addDirectory('resource', dn)
433
434 def _openItem(self):
435 """
436 Protected slot to handle the open popup menu entry.
437 """
438 self.__openFile()
439
440 def __openFile(self):
441 """
442 Private slot to handle the Open menu action.
443 """
444 itmList = self.getSelectedItems()
445 for itm in itmList[:]:
446 if isinstance(itm, ProjectBrowserFileItem):
447 self.sourceFile.emit(itm.fileName())
448
449 def __newResource(self):
450 """
451 Private slot to handle the New Resource menu action.
452 """
453 itm = self.model().item(self.currentIndex())
454 if itm is None:
455 path = self.project.ppath
456 else:
457 try:
458 path = os.path.dirname(itm.fileName())
459 except AttributeError:
460 try:
461 path = itm.dirName()
462 except AttributeError:
463 path = os.path.join(self.project.ppath, itm.data(0))
464
465 if self.hooks["newResource"] is not None:
466 self.hooks["newResource"](path)
467 else:
468 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
469 self,
470 self.tr("New Resource"),
471 path,
472 self.tr("Qt Resource Files (*.qrc)"),
473 "",
474 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
475
476 if not fname:
477 # user aborted or didn't enter a filename
478 return
479
480 ext = QFileInfo(fname).suffix()
481 if not ext:
482 ex = selectedFilter.split("(*")[1].split(")")[0]
483 if ex:
484 fname += ex
485
486 if os.path.exists(fname):
487 res = E5MessageBox.yesNo(
488 self,
489 self.tr("New Resource"),
490 self.tr("The file already exists! Overwrite it?"),
491 icon=E5MessageBox.Warning)
492 if not res:
493 # user selected to not overwrite
494 return
495
496 try:
497 newline = (None if self.project.useSystemEol()
498 else self.project.getEolString())
499 with open(fname, 'w', encoding="utf-8",
500 newline=newline) as rcfile:
501 rcfile.write('<!DOCTYPE RCC>\n')
502 rcfile.write('<RCC version="1.0">\n')
503 rcfile.write('<qresource>\n')
504 rcfile.write('</qresource>\n')
505 rcfile.write('</RCC>\n')
506 except OSError as e:
507 E5MessageBox.critical(
508 self,
509 self.tr("New Resource"),
510 self.tr(
511 "<p>The new resource file <b>{0}</b> could not"
512 " be created.<br>Problem: {1}</p>")
513 .format(fname, str(e)))
514 return
515
516 self.project.appendFile(fname)
517 self.sourceFile.emit(fname)
518
519 def __deleteFile(self):
520 """
521 Private method to delete a resource file from the project.
522 """
523 itmList = self.getSelectedItems()
524
525 files = []
526 fullNames = []
527 for itm in itmList:
528 fn2 = itm.fileName()
529 fullNames.append(fn2)
530 fn = self.project.getRelativePath(fn2)
531 files.append(fn)
532
533 from UI.DeleteFilesConfirmationDialog import (
534 DeleteFilesConfirmationDialog
535 )
536 dlg = DeleteFilesConfirmationDialog(
537 self.parent(),
538 self.tr("Delete resources"),
539 self.tr(
540 "Do you really want to delete these resources from the"
541 " project?"),
542 files)
543
544 if dlg.exec() == QDialog.DialogCode.Accepted:
545 for fn2, fn in zip(fullNames, files):
546 self.closeSourceWindow.emit(fn2)
547 self.project.deleteFile(fn)
548
549 ###########################################################################
550 ## Methods to handle the various compile commands
551 ###########################################################################
552
553 def __readStdout(self):
554 """
555 Private slot to handle the readyReadStandardOutput signal of the
556 pyrcc5/pyside2-rcc/pyside6-rcc process.
557 """
558 if self.compileProc is None:
559 return
560 self.compileProc.setReadChannel(QProcess.ProcessChannel.StandardOutput)
561
562 while self.compileProc and self.compileProc.canReadLine():
563 self.buf += str(self.compileProc.readLine(),
564 Preferences.getSystem("IOEncoding"),
565 'replace')
566
567 def __readStderr(self):
568 """
569 Private slot to handle the readyReadStandardError signal of the
570 pyrcc5/pyside2-rcc/pyside6-rcc process.
571 """
572 if self.compileProc is None:
573 return
574
575 ioEncoding = Preferences.getSystem("IOEncoding")
576
577 self.compileProc.setReadChannel(QProcess.ProcessChannel.StandardError)
578 while self.compileProc and self.compileProc.canReadLine():
579 s = self.rccCompiler + ': '
580 error = str(self.compileProc.readLine(),
581 ioEncoding, 'replace')
582 s += error
583 self.appendStderr.emit(s)
584
585 def __compileQRCDone(self, exitCode, exitStatus):
586 """
587 Private slot to handle the finished signal of the compile process.
588
589 @param exitCode exit code of the process (integer)
590 @param exitStatus exit status of the process (QProcess.ExitStatus)
591 """
592 self.compileRunning = False
593 e5App().getObject("ViewManager").enableEditorsCheckFocusIn(True)
594 ui = e5App().getObject("UserInterface")
595 if (
596 exitStatus == QProcess.ExitStatus.NormalExit and
597 exitCode == 0 and
598 self.buf
599 ):
600 ofn = os.path.join(self.project.ppath, self.compiledFile)
601 try:
602 newline = (None if self.project.useSystemEol()
603 else self.project.getEolString())
604 with open(ofn, "w", encoding="utf-8", newline=newline) as f:
605 for line in self.buf.splitlines():
606 f.write(line + "\n")
607 if self.compiledFile not in self.project.pdata["SOURCES"]:
608 self.project.appendFile(ofn)
609 ui.showNotification(
610 UI.PixmapCache.getPixmap("resourcesCompiler48"),
611 self.tr("Resource Compilation"),
612 self.tr("The compilation of the resource file"
613 " was successful."))
614 except OSError as msg:
615 if not self.noDialog:
616 E5MessageBox.information(
617 self,
618 self.tr("Resource Compilation"),
619 self.tr(
620 "<p>The compilation of the resource file"
621 " failed.</p><p>Reason: {0}</p>").format(str(msg)))
622 else:
623 ui.showNotification(
624 UI.PixmapCache.getPixmap("resourcesCompiler48"),
625 self.tr("Resource Compilation"),
626 self.tr(
627 "The compilation of the resource file failed."),
628 kind=NotificationTypes.CRITICAL,
629 timeout=0)
630 self.compileProc = None
631
632 def __compileQRC(self, fn, noDialog=False, progress=None):
633 """
634 Private method to compile a .qrc file to a .py file.
635
636 @param fn filename of the .ui file to be compiled
637 @param noDialog flag indicating silent operations
638 @param progress reference to the progress dialog
639 @return reference to the compile process (QProcess)
640 """
641 self.compileProc = QProcess()
642 args = []
643 self.buf = ""
644
645 if self.project.getProjectLanguage() == "Python3":
646 if self.project.getProjectType() in ["PyQt5", "PyQt5C",
647 "E6Plugin"]:
648 self.rccCompiler = Utilities.generatePyQtToolPath('pyrcc5')
649 elif self.project.getProjectType() in ["PySide2", "PySide2C"]:
650 self.rccCompiler = Utilities.generatePySideToolPath(
651 'pyside2-rcc', variant=2)
652 elif self.project.getProjectType() in ["PySide6", "PySide6C"]:
653 self.rccCompiler = Utilities.generatePySideToolPath(
654 'pyside6-rcc', variant=6)
655 else:
656 return None
657 defaultParameters = self.project.getDefaultRccCompilerParameters()
658 rccParameters = self.project.pdata["RCCPARAMS"]
659 if (
660 rccParameters["CompressionThreshold"] !=
661 defaultParameters["CompressionThreshold"]
662 ):
663 args.append("-threshold")
664 args.append(str(rccParameters["CompressionThreshold"]))
665 if (
666 rccParameters["CompressLevel"] !=
667 defaultParameters["CompressLevel"]
668 ):
669 args.append("-compress")
670 args.append(str(rccParameters["CompressLevel"]))
671 if (
672 rccParameters["CompressionDisable"] !=
673 defaultParameters["CompressionDisable"]
674 ):
675 args.append("-no-compress")
676 if rccParameters["PathPrefix"] != defaultParameters["PathPrefix"]:
677 args.append("-root")
678 args.append(rccParameters["PathPrefix"])
679 else:
680 return None
681
682 rcc = self.rccCompiler
683
684 ofn, ext = os.path.splitext(fn)
685 fn = os.path.join(self.project.ppath, fn)
686
687 dirname, filename = os.path.split(ofn)
688 if self.project.getProjectLanguage() == "Python3":
689 self.compiledFile = os.path.join(
690 dirname, self.RCFilenameFormatPython.format(filename))
691 elif self.project.getProjectLanguage() == "Ruby":
692 self.compiledFile = os.path.join(
693 dirname, self.RCFilenameFormatRuby.format(filename))
694
695 args.append(fn)
696 self.compileProc.finished.connect(self.__compileQRCDone)
697 self.compileProc.readyReadStandardOutput.connect(self.__readStdout)
698 self.compileProc.readyReadStandardError.connect(self.__readStderr)
699
700 self.noDialog = noDialog
701 self.compileProc.start(rcc, args)
702 procStarted = self.compileProc.waitForStarted(5000)
703 if procStarted:
704 self.compileRunning = True
705 e5App().getObject("ViewManager").enableEditorsCheckFocusIn(False)
706 return self.compileProc
707 else:
708 self.compileRunning = False
709 if progress is not None:
710 progress.cancel()
711 E5MessageBox.critical(
712 self,
713 self.tr('Process Generation Error'),
714 self.tr(
715 'Could not start {0}.<br>'
716 'Ensure that it is in the search path.'
717 ).format(self.rccCompiler))
718 return None
719
720 def __compileResource(self):
721 """
722 Private method to compile a resource to a source file.
723 """
724 itm = self.model().item(self.currentIndex())
725 fn2 = itm.fileName()
726 fn = self.project.getRelativePath(fn2)
727 if self.hooks["compileResource"] is not None:
728 self.hooks["compileResource"](fn)
729 else:
730 self.__compileQRC(fn)
731
732 def __compileAllResources(self):
733 """
734 Private method to compile all resources to source files.
735 """
736 if self.hooks["compileAllResources"] is not None:
737 self.hooks["compileAllResources"](self.project.pdata["RESOURCES"])
738 else:
739 numResources = len(self.project.pdata["RESOURCES"])
740 progress = E5ProgressDialog(
741 self.tr("Compiling resources..."),
742 self.tr("Abort"), 0, numResources,
743 self.tr("%v/%m Resources"), self)
744 progress.setModal(True)
745 progress.setMinimumDuration(0)
746 progress.setWindowTitle(self.tr("Resources"))
747
748 for prog, fn in enumerate(self.project.pdata["RESOURCES"]):
749 progress.setValue(prog)
750 if progress.wasCanceled():
751 break
752 proc = self.__compileQRC(fn, True, progress)
753 if proc is not None:
754 while proc.state() == QProcess.ProcessState.Running:
755 QApplication.processEvents()
756 QThread.msleep(300)
757 QApplication.processEvents()
758 else:
759 break
760 progress.setValue(numResources)
761
762 def __compileSelectedResources(self):
763 """
764 Private method to compile selected resources to source files.
765 """
766 items = self.getSelectedItems()
767 files = [self.project.getRelativePath(itm.fileName())
768 for itm in items]
769
770 if self.hooks["compileSelectedResources"] is not None:
771 self.hooks["compileSelectedResources"](files)
772 else:
773 numResources = len(files)
774 progress = E5ProgressDialog(
775 self.tr("Compiling resources..."),
776 self.tr("Abort"), 0, numResources,
777 self.tr("%v/%m Resources"), self)
778 progress.setModal(True)
779 progress.setMinimumDuration(0)
780 progress.setWindowTitle(self.tr("Resources"))
781
782 for prog, fn in enumerate(files):
783 progress.setValue(prog)
784 if progress.wasCanceled():
785 break
786 if not fn.endswith('.ui.h'):
787 proc = self.__compileQRC(fn, True, progress)
788 if proc is not None:
789 while proc.state() == QProcess.ProcessState.Running:
790 QApplication.processEvents()
791 QThread.msleep(300)
792 QApplication.processEvents()
793 else:
794 break
795 progress.setValue(numResources)
796
797 def __checkResourcesNewer(self, filename, mtime):
798 """
799 Private method to check, if any file referenced in a resource
800 file is newer than a given time.
801
802 @param filename filename of the resource file (string)
803 @param mtime modification time to check against
804 @return flag indicating some file is newer (boolean)
805 """
806 try:
807 with open(filename, "r", encoding="utf-8") as f:
808 buf = f.read()
809 except OSError:
810 return False
811
812 qrcDirName = os.path.dirname(filename)
813 lbuf = ""
814 for line in buf.splitlines():
815 line = line.strip()
816 if (
817 line.lower().startswith("<file>") or
818 line.lower().startswith("<file ")
819 ):
820 lbuf = line
821 elif lbuf:
822 lbuf = "{0}{1}".format(lbuf, line)
823 if lbuf.lower().endswith("</file>"):
824 rfile = lbuf.split(">", 1)[1].split("<", 1)[0]
825 if not os.path.isabs(rfile):
826 rfile = os.path.join(qrcDirName, rfile)
827 if (
828 os.path.exists(rfile) and
829 os.stat(rfile).st_mtime > mtime
830 ):
831 return True
832
833 lbuf = ""
834
835 return False
836
837 def compileChangedResources(self):
838 """
839 Public method to compile all changed resources to source files.
840 """
841 if self.hooks["compileChangedResources"] is not None:
842 self.hooks["compileChangedResources"](
843 self.project.pdata["RESOURCES"])
844 else:
845 progress = E5ProgressDialog(
846 self.tr("Determining changed resources..."),
847 self.tr("Abort"), 0, 100, self.tr("%v/%m Resources"))
848 progress.setMinimumDuration(0)
849 progress.setWindowTitle(self.tr("Resources"))
850
851 # get list of changed resources
852 changedResources = []
853 progress.setMaximum(len(self.project.pdata["RESOURCES"]))
854 for prog, fn in enumerate(self.project.pdata["RESOURCES"]):
855 progress.setValue(prog)
856 QApplication.processEvents()
857 ifn = os.path.join(self.project.ppath, fn)
858 if self.project.getProjectLanguage() == "Python3":
859 dirname, filename = os.path.split(os.path.splitext(ifn)[0])
860 ofn = os.path.join(
861 dirname, self.RCFilenameFormatPython.format(filename))
862 elif self.project.getProjectLanguage() == "Ruby":
863 dirname, filename = os.path.split(os.path.splitext(ifn)[0])
864 ofn = os.path.join(
865 dirname, self.RCFilenameFormatRuby.format(filename))
866 else:
867 return
868 if (
869 not os.path.exists(ofn) or
870 os.stat(ifn).st_mtime > os.stat(ofn).st_mtime or
871 self.__checkResourcesNewer(ifn, os.stat(ofn).st_mtime)
872 ):
873 changedResources.append(fn)
874 progress.setValue(len(self.project.pdata["RESOURCES"]))
875 QApplication.processEvents()
876
877 if changedResources:
878 progress.setLabelText(
879 self.tr("Compiling changed resources..."))
880 progress.setMaximum(len(changedResources))
881 progress.setValue(0)
882 QApplication.processEvents()
883 for prog, fn in enumerate(changedResources):
884 progress.setValue(prog)
885 if progress.wasCanceled():
886 break
887 proc = self.__compileQRC(fn, True, progress)
888 if proc is not None:
889 while proc.state() == QProcess.ProcessState.Running:
890 QApplication.processEvents()
891 QThread.msleep(300)
892 QApplication.processEvents()
893 else:
894 break
895 progress.setValue(len(changedResources))
896 QApplication.processEvents()
897
898 def handlePreferencesChanged(self):
899 """
900 Public slot used to handle the preferencesChanged signal.
901 """
902 ProjectBaseBrowser.handlePreferencesChanged(self)
903
904 def __configureRccCompiler(self):
905 """
906 Private slot to configure some non-common rcc compiler options.
907 """
908 from .RccCompilerOptionsDialog import RccCompilerOptionsDialog
909
910 params = self.project.pdata["RCCPARAMS"]
911
912 dlg = RccCompilerOptionsDialog(params)
913 if dlg.exec() == QDialog.DialogCode.Accepted:
914 threshold, compression, noCompression, root = dlg.getData()
915 if threshold != params["CompressionThreshold"]:
916 params["CompressionThreshold"] = threshold
917 self.project.setDirty(True)
918 if compression != params["CompressLevel"]:
919 params["CompressLevel"] = compression
920 self.project.setDirty(True)
921 if noCompression != params["CompressionDisable"]:
922 params["CompressionDisable"] = noCompression
923 self.project.setDirty(True)
924 if root != params["PathPrefix"]:
925 params["PathPrefix"] = root
926 self.project.setDirty(True)
927
928 ###########################################################################
929 ## Support for hooks below
930 ###########################################################################
931
932 def _initHookMethods(self):
933 """
934 Protected method to initialize the hooks dictionary.
935
936 Supported hook methods are:
937 <ul>
938 <li>compileResource: takes filename as parameter</li>
939 <li>compileAllResources: takes list of filenames as parameter</li>
940 <li>compileChangedResources: takes list of filenames as parameter</li>
941 <li>compileSelectedResources: takes list of all form filenames as
942 parameter</li>
943 <li>newResource: takes full directory path of new file as
944 parameter</li>
945 </ul>
946
947 <b>Note</b>: Filenames are relative to the project directory, if not
948 specified differently.
949 """
950 self.hooks = {
951 "compileResource": None,
952 "compileAllResources": None,
953 "compileChangedResources": None,
954 "compileSelectedResources": None,
955 "newResource": None,
956 }

eric ide

mercurial