diff -r 000000000000 -r de9c2efb9d02 Project/CreateDialogCodeDialog.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Project/CreateDialogCodeDialog.py Mon Dec 28 16:03:33 2009 +0000 @@ -0,0 +1,441 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2007 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to generate code for a Qt4 dialog. +""" + +import os +import sys + +from PyQt4.QtCore import * +from PyQt4.QtGui import * +from PyQt4 import uic + +from E4Gui.E4Application import e4App + +from NewDialogClassDialog import NewDialogClassDialog +from Ui_CreateDialogCodeDialog import Ui_CreateDialogCodeDialog + +from Utilities import ModuleParser + +import UI.PixmapCache + +from eric4config import getConfig + +pyqtSignatureRole = Qt.UserRole + 1 +pythonSignatureRole = Qt.UserRole + 2 +rubySignatureRole = Qt.UserRole + 3 + +class CreateDialogCodeDialog(QDialog, Ui_CreateDialogCodeDialog): + """ + Class implementing a dialog to generate code for a Qt4 dialog. + """ + def __init__(self, formName, project, parent = None): + """ + Constructor + + @param formName name of the file containing the form (string) + @param project reference to the project object + @param parent parent widget if the dialog (QWidget) + """ + QDialog.__init__(self, parent) + self.setupUi(self) + + self.okButton = self.buttonBox.button(QDialogButtonBox.Ok) + + self.slotsView.header().hide() + + self.project = project + + self.formFile = formName + filename, ext = os.path.splitext(self.formFile) + self.srcFile = '%s%s' % (filename, self.project.getDefaultSourceExtension()) + + self.slotsModel = QStandardItemModel() + self.proxyModel = QSortFilterProxyModel() + self.proxyModel.setDynamicSortFilter(True) + self.proxyModel.setSourceModel(self.slotsModel) + self.slotsView.setModel(self.proxyModel) + + self.clearFilterButton.setIcon(UI.PixmapCache.getIcon("clearLeft.png")) + + # initialize some member variables + self.__initError = False + self.__module = None + + if os.path.exists(self.srcFile): + vm = e4App().getObject("ViewManager") + ed = vm.getOpenEditor(self.srcFile) + if ed and not vm.checkDirty(ed): + self.__initError = True + return + + self.__module = ModuleParser.readModule(self.srcFile, caching = False) + + if self.__module is not None: + self.filenameEdit.setText(self.srcFile) + + classesList = [] + for cls in self.__module.classes.values(): + classesList.append(cls.name) + classesList.sort() + self.classNameCombo.addItems(classesList) + + if os.path.exists(self.srcFile) and self.classNameCombo.count() == 0: + self.__initError = True + QMessageBox.critical(None, + self.trUtf8("Create Dialog Code"), + self.trUtf8("""The file <b>{0}</b> exists but does not contain""" + """ any classes.""").format(self.srcFile), + QMessageBox.StandardButtons(\ + QMessageBox.Abort)) + + self.okButton.setEnabled(self.classNameCombo.count() > 0) + + self.__updateSlotsModel() + + def initError(self): + """ + Public method to determine, if there was an initialzation error. + + @return flag indicating an initialzation error (boolean) + """ + return self.__initError + + def __objectName(self): + """ + Private method to get the object name of the dialog. + + @return object name (string) + """ + try: + dlg = uic.loadUi(self.formFile) + return dlg.objectName() + except AttributeError, err: + QMessageBox.critical(self, + self.trUtf8("uic error"), + self.trUtf8("""<p>There was an error loading the form <b>{0}</b>.</p>""" + """<p>{1}</p>""").format(self.formFile, unicode(err)), + QMessageBox.StandardButtons(\ + QMessageBox.Ok)) + return "" + + def __className(self): + """ + Private method to get the class name of the dialog. + + @return class name (sting) + """ + try: + dlg = uic.loadUi(self.formFile) + return dlg.metaObject().className() + except AttributeError, err: + QMessageBox.critical(self, + self.trUtf8("uic error"), + self.trUtf8("""<p>There was an error loading the form <b>{0}</b>.</p>""" + """<p>{1}</p>""").format(self.formFile, unicode(err)), + QMessageBox.StandardButtons(\ + QMessageBox.Ok)) + return "" + + def __signatures(self): + """ + Private slot to get the signatures. + + @return list of signatures (list of strings) + """ + if self.__module is None: + return [] + + signatures = [] + clsName = self.classNameCombo.currentText() + if clsName: + cls = self.__module.classes[clsName] + for meth in cls.methods.values(): + if meth.name.startswith("on_"): + if meth.pyqtSignature is not None: + sig = ", ".join([str(QMetaObject.normalizedType(t)) \ + for t in meth.pyqtSignature.split(",")]) + signatures.append("%s(%s)" % (meth.name, sig)) + else: + signatures.append(meth.name) + return signatures + + def __updateSlotsModel(self): + """ + Private slot to update the slots tree display. + """ + self.filterEdit.clear() + + try: + dlg = uic.loadUi(self.formFile) + objects = dlg.findChildren(QWidget) + dlg.findChildren(QAction) + + signatureList = self.__signatures() + + self.slotsModel.clear() + self.slotsModel.setHorizontalHeaderLabels([""]) + for obj in objects: + name = obj.objectName() + if not name: + continue + + metaObject = obj.metaObject() + className = metaObject.className() + itm = QStandardItem("%s (%s)" % (name, className)) + self.slotsModel.appendRow(itm) + for index in range(metaObject.methodCount()): + metaMethod = metaObject.method(index) + if metaMethod.methodType() == QMetaMethod.Signal: + itm2 = QStandardItem("on_%s_%s" % (name, metaMethod.signature())) + itm.appendRow(itm2) + if self.__module is not None: + method = "on_%s_%s" % \ + (name, metaMethod.signature().split("(")[0]) + method2 = "on_%s_%s" % \ + (name, + QMetaObject.normalizedSignature(metaMethod.signature())) + + if method2 in signatureList or method in signatureList: + itm2.setFlags(Qt.ItemFlags(Qt.ItemIsEnabled)) + itm2.setCheckState(Qt.Checked) + itm2.setForeground(QBrush(Qt.blue)) + continue + + pyqtSignature = \ + ", ".join([str(t) for t in metaMethod.parameterTypes()]) + + parameterNames = metaMethod.parameterNames() + if parameterNames: + for index in range(len(parameterNames)): + if not parameterNames[index]: + parameterNames[index] = QByteArray("p%d" % index) + methNamesSig = \ + ", ".join([str(n) for n in parameterNames]) + + if methNamesSig: + pythonSignature = "on_%s_%s(self, %s)" % \ + (name, + metaMethod.signature().split("(")[0], + methNamesSig + ) + else: + pythonSignature = "on_%s_%s(self)" % \ + (name, + metaMethod.signature().split("(")[0] + ) + itm2.setData(QVariant(pyqtSignature), pyqtSignatureRole) + itm2.setData(QVariant(pythonSignature), pythonSignatureRole) + + itm2.setFlags(Qt.ItemFlags(\ + Qt.ItemIsUserCheckable | \ + Qt.ItemIsEnabled | \ + Qt.ItemIsSelectable) + ) + itm2.setCheckState(Qt.Unchecked) + + self.slotsView.sortByColumn(0, Qt.AscendingOrder) + except (AttributeError, ImportError), err: + QMessageBox.critical(self, + self.trUtf8("uic error"), + self.trUtf8("""<p>There was an error loading the form <b>{0}</b>.</p>""" + """<p>{1}</p>""").format(self.formFile, unicode(err)), + QMessageBox.StandardButtons(\ + QMessageBox.Ok)) + + def __generateCode(self): + """ + Private slot to generate the code as requested by the user. + """ + # first decide on extension + if self.filenameEdit.text().endswith(".py") or \ + self.filenameEdit.text().endswith(".pyw"): + self.__generatePythonCode() + elif self.filenameEdit.text().endswith(".rb"): + pass + # second decide on project language + elif self.project.getProjectLanguage() in ["Python", "Python3"]: + self.__generatePythonCode() + elif self.project.getProjectLanguage() == "Ruby": + pass + else: + # assume Python (our global default) + self.__generatePythonCode() + + def __generatePythonCode(self): + """ + Private slot to generate Python code as requested by the user. + """ + # init some variables + sourceImpl = [] + appendAtIndex = -1 + indentStr = " " + slotsCode = [] + + if self.__module is None: + # new file + try: + tmplName = os.path.join(getConfig('ericCodeTemplatesDir'), "impl.py.tmpl") + tmplFile = open(tmplName, 'rb') + template = tmplFile.read() + tmplFile.close() + except IOError, why: + QMessageBox.critical(self, + self.trUtf8("Code Generation"), + self.trUtf8("""<p>Could not open the code template file "{0}".</p>""" + """<p>Reason: {1}</p>""")\ + .format(tmplName, unicode(why)), + QMessageBox.StandardButtons(\ + QMessageBox.Ok)) + return + + objName = self.__objectName() + if objName: + template = template\ + .replace("$FORMFILE$", + os.path.splitext(os.path.basename(self.formFile))[0])\ + .replace("$FORMCLASS$", objName)\ + .replace("$CLASSNAME$", self.classNameCombo.currentText())\ + .replace("$SUPERCLASS$", self.__className()) + + sourceImpl = template.splitlines(True) + appendAtIndex = -1 + + # determine indent string + for line in sourceImpl: + if line.lstrip().startswith("def __init__"): + indentStr = line.replace(line.lstrip(), "") + break + else: + # extend existing file + try: + srcFile = open(self.srcFile, 'rb') + sourceImpl = srcFile.readlines() + srcFile.close() + if not sourceImpl[-1].endswith(os.linesep): + sourceImpl[-1] = "%s%s" % (sourceImpl[-1], os.linesep) + except IOError, why: + QMessageBox.critical(self, + self.trUtf8("Code Generation"), + self.trUtf8("""<p>Could not open the source file "{0}".</p>""" + """<p>Reason: {1}</p>""")\ + .format(self.srcFile, unicode(why)), + QMessageBox.StandardButtons(\ + QMessageBox.Ok)) + return + + cls = self.__module.classes[self.classNameCombo.currentText()] + if cls.endlineno == len(sourceImpl) or cls.endlineno == -1: + appendAtIndex = -1 + # delete empty lines at end + while not sourceImpl[-1].strip(): + del sourceImpl[-1] + else: + appendAtIndex = cls.endlineno - 1 + + # determine indent string + for line in sourceImpl[cls.lineno:cls.endlineno+1]: + if line.lstrip().startswith("def __init__"): + indentStr = line.replace(line.lstrip(), "") + break + + # do the coding stuff + for row in range(self.slotsModel.rowCount()): + topItem = self.slotsModel.item(row) + for childRow in range(topItem.rowCount()): + child = topItem.child(childRow) + if child.checkState() and \ + child.flags() & Qt.ItemFlags(Qt.ItemIsUserCheckable): + slotsCode.append('%s\n' % indentStr) + # TODO: adjust to new signal/slot mechanism + slotsCode.append('%s@pyqtSlot(%s)\n' % \ + (indentStr, child.data(pyqtSignatureRole).toString())) + slotsCode.append('%sdef %s:\n' % \ + (indentStr, child.data(pythonSignatureRole).toString())) + slotsCode.append('%s"""\n' % (indentStr * 2,)) + slotsCode.append('%sSlot documentation goes here.\n' % \ + (indentStr * 2,)) + slotsCode.append('%s"""\n' % (indentStr * 2,)) + slotsCode.append('%s# %s: not implemented yet\n' % \ + (indentStr * 2, "TODO")) + slotsCode.append('%sraise NotImplementedError\n' % (indentStr * 2,)) + + if appendAtIndex == -1: + sourceImpl.extend(slotsCode) + else: + sourceImpl[appendAtIndex:appendAtIndex] = slotsCode + + # write the new code + try: + srcFile = open(self.filenameEdit.text(), 'wb') + srcFile.write("".join(sourceImpl)) + srcFile.close() + except IOError, why: + QMessageBox.critical(self, + self.trUtf8("Code Generation"), + self.trUtf8("""<p>Could not write the source file "{0}".</p>""" + """<p>Reason: {1}</p>""")\ + .format(self.filenameEdit.text(), unicode(why)), + QMessageBox.StandardButtons(\ + QMessageBox.Ok)) + return + + self.project.appendFile(unicode(self.filenameEdit.text())) + + @pyqtSlot(int) + def on_classNameCombo_activated(self, index): + """ + Private slot to handle the activated signal of the classname combo. + + @param index index of the activated item (integer) + """ + self.__updateSlotsModel() + + def on_filterEdit_textChanged(self, text): + """ + Private slot called, when thext of the filter edit has changed. + + @param text changed text (string) + """ + re = QRegExp(text, Qt.CaseInsensitive, QRegExp.RegExp2) + self.proxyModel.setFilterRegExp(re) + + @pyqtSlot() + def on_clearFilterButton_clicked(self): + """ + Private slot called by a click of the clear filter button. + """ + self.filterEdit.clear() + + @pyqtSlot() + def on_newButton_clicked(self): + """ + Private slot called to enter the data for a new dialog class. + """ + path, file = os.path.split(self.srcFile) + objName = self.__objectName() + if objName: + dlg = NewDialogClassDialog(objName, file, path, self) + if dlg.exec_() == QDialog.Accepted: + className, fileName = dlg.getData() + + self.classNameCombo.clear() + self.classNameCombo.addItem(className) + self.srcFile = fileName + self.filenameEdit.setText(self.srcFile) + self.__module = None + + self.okButton.setEnabled(self.classNameCombo.count() > 0) + + def on_buttonBox_clicked(self, button): + """ + Private slot to handle the buttonBox clicked signal. + + @param button reference to the button that was clicked (QAbstractButton) + """ + if button == self.okButton: + self.__generateCode() + self.accept()