Project/CreateDialogCodeDialog.py

changeset 0
de9c2efb9d02
child 6
52e8c820d0dd
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to generate code for a Qt4 dialog.
8 """
9
10 import os
11 import sys
12
13 from PyQt4.QtCore import *
14 from PyQt4.QtGui import *
15 from PyQt4 import uic
16
17 from E4Gui.E4Application import e4App
18
19 from NewDialogClassDialog import NewDialogClassDialog
20 from Ui_CreateDialogCodeDialog import Ui_CreateDialogCodeDialog
21
22 from Utilities import ModuleParser
23
24 import UI.PixmapCache
25
26 from eric4config import getConfig
27
28 pyqtSignatureRole = Qt.UserRole + 1
29 pythonSignatureRole = Qt.UserRole + 2
30 rubySignatureRole = Qt.UserRole + 3
31
32 class CreateDialogCodeDialog(QDialog, Ui_CreateDialogCodeDialog):
33 """
34 Class implementing a dialog to generate code for a Qt4 dialog.
35 """
36 def __init__(self, formName, project, parent = None):
37 """
38 Constructor
39
40 @param formName name of the file containing the form (string)
41 @param project reference to the project object
42 @param parent parent widget if the dialog (QWidget)
43 """
44 QDialog.__init__(self, parent)
45 self.setupUi(self)
46
47 self.okButton = self.buttonBox.button(QDialogButtonBox.Ok)
48
49 self.slotsView.header().hide()
50
51 self.project = project
52
53 self.formFile = formName
54 filename, ext = os.path.splitext(self.formFile)
55 self.srcFile = '%s%s' % (filename, self.project.getDefaultSourceExtension())
56
57 self.slotsModel = QStandardItemModel()
58 self.proxyModel = QSortFilterProxyModel()
59 self.proxyModel.setDynamicSortFilter(True)
60 self.proxyModel.setSourceModel(self.slotsModel)
61 self.slotsView.setModel(self.proxyModel)
62
63 self.clearFilterButton.setIcon(UI.PixmapCache.getIcon("clearLeft.png"))
64
65 # initialize some member variables
66 self.__initError = False
67 self.__module = None
68
69 if os.path.exists(self.srcFile):
70 vm = e4App().getObject("ViewManager")
71 ed = vm.getOpenEditor(self.srcFile)
72 if ed and not vm.checkDirty(ed):
73 self.__initError = True
74 return
75
76 self.__module = ModuleParser.readModule(self.srcFile, caching = False)
77
78 if self.__module is not None:
79 self.filenameEdit.setText(self.srcFile)
80
81 classesList = []
82 for cls in self.__module.classes.values():
83 classesList.append(cls.name)
84 classesList.sort()
85 self.classNameCombo.addItems(classesList)
86
87 if os.path.exists(self.srcFile) and self.classNameCombo.count() == 0:
88 self.__initError = True
89 QMessageBox.critical(None,
90 self.trUtf8("Create Dialog Code"),
91 self.trUtf8("""The file <b>{0}</b> exists but does not contain"""
92 """ any classes.""").format(self.srcFile),
93 QMessageBox.StandardButtons(\
94 QMessageBox.Abort))
95
96 self.okButton.setEnabled(self.classNameCombo.count() > 0)
97
98 self.__updateSlotsModel()
99
100 def initError(self):
101 """
102 Public method to determine, if there was an initialzation error.
103
104 @return flag indicating an initialzation error (boolean)
105 """
106 return self.__initError
107
108 def __objectName(self):
109 """
110 Private method to get the object name of the dialog.
111
112 @return object name (string)
113 """
114 try:
115 dlg = uic.loadUi(self.formFile)
116 return dlg.objectName()
117 except AttributeError, err:
118 QMessageBox.critical(self,
119 self.trUtf8("uic error"),
120 self.trUtf8("""<p>There was an error loading the form <b>{0}</b>.</p>"""
121 """<p>{1}</p>""").format(self.formFile, unicode(err)),
122 QMessageBox.StandardButtons(\
123 QMessageBox.Ok))
124 return ""
125
126 def __className(self):
127 """
128 Private method to get the class name of the dialog.
129
130 @return class name (sting)
131 """
132 try:
133 dlg = uic.loadUi(self.formFile)
134 return dlg.metaObject().className()
135 except AttributeError, err:
136 QMessageBox.critical(self,
137 self.trUtf8("uic error"),
138 self.trUtf8("""<p>There was an error loading the form <b>{0}</b>.</p>"""
139 """<p>{1}</p>""").format(self.formFile, unicode(err)),
140 QMessageBox.StandardButtons(\
141 QMessageBox.Ok))
142 return ""
143
144 def __signatures(self):
145 """
146 Private slot to get the signatures.
147
148 @return list of signatures (list of strings)
149 """
150 if self.__module is None:
151 return []
152
153 signatures = []
154 clsName = self.classNameCombo.currentText()
155 if clsName:
156 cls = self.__module.classes[clsName]
157 for meth in cls.methods.values():
158 if meth.name.startswith("on_"):
159 if meth.pyqtSignature is not None:
160 sig = ", ".join([str(QMetaObject.normalizedType(t)) \
161 for t in meth.pyqtSignature.split(",")])
162 signatures.append("%s(%s)" % (meth.name, sig))
163 else:
164 signatures.append(meth.name)
165 return signatures
166
167 def __updateSlotsModel(self):
168 """
169 Private slot to update the slots tree display.
170 """
171 self.filterEdit.clear()
172
173 try:
174 dlg = uic.loadUi(self.formFile)
175 objects = dlg.findChildren(QWidget) + dlg.findChildren(QAction)
176
177 signatureList = self.__signatures()
178
179 self.slotsModel.clear()
180 self.slotsModel.setHorizontalHeaderLabels([""])
181 for obj in objects:
182 name = obj.objectName()
183 if not name:
184 continue
185
186 metaObject = obj.metaObject()
187 className = metaObject.className()
188 itm = QStandardItem("%s (%s)" % (name, className))
189 self.slotsModel.appendRow(itm)
190 for index in range(metaObject.methodCount()):
191 metaMethod = metaObject.method(index)
192 if metaMethod.methodType() == QMetaMethod.Signal:
193 itm2 = QStandardItem("on_%s_%s" % (name, metaMethod.signature()))
194 itm.appendRow(itm2)
195 if self.__module is not None:
196 method = "on_%s_%s" % \
197 (name, metaMethod.signature().split("(")[0])
198 method2 = "on_%s_%s" % \
199 (name,
200 QMetaObject.normalizedSignature(metaMethod.signature()))
201
202 if method2 in signatureList or method in signatureList:
203 itm2.setFlags(Qt.ItemFlags(Qt.ItemIsEnabled))
204 itm2.setCheckState(Qt.Checked)
205 itm2.setForeground(QBrush(Qt.blue))
206 continue
207
208 pyqtSignature = \
209 ", ".join([str(t) for t in metaMethod.parameterTypes()])
210
211 parameterNames = metaMethod.parameterNames()
212 if parameterNames:
213 for index in range(len(parameterNames)):
214 if not parameterNames[index]:
215 parameterNames[index] = QByteArray("p%d" % index)
216 methNamesSig = \
217 ", ".join([str(n) for n in parameterNames])
218
219 if methNamesSig:
220 pythonSignature = "on_%s_%s(self, %s)" % \
221 (name,
222 metaMethod.signature().split("(")[0],
223 methNamesSig
224 )
225 else:
226 pythonSignature = "on_%s_%s(self)" % \
227 (name,
228 metaMethod.signature().split("(")[0]
229 )
230 itm2.setData(QVariant(pyqtSignature), pyqtSignatureRole)
231 itm2.setData(QVariant(pythonSignature), pythonSignatureRole)
232
233 itm2.setFlags(Qt.ItemFlags(\
234 Qt.ItemIsUserCheckable | \
235 Qt.ItemIsEnabled | \
236 Qt.ItemIsSelectable)
237 )
238 itm2.setCheckState(Qt.Unchecked)
239
240 self.slotsView.sortByColumn(0, Qt.AscendingOrder)
241 except (AttributeError, ImportError), err:
242 QMessageBox.critical(self,
243 self.trUtf8("uic error"),
244 self.trUtf8("""<p>There was an error loading the form <b>{0}</b>.</p>"""
245 """<p>{1}</p>""").format(self.formFile, unicode(err)),
246 QMessageBox.StandardButtons(\
247 QMessageBox.Ok))
248
249 def __generateCode(self):
250 """
251 Private slot to generate the code as requested by the user.
252 """
253 # first decide on extension
254 if self.filenameEdit.text().endswith(".py") or \
255 self.filenameEdit.text().endswith(".pyw"):
256 self.__generatePythonCode()
257 elif self.filenameEdit.text().endswith(".rb"):
258 pass
259 # second decide on project language
260 elif self.project.getProjectLanguage() in ["Python", "Python3"]:
261 self.__generatePythonCode()
262 elif self.project.getProjectLanguage() == "Ruby":
263 pass
264 else:
265 # assume Python (our global default)
266 self.__generatePythonCode()
267
268 def __generatePythonCode(self):
269 """
270 Private slot to generate Python code as requested by the user.
271 """
272 # init some variables
273 sourceImpl = []
274 appendAtIndex = -1
275 indentStr = " "
276 slotsCode = []
277
278 if self.__module is None:
279 # new file
280 try:
281 tmplName = os.path.join(getConfig('ericCodeTemplatesDir'), "impl.py.tmpl")
282 tmplFile = open(tmplName, 'rb')
283 template = tmplFile.read()
284 tmplFile.close()
285 except IOError, why:
286 QMessageBox.critical(self,
287 self.trUtf8("Code Generation"),
288 self.trUtf8("""<p>Could not open the code template file "{0}".</p>"""
289 """<p>Reason: {1}</p>""")\
290 .format(tmplName, unicode(why)),
291 QMessageBox.StandardButtons(\
292 QMessageBox.Ok))
293 return
294
295 objName = self.__objectName()
296 if objName:
297 template = template\
298 .replace("$FORMFILE$",
299 os.path.splitext(os.path.basename(self.formFile))[0])\
300 .replace("$FORMCLASS$", objName)\
301 .replace("$CLASSNAME$", self.classNameCombo.currentText())\
302 .replace("$SUPERCLASS$", self.__className())
303
304 sourceImpl = template.splitlines(True)
305 appendAtIndex = -1
306
307 # determine indent string
308 for line in sourceImpl:
309 if line.lstrip().startswith("def __init__"):
310 indentStr = line.replace(line.lstrip(), "")
311 break
312 else:
313 # extend existing file
314 try:
315 srcFile = open(self.srcFile, 'rb')
316 sourceImpl = srcFile.readlines()
317 srcFile.close()
318 if not sourceImpl[-1].endswith(os.linesep):
319 sourceImpl[-1] = "%s%s" % (sourceImpl[-1], os.linesep)
320 except IOError, why:
321 QMessageBox.critical(self,
322 self.trUtf8("Code Generation"),
323 self.trUtf8("""<p>Could not open the source file "{0}".</p>"""
324 """<p>Reason: {1}</p>""")\
325 .format(self.srcFile, unicode(why)),
326 QMessageBox.StandardButtons(\
327 QMessageBox.Ok))
328 return
329
330 cls = self.__module.classes[self.classNameCombo.currentText()]
331 if cls.endlineno == len(sourceImpl) or cls.endlineno == -1:
332 appendAtIndex = -1
333 # delete empty lines at end
334 while not sourceImpl[-1].strip():
335 del sourceImpl[-1]
336 else:
337 appendAtIndex = cls.endlineno - 1
338
339 # determine indent string
340 for line in sourceImpl[cls.lineno:cls.endlineno+1]:
341 if line.lstrip().startswith("def __init__"):
342 indentStr = line.replace(line.lstrip(), "")
343 break
344
345 # do the coding stuff
346 for row in range(self.slotsModel.rowCount()):
347 topItem = self.slotsModel.item(row)
348 for childRow in range(topItem.rowCount()):
349 child = topItem.child(childRow)
350 if child.checkState() and \
351 child.flags() & Qt.ItemFlags(Qt.ItemIsUserCheckable):
352 slotsCode.append('%s\n' % indentStr)
353 # TODO: adjust to new signal/slot mechanism
354 slotsCode.append('%s@pyqtSlot(%s)\n' % \
355 (indentStr, child.data(pyqtSignatureRole).toString()))
356 slotsCode.append('%sdef %s:\n' % \
357 (indentStr, child.data(pythonSignatureRole).toString()))
358 slotsCode.append('%s"""\n' % (indentStr * 2,))
359 slotsCode.append('%sSlot documentation goes here.\n' % \
360 (indentStr * 2,))
361 slotsCode.append('%s"""\n' % (indentStr * 2,))
362 slotsCode.append('%s# %s: not implemented yet\n' % \
363 (indentStr * 2, "TODO"))
364 slotsCode.append('%sraise NotImplementedError\n' % (indentStr * 2,))
365
366 if appendAtIndex == -1:
367 sourceImpl.extend(slotsCode)
368 else:
369 sourceImpl[appendAtIndex:appendAtIndex] = slotsCode
370
371 # write the new code
372 try:
373 srcFile = open(self.filenameEdit.text(), 'wb')
374 srcFile.write("".join(sourceImpl))
375 srcFile.close()
376 except IOError, why:
377 QMessageBox.critical(self,
378 self.trUtf8("Code Generation"),
379 self.trUtf8("""<p>Could not write the source file "{0}".</p>"""
380 """<p>Reason: {1}</p>""")\
381 .format(self.filenameEdit.text(), unicode(why)),
382 QMessageBox.StandardButtons(\
383 QMessageBox.Ok))
384 return
385
386 self.project.appendFile(unicode(self.filenameEdit.text()))
387
388 @pyqtSlot(int)
389 def on_classNameCombo_activated(self, index):
390 """
391 Private slot to handle the activated signal of the classname combo.
392
393 @param index index of the activated item (integer)
394 """
395 self.__updateSlotsModel()
396
397 def on_filterEdit_textChanged(self, text):
398 """
399 Private slot called, when thext of the filter edit has changed.
400
401 @param text changed text (string)
402 """
403 re = QRegExp(text, Qt.CaseInsensitive, QRegExp.RegExp2)
404 self.proxyModel.setFilterRegExp(re)
405
406 @pyqtSlot()
407 def on_clearFilterButton_clicked(self):
408 """
409 Private slot called by a click of the clear filter button.
410 """
411 self.filterEdit.clear()
412
413 @pyqtSlot()
414 def on_newButton_clicked(self):
415 """
416 Private slot called to enter the data for a new dialog class.
417 """
418 path, file = os.path.split(self.srcFile)
419 objName = self.__objectName()
420 if objName:
421 dlg = NewDialogClassDialog(objName, file, path, self)
422 if dlg.exec_() == QDialog.Accepted:
423 className, fileName = dlg.getData()
424
425 self.classNameCombo.clear()
426 self.classNameCombo.addItem(className)
427 self.srcFile = fileName
428 self.filenameEdit.setText(self.srcFile)
429 self.__module = None
430
431 self.okButton.setEnabled(self.classNameCombo.count() > 0)
432
433 def on_buttonBox_clicked(self, button):
434 """
435 Private slot to handle the buttonBox clicked signal.
436
437 @param button reference to the button that was clicked (QAbstractButton)
438 """
439 if button == self.okButton:
440 self.__generateCode()
441 self.accept()

eric ide

mercurial