|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2002 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a class used to display the forms part of the project. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import sys |
|
12 import shutil |
|
13 import contextlib |
|
14 |
|
15 from PyQt5.QtCore import QThread, QFileInfo, pyqtSignal, QProcess |
|
16 from PyQt5.QtWidgets import QDialog, QInputDialog, QApplication, QMenu |
|
17 |
|
18 from E5Gui.E5Application import e5App |
|
19 from E5Gui import E5MessageBox, E5FileDialog |
|
20 from E5Gui.E5ProgressDialog import E5ProgressDialog |
|
21 |
|
22 from .ProjectBrowserModel import ( |
|
23 ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem, |
|
24 ProjectBrowserDirectoryItem, ProjectBrowserFormType |
|
25 ) |
|
26 from .ProjectBaseBrowser import ProjectBaseBrowser |
|
27 |
|
28 import UI.PixmapCache |
|
29 from UI.NotificationWidget import NotificationTypes |
|
30 |
|
31 |
|
32 import Preferences |
|
33 import Utilities |
|
34 |
|
35 from eric6config import getConfig |
|
36 |
|
37 |
|
38 class ProjectFormsBrowser(ProjectBaseBrowser): |
|
39 """ |
|
40 A class used to display the forms part of the project. |
|
41 |
|
42 @signal appendStderr(str) emitted after something was received from |
|
43 a QProcess on stderr |
|
44 @signal uipreview(str) emitted to preview a forms file |
|
45 @signal showMenu(str, QMenu) emitted when a menu is about to be shown. The |
|
46 name of the menu and a reference to the menu are given. |
|
47 @signal menusAboutToBeCreated() emitted when the context menus are about to |
|
48 be created. This is the right moment to add or remove hook methods. |
|
49 """ |
|
50 appendStderr = pyqtSignal(str) |
|
51 uipreview = pyqtSignal(str) |
|
52 showMenu = pyqtSignal(str, QMenu) |
|
53 menusAboutToBeCreated = pyqtSignal() |
|
54 |
|
55 Pyuic5IndentDefault = 4 |
|
56 Pyuic6IndentDefault = 4 |
|
57 |
|
58 def __init__(self, project, parent=None): |
|
59 """ |
|
60 Constructor |
|
61 |
|
62 @param project reference to the project object |
|
63 @param parent parent widget of this browser (QWidget) |
|
64 """ |
|
65 ProjectBaseBrowser.__init__(self, project, ProjectBrowserFormType, |
|
66 parent) |
|
67 |
|
68 self.selectedItemsFilter = [ProjectBrowserFileItem, |
|
69 ProjectBrowserSimpleDirectoryItem] |
|
70 |
|
71 self.setWindowTitle(self.tr('Forms')) |
|
72 |
|
73 self.setWhatsThis(self.tr( |
|
74 """<b>Project Forms Browser</b>""" |
|
75 """<p>This allows to easily see all forms contained in the""" |
|
76 """ current project. Several actions can be executed via the""" |
|
77 """ context menu.</p>""" |
|
78 )) |
|
79 |
|
80 # templates for Qt |
|
81 # these two lists have to stay in sync |
|
82 self.templates4 = [ |
|
83 'dialog4.tmpl', 'widget4.tmpl', 'mainwindow4.tmpl', |
|
84 'dialogbuttonboxbottom4.tmpl', 'dialogbuttonboxright4.tmpl', |
|
85 'dialogbuttonsbottom4.tmpl', 'dialogbuttonsbottomcenter4.tmpl', |
|
86 'dialogbuttonsright4.tmpl', '', 'wizard4.tmpl', 'wizardpage4.tmpl', |
|
87 'qdockwidget4.tmpl', 'qframe4.tmpl', 'qgroupbox4.tmpl', |
|
88 'qscrollarea4.tmpl', 'qmdiarea4.tmpl', 'qtabwidget4.tmpl', |
|
89 'qtoolbox4.tmpl', 'qstackedwidget4.tmpl' |
|
90 ] |
|
91 self.templateTypes4 = [ |
|
92 self.tr("Dialog"), |
|
93 self.tr("Widget"), |
|
94 self.tr("Main Window"), |
|
95 self.tr("Dialog with Buttonbox (Bottom)"), |
|
96 self.tr("Dialog with Buttonbox (Right)"), |
|
97 self.tr("Dialog with Buttons (Bottom)"), |
|
98 self.tr("Dialog with Buttons (Bottom-Center)"), |
|
99 self.tr("Dialog with Buttons (Right)"), |
|
100 '', |
|
101 self.tr("QWizard"), |
|
102 self.tr("QWizardPage"), |
|
103 self.tr("QDockWidget"), |
|
104 self.tr("QFrame"), |
|
105 self.tr("QGroupBox"), |
|
106 self.tr("QScrollArea"), |
|
107 self.tr("QMdiArea"), |
|
108 self.tr("QTabWidget"), |
|
109 self.tr("QToolBox"), |
|
110 self.tr("QStackedWidget"), |
|
111 ] |
|
112 |
|
113 self.compileProc = None |
|
114 self.__uicompiler = "" |
|
115 |
|
116 self.project.projectClosed.connect(self.__resetUiCompiler) |
|
117 self.project.projectPropertiesChanged.connect(self.__resetUiCompiler) |
|
118 |
|
119 def _createPopupMenus(self): |
|
120 """ |
|
121 Protected overloaded method to generate the popup menu. |
|
122 """ |
|
123 self.menuActions = [] |
|
124 self.multiMenuActions = [] |
|
125 self.dirMenuActions = [] |
|
126 self.dirMultiMenuActions = [] |
|
127 |
|
128 self.menusAboutToBeCreated.emit() |
|
129 |
|
130 projectType = self.project.getProjectType() |
|
131 |
|
132 self.menu = QMenu(self) |
|
133 if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: |
|
134 self.menu.addAction( |
|
135 self.tr('Compile form'), self.__compileForm) |
|
136 self.menu.addAction( |
|
137 self.tr('Compile all forms'), |
|
138 self.__compileAllForms) |
|
139 self.menu.addAction( |
|
140 self.tr('Generate Dialog Code...'), |
|
141 self.__generateDialogCode) |
|
142 self.menu.addSeparator() |
|
143 self.__pyuicConfigAct = self.menu.addAction( |
|
144 self.tr('Configure uic Compiler'), |
|
145 self.__configureUicCompiler) |
|
146 self.menu.addSeparator() |
|
147 self.menu.addAction( |
|
148 self.tr('Open in Qt-Designer'), self.__openFile) |
|
149 self.menu.addAction( |
|
150 self.tr('Open in Editor'), self.__openFileInEditor) |
|
151 self.menu.addSeparator() |
|
152 self.menu.addAction(self.tr('Preview form'), self.__UIPreview) |
|
153 self.menu.addAction( |
|
154 self.tr('Preview translations'), self.__TRPreview) |
|
155 else: |
|
156 if self.hooks["compileForm"] is not None: |
|
157 self.menu.addAction( |
|
158 self.hooksMenuEntries.get( |
|
159 "compileForm", |
|
160 self.tr('Compile form')), self.__compileForm) |
|
161 if self.hooks["compileAllForms"] is not None: |
|
162 self.menu.addAction( |
|
163 self.hooksMenuEntries.get( |
|
164 "compileAllForms", |
|
165 self.tr('Compile all forms')), |
|
166 self.__compileAllForms) |
|
167 if self.hooks["generateDialogCode"] is not None: |
|
168 self.menu.addAction( |
|
169 self.hooksMenuEntries.get( |
|
170 "generateDialogCode", |
|
171 self.tr('Generate Dialog Code...')), |
|
172 self.__generateDialogCode) |
|
173 if ( |
|
174 self.hooks["compileForm"] is not None or |
|
175 self.hooks["compileAllForms"] is not None or |
|
176 self.hooks["generateDialogCode"] is not None |
|
177 ): |
|
178 self.menu.addSeparator() |
|
179 if self.hooks["open"] is not None: |
|
180 self.menu.addAction( |
|
181 self.hooksMenuEntries.get("open", self.tr('Open')), |
|
182 self.__openFile) |
|
183 self.menu.addAction(self.tr('Open'), self.__openFileInEditor) |
|
184 self.menu.addSeparator() |
|
185 act = self.menu.addAction(self.tr('Rename file'), self._renameFile) |
|
186 self.menuActions.append(act) |
|
187 act = self.menu.addAction( |
|
188 self.tr('Remove from project'), self._removeFile) |
|
189 self.menuActions.append(act) |
|
190 act = self.menu.addAction(self.tr('Delete'), self.__deleteFile) |
|
191 self.menuActions.append(act) |
|
192 self.menu.addSeparator() |
|
193 if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: |
|
194 self.menu.addAction(self.tr('New form...'), self.__newForm) |
|
195 else: |
|
196 if self.hooks["newForm"] is not None: |
|
197 self.menu.addAction( |
|
198 self.hooksMenuEntries.get( |
|
199 "newForm", self.tr('New form...')), self.__newForm) |
|
200 self.menu.addAction(self.tr('Add forms...'), self.__addFormFiles) |
|
201 self.menu.addAction( |
|
202 self.tr('Add forms directory...'), self.__addFormsDirectory) |
|
203 self.menu.addSeparator() |
|
204 self.menu.addAction( |
|
205 self.tr('Copy Path to Clipboard'), self._copyToClipboard) |
|
206 self.menu.addSeparator() |
|
207 self.menu.addAction( |
|
208 self.tr('Expand all directories'), self._expandAllDirs) |
|
209 self.menu.addAction( |
|
210 self.tr('Collapse all directories'), self._collapseAllDirs) |
|
211 self.menu.addSeparator() |
|
212 self.menu.addAction(self.tr('Configure...'), self._configure) |
|
213 |
|
214 self.backMenu = QMenu(self) |
|
215 if ( |
|
216 projectType in [ |
|
217 "PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6" |
|
218 ] or self.hooks["compileAllForms"] is not None |
|
219 ): |
|
220 self.backMenu.addAction( |
|
221 self.tr('Compile all forms'), self.__compileAllForms) |
|
222 self.backMenu.addSeparator() |
|
223 self.__pyuicBackConfigAct = self.backMenu.addAction( |
|
224 self.tr('Configure uic Compiler'), |
|
225 self.__configureUicCompiler) |
|
226 self.backMenu.addSeparator() |
|
227 self.backMenu.addAction(self.tr('New form...'), self.__newForm) |
|
228 else: |
|
229 if self.hooks["newForm"] is not None: |
|
230 self.backMenu.addAction( |
|
231 self.hooksMenuEntries.get( |
|
232 "newForm", self.tr('New form...')), self.__newForm) |
|
233 self.backMenu.addAction( |
|
234 self.tr('Add forms...'), self.project.addUiFiles) |
|
235 self.backMenu.addAction( |
|
236 self.tr('Add forms directory...'), self.project.addUiDir) |
|
237 self.backMenu.addSeparator() |
|
238 self.backMenu.addAction( |
|
239 self.tr('Expand all directories'), self._expandAllDirs) |
|
240 self.backMenu.addAction( |
|
241 self.tr('Collapse all directories'), self._collapseAllDirs) |
|
242 self.backMenu.addSeparator() |
|
243 self.backMenu.addAction(self.tr('Configure...'), self._configure) |
|
244 self.backMenu.setEnabled(False) |
|
245 |
|
246 # create the menu for multiple selected files |
|
247 self.multiMenu = QMenu(self) |
|
248 if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: |
|
249 self.multiMenu.addAction( |
|
250 self.tr('Compile forms'), self.__compileSelectedForms) |
|
251 self.multiMenu.addSeparator() |
|
252 self.__pyuicMultiConfigAct = self.multiMenu.addAction( |
|
253 self.tr('Configure uic Compiler'), |
|
254 self.__configureUicCompiler) |
|
255 self.multiMenu.addSeparator() |
|
256 self.multiMenu.addAction( |
|
257 self.tr('Open in Qt-Designer'), self.__openFile) |
|
258 self.multiMenu.addAction( |
|
259 self.tr('Open in Editor'), self.__openFileInEditor) |
|
260 self.multiMenu.addSeparator() |
|
261 self.multiMenu.addAction( |
|
262 self.tr('Preview translations'), self.__TRPreview) |
|
263 else: |
|
264 if self.hooks["compileSelectedForms"] is not None: |
|
265 act = self.multiMenu.addAction( |
|
266 self.hooksMenuEntries.get( |
|
267 "compileSelectedForms", |
|
268 self.tr('Compile forms')), |
|
269 self.__compileSelectedForms) |
|
270 self.multiMenu.addSeparator() |
|
271 if self.hooks["open"] is not None: |
|
272 self.multiMenu.addAction( |
|
273 self.hooksMenuEntries.get("open", self.tr('Open')), |
|
274 self.__openFile) |
|
275 self.multiMenu.addAction( |
|
276 self.tr('Open'), self.__openFileInEditor) |
|
277 self.multiMenu.addSeparator() |
|
278 act = self.multiMenu.addAction( |
|
279 self.tr('Remove from project'), self._removeFile) |
|
280 self.multiMenuActions.append(act) |
|
281 act = self.multiMenu.addAction( |
|
282 self.tr('Delete'), self.__deleteFile) |
|
283 self.multiMenuActions.append(act) |
|
284 self.multiMenu.addSeparator() |
|
285 self.multiMenu.addAction( |
|
286 self.tr('Expand all directories'), self._expandAllDirs) |
|
287 self.multiMenu.addAction( |
|
288 self.tr('Collapse all directories'), self._collapseAllDirs) |
|
289 self.multiMenu.addSeparator() |
|
290 self.multiMenu.addAction(self.tr('Configure...'), self._configure) |
|
291 |
|
292 self.dirMenu = QMenu(self) |
|
293 if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: |
|
294 self.dirMenu.addAction( |
|
295 self.tr('Compile all forms'), self.__compileAllForms) |
|
296 self.dirMenu.addSeparator() |
|
297 self.__pyuicDirConfigAct = self.dirMenu.addAction( |
|
298 self.tr('Configure uic Compiler'), |
|
299 self.__configureUicCompiler) |
|
300 self.dirMenu.addSeparator() |
|
301 else: |
|
302 if self.hooks["compileAllForms"] is not None: |
|
303 self.dirMenu.addAction( |
|
304 self.hooksMenuEntries.get( |
|
305 "compileAllForms", |
|
306 self.tr('Compile all forms')), |
|
307 self.__compileAllForms) |
|
308 self.dirMenu.addSeparator() |
|
309 act = self.dirMenu.addAction( |
|
310 self.tr('Remove from project'), self._removeDir) |
|
311 self.dirMenuActions.append(act) |
|
312 act = self.dirMenu.addAction( |
|
313 self.tr('Delete'), self._deleteDirectory) |
|
314 self.dirMenuActions.append(act) |
|
315 self.dirMenu.addSeparator() |
|
316 if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: |
|
317 self.dirMenu.addAction(self.tr('New form...'), self.__newForm) |
|
318 else: |
|
319 if self.hooks["newForm"] is not None: |
|
320 self.dirMenu.addAction( |
|
321 self.hooksMenuEntries.get( |
|
322 "newForm", |
|
323 self.tr('New form...')), self.__newForm) |
|
324 self.dirMenu.addAction( |
|
325 self.tr('Add forms...'), self.__addFormFiles) |
|
326 self.dirMenu.addAction( |
|
327 self.tr('Add forms directory...'), self.__addFormsDirectory) |
|
328 self.dirMenu.addSeparator() |
|
329 self.dirMenu.addAction( |
|
330 self.tr('Copy Path to Clipboard'), self._copyToClipboard) |
|
331 self.dirMenu.addSeparator() |
|
332 self.dirMenu.addAction( |
|
333 self.tr('Expand all directories'), self._expandAllDirs) |
|
334 self.dirMenu.addAction( |
|
335 self.tr('Collapse all directories'), self._collapseAllDirs) |
|
336 self.dirMenu.addSeparator() |
|
337 self.dirMenu.addAction(self.tr('Configure...'), self._configure) |
|
338 |
|
339 self.dirMultiMenu = QMenu(self) |
|
340 if projectType in ["PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6"]: |
|
341 self.dirMultiMenu.addAction( |
|
342 self.tr('Compile all forms'), self.__compileAllForms) |
|
343 self.dirMultiMenu.addSeparator() |
|
344 self.__pyuicDirMultiConfigAct = self.dirMultiMenu.addAction( |
|
345 self.tr('Configure uic Compiler'), |
|
346 self.__configureUicCompiler) |
|
347 self.dirMultiMenu.addSeparator() |
|
348 else: |
|
349 if self.hooks["compileAllForms"] is not None: |
|
350 self.dirMultiMenu.addAction( |
|
351 self.hooksMenuEntries.get( |
|
352 "compileAllForms", |
|
353 self.tr('Compile all forms')), |
|
354 self.__compileAllForms) |
|
355 self.dirMultiMenu.addSeparator() |
|
356 self.dirMultiMenu.addAction( |
|
357 self.tr('Add forms...'), self.project.addUiFiles) |
|
358 self.dirMultiMenu.addAction( |
|
359 self.tr('Add forms directory...'), self.project.addUiDir) |
|
360 self.dirMultiMenu.addSeparator() |
|
361 self.dirMultiMenu.addAction( |
|
362 self.tr('Expand all directories'), self._expandAllDirs) |
|
363 self.dirMultiMenu.addAction( |
|
364 self.tr('Collapse all directories'), self._collapseAllDirs) |
|
365 self.dirMultiMenu.addSeparator() |
|
366 self.dirMultiMenu.addAction( |
|
367 self.tr('Configure...'), self._configure) |
|
368 |
|
369 self.menu.aboutToShow.connect(self.__showContextMenu) |
|
370 self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti) |
|
371 self.dirMenu.aboutToShow.connect(self.__showContextMenuDir) |
|
372 self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti) |
|
373 self.backMenu.aboutToShow.connect(self.__showContextMenuBack) |
|
374 self.mainMenu = self.menu |
|
375 |
|
376 def _contextMenuRequested(self, coord): |
|
377 """ |
|
378 Protected slot to show the context menu. |
|
379 |
|
380 @param coord the position of the mouse pointer (QPoint) |
|
381 """ |
|
382 if not self.project.isOpen(): |
|
383 return |
|
384 |
|
385 enable = ( |
|
386 self.project.getProjectType() in ("PyQt5", "PyQt6", "E6Plugin") |
|
387 ) |
|
388 self.__pyuicConfigAct.setEnabled(enable) |
|
389 self.__pyuicMultiConfigAct.setEnabled(enable) |
|
390 self.__pyuicDirConfigAct.setEnabled(enable) |
|
391 self.__pyuicDirMultiConfigAct.setEnabled(enable) |
|
392 self.__pyuicBackConfigAct.setEnabled(enable) |
|
393 |
|
394 with contextlib.suppress(Exception): |
|
395 categories = self.getSelectedItemsCountCategorized( |
|
396 [ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem]) |
|
397 cnt = categories["sum"] |
|
398 if cnt <= 1: |
|
399 index = self.indexAt(coord) |
|
400 if index.isValid(): |
|
401 self._selectSingleItem(index) |
|
402 categories = self.getSelectedItemsCountCategorized( |
|
403 [ProjectBrowserFileItem, |
|
404 ProjectBrowserSimpleDirectoryItem]) |
|
405 cnt = categories["sum"] |
|
406 |
|
407 bfcnt = categories[str(ProjectBrowserFileItem)] |
|
408 sdcnt = categories[str(ProjectBrowserSimpleDirectoryItem)] |
|
409 if cnt > 1 and cnt == bfcnt: |
|
410 self.multiMenu.popup(self.mapToGlobal(coord)) |
|
411 elif cnt > 1 and cnt == sdcnt: |
|
412 self.dirMultiMenu.popup(self.mapToGlobal(coord)) |
|
413 else: |
|
414 index = self.indexAt(coord) |
|
415 if cnt == 1 and index.isValid(): |
|
416 if bfcnt == 1: |
|
417 self.menu.popup(self.mapToGlobal(coord)) |
|
418 elif sdcnt == 1: |
|
419 self.dirMenu.popup(self.mapToGlobal(coord)) |
|
420 else: |
|
421 self.backMenu.popup(self.mapToGlobal(coord)) |
|
422 else: |
|
423 self.backMenu.popup(self.mapToGlobal(coord)) |
|
424 |
|
425 def __showContextMenu(self): |
|
426 """ |
|
427 Private slot called by the menu aboutToShow signal. |
|
428 """ |
|
429 ProjectBaseBrowser._showContextMenu(self, self.menu) |
|
430 |
|
431 self.showMenu.emit("Main", self.menu) |
|
432 |
|
433 def __showContextMenuMulti(self): |
|
434 """ |
|
435 Private slot called by the multiMenu aboutToShow signal. |
|
436 """ |
|
437 ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu) |
|
438 |
|
439 self.showMenu.emit("MainMulti", self.multiMenu) |
|
440 |
|
441 def __showContextMenuDir(self): |
|
442 """ |
|
443 Private slot called by the dirMenu aboutToShow signal. |
|
444 """ |
|
445 ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu) |
|
446 |
|
447 self.showMenu.emit("MainDir", self.dirMenu) |
|
448 |
|
449 def __showContextMenuDirMulti(self): |
|
450 """ |
|
451 Private slot called by the dirMultiMenu aboutToShow signal. |
|
452 """ |
|
453 ProjectBaseBrowser._showContextMenuDirMulti(self, self.dirMultiMenu) |
|
454 |
|
455 self.showMenu.emit("MainDirMulti", self.dirMultiMenu) |
|
456 |
|
457 def __showContextMenuBack(self): |
|
458 """ |
|
459 Private slot called by the backMenu aboutToShow signal. |
|
460 """ |
|
461 ProjectBaseBrowser._showContextMenuBack(self, self.backMenu) |
|
462 |
|
463 self.showMenu.emit("MainBack", self.backMenu) |
|
464 |
|
465 def __addFormFiles(self): |
|
466 """ |
|
467 Private method to add form files to the project. |
|
468 """ |
|
469 itm = self.model().item(self.currentIndex()) |
|
470 if isinstance(itm, ProjectBrowserFileItem): |
|
471 dn = os.path.dirname(itm.fileName()) |
|
472 elif isinstance( |
|
473 itm, |
|
474 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem) |
|
475 ): |
|
476 dn = itm.dirName() |
|
477 else: |
|
478 dn = None |
|
479 self.project.addFiles('form', dn) |
|
480 |
|
481 def __addFormsDirectory(self): |
|
482 """ |
|
483 Private method to add form files of a directory to the project. |
|
484 """ |
|
485 itm = self.model().item(self.currentIndex()) |
|
486 if isinstance(itm, ProjectBrowserFileItem): |
|
487 dn = os.path.dirname(itm.fileName()) |
|
488 elif isinstance( |
|
489 itm, |
|
490 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem) |
|
491 ): |
|
492 dn = itm.dirName() |
|
493 else: |
|
494 dn = None |
|
495 self.project.addDirectory('form', dn) |
|
496 |
|
497 def __openFile(self): |
|
498 """ |
|
499 Private slot to handle the Open menu action. |
|
500 """ |
|
501 itmList = self.getSelectedItems() |
|
502 for itm in itmList[:]: |
|
503 with contextlib.suppress(Exception): |
|
504 if isinstance(itm, ProjectBrowserFileItem): |
|
505 # hook support |
|
506 if self.hooks["open"] is not None: |
|
507 self.hooks["open"](itm.fileName()) |
|
508 else: |
|
509 self.designerFile.emit(itm.fileName()) |
|
510 |
|
511 def __openFileInEditor(self): |
|
512 """ |
|
513 Private slot to handle the Open in Editor menu action. |
|
514 """ |
|
515 itmList = self.getSelectedItems() |
|
516 for itm in itmList[:]: |
|
517 self.sourceFile.emit(itm.fileName()) |
|
518 |
|
519 def _openItem(self): |
|
520 """ |
|
521 Protected slot to handle the open popup menu entry. |
|
522 """ |
|
523 itmList = self.getSelectedItems() |
|
524 for itm in itmList: |
|
525 if isinstance(itm, ProjectBrowserFileItem): |
|
526 if itm.isDesignerFile(): |
|
527 self.designerFile.emit(itm.fileName()) |
|
528 else: |
|
529 self.sourceFile.emit(itm.fileName()) |
|
530 |
|
531 def __UIPreview(self): |
|
532 """ |
|
533 Private slot to handle the Preview menu action. |
|
534 """ |
|
535 itmList = self.getSelectedItems() |
|
536 self.uipreview.emit(itmList[0].fileName()) |
|
537 |
|
538 def __TRPreview(self): |
|
539 """ |
|
540 Private slot to handle the Preview translations action. |
|
541 """ |
|
542 fileNames = [] |
|
543 for itm in self.getSelectedItems(): |
|
544 fileNames.append(itm.fileName()) |
|
545 trfiles = sorted(self.project.pdata["TRANSLATIONS"][:]) |
|
546 fileNames.extend([os.path.join(self.project.ppath, trfile) |
|
547 for trfile in trfiles |
|
548 if trfile.endswith('.qm')]) |
|
549 self.trpreview[list].emit(fileNames) |
|
550 |
|
551 def __newForm(self): |
|
552 """ |
|
553 Private slot to handle the New Form menu action. |
|
554 """ |
|
555 itm = self.model().item(self.currentIndex()) |
|
556 if itm is None: |
|
557 path = self.project.ppath |
|
558 else: |
|
559 try: |
|
560 path = os.path.dirname(itm.fileName()) |
|
561 except AttributeError: |
|
562 try: |
|
563 path = itm.dirName() |
|
564 except AttributeError: |
|
565 path = os.path.join(self.project.ppath, itm.data(0)) |
|
566 |
|
567 if self.hooks["newForm"] is not None: |
|
568 self.hooks["newForm"](path) |
|
569 else: |
|
570 if self.project.getProjectType() in [ |
|
571 "PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6" |
|
572 ]: |
|
573 self.__newUiForm(path) |
|
574 |
|
575 def __newUiForm(self, path): |
|
576 """ |
|
577 Private slot to handle the New Form menu action for Qt-related |
|
578 projects. |
|
579 |
|
580 @param path full directory path for the new form file (string) |
|
581 """ |
|
582 selectedForm, ok = QInputDialog.getItem( |
|
583 None, |
|
584 self.tr("New Form"), |
|
585 self.tr("Select a form type:"), |
|
586 self.templateTypes4, |
|
587 0, False) |
|
588 if not ok or not selectedForm: |
|
589 # user pressed cancel |
|
590 return |
|
591 |
|
592 templateIndex = self.templateTypes4.index(selectedForm) |
|
593 templateFile = os.path.join( |
|
594 getConfig('ericTemplatesDir'), self.templates4[templateIndex]) |
|
595 |
|
596 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
|
597 self, |
|
598 self.tr("New Form"), |
|
599 path, |
|
600 self.tr("Qt User-Interface Files (*.ui);;All Files (*)"), |
|
601 "", |
|
602 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
|
603 |
|
604 if not fname: |
|
605 # user aborted or didn't enter a filename |
|
606 return |
|
607 |
|
608 ext = QFileInfo(fname).suffix() |
|
609 if not ext: |
|
610 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
611 if ex: |
|
612 fname += ex |
|
613 |
|
614 if os.path.exists(fname): |
|
615 res = E5MessageBox.yesNo( |
|
616 self, |
|
617 self.tr("New Form"), |
|
618 self.tr("The file already exists! Overwrite it?"), |
|
619 icon=E5MessageBox.Warning) |
|
620 if not res: |
|
621 # user selected to not overwrite |
|
622 return |
|
623 |
|
624 try: |
|
625 shutil.copy(templateFile, fname) |
|
626 except OSError as e: |
|
627 E5MessageBox.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(fname, str(e))) |
|
633 return |
|
634 |
|
635 self.project.appendFile(fname) |
|
636 self.designerFile.emit(fname) |
|
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", "E6Plugin"]: |
|
685 self.__uicompiler = Utilities.generatePyQtToolPath( |
|
686 'pyuic5', ["py3uic5"]) |
|
687 elif self.project.getProjectType() in ["PyQt6"]: |
|
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 e5App().getObject("ViewManager").enableEditorsCheckFocusIn(True) |
|
749 ui = e5App().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() == "PyQt6": |
|
821 # PyQt6 |
|
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 e5App().getObject("ViewManager").enableEditorsCheckFocusIn(False) |
|
861 return self.compileProc |
|
862 else: |
|
863 self.compileRunning = False |
|
864 if progress is not None: |
|
865 progress.cancel() |
|
866 E5MessageBox.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 = E5ProgressDialog( |
|
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 QApplication.processEvents() |
|
937 QThread.msleep(300) |
|
938 QApplication.processEvents() |
|
939 else: |
|
940 break |
|
941 progress.setValue(numForms) |
|
942 |
|
943 def __compileSelectedForms(self): |
|
944 """ |
|
945 Private method to compile selected forms to source files. |
|
946 """ |
|
947 items = self.getSelectedItems() |
|
948 files = [self.project.getRelativePath(itm.fileName()) |
|
949 for itm in items] |
|
950 |
|
951 if self.hooks["compileSelectedForms"] is not None: |
|
952 self.hooks["compileSelectedForms"](files) |
|
953 else: |
|
954 numForms = len(files) |
|
955 progress = E5ProgressDialog( |
|
956 self.tr("Compiling forms..."), |
|
957 self.tr("Abort"), 0, numForms, |
|
958 self.tr("%v/%m Forms"), self) |
|
959 progress.setModal(True) |
|
960 progress.setMinimumDuration(0) |
|
961 progress.setWindowTitle(self.tr("Forms")) |
|
962 |
|
963 for prog, fn in enumerate(files): |
|
964 progress.setValue(prog) |
|
965 if progress.wasCanceled(): |
|
966 break |
|
967 |
|
968 proc = self.__compileUI(fn, True, progress) |
|
969 if proc is not None: |
|
970 while proc.state() == QProcess.ProcessState.Running: |
|
971 QApplication.processEvents() |
|
972 QThread.msleep(300) |
|
973 QApplication.processEvents() |
|
974 else: |
|
975 break |
|
976 progress.setValue(numForms) |
|
977 |
|
978 def compileChangedForms(self): |
|
979 """ |
|
980 Public method to compile all changed forms to source files. |
|
981 """ |
|
982 if self.hooks["compileChangedForms"] is not None: |
|
983 self.hooks["compileChangedForms"](self.project.pdata["FORMS"]) |
|
984 else: |
|
985 if self.project.getProjectType() not in [ |
|
986 "PyQt5", "PyQt6", "E6Plugin", "PySide2", "PySide6" |
|
987 ]: |
|
988 # ignore the request for non Qt GUI projects |
|
989 return |
|
990 |
|
991 progress = E5ProgressDialog( |
|
992 self.tr("Determining changed forms..."), |
|
993 self.tr("Abort"), 0, 100, self.tr("%v/%m Forms")) |
|
994 progress.setMinimumDuration(0) |
|
995 progress.setWindowTitle(self.tr("Forms")) |
|
996 |
|
997 # get list of changed forms |
|
998 changedForms = [] |
|
999 progress.setMaximum(len(self.project.pdata["FORMS"])) |
|
1000 for prog, fn in enumerate(self.project.pdata["FORMS"]): |
|
1001 progress.setValue(prog) |
|
1002 QApplication.processEvents() |
|
1003 |
|
1004 ifn = os.path.join(self.project.ppath, fn) |
|
1005 if self.project.getProjectLanguage() == "Python3": |
|
1006 dirname, filename = os.path.split(os.path.splitext(ifn)[0]) |
|
1007 ofn = os.path.join(dirname, "Ui_" + filename + ".py") |
|
1008 elif self.project.getProjectLanguage() == "Ruby": |
|
1009 ofn = os.path.splitext(ifn)[0] + '.rb' |
|
1010 if ( |
|
1011 not os.path.exists(ofn) or |
|
1012 os.stat(ifn).st_mtime > os.stat(ofn).st_mtime |
|
1013 ): |
|
1014 changedForms.append(fn) |
|
1015 progress.setValue(len(self.project.pdata["FORMS"])) |
|
1016 QApplication.processEvents() |
|
1017 |
|
1018 if changedForms: |
|
1019 progress.setLabelText( |
|
1020 self.tr("Compiling changed forms...")) |
|
1021 progress.setMaximum(len(changedForms)) |
|
1022 progress.setValue(prog) |
|
1023 QApplication.processEvents() |
|
1024 for prog, fn in enumerate(changedForms): |
|
1025 progress.setValue(prog) |
|
1026 if progress.wasCanceled(): |
|
1027 break |
|
1028 |
|
1029 proc = self.__compileUI(fn, True, progress) |
|
1030 if proc is not None: |
|
1031 while proc.state() == QProcess.ProcessState.Running: |
|
1032 QApplication.processEvents() |
|
1033 QThread.msleep(300) |
|
1034 QApplication.processEvents() |
|
1035 else: |
|
1036 break |
|
1037 progress.setValue(len(changedForms)) |
|
1038 QApplication.processEvents() |
|
1039 |
|
1040 def handlePreferencesChanged(self): |
|
1041 """ |
|
1042 Public slot used to handle the preferencesChanged signal. |
|
1043 """ |
|
1044 ProjectBaseBrowser.handlePreferencesChanged(self) |
|
1045 |
|
1046 self.__resetUiCompiler() |
|
1047 |
|
1048 def __configureUicCompiler(self): |
|
1049 """ |
|
1050 Private slot to configure some non-common uic compiler options. |
|
1051 """ |
|
1052 from .UicCompilerOptionsDialog import UicCompilerOptionsDialog |
|
1053 |
|
1054 params = self.project.pdata["UICPARAMS"] |
|
1055 |
|
1056 if self.project.getProjectType() in ["PyQt5", "PyQt6", "E6Plugin"]: |
|
1057 dlg = UicCompilerOptionsDialog(params, self.getUiCompiler()) |
|
1058 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
1059 package, suffix, root = dlg.getData() |
|
1060 if package != params["Package"]: |
|
1061 params["Package"] = package |
|
1062 self.project.setDirty(True) |
|
1063 if suffix != params["RcSuffix"]: |
|
1064 params["RcSuffix"] = suffix |
|
1065 self.project.setDirty(True) |
|
1066 if root != params["PackagesRoot"]: |
|
1067 params["PackagesRoot"] = root |
|
1068 self.project.setDirty(True) |
|
1069 |
|
1070 ########################################################################### |
|
1071 ## Support for hooks below |
|
1072 ########################################################################### |
|
1073 |
|
1074 def _initHookMethods(self): |
|
1075 """ |
|
1076 Protected method to initialize the hooks dictionary. |
|
1077 |
|
1078 Supported hook methods are: |
|
1079 <ul> |
|
1080 <li>compileForm: takes filename as parameter</li> |
|
1081 <li>compileAllForms: takes list of filenames as parameter</li> |
|
1082 <li>compileSelectedForms: takes list of filenames as parameter</li> |
|
1083 <li>compileChangedForms: takes list of filenames as parameter</li> |
|
1084 <li>generateDialogCode: takes filename as parameter</li> |
|
1085 <li>newForm: takes full directory path of new file as parameter</li> |
|
1086 <li>open: takes a filename as parameter</li> |
|
1087 </ul> |
|
1088 |
|
1089 <b>Note</b>: Filenames are relative to the project directory, if not |
|
1090 specified differently. |
|
1091 """ |
|
1092 self.hooks = { |
|
1093 "compileForm": None, |
|
1094 "compileAllForms": None, |
|
1095 "compileChangedForms": None, |
|
1096 "compileSelectedForms": None, |
|
1097 "generateDialogCode": None, |
|
1098 "newForm": None, |
|
1099 "open": None, |
|
1100 } |