src/eric7/Project/ProjectFormsBrowser.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 forms part of the project.
8 """
9
10 import os
11 import sys
12 import shutil
13 import contextlib
14 import pathlib
15
16 from PyQt6.QtCore import QThread, pyqtSignal, QProcess
17 from PyQt6.QtWidgets import QDialog, QInputDialog, QApplication, QMenu
18
19 from EricWidgets.EricApplication import ericApp
20 from EricWidgets import EricMessageBox, EricFileDialog
21 from EricWidgets.EricProgressDialog import EricProgressDialog
22
23 from .ProjectBrowserModel import (
24 ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem,
25 ProjectBrowserDirectoryItem, ProjectBrowserFormType
26 )
27 from .ProjectBaseBrowser import ProjectBaseBrowser
28
29 import UI.PixmapCache
30 from UI.NotificationWidget import NotificationTypes
31
32
33 import Preferences
34 import Utilities
35
36 from eric7config import getConfig
37
38
39 class ProjectFormsBrowser(ProjectBaseBrowser):
40 """
41 A class used to display the forms part of the project.
42
43 @signal appendStderr(str) emitted after something was received from
44 a QProcess on stderr
45 @signal uipreview(str) emitted to preview a forms file
46 @signal showMenu(str, QMenu) emitted when a menu is about to be shown. The
47 name of the menu and a reference to the menu are given.
48 @signal menusAboutToBeCreated() emitted when the context menus are about to
49 be created. This is the right moment to add or remove hook methods.
50 """
51 appendStderr = pyqtSignal(str)
52 uipreview = pyqtSignal(str)
53 showMenu = pyqtSignal(str, QMenu)
54 menusAboutToBeCreated = pyqtSignal()
55
56 Pyuic5IndentDefault = 4
57 Pyuic6IndentDefault = 4
58
59 def __init__(self, project, parent=None):
60 """
61 Constructor
62
63 @param project reference to the project object
64 @param parent parent widget of this browser (QWidget)
65 """
66 ProjectBaseBrowser.__init__(self, project, ProjectBrowserFormType,
67 parent)
68
69 self.selectedItemsFilter = [ProjectBrowserFileItem,
70 ProjectBrowserSimpleDirectoryItem]
71
72 self.setWindowTitle(self.tr('Forms'))
73
74 self.setWhatsThis(self.tr(
75 """<b>Project Forms Browser</b>"""
76 """<p>This allows to easily see all forms contained in the"""
77 """ current project. Several actions can be executed via the"""
78 """ context menu.</p>"""
79 ))
80
81 # templates for Qt
82 # these two lists have to stay in sync
83 self.templates4 = [
84 'dialog4.tmpl', 'widget4.tmpl', 'mainwindow4.tmpl',
85 'dialogbuttonboxbottom4.tmpl', 'dialogbuttonboxright4.tmpl',
86 'dialogbuttonsbottom4.tmpl', 'dialogbuttonsbottomcenter4.tmpl',
87 'dialogbuttonsright4.tmpl', '', 'wizard4.tmpl', 'wizardpage4.tmpl',
88 'qdockwidget4.tmpl', 'qframe4.tmpl', 'qgroupbox4.tmpl',
89 'qscrollarea4.tmpl', 'qmdiarea4.tmpl', 'qtabwidget4.tmpl',
90 'qtoolbox4.tmpl', 'qstackedwidget4.tmpl'
91 ]
92 self.templateTypes4 = [
93 self.tr("Dialog"),
94 self.tr("Widget"),
95 self.tr("Main Window"),
96 self.tr("Dialog with Buttonbox (Bottom)"),
97 self.tr("Dialog with Buttonbox (Right)"),
98 self.tr("Dialog with Buttons (Bottom)"),
99 self.tr("Dialog with Buttons (Bottom-Center)"),
100 self.tr("Dialog with Buttons (Right)"),
101 '',
102 self.tr("QWizard"),
103 self.tr("QWizardPage"),
104 self.tr("QDockWidget"),
105 self.tr("QFrame"),
106 self.tr("QGroupBox"),
107 self.tr("QScrollArea"),
108 self.tr("QMdiArea"),
109 self.tr("QTabWidget"),
110 self.tr("QToolBox"),
111 self.tr("QStackedWidget"),
112 ]
113
114 self.compileProc = None
115 self.__uicompiler = ""
116
117 self.project.projectClosed.connect(self.__resetUiCompiler)
118 self.project.projectPropertiesChanged.connect(self.__resetUiCompiler)
119
120 def _createPopupMenus(self):
121 """
122 Protected overloaded method to generate the popup menu.
123 """
124 self.menuActions = []
125 self.multiMenuActions = []
126 self.dirMenuActions = []
127 self.dirMultiMenuActions = []
128
129 self.menusAboutToBeCreated.emit()
130
131 projectType = self.project.getProjectType()
132
133 self.menu = QMenu(self)
134 if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
135 self.menu.addAction(
136 self.tr('Compile form'), self.__compileForm)
137 self.menu.addAction(
138 self.tr('Compile all forms'),
139 self.__compileAllForms)
140 self.menu.addAction(
141 self.tr('Generate Dialog Code...'),
142 self.__generateDialogCode)
143 self.menu.addSeparator()
144 self.__pyuicConfigAct = self.menu.addAction(
145 self.tr('Configure uic Compiler'),
146 self.__configureUicCompiler)
147 self.menu.addSeparator()
148 self.menu.addAction(
149 self.tr('Open in Qt-Designer'), self.__openFile)
150 self.menu.addAction(
151 self.tr('Open in Editor'), self.__openFileInEditor)
152 self.menu.addSeparator()
153 self.menu.addAction(self.tr('Preview form'), self.__UIPreview)
154 self.menu.addAction(
155 self.tr('Preview translations'), self.__TRPreview)
156 else:
157 if self.hooks["compileForm"] is not None:
158 self.menu.addAction(
159 self.hooksMenuEntries.get(
160 "compileForm",
161 self.tr('Compile form')), self.__compileForm)
162 if self.hooks["compileAllForms"] is not None:
163 self.menu.addAction(
164 self.hooksMenuEntries.get(
165 "compileAllForms",
166 self.tr('Compile all forms')),
167 self.__compileAllForms)
168 if self.hooks["generateDialogCode"] is not None:
169 self.menu.addAction(
170 self.hooksMenuEntries.get(
171 "generateDialogCode",
172 self.tr('Generate Dialog Code...')),
173 self.__generateDialogCode)
174 if (
175 self.hooks["compileForm"] is not None or
176 self.hooks["compileAllForms"] is not None or
177 self.hooks["generateDialogCode"] is not None
178 ):
179 self.menu.addSeparator()
180 if self.hooks["open"] is not None:
181 self.menu.addAction(
182 self.hooksMenuEntries.get("open", self.tr('Open')),
183 self.__openFile)
184 self.menu.addAction(self.tr('Open'), self.__openFileInEditor)
185 self.menu.addSeparator()
186 act = self.menu.addAction(self.tr('Rename file'), self._renameFile)
187 self.menuActions.append(act)
188 act = self.menu.addAction(
189 self.tr('Remove from project'), self._removeFile)
190 self.menuActions.append(act)
191 act = self.menu.addAction(self.tr('Delete'), self.__deleteFile)
192 self.menuActions.append(act)
193 self.menu.addSeparator()
194 if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
195 self.menu.addAction(self.tr('New form...'), self.__newForm)
196 else:
197 if self.hooks["newForm"] is not None:
198 self.menu.addAction(
199 self.hooksMenuEntries.get(
200 "newForm", self.tr('New form...')), self.__newForm)
201 self.menu.addAction(self.tr('Add forms...'), self.__addFormFiles)
202 self.menu.addAction(
203 self.tr('Add forms directory...'), self.__addFormsDirectory)
204 self.menu.addSeparator()
205 self.menu.addAction(
206 self.tr('Copy Path to Clipboard'), self._copyToClipboard)
207 self.menu.addSeparator()
208 self.menu.addAction(
209 self.tr('Expand all directories'), self._expandAllDirs)
210 self.menu.addAction(
211 self.tr('Collapse all directories'), self._collapseAllDirs)
212 self.menu.addSeparator()
213 self.menu.addAction(self.tr('Configure...'), self._configure)
214
215 self.backMenu = QMenu(self)
216 if (
217 projectType in [
218 "PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"
219 ] or self.hooks["compileAllForms"] is not None
220 ):
221 self.backMenu.addAction(
222 self.tr('Compile all forms'), self.__compileAllForms)
223 self.backMenu.addSeparator()
224 self.__pyuicBackConfigAct = self.backMenu.addAction(
225 self.tr('Configure uic Compiler'),
226 self.__configureUicCompiler)
227 self.backMenu.addSeparator()
228 self.backMenu.addAction(self.tr('New form...'), self.__newForm)
229 else:
230 if self.hooks["newForm"] is not None:
231 self.backMenu.addAction(
232 self.hooksMenuEntries.get(
233 "newForm", self.tr('New form...')), self.__newForm)
234 self.backMenu.addAction(
235 self.tr('Add forms...'), self.project.addUiFiles)
236 self.backMenu.addAction(
237 self.tr('Add forms directory...'), self.project.addUiDir)
238 self.backMenu.addSeparator()
239 self.backMenu.addAction(
240 self.tr('Expand all directories'), self._expandAllDirs)
241 self.backMenu.addAction(
242 self.tr('Collapse all directories'), self._collapseAllDirs)
243 self.backMenu.addSeparator()
244 self.backMenu.addAction(self.tr('Configure...'), self._configure)
245 self.backMenu.setEnabled(False)
246
247 # create the menu for multiple selected files
248 self.multiMenu = QMenu(self)
249 if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
250 self.multiMenu.addAction(
251 self.tr('Compile forms'), self.__compileSelectedForms)
252 self.multiMenu.addSeparator()
253 self.__pyuicMultiConfigAct = self.multiMenu.addAction(
254 self.tr('Configure uic Compiler'),
255 self.__configureUicCompiler)
256 self.multiMenu.addSeparator()
257 self.multiMenu.addAction(
258 self.tr('Open in Qt-Designer'), self.__openFile)
259 self.multiMenu.addAction(
260 self.tr('Open in Editor'), self.__openFileInEditor)
261 self.multiMenu.addSeparator()
262 self.multiMenu.addAction(
263 self.tr('Preview translations'), self.__TRPreview)
264 else:
265 if self.hooks["compileSelectedForms"] is not None:
266 act = self.multiMenu.addAction(
267 self.hooksMenuEntries.get(
268 "compileSelectedForms",
269 self.tr('Compile forms')),
270 self.__compileSelectedForms)
271 self.multiMenu.addSeparator()
272 if self.hooks["open"] is not None:
273 self.multiMenu.addAction(
274 self.hooksMenuEntries.get("open", self.tr('Open')),
275 self.__openFile)
276 self.multiMenu.addAction(
277 self.tr('Open'), self.__openFileInEditor)
278 self.multiMenu.addSeparator()
279 act = self.multiMenu.addAction(
280 self.tr('Remove from project'), self._removeFile)
281 self.multiMenuActions.append(act)
282 act = self.multiMenu.addAction(
283 self.tr('Delete'), self.__deleteFile)
284 self.multiMenuActions.append(act)
285 self.multiMenu.addSeparator()
286 self.multiMenu.addAction(
287 self.tr('Expand all directories'), self._expandAllDirs)
288 self.multiMenu.addAction(
289 self.tr('Collapse all directories'), self._collapseAllDirs)
290 self.multiMenu.addSeparator()
291 self.multiMenu.addAction(self.tr('Configure...'), self._configure)
292
293 self.dirMenu = QMenu(self)
294 if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
295 self.dirMenu.addAction(
296 self.tr('Compile all forms'), self.__compileAllForms)
297 self.dirMenu.addSeparator()
298 self.__pyuicDirConfigAct = self.dirMenu.addAction(
299 self.tr('Configure uic Compiler'),
300 self.__configureUicCompiler)
301 self.dirMenu.addSeparator()
302 else:
303 if self.hooks["compileAllForms"] is not None:
304 self.dirMenu.addAction(
305 self.hooksMenuEntries.get(
306 "compileAllForms",
307 self.tr('Compile all forms')),
308 self.__compileAllForms)
309 self.dirMenu.addSeparator()
310 act = self.dirMenu.addAction(
311 self.tr('Remove from project'), self._removeDir)
312 self.dirMenuActions.append(act)
313 act = self.dirMenu.addAction(
314 self.tr('Delete'), self._deleteDirectory)
315 self.dirMenuActions.append(act)
316 self.dirMenu.addSeparator()
317 if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
318 self.dirMenu.addAction(self.tr('New form...'), self.__newForm)
319 else:
320 if self.hooks["newForm"] is not None:
321 self.dirMenu.addAction(
322 self.hooksMenuEntries.get(
323 "newForm",
324 self.tr('New form...')), self.__newForm)
325 self.dirMenu.addAction(
326 self.tr('Add forms...'), self.__addFormFiles)
327 self.dirMenu.addAction(
328 self.tr('Add forms directory...'), self.__addFormsDirectory)
329 self.dirMenu.addSeparator()
330 self.dirMenu.addAction(
331 self.tr('Copy Path to Clipboard'), self._copyToClipboard)
332 self.dirMenu.addSeparator()
333 self.dirMenu.addAction(
334 self.tr('Expand all directories'), self._expandAllDirs)
335 self.dirMenu.addAction(
336 self.tr('Collapse all directories'), self._collapseAllDirs)
337 self.dirMenu.addSeparator()
338 self.dirMenu.addAction(self.tr('Configure...'), self._configure)
339
340 self.dirMultiMenu = QMenu(self)
341 if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
342 self.dirMultiMenu.addAction(
343 self.tr('Compile all forms'), self.__compileAllForms)
344 self.dirMultiMenu.addSeparator()
345 self.__pyuicDirMultiConfigAct = self.dirMultiMenu.addAction(
346 self.tr('Configure uic Compiler'),
347 self.__configureUicCompiler)
348 self.dirMultiMenu.addSeparator()
349 else:
350 if self.hooks["compileAllForms"] is not None:
351 self.dirMultiMenu.addAction(
352 self.hooksMenuEntries.get(
353 "compileAllForms",
354 self.tr('Compile all forms')),
355 self.__compileAllForms)
356 self.dirMultiMenu.addSeparator()
357 self.dirMultiMenu.addAction(
358 self.tr('Add forms...'), self.project.addUiFiles)
359 self.dirMultiMenu.addAction(
360 self.tr('Add forms directory...'), self.project.addUiDir)
361 self.dirMultiMenu.addSeparator()
362 self.dirMultiMenu.addAction(
363 self.tr('Expand all directories'), self._expandAllDirs)
364 self.dirMultiMenu.addAction(
365 self.tr('Collapse all directories'), self._collapseAllDirs)
366 self.dirMultiMenu.addSeparator()
367 self.dirMultiMenu.addAction(
368 self.tr('Configure...'), self._configure)
369
370 self.menu.aboutToShow.connect(self.__showContextMenu)
371 self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
372 self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
373 self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti)
374 self.backMenu.aboutToShow.connect(self.__showContextMenuBack)
375 self.mainMenu = self.menu
376
377 def _contextMenuRequested(self, coord):
378 """
379 Protected slot to show the context menu.
380
381 @param coord the position of the mouse pointer (QPoint)
382 """
383 if not self.project.isOpen():
384 return
385
386 enable = (
387 self.project.getProjectType() in ("PyQt5", "PyQt6", "E7Plugin")
388 )
389 self.__pyuicConfigAct.setEnabled(enable)
390 self.__pyuicMultiConfigAct.setEnabled(enable)
391 self.__pyuicDirConfigAct.setEnabled(enable)
392 self.__pyuicDirMultiConfigAct.setEnabled(enable)
393 self.__pyuicBackConfigAct.setEnabled(enable)
394
395 with contextlib.suppress(Exception):
396 categories = self.getSelectedItemsCountCategorized(
397 [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem])
398 cnt = categories["sum"]
399 if cnt <= 1:
400 index = self.indexAt(coord)
401 if index.isValid():
402 self._selectSingleItem(index)
403 categories = self.getSelectedItemsCountCategorized(
404 [ProjectBrowserFileItem,
405 ProjectBrowserSimpleDirectoryItem])
406 cnt = categories["sum"]
407
408 bfcnt = categories[str(ProjectBrowserFileItem)]
409 sdcnt = categories[str(ProjectBrowserSimpleDirectoryItem)]
410 if cnt > 1 and cnt == bfcnt:
411 self.multiMenu.popup(self.mapToGlobal(coord))
412 elif cnt > 1 and cnt == sdcnt:
413 self.dirMultiMenu.popup(self.mapToGlobal(coord))
414 else:
415 index = self.indexAt(coord)
416 if cnt == 1 and index.isValid():
417 if bfcnt == 1:
418 self.menu.popup(self.mapToGlobal(coord))
419 elif sdcnt == 1:
420 self.dirMenu.popup(self.mapToGlobal(coord))
421 else:
422 self.backMenu.popup(self.mapToGlobal(coord))
423 else:
424 self.backMenu.popup(self.mapToGlobal(coord))
425
426 def __showContextMenu(self):
427 """
428 Private slot called by the menu aboutToShow signal.
429 """
430 ProjectBaseBrowser._showContextMenu(self, self.menu)
431
432 self.showMenu.emit("Main", self.menu)
433
434 def __showContextMenuMulti(self):
435 """
436 Private slot called by the multiMenu aboutToShow signal.
437 """
438 ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu)
439
440 self.showMenu.emit("MainMulti", self.multiMenu)
441
442 def __showContextMenuDir(self):
443 """
444 Private slot called by the dirMenu aboutToShow signal.
445 """
446 ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu)
447
448 self.showMenu.emit("MainDir", self.dirMenu)
449
450 def __showContextMenuDirMulti(self):
451 """
452 Private slot called by the dirMultiMenu aboutToShow signal.
453 """
454 ProjectBaseBrowser._showContextMenuDirMulti(self, self.dirMultiMenu)
455
456 self.showMenu.emit("MainDirMulti", self.dirMultiMenu)
457
458 def __showContextMenuBack(self):
459 """
460 Private slot called by the backMenu aboutToShow signal.
461 """
462 ProjectBaseBrowser._showContextMenuBack(self, self.backMenu)
463
464 self.showMenu.emit("MainBack", self.backMenu)
465
466 def __addFormFiles(self):
467 """
468 Private method to add form files to the project.
469 """
470 itm = self.model().item(self.currentIndex())
471 if isinstance(itm, ProjectBrowserFileItem):
472 dn = os.path.dirname(itm.fileName())
473 elif isinstance(
474 itm,
475 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem)
476 ):
477 dn = itm.dirName()
478 else:
479 dn = None
480 self.project.addFiles('form', dn)
481
482 def __addFormsDirectory(self):
483 """
484 Private method to add form files of a directory to the project.
485 """
486 itm = self.model().item(self.currentIndex())
487 if isinstance(itm, ProjectBrowserFileItem):
488 dn = os.path.dirname(itm.fileName())
489 elif isinstance(
490 itm,
491 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem)
492 ):
493 dn = itm.dirName()
494 else:
495 dn = None
496 self.project.addDirectory('form', dn)
497
498 def __openFile(self):
499 """
500 Private slot to handle the Open menu action.
501 """
502 itmList = self.getSelectedItems()
503 for itm in itmList[:]:
504 with contextlib.suppress(Exception):
505 if isinstance(itm, ProjectBrowserFileItem):
506 # hook support
507 if self.hooks["open"] is not None:
508 self.hooks["open"](itm.fileName())
509 else:
510 self.designerFile.emit(itm.fileName())
511
512 def __openFileInEditor(self):
513 """
514 Private slot to handle the Open in Editor menu action.
515 """
516 itmList = self.getSelectedItems()
517 for itm in itmList[:]:
518 self.sourceFile.emit(itm.fileName())
519
520 def _openItem(self):
521 """
522 Protected slot to handle the open popup menu entry.
523 """
524 itmList = self.getSelectedItems()
525 for itm in itmList:
526 if isinstance(itm, ProjectBrowserFileItem):
527 if itm.isDesignerFile():
528 self.designerFile.emit(itm.fileName())
529 else:
530 self.sourceFile.emit(itm.fileName())
531
532 def __UIPreview(self):
533 """
534 Private slot to handle the Preview menu action.
535 """
536 itmList = self.getSelectedItems()
537 self.uipreview.emit(itmList[0].fileName())
538
539 def __TRPreview(self):
540 """
541 Private slot to handle the Preview translations action.
542 """
543 fileNames = []
544 for itm in self.getSelectedItems():
545 fileNames.append(itm.fileName())
546 trfiles = sorted(self.project.pdata["TRANSLATIONS"][:])
547 fileNames.extend([os.path.join(self.project.ppath, trfile)
548 for trfile in trfiles
549 if trfile.endswith('.qm')])
550 self.trpreview[list].emit(fileNames)
551
552 def __newForm(self):
553 """
554 Private slot to handle the New Form menu action.
555 """
556 itm = self.model().item(self.currentIndex())
557 if itm is None:
558 path = self.project.ppath
559 else:
560 try:
561 path = os.path.dirname(itm.fileName())
562 except AttributeError:
563 try:
564 path = itm.dirName()
565 except AttributeError:
566 path = os.path.join(self.project.ppath, itm.data(0))
567
568 if self.hooks["newForm"] is not None:
569 self.hooks["newForm"](path)
570 else:
571 if self.project.getProjectType() in [
572 "PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"
573 ]:
574 self.__newUiForm(path)
575
576 def __newUiForm(self, path):
577 """
578 Private slot to handle the New Form menu action for Qt-related
579 projects.
580
581 @param path full directory path for the new form file (string)
582 """
583 selectedForm, ok = QInputDialog.getItem(
584 None,
585 self.tr("New Form"),
586 self.tr("Select a form type:"),
587 self.templateTypes4,
588 0, False)
589 if not ok or not selectedForm:
590 # user pressed cancel
591 return
592
593 templateIndex = self.templateTypes4.index(selectedForm)
594 templateFile = os.path.join(
595 getConfig('ericTemplatesDir'), self.templates4[templateIndex])
596
597 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
598 self,
599 self.tr("New Form"),
600 path,
601 self.tr("Qt User-Interface Files (*.ui);;All Files (*)"),
602 "",
603 EricFileDialog.DontConfirmOverwrite)
604
605 if not fname:
606 # user aborted or didn't enter a filename
607 return
608
609 fpath = pathlib.Path(fname)
610 if not fpath.suffix:
611 ex = selectedFilter.split("(*")[1].split(")")[0]
612 if ex:
613 fpath = fpath.with_suffix(ex)
614 if fpath.exists():
615 res = EricMessageBox.yesNo(
616 self,
617 self.tr("New Form"),
618 self.tr("The file already exists! Overwrite it?"),
619 icon=EricMessageBox.Warning)
620 if not res:
621 # user selected to not overwrite
622 return
623
624 try:
625 shutil.copy(templateFile, fpath)
626 except OSError as err:
627 EricMessageBox.critical(
628 self,
629 self.tr("New Form"),
630 self.tr(
631 "<p>The new form file <b>{0}</b> could not be created.<br>"
632 "Problem: {1}</p>").format(fpath, str(err)))
633 return
634
635 self.project.appendFile(str(fpath))
636 self.designerFile.emit(str(fpath))
637
638 def __deleteFile(self):
639 """
640 Private method to delete a form file from the project.
641 """
642 itmList = self.getSelectedItems()
643
644 files = []
645 fullNames = []
646 for itm in itmList:
647 fn2 = itm.fileName()
648 fullNames.append(fn2)
649 fn = self.project.getRelativePath(fn2)
650 files.append(fn)
651
652 from UI.DeleteFilesConfirmationDialog import (
653 DeleteFilesConfirmationDialog
654 )
655 dlg = DeleteFilesConfirmationDialog(
656 self.parent(),
657 self.tr("Delete forms"),
658 self.tr(
659 "Do you really want to delete these forms from the project?"),
660 files)
661
662 if dlg.exec() == QDialog.DialogCode.Accepted:
663 for fn2, fn in zip(fullNames, files):
664 self.closeSourceWindow.emit(fn2)
665 self.project.deleteFile(fn)
666
667 ###########################################################################
668 ## Methods to handle the various compile commands
669 ###########################################################################
670
671 def __resetUiCompiler(self):
672 """
673 Private slot to reset the determined UI compiler executable.
674 """
675 self.__uicompiler = ""
676
677 def __determineUiCompiler(self):
678 """
679 Private method to determine the UI compiler for the project.
680 """
681 self.__resetUiCompiler()
682
683 if self.project.getProjectLanguage() == "Python3":
684 if self.project.getProjectType() in ["PyQt5"]:
685 self.__uicompiler = Utilities.generatePyQtToolPath(
686 'pyuic5', ["py3uic5"])
687 elif self.project.getProjectType() in ["PyQt6", "E7Plugin"]:
688 self.__uicompiler = Utilities.generatePyQtToolPath(
689 'pyuic6')
690 elif self.project.getProjectType() == "PySide2":
691 self.__uicompiler = Utilities.generatePySideToolPath(
692 'pyside2-uic', variant=2)
693 elif self.project.getProjectType() == "PySide6":
694 self.__uicompiler = Utilities.generatePySideToolPath(
695 'pyside6-uic', variant=6)
696
697 def getUiCompiler(self):
698 """
699 Public method to get the UI compiler executable of the project.
700
701 @return UI compiler executable
702 @rtype str
703 """
704 if not self.__uicompiler:
705 self.__determineUiCompiler()
706
707 return self.__uicompiler
708
709 def __readStdout(self):
710 """
711 Private slot to handle the readyReadStandardOutput signal of the
712 pyuic5/pyuic6/pyside2-uic/pyside6-uic process.
713 """
714 if self.compileProc is None:
715 return
716 self.compileProc.setReadChannel(QProcess.ProcessChannel.StandardOutput)
717
718 while self.compileProc and self.compileProc.canReadLine():
719 self.buf += str(self.compileProc.readLine(),
720 "utf-8", 'replace')
721
722 def __readStderr(self):
723 """
724 Private slot to handle the readyReadStandardError signal of the
725 pyuic5/pyuic6/pyside2-uic/pyside6-uic process.
726 """
727 if self.compileProc is None:
728 return
729
730 ioEncoding = Preferences.getSystem("IOEncoding")
731
732 self.compileProc.setReadChannel(QProcess.ProcessChannel.StandardError)
733 while self.compileProc and self.compileProc.canReadLine():
734 s = self.__uicompiler + ': '
735 error = str(self.compileProc.readLine(),
736 ioEncoding, 'replace')
737 s += error
738 self.appendStderr.emit(s)
739
740 def __compileUIDone(self, exitCode, exitStatus):
741 """
742 Private slot to handle the finished signal of the pyuic/rbuic process.
743
744 @param exitCode exit code of the process (integer)
745 @param exitStatus exit status of the process (QProcess.ExitStatus)
746 """
747 self.compileRunning = False
748 ericApp().getObject("ViewManager").enableEditorsCheckFocusIn(True)
749 ui = ericApp().getObject("UserInterface")
750 if (
751 exitStatus == QProcess.ExitStatus.NormalExit and
752 exitCode == 0 and
753 self.buf
754 ):
755 ofn = os.path.join(self.project.ppath, self.compiledFile)
756 try:
757 newline = (None if self.project.useSystemEol()
758 else self.project.getEolString())
759 with open(ofn, "w", encoding="utf-8", newline=newline) as f:
760 for line in self.buf.splitlines():
761 f.write(line + "\n")
762 if self.compiledFile not in self.project.pdata["SOURCES"]:
763 self.project.appendFile(ofn)
764 ui.showNotification(
765 UI.PixmapCache.getPixmap("designer48"),
766 self.tr("Form Compilation"),
767 self.tr("The compilation of the form file"
768 " was successful."))
769 self.project.projectFormCompiled.emit(self.compiledFile)
770 except OSError as msg:
771 ui.showNotification(
772 UI.PixmapCache.getPixmap("designer48"),
773 self.tr("Form Compilation"),
774 self.tr(
775 "<p>The compilation of the form file failed.</p>"
776 "<p>Reason: {0}</p>").format(str(msg)),
777 kind=NotificationTypes.CRITICAL,
778 timeout=0)
779 else:
780 ui.showNotification(
781 UI.PixmapCache.getPixmap("designer48"),
782 self.tr("Form Compilation"),
783 self.tr("The compilation of the form file failed."),
784 kind=NotificationTypes.CRITICAL,
785 timeout=0)
786 self.compileProc = None
787
788 def __compileUI(self, fn, noDialog=False, progress=None):
789 """
790 Private method to compile a .ui file to a .py/.rb file.
791
792 @param fn filename of the .ui file to be compiled
793 @param noDialog flag indicating silent operations
794 @param progress reference to the progress dialog
795 @return reference to the compile process (QProcess)
796 """
797 self.compileProc = QProcess()
798 args = []
799 self.buf = ""
800
801 uicompiler = self.getUiCompiler()
802 if not uicompiler:
803 return None
804
805 ofn, ext = os.path.splitext(fn)
806 fn = os.path.join(self.project.ppath, fn)
807
808 if self.project.getProjectLanguage() == "Python3":
809 dirname, filename = os.path.split(ofn)
810 self.compiledFile = os.path.join(dirname, "Ui_" + filename + ".py")
811
812 if self.project.getProjectType() == "PySide2":
813 # PySide2
814 if Preferences.getQt("PySide2FromImports"):
815 args.append("--from-imports")
816 elif self.project.getProjectType() == "PySide6":
817 # PySide6
818 if Preferences.getQt("PySide6FromImports"):
819 args.append("--from-imports")
820 elif self.project.getProjectType() in ("PyQt6", "E7Plugin"):
821 # PyQt6 and E7Plugin
822 if Preferences.getQt("Pyuic6Execute"):
823 args.append("-x")
824 indentWidth = Preferences.getQt("Pyuic6Indent")
825 if indentWidth != self.Pyuic6IndentDefault:
826 args.append("--indent={0}".format(indentWidth))
827 else:
828 # PyQt5
829 if Preferences.getQt("PyuicExecute"):
830 args.append("-x")
831 indentWidth = Preferences.getQt("PyuicIndent")
832 if indentWidth != self.Pyuic5IndentDefault:
833 args.append("--indent={0}".format(indentWidth))
834 if (
835 'uic5' in uicompiler and
836 self.project.pdata["UICPARAMS"]["Package"]
837 ):
838 args.append("--import-from={0}".format(
839 self.project.pdata["UICPARAMS"]["Package"]))
840 elif Preferences.getQt("PyuicFromImports"):
841 args.append("--from-imports")
842 if self.project.pdata["UICPARAMS"]["RcSuffix"]:
843 args.append("--resource-suffix={0}".format(
844 self.project.pdata["UICPARAMS"]["RcSuffix"]))
845 elif self.project.getProjectLanguage() == "Ruby":
846 self.compiledFile = ofn + '.rb'
847 args.append('-x')
848
849 args.append(fn)
850 self.compileProc.finished.connect(self.__compileUIDone)
851 self.compileProc.readyReadStandardOutput.connect(self.__readStdout)
852 self.compileProc.readyReadStandardError.connect(self.__readStderr)
853
854 self.noDialog = noDialog
855 self.compileProc.setWorkingDirectory(self.project.getProjectPath())
856 self.compileProc.start(uicompiler, args)
857 procStarted = self.compileProc.waitForStarted(5000)
858 if procStarted:
859 self.compileRunning = True
860 ericApp().getObject("ViewManager").enableEditorsCheckFocusIn(False)
861 return self.compileProc
862 else:
863 self.compileRunning = False
864 if progress is not None:
865 progress.cancel()
866 EricMessageBox.critical(
867 self,
868 self.tr('Process Generation Error'),
869 self.tr(
870 'Could not start {0}.<br>'
871 'Ensure that it is in the search path.'
872 ).format(uicompiler))
873 return None
874
875 def __generateDialogCode(self):
876 """
877 Private method to generate dialog code for the form (Qt only).
878 """
879 itm = self.model().item(self.currentIndex())
880 fn = itm.fileName()
881
882 if self.hooks["generateDialogCode"] is not None:
883 self.hooks["generateDialogCode"](fn)
884 else:
885 from .CreateDialogCodeDialog import CreateDialogCodeDialog
886
887 # change environment
888 sys.path.insert(0, self.project.getProjectPath())
889 cwd = os.getcwd()
890 os.chdir(os.path.dirname(os.path.abspath(fn)))
891
892 dlg = CreateDialogCodeDialog(fn, self.project, self)
893 if not dlg.initError():
894 dlg.exec()
895
896 # reset the environment
897 os.chdir(cwd)
898 del sys.path[0]
899
900 def __compileForm(self):
901 """
902 Private method to compile a form to a source file.
903 """
904 itm = self.model().item(self.currentIndex())
905 fn2 = itm.fileName()
906 fn = self.project.getRelativePath(fn2)
907 if self.hooks["compileForm"] is not None:
908 self.hooks["compileForm"](fn)
909 else:
910 self.__compileUI(fn)
911
912 def __compileAllForms(self):
913 """
914 Private method to compile all forms to source files.
915 """
916 if self.hooks["compileAllForms"] is not None:
917 self.hooks["compileAllForms"](self.project.pdata["FORMS"])
918 else:
919 numForms = len(self.project.pdata["FORMS"])
920 progress = EricProgressDialog(
921 self.tr("Compiling forms..."),
922 self.tr("Abort"), 0, numForms,
923 self.tr("%v/%m Forms"), self)
924 progress.setModal(True)
925 progress.setMinimumDuration(0)
926 progress.setWindowTitle(self.tr("Forms"))
927
928 for prog, fn in enumerate(self.project.pdata["FORMS"]):
929 progress.setValue(prog)
930 if progress.wasCanceled():
931 break
932
933 proc = self.__compileUI(fn, True, progress)
934 if proc is not None:
935 while proc.state() == QProcess.ProcessState.Running:
936 QThread.msleep(100)
937 QApplication.processEvents()
938 else:
939 break
940 progress.setValue(numForms)
941
942 def __compileSelectedForms(self):
943 """
944 Private method to compile selected forms to source files.
945 """
946 items = self.getSelectedItems()
947 files = [self.project.getRelativePath(itm.fileName())
948 for itm in items]
949
950 if self.hooks["compileSelectedForms"] is not None:
951 self.hooks["compileSelectedForms"](files)
952 else:
953 numForms = len(files)
954 progress = EricProgressDialog(
955 self.tr("Compiling forms..."),
956 self.tr("Abort"), 0, numForms,
957 self.tr("%v/%m Forms"), self)
958 progress.setModal(True)
959 progress.setMinimumDuration(0)
960 progress.setWindowTitle(self.tr("Forms"))
961
962 for prog, fn in enumerate(files):
963 progress.setValue(prog)
964 if progress.wasCanceled():
965 break
966
967 proc = self.__compileUI(fn, True, progress)
968 if proc is not None:
969 while proc.state() == QProcess.ProcessState.Running:
970 QThread.msleep(100)
971 QApplication.processEvents()
972 else:
973 break
974 progress.setValue(numForms)
975
976 def compileChangedForms(self):
977 """
978 Public method to compile all changed forms to source files.
979 """
980 if self.hooks["compileChangedForms"] is not None:
981 self.hooks["compileChangedForms"](self.project.pdata["FORMS"])
982 else:
983 if self.project.getProjectType() not in [
984 "PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"
985 ]:
986 # ignore the request for non Qt GUI projects
987 return
988
989 if len(self.project.pdata["FORMS"]) == 0:
990 # The project does not contain form files.
991 return
992
993 progress = EricProgressDialog(
994 self.tr("Determining changed forms..."),
995 self.tr("Abort"), 0, 100, self.tr("%v/%m Forms"), self)
996 progress.setMinimumDuration(0)
997 progress.setWindowTitle(self.tr("Forms"))
998
999 # get list of changed forms
1000 changedForms = []
1001 progress.setMaximum(len(self.project.pdata["FORMS"]))
1002 for prog, fn in enumerate(self.project.pdata["FORMS"]):
1003 progress.setValue(prog)
1004 QApplication.processEvents()
1005
1006 ifn = os.path.join(self.project.ppath, fn)
1007 if self.project.getProjectLanguage() == "Python3":
1008 dirname, filename = os.path.split(os.path.splitext(ifn)[0])
1009 ofn = os.path.join(dirname, "Ui_" + filename + ".py")
1010 elif self.project.getProjectLanguage() == "Ruby":
1011 ofn = os.path.splitext(ifn)[0] + '.rb'
1012 if (
1013 not os.path.exists(ofn) or
1014 os.stat(ifn).st_mtime > os.stat(ofn).st_mtime
1015 ):
1016 changedForms.append(fn)
1017 progress.setValue(len(self.project.pdata["FORMS"]))
1018 QApplication.processEvents()
1019
1020 if changedForms:
1021 progress.setLabelText(
1022 self.tr("Compiling changed forms..."))
1023 progress.setMaximum(len(changedForms))
1024 progress.setValue(prog)
1025 QApplication.processEvents()
1026 for prog, fn in enumerate(changedForms):
1027 progress.setValue(prog)
1028 if progress.wasCanceled():
1029 break
1030
1031 proc = self.__compileUI(fn, True, progress)
1032 if proc is not None:
1033 while proc.state() == QProcess.ProcessState.Running:
1034 QApplication.processEvents()
1035 QThread.msleep(300)
1036 QApplication.processEvents()
1037 else:
1038 break
1039 progress.setValue(len(changedForms))
1040 QApplication.processEvents()
1041
1042 def handlePreferencesChanged(self):
1043 """
1044 Public slot used to handle the preferencesChanged signal.
1045 """
1046 ProjectBaseBrowser.handlePreferencesChanged(self)
1047
1048 self.__resetUiCompiler()
1049
1050 def __configureUicCompiler(self):
1051 """
1052 Private slot to configure some non-common uic compiler options.
1053 """
1054 from .UicCompilerOptionsDialog import UicCompilerOptionsDialog
1055
1056 params = self.project.pdata["UICPARAMS"]
1057
1058 if self.project.getProjectType() in ["PyQt5", "PyQt6", "E7Plugin"]:
1059 dlg = UicCompilerOptionsDialog(params, self.getUiCompiler())
1060 if dlg.exec() == QDialog.DialogCode.Accepted:
1061 package, suffix, root = dlg.getData()
1062 if package != params["Package"]:
1063 params["Package"] = package
1064 self.project.setDirty(True)
1065 if suffix != params["RcSuffix"]:
1066 params["RcSuffix"] = suffix
1067 self.project.setDirty(True)
1068 if root != params["PackagesRoot"]:
1069 params["PackagesRoot"] = root
1070 self.project.setDirty(True)
1071
1072 ###########################################################################
1073 ## Support for hooks below
1074 ###########################################################################
1075
1076 def _initHookMethods(self):
1077 """
1078 Protected method to initialize the hooks dictionary.
1079
1080 Supported hook methods are:
1081 <ul>
1082 <li>compileForm: takes filename as parameter</li>
1083 <li>compileAllForms: takes list of filenames as parameter</li>
1084 <li>compileSelectedForms: takes list of filenames as parameter</li>
1085 <li>compileChangedForms: takes list of filenames as parameter</li>
1086 <li>generateDialogCode: takes filename as parameter</li>
1087 <li>newForm: takes full directory path of new file as parameter</li>
1088 <li>open: takes a filename as parameter</li>
1089 </ul>
1090
1091 <b>Note</b>: Filenames are relative to the project directory, if not
1092 specified differently.
1093 """
1094 self.hooks = {
1095 "compileForm": None,
1096 "compileAllForms": None,
1097 "compileChangedForms": None,
1098 "compileSelectedForms": None,
1099 "generateDialogCode": None,
1100 "newForm": None,
1101 "open": None,
1102 }

eric ide

mercurial