|
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 } |