Thu, 27 Jun 2013 19:51:57 +0200
Started to rework the QRegularExpression wizard because PyQt4 and PyQt5 cannot be mixed.
--- a/Plugins/WizardPlugins/QRegularExpressionWizard/QRegularExpressionWizardDialog.py Tue Jun 25 19:14:14 2013 +0200 +++ b/Plugins/WizardPlugins/QRegularExpressionWizard/QRegularExpressionWizardDialog.py Thu Jun 27 19:51:57 2013 +0200 @@ -9,13 +9,10 @@ import os import re +import sys +import json -from PyQt4.QtCore import QFileInfo, pyqtSlot, qVersion -try: - from PyQt4.QtCore import QRegularExpression - AVAILABLE = True -except ImportError: - AVAILABLE = False +from PyQt4.QtCore import QFileInfo, pyqtSlot, qVersion, QProcess, QByteArray from PyQt4.QtGui import QWidget, QDialog, QInputDialog, QApplication, QClipboard, \ QTextCursor, QDialogButtonBox, QVBoxLayout, QTableWidgetItem @@ -68,13 +65,25 @@ self.namedGroups = re.compile(r"""\(?P<([^>]+)>""").findall + # start the PyQt5 server part + self.__pyqt5Available = False + self.__pyqt5Server = QProcess(self) + self.__pyqt5Server.start(sys.executable, [ + os.path.join(os.path.dirname(__file__), "QRegularExpressionWizardServer.py")]) + if self.__pyqt5Server.waitForStarted(10000): + self.__pyqt5Server.setReadChannel(QProcess.StandardOutput) + if self.__sendCommand("available"): + response = self.__receiveResponse() + if response and response["available"]: + self.__pyqt5Available = True + self.saveButton = \ self.buttonBox.addButton(self.trUtf8("Save"), QDialogButtonBox.ActionRole) self.saveButton.setToolTip(self.trUtf8("Save the regular expression to a file")) self.loadButton = \ self.buttonBox.addButton(self.trUtf8("Load"), QDialogButtonBox.ActionRole) self.loadButton.setToolTip(self.trUtf8("Load a regular expression from a file")) - if qVersion() >= "5.0.0" and AVAILABLE: + if qVersion() >= "5.0.0" and self.__pyqt5Available: self.validateButton = self.buttonBox.addButton( self.trUtf8("Validate"), QDialogButtonBox.ActionRole) self.validateButton.setToolTip(self.trUtf8("Validate the regular expression")) @@ -106,7 +115,51 @@ self.variableLine.hide() self.importCheckBox.hide() self.regexpTextEdit.setFocus() - + + def __sendCommand(self, command, **kw): + """ + Private method to send a command to the PyQt5 server. + + @param commandDict dictionary with command string and related data (dict) + @return flag indicating a successful transmission (boolean) + """ + result = False + if command: + commandDict = {"command": command} + commandDict.update(kw) + commandStr = json.dumps(commandDict) + "\n" + data = QByteArray(commandStr.encode("utf-8")) + self.__pyqt5Server.write(data) + result = self.__pyqt5Server.waitForBytesWritten(10000) + return result + + def __receiveResponse(self): + """ + Private method to receive a response from the PyQt5 server. + + @return response dictionary (dict) + """ + responseDict = {} + if self.__pyqt5Server.waitForReadyRead(10000): + data = bytes(self.__pyqt5Server.readAllStandardOutput()) + responseStr = data.decode("utf-8") + responseDict = json.loads(responseStr) + if responseDict["error"]: + E5MessageBox.critical(self, + self.trUtf8("Communication Error"), + self.trUtf8("""<p>The PyQt5 backend reported an error.</p>""" + """<p>{0}</p>""").format(responseDict["error"])) + responseDict = {} + + return responseDict + + def shutdown(self): + """ + Public method to shut down the PyQt5 server part. + """ + self.__sendCommand("exit") + self.__pyqt5Server.waitForFinished(5000) + def __insertString(self, s, steps=0): """ Private method to insert a string into line edit and move cursor. @@ -393,44 +446,55 @@ """ Private slot to validate the entered QRegularExpression. """ - if qVersion() < "5.0.0" or not AVAILABLE: + if qVersion() < "5.0.0" or not self.__pyqt5Available: # only available for Qt5 return - regex = self.regexpTextEdit.toPlainText() - if regex: - options = QRegularExpression.NoPatternOption + regexp = self.regexpTextEdit.toPlainText() + if regexp: + options = [] if self.caseInsensitiveCheckBox.isChecked(): - options |= QRegularExpression.CaseInsensitiveOption + options.append("CaseInsensitiveOption") if self.multilineCheckBox.isChecked(): - options |= QRegularExpression.MultilineOption + options.append("MultilineOption") if self.dotallCheckBox.isChecked(): - options |= QRegularExpression.DotMatchesEverythingOption + options.append("DotMatchesEverythingOption") if self.extendedCheckBox.isChecked(): - options |= QRegularExpression.ExtendedPatternSyntaxOption + options.append("ExtendedPatternSyntaxOption") if self.greedinessCheckBox.isChecked(): - options |= QRegularExpression.InvertedGreedinessOption + options.append("InvertedGreedinessOption") if self.unicodeCheckBox.isChecked(): - options |= QRegularExpression.UseUnicodePropertiesOption + options.append("UseUnicodePropertiesOption") if self.captureCheckBox.isChecked(): - options |= QRegularExpression.DontCaptureOption + options.append("DontCaptureOption") - re = QRegularExpression(regex, options) - if re.isValid(): - E5MessageBox.information(self, - self.trUtf8("Validation"), - self.trUtf8("""The regular expression is valid.""")) + if self.__sendCommand("validate", options=options, regexp=regexp): + response = self.__receiveResponse() + if response and "valid" in response: + if response["valid"]: + E5MessageBox.information(self, + self.trUtf8("Validation"), + self.trUtf8("""The regular expression is valid.""")) + else: + E5MessageBox.critical(self, + self.trUtf8("Error"), + self.trUtf8("""Invalid regular expression: {0}""") + .format(response["errorMessage"])) + # move cursor to error offset + offset = response["errorOffset"] + tc = self.regexpTextEdit.textCursor() + tc.setPosition(offset) + self.regexpTextEdit.setTextCursor(tc) + self.regexpTextEdit.setFocus() + return + else: + E5MessageBox.critical(self, + self.trUtf8("Communication Error"), + self.trUtf8("""Invalid response received from PyQt5 backend.""")) else: E5MessageBox.critical(self, - self.trUtf8("Error"), - self.trUtf8("""Invalid regular expression: {0}""") - .format(re.errorString())) - # move cursor to error offset - offset = re.errorPatternOffset() - tc = self.regexpTextEdit.textCursor() - tc.setPosition(offset) - self.regexpTextEdit.setTextCursor(tc) - return + self.trUtf8("Communication Error"), + self.trUtf8("""Communication with PyQt5 backend failed.""")) else: E5MessageBox.critical(self, self.trUtf8("Error"), @@ -446,7 +510,7 @@ @param startpos starting position for the QRegularExpression matching """ - if qVersion() < "5.0.0" or not AVAILABLE: + if qVersion() < "5.0.0" or not self.__pyqt5Available: # only available for Qt5 return @@ -664,6 +728,20 @@ @return generated code (string) """ return self.cw.getCode(indLevel, indString) + + def accept(self): + """ + Public slot to hide the dialog and set the result code to Accepted. + """ + self.cw.shutdown() + super().accept() + + def reject(self): + """ + Public slot to hide the dialog and set the result code to Rejected. + """ + self.cw.shutdown() + super().reject() class QRegularExpressionWizardWindow(E5MainWindow): @@ -687,3 +765,12 @@ self.cw.buttonBox.accepted[()].connect(self.close) self.cw.buttonBox.rejected[()].connect(self.close) + + def closeEvent(self, evt): + """ + Protected method handling the close event. + + @param evt close event (QCloseEvent) + """ + self.cw.shutdown() + super().closeEvent(evt)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/WizardPlugins/QRegularExpressionWizard/QRegularExpressionWizardServer.py Thu Jun 27 19:51:57 2013 +0200 @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013 Detlev Offenbach <detlev@die-offenbachs.de> +# + +import json +import sys + +def printerr(string): + sys.stderr.write(string) + sys.stderr.flush() + +def rxValidate(regexp, options): + """ + Function to validate the given regular expression. + + @param regexp regular expression to validate (string) + @param options list of options (list of string) + @return tuple of flag indicating validity (boolean), error + string (string) and error offset (integer) + """ + try: + from PyQt5.QtCore import QRegularExpression + rxOptions = QRegularExpression.NoPatternOption + if "CaseInsensitiveOption" in options: + rxOptions |= QRegularExpression.CaseInsensitiveOption + if "MultilineOption" in options: + rxOptions |= QRegularExpression.MultilineOption + if "DotMatchesEverythingOption" in options: + rxOptions |= QRegularExpression.DotMatchesEverythingOption + if "ExtendedPatternSyntaxOption" in options: + rxOptions |= QRegularExpression.ExtendedPatternSyntaxOption + if "InvertedGreedinessOption" in options: + rxOptions |= QRegularExpression.InvertedGreedinessOption + if "UseUnicodePropertiesOption" in options: + rxOptions |= QRegularExpression.UseUnicodePropertiesOption + if "DontCaptureOption" in options: + rxOptions |= QRegularExpression.DontCaptureOption + + error = "" + errorOffset = -1 + re = QRegularExpression(regexp, rxOptions) + valid = re.isValid() + if not valid: + error = re.errorString() + errorOffset = re.patternErrorOffset() + except ImportError: + valid = False + error = "ImportError" + errorOffset = 0 + + return valid, error, errorOffset + + +if __name__ == "__main__": + while True: + commandStr = sys.stdin.readline() + try: + commandDict = json.loads(commandStr) + responseDict = {"error": ""} + printerr(str(commandDict)) + if "command" in commandDict: + command = commandDict["command"] + if command == "exit": + break + elif command == "available": + try: + import PyQt5 # __IGNORE_WARNING__ + responseDict["available"] = True + except ImportError: + responseDict["available"] = False + elif command == "validate": + valid, error, errorOffset = rxValidate(commandDict["regexp"], + commandDict["options"]) + responseDict["valid"] = valid + responseDict["errorMessage"] = error + responseDict["errorOffset"] = errorOffset + except ValueError as err: + responseDict = {"error": str(err)} + except Exception as err: + responseDict = {"error": str(err)} + responseStr = json.dumps(responseDict) + sys.stdout.write(responseStr) + sys.stdout.flush() + + sys.exit(0)
--- a/eric5.e4p Tue Jun 25 19:14:14 2013 +0200 +++ b/eric5.e4p Thu Jun 27 19:51:57 2013 +0200 @@ -1087,6 +1087,7 @@ <Source>Plugins/WizardPlugins/QRegularExpressionWizard/QRegularExpressionWizardRepeatDialog.py</Source> <Source>eric5_qregularexpression.py</Source> <Source>eric5_qregularexpression.pyw</Source> + <Source>Plugins/WizardPlugins/QRegularExpressionWizard/QRegularExpressionWizardServer.py</Source> </Sources> <Forms> <Form>PyUnit/UnittestDialog.ui</Form>