src/eric7/Project/ProjectResourcesBrowser.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
506e35e424d5
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2022 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 import pathlib
13
14 from PyQt6.QtCore import QThread, pyqtSignal, QProcess
15 from PyQt6.QtWidgets import QDialog, QApplication, QMenu
16
17 from EricWidgets.EricApplication import ericApp
18 from EricWidgets import EricMessageBox, EricFileDialog
19 from EricWidgets.EricProgressDialog import EricProgressDialog
20
21 from .ProjectBrowserModel import (
22 ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem,
23 ProjectBrowserDirectoryItem, ProjectBrowserResourceType
24 )
25 from .ProjectBaseBrowser import ProjectBaseBrowser
26
27 import UI.PixmapCache
28 from UI.NotificationWidget import NotificationTypes
29
30 import Preferences
31 import Utilities
32
33
34 class ProjectResourcesBrowser(ProjectBaseBrowser):
35 """
36 A class used to display the resources part of the project.
37
38 @signal appendStderr(str) emitted after something was received from
39 a QProcess on stderr
40 @signal showMenu(str, QMenu) emitted when a menu is about to be shown.
41 The name of the menu and a reference to the menu are given.
42 """
43 appendStderr = pyqtSignal(str)
44 showMenu = pyqtSignal(str, QMenu)
45
46 RCFilenameFormatPython = "{0}_rc.py"
47 RCFilenameFormatRuby = "{0}_rc.rb"
48
49 def __init__(self, project, parent=None):
50 """
51 Constructor
52
53 @param project reference to the project object
54 @param parent parent widget of this browser (QWidget)
55 """
56 ProjectBaseBrowser.__init__(self, project, ProjectBrowserResourceType,
57 parent)
58
59 self.selectedItemsFilter = [ProjectBrowserFileItem,
60 ProjectBrowserSimpleDirectoryItem]
61
62 self.setWindowTitle(self.tr('Resources'))
63
64 self.setWhatsThis(self.tr(
65 """<b>Project Resources Browser</b>"""
66 """<p>This allows to easily see all resources contained in the"""
67 """ current project. Several actions can be executed via the"""
68 """ context menu.</p>"""
69 ))
70
71 self.compileProc = None
72
73 def _createPopupMenus(self):
74 """
75 Protected overloaded method to generate the popup menu.
76 """
77 self.menuActions = []
78 self.multiMenuActions = []
79 self.dirMenuActions = []
80 self.dirMultiMenuActions = []
81
82 self.menu = QMenu(self)
83 if self.project.getProjectType() in [
84 "PyQt5", "PyQt5C",
85 "PySide2", "PySide2C", "PySide6", "PySide6C"
86 ]:
87 self.menu.addAction(
88 self.tr('Compile resource'),
89 self.__compileResource)
90 self.menu.addAction(
91 self.tr('Compile all resources'),
92 self.__compileAllResources)
93 self.menu.addSeparator()
94 self.menu.addAction(
95 self.tr('Configure rcc Compiler'),
96 self.__configureRccCompiler)
97 self.menu.addSeparator()
98 else:
99 if self.hooks["compileResource"] is not None:
100 self.menu.addAction(
101 self.hooksMenuEntries.get(
102 "compileResource",
103 self.tr('Compile resource')),
104 self.__compileResource)
105 if self.hooks["compileAllResources"] is not None:
106 self.menu.addAction(
107 self.hooksMenuEntries.get(
108 "compileAllResources",
109 self.tr('Compile all resources')),
110 self.__compileAllResources)
111 if (
112 self.hooks["compileResource"] is not None or
113 self.hooks["compileAllResources"] is not None
114 ):
115 self.menu.addSeparator()
116 self.menu.addAction(self.tr('Open'), self.__openFile)
117 self.menu.addSeparator()
118 act = self.menu.addAction(self.tr('Rename file'), self._renameFile)
119 self.menuActions.append(act)
120 act = self.menu.addAction(
121 self.tr('Remove from project'), self._removeFile)
122 self.menuActions.append(act)
123 act = self.menu.addAction(self.tr('Delete'), self.__deleteFile)
124 self.menuActions.append(act)
125 self.menu.addSeparator()
126 if self.project.getProjectType() in [
127 "PyQt5", "PyQt5C",
128 "PySide2", "PySide2C", "PySide6", "PySide6C"
129 ]:
130 self.menu.addAction(
131 self.tr('New resource...'), self.__newResource)
132 else:
133 if self.hooks["newResource"] is not None:
134 self.menu.addAction(
135 self.hooksMenuEntries.get(
136 "newResource",
137 self.tr('New resource...')), self.__newResource)
138 self.menu.addAction(
139 self.tr('Add resources...'), self.__addResourceFiles)
140 self.menu.addAction(
141 self.tr('Add resources directory...'),
142 self.__addResourcesDirectory)
143 self.menu.addSeparator()
144 self.menu.addAction(
145 self.tr('Copy Path to Clipboard'), self._copyToClipboard)
146 self.menu.addSeparator()
147 self.menu.addAction(
148 self.tr('Expand all directories'), self._expandAllDirs)
149 self.menu.addAction(
150 self.tr('Collapse all directories'), self._collapseAllDirs)
151 self.menu.addSeparator()
152 self.menu.addAction(self.tr('Configure...'), self._configure)
153
154 self.backMenu = QMenu(self)
155 if self.project.getProjectType() in [
156 "PyQt5", "PyQt5C",
157 "PySide2", "PySide2C", "PySide6", "PySide6C"
158 ]:
159 self.backMenu.addAction(
160 self.tr('Compile all resources'),
161 self.__compileAllResources)
162 self.backMenu.addSeparator()
163 self.backMenu.addAction(
164 self.tr('Configure rcc Compiler'),
165 self.__configureRccCompiler)
166 self.backMenu.addSeparator()
167 self.backMenu.addAction(
168 self.tr('New resource...'), self.__newResource)
169 else:
170 if self.hooks["compileAllResources"] is not None:
171 self.backMenu.addAction(
172 self.hooksMenuEntries.get(
173 "compileAllResources",
174 self.tr('Compile all resources')),
175 self.__compileAllResources)
176 self.backMenu.addSeparator()
177 if self.hooks["newResource"] is not None:
178 self.backMenu.addAction(
179 self.hooksMenuEntries.get(
180 "newResource",
181 self.tr('New resource...')), self.__newResource)
182 self.backMenu.addAction(
183 self.tr('Add resources...'), self.project.addResourceFiles)
184 self.backMenu.addAction(
185 self.tr('Add resources directory...'),
186 self.project.addResourceDir)
187 self.backMenu.addSeparator()
188 self.backMenu.addAction(
189 self.tr('Expand all directories'), self._expandAllDirs)
190 self.backMenu.addAction(
191 self.tr('Collapse all directories'), self._collapseAllDirs)
192 self.backMenu.addSeparator()
193 self.backMenu.addAction(self.tr('Configure...'), self._configure)
194 self.backMenu.setEnabled(False)
195
196 # create the menu for multiple selected files
197 self.multiMenu = QMenu(self)
198 if self.project.getProjectType() in [
199 "PyQt5", "PyQt5C",
200 "PySide2", "PySide2C", "PySide6", "PySide6C"
201 ]:
202 act = self.multiMenu.addAction(
203 self.tr('Compile resources'),
204 self.__compileSelectedResources)
205 self.multiMenu.addSeparator()
206 self.multiMenu.addAction(
207 self.tr('Configure rcc Compiler'),
208 self.__configureRccCompiler)
209 self.multiMenu.addSeparator()
210 else:
211 if self.hooks["compileSelectedResources"] is not None:
212 act = self.multiMenu.addAction(
213 self.hooksMenuEntries.get(
214 "compileSelectedResources",
215 self.tr('Compile resources')),
216 self.__compileSelectedResources)
217 self.multiMenu.addSeparator()
218 self.multiMenu.addAction(self.tr('Open'), self.__openFile)
219 self.multiMenu.addSeparator()
220 act = self.multiMenu.addAction(
221 self.tr('Remove from project'), self._removeFile)
222 self.multiMenuActions.append(act)
223 act = self.multiMenu.addAction(
224 self.tr('Delete'), self.__deleteFile)
225 self.multiMenuActions.append(act)
226 self.multiMenu.addSeparator()
227 self.multiMenu.addAction(
228 self.tr('Expand all directories'), self._expandAllDirs)
229 self.multiMenu.addAction(
230 self.tr('Collapse all directories'), self._collapseAllDirs)
231 self.multiMenu.addSeparator()
232 self.multiMenu.addAction(self.tr('Configure...'), self._configure)
233
234 self.dirMenu = QMenu(self)
235 if self.project.getProjectType() in [
236 "PyQt5", "PyQt5C",
237 "PySide2", "PySide2C", "PySide6", "PySide6C"
238 ]:
239 self.dirMenu.addAction(
240 self.tr('Compile all resources'),
241 self.__compileAllResources)
242 self.dirMenu.addSeparator()
243 self.dirMenu.addAction(
244 self.tr('Configure rcc Compiler'),
245 self.__configureRccCompiler)
246 self.dirMenu.addSeparator()
247 else:
248 if self.hooks["compileAllResources"] is not None:
249 self.dirMenu.addAction(
250 self.hooksMenuEntries.get(
251 "compileAllResources",
252 self.tr('Compile all resources')),
253 self.__compileAllResources)
254 self.dirMenu.addSeparator()
255 act = self.dirMenu.addAction(
256 self.tr('Remove from project'), self._removeDir)
257 self.dirMenuActions.append(act)
258 act = self.dirMenu.addAction(
259 self.tr('Delete'), self._deleteDirectory)
260 self.dirMenuActions.append(act)
261 self.dirMenu.addSeparator()
262 self.dirMenu.addAction(
263 self.tr('New resource...'), self.__newResource)
264 self.dirMenu.addAction(
265 self.tr('Add resources...'), self.__addResourceFiles)
266 self.dirMenu.addAction(
267 self.tr('Add resources directory...'),
268 self.__addResourcesDirectory)
269 self.dirMenu.addSeparator()
270 self.dirMenu.addAction(
271 self.tr('Copy Path to Clipboard'), self._copyToClipboard)
272 self.dirMenu.addSeparator()
273 self.dirMenu.addAction(
274 self.tr('Expand all directories'), self._expandAllDirs)
275 self.dirMenu.addAction(
276 self.tr('Collapse all directories'), self._collapseAllDirs)
277 self.dirMenu.addSeparator()
278 self.dirMenu.addAction(self.tr('Configure...'), self._configure)
279
280 self.dirMultiMenu = QMenu(self)
281 if self.project.getProjectType() in [
282 "PyQt5", "PyQt5C",
283 "PySide2", "PySide2C", "PySide6", "PySide6C"
284 ]:
285 self.dirMultiMenu.addAction(
286 self.tr('Compile all resources'),
287 self.__compileAllResources)
288 self.dirMultiMenu.addSeparator()
289 self.dirMultiMenu.addAction(
290 self.tr('Configure rcc Compiler'),
291 self.__configureRccCompiler)
292 self.dirMultiMenu.addSeparator()
293 else:
294 if self.hooks["compileAllResources"] is not None:
295 self.dirMultiMenu.addAction(
296 self.hooksMenuEntries.get(
297 "compileAllResources",
298 self.tr('Compile all resources')),
299 self.__compileAllResources)
300 self.dirMultiMenu.addSeparator()
301 self.dirMultiMenu.addAction(
302 self.tr('Add resources...'),
303 self.project.addResourceFiles)
304 self.dirMultiMenu.addAction(
305 self.tr('Add resources directory...'),
306 self.project.addResourceDir)
307 self.dirMultiMenu.addSeparator()
308 self.dirMultiMenu.addAction(
309 self.tr('Expand all directories'), self._expandAllDirs)
310 self.dirMultiMenu.addAction(
311 self.tr('Collapse all directories'), self._collapseAllDirs)
312 self.dirMultiMenu.addSeparator()
313 self.dirMultiMenu.addAction(
314 self.tr('Configure...'), self._configure)
315
316 self.menu.aboutToShow.connect(self.__showContextMenu)
317 self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
318 self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
319 self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti)
320 self.backMenu.aboutToShow.connect(self.__showContextMenuBack)
321 self.mainMenu = self.menu
322
323 def _contextMenuRequested(self, coord):
324 """
325 Protected slot to show the context menu.
326
327 @param coord the position of the mouse pointer (QPoint)
328 """
329 if not self.project.isOpen():
330 return
331
332 with contextlib.suppress(Exception):
333 categories = self.getSelectedItemsCountCategorized(
334 [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem])
335 cnt = categories["sum"]
336 if cnt <= 1:
337 index = self.indexAt(coord)
338 if index.isValid():
339 self._selectSingleItem(index)
340 categories = self.getSelectedItemsCountCategorized(
341 [ProjectBrowserFileItem,
342 ProjectBrowserSimpleDirectoryItem])
343 cnt = categories["sum"]
344
345 bfcnt = categories[str(ProjectBrowserFileItem)]
346 sdcnt = categories[str(ProjectBrowserSimpleDirectoryItem)]
347 if cnt > 1 and cnt == bfcnt:
348 self.multiMenu.popup(self.mapToGlobal(coord))
349 elif cnt > 1 and cnt == sdcnt:
350 self.dirMultiMenu.popup(self.mapToGlobal(coord))
351 else:
352 index = self.indexAt(coord)
353 if cnt == 1 and index.isValid():
354 if bfcnt == 1:
355 self.menu.popup(self.mapToGlobal(coord))
356 elif sdcnt == 1:
357 self.dirMenu.popup(self.mapToGlobal(coord))
358 else:
359 self.backMenu.popup(self.mapToGlobal(coord))
360 else:
361 self.backMenu.popup(self.mapToGlobal(coord))
362
363 def __showContextMenu(self):
364 """
365 Private slot called by the menu aboutToShow signal.
366 """
367 ProjectBaseBrowser._showContextMenu(self, self.menu)
368
369 self.showMenu.emit("Main", self.menu)
370
371 def __showContextMenuMulti(self):
372 """
373 Private slot called by the multiMenu aboutToShow signal.
374 """
375 ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu)
376
377 self.showMenu.emit("MainMulti", self.multiMenu)
378
379 def __showContextMenuDir(self):
380 """
381 Private slot called by the dirMenu aboutToShow signal.
382 """
383 ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu)
384
385 self.showMenu.emit("MainDir", self.dirMenu)
386
387 def __showContextMenuDirMulti(self):
388 """
389 Private slot called by the dirMultiMenu aboutToShow signal.
390 """
391 ProjectBaseBrowser._showContextMenuDirMulti(self, self.dirMultiMenu)
392
393 self.showMenu.emit("MainDirMulti", self.dirMultiMenu)
394
395 def __showContextMenuBack(self):
396 """
397 Private slot called by the backMenu aboutToShow signal.
398 """
399 ProjectBaseBrowser._showContextMenuBack(self, self.backMenu)
400
401 self.showMenu.emit("MainBack", self.backMenu)
402
403 def __addResourceFiles(self):
404 """
405 Private method to add resource files to the project.
406 """
407 itm = self.model().item(self.currentIndex())
408 if isinstance(itm, ProjectBrowserFileItem):
409 dn = os.path.dirname(itm.fileName())
410 elif isinstance(
411 itm,
412 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem)
413 ):
414 dn = itm.dirName()
415 else:
416 dn = None
417 self.project.addFiles('resource', dn)
418
419 def __addResourcesDirectory(self):
420 """
421 Private method to add resource files of a directory to the project.
422 """
423 itm = self.model().item(self.currentIndex())
424 if isinstance(itm, ProjectBrowserFileItem):
425 dn = os.path.dirname(itm.fileName())
426 elif isinstance(
427 itm,
428 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem)
429 ):
430 dn = itm.dirName()
431 else:
432 dn = None
433 self.project.addDirectory('resource', dn)
434
435 def _openItem(self):
436 """
437 Protected slot to handle the open popup menu entry.
438 """
439 self.__openFile()
440
441 def __openFile(self):
442 """
443 Private slot to handle the Open menu action.
444 """
445 itmList = self.getSelectedItems()
446 for itm in itmList[:]:
447 if isinstance(itm, ProjectBrowserFileItem):
448 self.sourceFile.emit(itm.fileName())
449
450 def __newResource(self):
451 """
452 Private slot to handle the New Resource menu action.
453 """
454 itm = self.model().item(self.currentIndex())
455 if itm is None:
456 path = self.project.ppath
457 else:
458 try:
459 path = os.path.dirname(itm.fileName())
460 except AttributeError:
461 try:
462 path = itm.dirName()
463 except AttributeError:
464 path = os.path.join(self.project.ppath, itm.data(0))
465
466 if self.hooks["newResource"] is not None:
467 self.hooks["newResource"](path)
468 else:
469 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
470 self,
471 self.tr("New Resource"),
472 path,
473 self.tr("Qt Resource Files (*.qrc)"),
474 "",
475 EricFileDialog.DontConfirmOverwrite)
476
477 if not fname:
478 # user aborted or didn't enter a filename
479 return
480
481 fpath = pathlib.Path(fname)
482 if not fpath.suffix:
483 ex = selectedFilter.split("(*")[1].split(")")[0]
484 if ex:
485 fpath = fpath.with_suffix(ex)
486 if fpath.exists():
487 res = EricMessageBox.yesNo(
488 self,
489 self.tr("New Resource"),
490 self.tr("The file already exists! Overwrite it?"),
491 icon=EricMessageBox.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 fpath.open('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 EricMessageBox.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(fpath, str(e)))
514 return
515
516 self.project.appendFile(str(fpath))
517 self.sourceFile.emit(str(fpath))
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 ericApp().getObject("ViewManager").enableEditorsCheckFocusIn(True)
594 ui = ericApp().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 EricMessageBox.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 self.rccCompiler = Utilities.generatePyQtToolPath('pyrcc5')
648 elif self.project.getProjectType() in ["PySide2", "PySide2C"]:
649 self.rccCompiler = Utilities.generatePySideToolPath(
650 'pyside2-rcc', variant=2)
651 elif self.project.getProjectType() in ["PySide6", "PySide6C"]:
652 self.rccCompiler = Utilities.generatePySideToolPath(
653 'pyside6-rcc', variant=6)
654 else:
655 return None
656 defaultParameters = self.project.getDefaultRccCompilerParameters()
657 rccParameters = self.project.pdata["RCCPARAMS"]
658 if (
659 rccParameters["CompressionThreshold"] !=
660 defaultParameters["CompressionThreshold"]
661 ):
662 args.append("-threshold")
663 args.append(str(rccParameters["CompressionThreshold"]))
664 if (
665 rccParameters["CompressLevel"] !=
666 defaultParameters["CompressLevel"]
667 ):
668 args.append("-compress")
669 args.append(str(rccParameters["CompressLevel"]))
670 if (
671 rccParameters["CompressionDisable"] !=
672 defaultParameters["CompressionDisable"]
673 ):
674 args.append("-no-compress")
675 if rccParameters["PathPrefix"] != defaultParameters["PathPrefix"]:
676 args.append("-root")
677 args.append(rccParameters["PathPrefix"])
678 else:
679 return None
680
681 rcc = self.rccCompiler
682
683 ofn, ext = os.path.splitext(fn)
684 fn = os.path.join(self.project.ppath, fn)
685
686 dirname, filename = os.path.split(ofn)
687 if self.project.getProjectLanguage() == "Python3":
688 self.compiledFile = os.path.join(
689 dirname, self.RCFilenameFormatPython.format(filename))
690 elif self.project.getProjectLanguage() == "Ruby":
691 self.compiledFile = os.path.join(
692 dirname, self.RCFilenameFormatRuby.format(filename))
693
694 args.append(fn)
695 self.compileProc.finished.connect(self.__compileQRCDone)
696 self.compileProc.readyReadStandardOutput.connect(self.__readStdout)
697 self.compileProc.readyReadStandardError.connect(self.__readStderr)
698
699 self.noDialog = noDialog
700 self.compileProc.start(rcc, args)
701 procStarted = self.compileProc.waitForStarted(5000)
702 if procStarted:
703 self.compileRunning = True
704 ericApp().getObject("ViewManager").enableEditorsCheckFocusIn(False)
705 return self.compileProc
706 else:
707 self.compileRunning = False
708 if progress is not None:
709 progress.cancel()
710 EricMessageBox.critical(
711 self,
712 self.tr('Process Generation Error'),
713 self.tr(
714 'Could not start {0}.<br>'
715 'Ensure that it is in the search path.'
716 ).format(self.rccCompiler))
717 return None
718
719 def __compileResource(self):
720 """
721 Private method to compile a resource to a source file.
722 """
723 itm = self.model().item(self.currentIndex())
724 fn2 = itm.fileName()
725 fn = self.project.getRelativePath(fn2)
726 if self.hooks["compileResource"] is not None:
727 self.hooks["compileResource"](fn)
728 else:
729 self.__compileQRC(fn)
730
731 def __compileAllResources(self):
732 """
733 Private method to compile all resources to source files.
734 """
735 if self.hooks["compileAllResources"] is not None:
736 self.hooks["compileAllResources"](self.project.pdata["RESOURCES"])
737 else:
738 numResources = len(self.project.pdata["RESOURCES"])
739 progress = EricProgressDialog(
740 self.tr("Compiling resources..."),
741 self.tr("Abort"), 0, numResources,
742 self.tr("%v/%m Resources"), self)
743 progress.setModal(True)
744 progress.setMinimumDuration(0)
745 progress.setWindowTitle(self.tr("Resources"))
746
747 for prog, fn in enumerate(self.project.pdata["RESOURCES"]):
748 progress.setValue(prog)
749 if progress.wasCanceled():
750 break
751 proc = self.__compileQRC(fn, True, progress)
752 if proc is not None:
753 while proc.state() == QProcess.ProcessState.Running:
754 QThread.msleep(100)
755 QApplication.processEvents()
756 else:
757 break
758 progress.setValue(numResources)
759
760 def __compileSelectedResources(self):
761 """
762 Private method to compile selected resources to source files.
763 """
764 items = self.getSelectedItems()
765 files = [self.project.getRelativePath(itm.fileName())
766 for itm in items]
767
768 if self.hooks["compileSelectedResources"] is not None:
769 self.hooks["compileSelectedResources"](files)
770 else:
771 numResources = len(files)
772 progress = EricProgressDialog(
773 self.tr("Compiling resources..."),
774 self.tr("Abort"), 0, numResources,
775 self.tr("%v/%m Resources"), self)
776 progress.setModal(True)
777 progress.setMinimumDuration(0)
778 progress.setWindowTitle(self.tr("Resources"))
779
780 for prog, fn in enumerate(files):
781 progress.setValue(prog)
782 if progress.wasCanceled():
783 break
784 if not fn.endswith('.ui.h'):
785 proc = self.__compileQRC(fn, True, progress)
786 if proc is not None:
787 while proc.state() == QProcess.ProcessState.Running:
788 QThread.msleep(100)
789 QApplication.processEvents()
790 else:
791 break
792 progress.setValue(numResources)
793
794 def __checkResourcesNewer(self, filename, mtime):
795 """
796 Private method to check, if any file referenced in a resource
797 file is newer than a given time.
798
799 @param filename filename of the resource file (string)
800 @param mtime modification time to check against
801 @return flag indicating some file is newer (boolean)
802 """
803 try:
804 with open(filename, "r", encoding="utf-8") as f:
805 buf = f.read()
806 except OSError:
807 return False
808
809 qrcDirName = os.path.dirname(filename)
810 lbuf = ""
811 for line in buf.splitlines():
812 line = line.strip()
813 if (
814 line.lower().startswith("<file>") or
815 line.lower().startswith("<file ")
816 ):
817 lbuf = line
818 elif lbuf:
819 lbuf = "{0}{1}".format(lbuf, line)
820 if lbuf.lower().endswith("</file>"):
821 rfile = lbuf.split(">", 1)[1].split("<", 1)[0]
822 if not os.path.isabs(rfile):
823 rfile = os.path.join(qrcDirName, rfile)
824 if (
825 os.path.exists(rfile) and
826 os.stat(rfile).st_mtime > mtime
827 ):
828 return True
829
830 lbuf = ""
831
832 return False
833
834 def compileChangedResources(self):
835 """
836 Public method to compile all changed resources to source files.
837 """
838 if self.hooks["compileChangedResources"] is not None:
839 self.hooks["compileChangedResources"](
840 self.project.pdata["RESOURCES"])
841 else:
842 if len(self.project.pdata["RESOURCES"]) == 0:
843 # The project does not contain resource files
844 return
845
846 progress = EricProgressDialog(
847 self.tr("Determining changed resources..."),
848 self.tr("Abort"), 0, 100, self.tr("%v/%m Resources"), self)
849 progress.setMinimumDuration(0)
850 progress.setWindowTitle(self.tr("Resources"))
851
852 # get list of changed resources
853 changedResources = []
854 progress.setMaximum(len(self.project.pdata["RESOURCES"]))
855 for prog, fn in enumerate(self.project.pdata["RESOURCES"]):
856 progress.setValue(prog)
857 QApplication.processEvents()
858 ifn = os.path.join(self.project.ppath, fn)
859 if self.project.getProjectLanguage() == "Python3":
860 dirname, filename = os.path.split(os.path.splitext(ifn)[0])
861 ofn = os.path.join(
862 dirname, self.RCFilenameFormatPython.format(filename))
863 elif self.project.getProjectLanguage() == "Ruby":
864 dirname, filename = os.path.split(os.path.splitext(ifn)[0])
865 ofn = os.path.join(
866 dirname, self.RCFilenameFormatRuby.format(filename))
867 else:
868 return
869 if (
870 not os.path.exists(ofn) or
871 os.stat(ifn).st_mtime > os.stat(ofn).st_mtime or
872 self.__checkResourcesNewer(ifn, os.stat(ofn).st_mtime)
873 ):
874 changedResources.append(fn)
875 progress.setValue(len(self.project.pdata["RESOURCES"]))
876 QApplication.processEvents()
877
878 if changedResources:
879 progress.setLabelText(
880 self.tr("Compiling changed resources..."))
881 progress.setMaximum(len(changedResources))
882 progress.setValue(0)
883 QApplication.processEvents()
884 for prog, fn in enumerate(changedResources):
885 progress.setValue(prog)
886 if progress.wasCanceled():
887 break
888 proc = self.__compileQRC(fn, True, progress)
889 if proc is not None:
890 while proc.state() == QProcess.ProcessState.Running:
891 QThread.msleep(100)
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