1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2013 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing the QRegularExpression wizard dialog. 8""" 9 10import os 11import re 12import sys 13import json 14 15from PyQt5.QtCore import QFileInfo, pyqtSlot, QProcess, QByteArray 16from PyQt5.QtGui import QClipboard, QTextCursor 17from PyQt5.QtWidgets import ( 18 QWidget, QDialog, QInputDialog, QApplication, QDialogButtonBox, 19 QVBoxLayout, QTableWidgetItem 20) 21 22from E5Gui import E5MessageBox, E5FileDialog 23from E5Gui.E5MainWindow import E5MainWindow 24 25from .Ui_QRegularExpressionWizardDialog import ( 26 Ui_QRegularExpressionWizardDialog 27) 28 29import UI.PixmapCache 30 31import Utilities 32import Preferences 33 34 35class QRegularExpressionWizardWidget(QWidget, 36 Ui_QRegularExpressionWizardDialog): 37 """ 38 Class implementing the QRegularExpression wizard dialog. 39 """ 40 def __init__(self, parent=None, fromEric=True): 41 """ 42 Constructor 43 44 @param parent parent widget (QWidget) 45 @param fromEric flag indicating a call from within eric 46 """ 47 super().__init__(parent) 48 self.setupUi(self) 49 50 # initialize icons of the tool buttons 51 self.commentButton.setIcon(UI.PixmapCache.getIcon("comment")) 52 self.charButton.setIcon(UI.PixmapCache.getIcon("characters")) 53 self.anycharButton.setIcon(UI.PixmapCache.getIcon("anychar")) 54 self.repeatButton.setIcon(UI.PixmapCache.getIcon("repeat")) 55 self.nonGroupButton.setIcon(UI.PixmapCache.getIcon("nongroup")) 56 self.atomicGroupButton.setIcon( 57 UI.PixmapCache.getIcon("atomicgroup")) 58 self.groupButton.setIcon(UI.PixmapCache.getIcon("group")) 59 self.namedGroupButton.setIcon(UI.PixmapCache.getIcon("namedgroup")) 60 self.namedReferenceButton.setIcon( 61 UI.PixmapCache.getIcon("namedreference")) 62 self.altnButton.setIcon(UI.PixmapCache.getIcon("altn")) 63 self.beglineButton.setIcon(UI.PixmapCache.getIcon("begline")) 64 self.endlineButton.setIcon(UI.PixmapCache.getIcon("endline")) 65 self.wordboundButton.setIcon( 66 UI.PixmapCache.getIcon("wordboundary")) 67 self.nonwordboundButton.setIcon( 68 UI.PixmapCache.getIcon("nonwordboundary")) 69 self.poslookaheadButton.setIcon( 70 UI.PixmapCache.getIcon("poslookahead")) 71 self.neglookaheadButton.setIcon( 72 UI.PixmapCache.getIcon("neglookahead")) 73 self.poslookbehindButton.setIcon( 74 UI.PixmapCache.getIcon("poslookbehind")) 75 self.neglookbehindButton.setIcon( 76 UI.PixmapCache.getIcon("neglookbehind")) 77 self.undoButton.setIcon(UI.PixmapCache.getIcon("editUndo")) 78 self.redoButton.setIcon(UI.PixmapCache.getIcon("editRedo")) 79 80 self.namedGroups = re.compile(r"""\(?P<([^>]+)>""").findall 81 82 # start the PyQt5 server part 83 self.__pyqt5Available = False 84 self.__pyqt5Server = QProcess(self) 85 self.__pyqt5Server.start( 86 sys.executable, [os.path.join( 87 os.path.dirname(__file__), "QRegularExpressionWizardServer.py") 88 ]) 89 if self.__pyqt5Server.waitForStarted(5000): 90 self.__pyqt5Server.setReadChannel( 91 QProcess.ProcessChannel.StandardOutput) 92 if self.__sendCommand("available"): 93 response = self.__receiveResponse() 94 if response and response["available"]: 95 self.__pyqt5Available = True 96 97 self.saveButton = self.buttonBox.addButton( 98 self.tr("Save"), QDialogButtonBox.ButtonRole.ActionRole) 99 self.saveButton.setToolTip( 100 self.tr("Save the regular expression to a file")) 101 self.loadButton = self.buttonBox.addButton( 102 self.tr("Load"), QDialogButtonBox.ButtonRole.ActionRole) 103 self.loadButton.setToolTip( 104 self.tr("Load a regular expression from a file")) 105 if self.__pyqt5Available: 106 self.validateButton = self.buttonBox.addButton( 107 self.tr("Validate"), QDialogButtonBox.ButtonRole.ActionRole) 108 self.validateButton.setToolTip( 109 self.tr("Validate the regular expression")) 110 self.executeButton = self.buttonBox.addButton( 111 self.tr("Execute"), QDialogButtonBox.ButtonRole.ActionRole) 112 self.executeButton.setToolTip( 113 self.tr("Execute the regular expression")) 114 self.nextButton = self.buttonBox.addButton( 115 self.tr("Next match"), QDialogButtonBox.ButtonRole.ActionRole) 116 self.nextButton.setToolTip( 117 self.tr("Show the next match of the regular expression")) 118 self.nextButton.setEnabled(False) 119 else: 120 self.validateButton = None 121 self.executeButton = None 122 self.nextButton = None 123 124 if fromEric: 125 self.buttonBox.setStandardButtons( 126 QDialogButtonBox.StandardButton.Cancel | 127 QDialogButtonBox.StandardButton.Ok) 128 self.copyButton = None 129 else: 130 self.copyButton = self.buttonBox.addButton( 131 self.tr("Copy"), QDialogButtonBox.ButtonRole.ActionRole) 132 self.copyButton.setToolTip( 133 self.tr("Copy the regular expression to the clipboard")) 134 self.buttonBox.setStandardButtons( 135 QDialogButtonBox.StandardButton.Close) 136 self.variableLabel.hide() 137 self.variableLineEdit.hide() 138 self.variableLine.hide() 139 self.regexpTextEdit.setFocus() 140 141 def __sendCommand(self, command, **kw): 142 """ 143 Private method to send a command to the PyQt5 server. 144 145 @param command dictionary with command string and related 146 data (dict) 147 @keyparam kw parameters for the command 148 @return flag indicating a successful transmission (boolean) 149 """ 150 result = False 151 if command: 152 commandDict = {"command": command} 153 commandDict.update(kw) 154 commandStr = json.dumps(commandDict) + "\n" 155 data = QByteArray(commandStr.encode("utf-8")) 156 self.__pyqt5Server.write(data) 157 result = self.__pyqt5Server.waitForBytesWritten(10000) 158 return result 159 160 def __receiveResponse(self): 161 """ 162 Private method to receive a response from the PyQt5 server. 163 164 @return response dictionary (dict) 165 """ 166 responseDict = {} 167 if self.__pyqt5Server.waitForReadyRead(10000): 168 data = bytes(self.__pyqt5Server.readAllStandardOutput()) 169 responseStr = data.decode("utf-8") 170 responseDict = json.loads(responseStr) 171 if responseDict["error"]: 172 E5MessageBox.critical( 173 self, 174 self.tr("Communication Error"), 175 self.tr("""<p>The PyQt5 backend reported""" 176 """ an error.</p><p>{0}</p>""") 177 .format(responseDict["error"])) 178 responseDict = {} 179 180 return responseDict 181 182 def shutdown(self): 183 """ 184 Public method to shut down the PyQt5 server part. 185 """ 186 self.__sendCommand("exit") 187 self.__pyqt5Server.waitForFinished(5000) 188 189 def __insertString(self, s, steps=0): 190 """ 191 Private method to insert a string into line edit and move cursor. 192 193 @param s string to be inserted into the regexp line edit 194 (string) 195 @param steps number of characters to move the cursor (integer). 196 Negative steps moves cursor back, positives forward. 197 """ 198 self.regexpTextEdit.insertPlainText(s) 199 tc = self.regexpTextEdit.textCursor() 200 if steps != 0: 201 if steps < 0: 202 act = QTextCursor.MoveOperation.Left 203 steps = abs(steps) 204 else: 205 act = QTextCursor.MoveOperation.Right 206 for _ in range(steps): 207 tc.movePosition(act) 208 self.regexpTextEdit.setTextCursor(tc) 209 210 @pyqtSlot() 211 def on_commentButton_clicked(self): 212 """ 213 Private slot to handle the comment toolbutton. 214 """ 215 self.__insertString("(?#)", -1) 216 217 @pyqtSlot() 218 def on_charButton_clicked(self): 219 """ 220 Private slot to handle the characters toolbutton. 221 """ 222 from .QRegularExpressionWizardCharactersDialog import ( 223 QRegularExpressionWizardCharactersDialog 224 ) 225 dlg = QRegularExpressionWizardCharactersDialog(self) 226 if dlg.exec() == QDialog.DialogCode.Accepted: 227 self.__insertString(dlg.getCharacters()) 228 229 @pyqtSlot() 230 def on_anycharButton_clicked(self): 231 """ 232 Private slot to handle the any character toolbutton. 233 """ 234 self.__insertString(".") 235 236 @pyqtSlot() 237 def on_repeatButton_clicked(self): 238 """ 239 Private slot to handle the repeat toolbutton. 240 """ 241 from .QRegularExpressionWizardRepeatDialog import ( 242 QRegularExpressionWizardRepeatDialog 243 ) 244 dlg = QRegularExpressionWizardRepeatDialog(self) 245 if dlg.exec() == QDialog.DialogCode.Accepted: 246 self.__insertString(dlg.getRepeat()) 247 248 @pyqtSlot() 249 def on_nonGroupButton_clicked(self): 250 """ 251 Private slot to handle the non group toolbutton. 252 """ 253 self.__insertString("(?:)", -1) 254 255 @pyqtSlot() 256 def on_atomicGroupButton_clicked(self): 257 """ 258 Private slot to handle the atomic non group toolbutton. 259 """ 260 self.__insertString("(?>)", -1) 261 262 @pyqtSlot() 263 def on_groupButton_clicked(self): 264 """ 265 Private slot to handle the group toolbutton. 266 """ 267 self.__insertString("()", -1) 268 269 @pyqtSlot() 270 def on_namedGroupButton_clicked(self): 271 """ 272 Private slot to handle the named group toolbutton. 273 """ 274 self.__insertString("(?P<>)", -2) 275 276 @pyqtSlot() 277 def on_namedReferenceButton_clicked(self): 278 """ 279 Private slot to handle the named reference toolbutton. 280 """ 281 # determine cursor position as length into text 282 length = self.regexpTextEdit.textCursor().position() 283 284 # only present group names that occur before the current 285 # cursor position 286 regex = self.regexpTextEdit.toPlainText()[:length] 287 names = self.namedGroups(regex) 288 if not names: 289 E5MessageBox.information( 290 self, 291 self.tr("Named reference"), 292 self.tr("""No named groups have been defined yet.""")) 293 return 294 295 groupName, ok = QInputDialog.getItem( 296 self, 297 self.tr("Named reference"), 298 self.tr("Select group name:"), 299 names, 300 0, True) 301 if ok and groupName: 302 self.__insertString("(?P={0})".format(groupName)) 303 304 @pyqtSlot() 305 def on_altnButton_clicked(self): 306 """ 307 Private slot to handle the alternatives toolbutton. 308 """ 309 self.__insertString("(|)", -2) 310 311 @pyqtSlot() 312 def on_beglineButton_clicked(self): 313 """ 314 Private slot to handle the begin line toolbutton. 315 """ 316 self.__insertString("^") 317 318 @pyqtSlot() 319 def on_endlineButton_clicked(self): 320 """ 321 Private slot to handle the end line toolbutton. 322 """ 323 self.__insertString("$") 324 325 @pyqtSlot() 326 def on_wordboundButton_clicked(self): 327 """ 328 Private slot to handle the word boundary toolbutton. 329 """ 330 self.__insertString("\\b") 331 332 @pyqtSlot() 333 def on_nonwordboundButton_clicked(self): 334 """ 335 Private slot to handle the non word boundary toolbutton. 336 """ 337 self.__insertString("\\B") 338 339 @pyqtSlot() 340 def on_poslookaheadButton_clicked(self): 341 """ 342 Private slot to handle the positive lookahead toolbutton. 343 """ 344 self.__insertString("(?=)", -1) 345 346 @pyqtSlot() 347 def on_neglookaheadButton_clicked(self): 348 """ 349 Private slot to handle the negative lookahead toolbutton. 350 """ 351 self.__insertString("(?!)", -1) 352 353 @pyqtSlot() 354 def on_poslookbehindButton_clicked(self): 355 """ 356 Private slot to handle the positive lookbehind toolbutton. 357 """ 358 self.__insertString("(?<=)", -1) 359 360 @pyqtSlot() 361 def on_neglookbehindButton_clicked(self): 362 """ 363 Private slot to handle the negative lookbehind toolbutton. 364 """ 365 self.__insertString("(?<!)", -1) 366 367 @pyqtSlot() 368 def on_undoButton_clicked(self): 369 """ 370 Private slot to handle the undo action. 371 """ 372 self.regexpTextEdit.document().undo() 373 374 @pyqtSlot() 375 def on_redoButton_clicked(self): 376 """ 377 Private slot to handle the redo action. 378 """ 379 self.regexpTextEdit.document().redo() 380 381 def on_buttonBox_clicked(self, button): 382 """ 383 Private slot called by a button of the button box clicked. 384 385 @param button button that was clicked (QAbstractButton) 386 """ 387 if button == self.validateButton: 388 self.on_validateButton_clicked() 389 elif button == self.executeButton: 390 self.on_executeButton_clicked() 391 elif button == self.saveButton: 392 self.on_saveButton_clicked() 393 elif button == self.loadButton: 394 self.on_loadButton_clicked() 395 elif button == self.nextButton: 396 self.on_nextButton_clicked() 397 elif self.copyButton and button == self.copyButton: 398 self.on_copyButton_clicked() 399 400 @pyqtSlot() 401 def on_saveButton_clicked(self): 402 """ 403 Private slot to save the QRegularExpression to a file. 404 """ 405 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( 406 self, 407 self.tr("Save regular expression"), 408 "", 409 self.tr("RegExp Files (*.rx);;All Files (*)"), 410 None, 411 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) 412 if fname: 413 ext = QFileInfo(fname).suffix() 414 if not ext: 415 ex = selectedFilter.split("(*")[1].split(")")[0] 416 if ex: 417 fname += ex 418 if QFileInfo(fname).exists(): 419 res = E5MessageBox.yesNo( 420 self, 421 self.tr("Save regular expression"), 422 self.tr("<p>The file <b>{0}</b> already exists." 423 " Overwrite it?</p>").format(fname), 424 icon=E5MessageBox.Warning) 425 if not res: 426 return 427 428 fname = Utilities.toNativeSeparators(fname) 429 try: 430 with open(fname, "w", encoding="utf-8") as f: 431 f.write(self.regexpTextEdit.toPlainText()) 432 except OSError as err: 433 E5MessageBox.information( 434 self, 435 self.tr("Save regular expression"), 436 self.tr("""<p>The regular expression could not""" 437 """ be saved.</p><p>Reason: {0}</p>""") 438 .format(str(err))) 439 440 @pyqtSlot() 441 def on_loadButton_clicked(self): 442 """ 443 Private slot to load a QRegularExpression from a file. 444 """ 445 fname = E5FileDialog.getOpenFileName( 446 self, 447 self.tr("Load regular expression"), 448 "", 449 self.tr("RegExp Files (*.rx);;All Files (*)")) 450 if fname: 451 fname = Utilities.toNativeSeparators(fname) 452 try: 453 with open(fname, "r", encoding="utf-8") as f: 454 regexp = f.read() 455 self.regexpTextEdit.setPlainText(regexp) 456 except OSError as err: 457 E5MessageBox.information( 458 self, 459 self.tr("Save regular expression"), 460 self.tr("""<p>The regular expression could not""" 461 """ be saved.</p><p>Reason: {0}</p>""") 462 .format(str(err))) 463 464 @pyqtSlot() 465 def on_copyButton_clicked(self): 466 """ 467 Private slot to copy the QRegularExpression string into the clipboard. 468 469 This slot is only available, if not called from within eric. 470 """ 471 escaped = self.regexpTextEdit.toPlainText() 472 if escaped: 473 escaped = escaped.replace("\\", "\\\\") 474 cb = QApplication.clipboard() 475 cb.setText(escaped, QClipboard.Mode.Clipboard) 476 if cb.supportsSelection(): 477 cb.setText(escaped, QClipboard.Mode.Selection) 478 479 @pyqtSlot() 480 def on_validateButton_clicked(self): 481 """ 482 Private slot to validate the entered QRegularExpression. 483 """ 484 if not self.__pyqt5Available: 485 # only available for PyQt5 486 return 487 488 regexp = self.regexpTextEdit.toPlainText() 489 if regexp: 490 options = [] 491 if self.caseInsensitiveCheckBox.isChecked(): 492 options.append("CaseInsensitiveOption") 493 if self.multilineCheckBox.isChecked(): 494 options.append("MultilineOption") 495 if self.dotallCheckBox.isChecked(): 496 options.append("DotMatchesEverythingOption") 497 if self.extendedCheckBox.isChecked(): 498 options.append("ExtendedPatternSyntaxOption") 499 if self.greedinessCheckBox.isChecked(): 500 options.append("InvertedGreedinessOption") 501 if self.unicodeCheckBox.isChecked(): 502 options.append("UseUnicodePropertiesOption") 503 if self.captureCheckBox.isChecked(): 504 options.append("DontCaptureOption") 505 506 if self.__sendCommand("validate", options=options, regexp=regexp): 507 response = self.__receiveResponse() 508 if response and "valid" in response: 509 if response["valid"]: 510 E5MessageBox.information( 511 self, 512 self.tr("Validation"), 513 self.tr( 514 """The regular expression is valid.""")) 515 else: 516 E5MessageBox.critical( 517 self, 518 self.tr("Error"), 519 self.tr("""Invalid regular expression: {0}""") 520 .format(response["errorMessage"])) 521 # move cursor to error offset 522 offset = response["errorOffset"] 523 tc = self.regexpTextEdit.textCursor() 524 tc.setPosition(offset) 525 self.regexpTextEdit.setTextCursor(tc) 526 self.regexpTextEdit.setFocus() 527 return 528 else: 529 E5MessageBox.critical( 530 self, 531 self.tr("Communication Error"), 532 self.tr("""Invalid response received from""" 533 """ PyQt5 backend.""")) 534 else: 535 E5MessageBox.critical( 536 self, 537 self.tr("Communication Error"), 538 self.tr("""Communication with PyQt5 backend""" 539 """ failed.""")) 540 else: 541 E5MessageBox.critical( 542 self, 543 self.tr("Error"), 544 self.tr("""A regular expression must be given.""")) 545 546 @pyqtSlot() 547 def on_executeButton_clicked(self, startpos=0): 548 """ 549 Private slot to execute the entered QRegularExpression on the test 550 text. 551 552 This slot will execute the entered QRegularExpression on the entered 553 test data and will display the result in the table part of the dialog. 554 555 @param startpos starting position for the QRegularExpression matching 556 """ 557 if not self.__pyqt5Available: 558 # only available for PyQt5 559 return 560 561 regexp = self.regexpTextEdit.toPlainText() 562 text = self.textTextEdit.toPlainText() 563 if regexp and text: 564 options = [] 565 if self.caseInsensitiveCheckBox.isChecked(): 566 options.append("CaseInsensitiveOption") 567 if self.multilineCheckBox.isChecked(): 568 options.append("MultilineOption") 569 if self.dotallCheckBox.isChecked(): 570 options.append("DotMatchesEverythingOption") 571 if self.extendedCheckBox.isChecked(): 572 options.append("ExtendedPatternSyntaxOption") 573 if self.greedinessCheckBox.isChecked(): 574 options.append("InvertedGreedinessOption") 575 if self.unicodeCheckBox.isChecked(): 576 options.append("UseUnicodePropertiesOption") 577 if self.captureCheckBox.isChecked(): 578 options.append("DontCaptureOption") 579 580 if self.__sendCommand("execute", options=options, regexp=regexp, 581 text=text, startpos=startpos): 582 response = self.__receiveResponse() 583 if response and ("valid" in response or "matched" in response): 584 if "valid" in response: 585 E5MessageBox.critical( 586 self, 587 self.tr("Error"), 588 self.tr("""Invalid regular expression: {0}""") 589 .format(response["errorMessage"])) 590 # move cursor to error offset 591 offset = response["errorOffset"] 592 tc = self.regexpTextEdit.textCursor() 593 tc.setPosition(offset) 594 self.regexpTextEdit.setTextCursor(tc) 595 self.regexpTextEdit.setFocus() 596 return 597 else: 598 row = 0 599 OFFSET = 5 600 601 self.resultTable.setColumnCount(0) 602 self.resultTable.setColumnCount(3) 603 self.resultTable.setRowCount(0) 604 self.resultTable.setRowCount(OFFSET) 605 self.resultTable.setItem( 606 row, 0, QTableWidgetItem(self.tr("Regexp"))) 607 self.resultTable.setItem( 608 row, 1, QTableWidgetItem(regexp)) 609 if response["matched"]: 610 captures = response["captures"] 611 # index 0 is the complete match 612 offset = captures[0][1] 613 self.lastMatchEnd = captures[0][2] 614 self.nextButton.setEnabled(True) 615 row += 1 616 self.resultTable.setItem( 617 row, 0, 618 QTableWidgetItem(self.tr("Offset"))) 619 self.resultTable.setItem( 620 row, 1, 621 QTableWidgetItem("{0:d}".format(offset))) 622 623 row += 1 624 self.resultTable.setItem( 625 row, 0, 626 QTableWidgetItem(self.tr("Captures"))) 627 self.resultTable.setItem( 628 row, 1, 629 QTableWidgetItem( 630 "{0:d}".format(len(captures) - 1))) 631 row += 1 632 self.resultTable.setItem( 633 row, 1, 634 QTableWidgetItem(self.tr("Text"))) 635 self.resultTable.setItem( 636 row, 2, 637 QTableWidgetItem(self.tr("Characters"))) 638 639 row += 1 640 self.resultTable.setItem( 641 row, 0, 642 QTableWidgetItem(self.tr("Match"))) 643 self.resultTable.setItem( 644 row, 1, 645 QTableWidgetItem(captures[0][0])) 646 self.resultTable.setItem( 647 row, 2, 648 QTableWidgetItem( 649 "{0:d}".format(captures[0][3]))) 650 651 for i in range(1, len(captures)): 652 if captures[i][0]: 653 row += 1 654 self.resultTable.insertRow(row) 655 self.resultTable.setItem( 656 row, 0, 657 QTableWidgetItem( 658 self.tr("Capture #{0}") 659 .format(i))) 660 self.resultTable.setItem( 661 row, 1, 662 QTableWidgetItem(captures[i][0])) 663 self.resultTable.setItem( 664 row, 2, 665 QTableWidgetItem( 666 "{0:d}".format(captures[i][3]))) 667 668 # highlight the matched text 669 tc = self.textTextEdit.textCursor() 670 tc.setPosition(offset) 671 tc.setPosition( 672 self.lastMatchEnd, 673 QTextCursor.MoveMode.KeepAnchor) 674 self.textTextEdit.setTextCursor(tc) 675 else: 676 self.nextButton.setEnabled(False) 677 self.resultTable.setRowCount(2) 678 row += 1 679 if startpos > 0: 680 self.resultTable.setItem( 681 row, 0, 682 QTableWidgetItem( 683 self.tr("No more matches"))) 684 else: 685 self.resultTable.setItem( 686 row, 0, 687 QTableWidgetItem( 688 self.tr("No matches"))) 689 690 # remove the highlight 691 tc = self.textTextEdit.textCursor() 692 tc.setPosition(0) 693 self.textTextEdit.setTextCursor(tc) 694 695 self.resultTable.resizeColumnsToContents() 696 self.resultTable.resizeRowsToContents() 697 self.resultTable.verticalHeader().hide() 698 self.resultTable.horizontalHeader().hide() 699 else: 700 E5MessageBox.critical( 701 self, 702 self.tr("Communication Error"), 703 self.tr("""Invalid response received from""" 704 """ PyQt5 backend.""")) 705 else: 706 E5MessageBox.critical( 707 self, 708 self.tr("Communication Error"), 709 self.tr("""Communication with PyQt5""" 710 """ backend failed.""")) 711 else: 712 E5MessageBox.critical( 713 self, 714 self.tr("Error"), 715 self.tr("""A regular expression and a text must""" 716 """ be given.""")) 717 718 @pyqtSlot() 719 def on_nextButton_clicked(self): 720 """ 721 Private slot to find the next match. 722 """ 723 self.on_executeButton_clicked(self.lastMatchEnd) 724 725 @pyqtSlot() 726 def on_regexpTextEdit_textChanged(self): 727 """ 728 Private slot called when the regexp changes. 729 """ 730 if self.nextButton: 731 self.nextButton.setEnabled(False) 732 733 def getCode(self, indLevel, indString): 734 """ 735 Public method to get the source code. 736 737 @param indLevel indentation level (int) 738 @param indString string used for indentation (space or tab) (string) 739 @return generated code (string) 740 """ 741 # calculate the indentation string 742 i1string = (indLevel + 1) * indString 743 estring = os.linesep + indLevel * indString 744 745 # now generate the code 746 reVar = self.variableLineEdit.text() 747 if not reVar: 748 reVar = "regexp" 749 750 regexp = self.regexpTextEdit.toPlainText() 751 752 options = [] 753 if self.caseInsensitiveCheckBox.isChecked(): 754 options.append( 755 "QRegularExpression.PatternOption.CaseInsensitiveOption") 756 if self.multilineCheckBox.isChecked(): 757 options.append( 758 "QRegularExpression.PatternOption.MultilineOption") 759 if self.dotallCheckBox.isChecked(): 760 options.append( 761 "QRegularExpression.PatternOption.DotMatchesEverythingOption") 762 if self.extendedCheckBox.isChecked(): 763 options.append( 764 "QRegularExpression.PatternOption.ExtendedPatternSyntaxOption") 765 if self.greedinessCheckBox.isChecked(): 766 options.append( 767 "QRegularExpression.PatternOption.InvertedGreedinessOption") 768 if self.unicodeCheckBox.isChecked(): 769 options.append( 770 "QRegularExpression.PatternOption.UseUnicodePropertiesOption") 771 if self.captureCheckBox.isChecked(): 772 options.append( 773 "QRegularExpression.PatternOption.DontCaptureOption") 774 options = " |{0}{1}".format(os.linesep, i1string).join(options) 775 776 code = '{0} = QRegularExpression('.format(reVar) 777 if options: 778 code += '{0}{1}r"""{2}""",'.format( 779 os.linesep, i1string, regexp.replace('"', '\\"')) 780 code += '{0}{1}{2}'.format(os.linesep, i1string, options) 781 else: 782 code += 'r"""{0}"""'.format(regexp.replace('"', '\\"')) 783 code += '){0}'.format(estring) 784 return code 785 786 787class QRegularExpressionWizardDialog(QDialog): 788 """ 789 Class for the dialog variant. 790 """ 791 def __init__(self, parent=None, fromEric=True): 792 """ 793 Constructor 794 795 @param parent parent widget (QWidget) 796 @param fromEric flag indicating a call from within eric 797 """ 798 super().__init__(parent) 799 self.setModal(fromEric) 800 self.setSizeGripEnabled(True) 801 802 self.__layout = QVBoxLayout(self) 803 self.__layout.setContentsMargins(0, 0, 0, 0) 804 self.setLayout(self.__layout) 805 806 self.cw = QRegularExpressionWizardWidget(self, fromEric) 807 size = self.cw.size() 808 self.__layout.addWidget(self.cw) 809 self.resize(size) 810 self.setWindowTitle(self.cw.windowTitle()) 811 812 self.cw.buttonBox.accepted.connect(self.accept) 813 self.cw.buttonBox.rejected.connect(self.reject) 814 815 def getCode(self, indLevel, indString): 816 """ 817 Public method to get the source code. 818 819 @param indLevel indentation level (int) 820 @param indString string used for indentation (space or tab) (string) 821 @return generated code (string) 822 """ 823 return self.cw.getCode(indLevel, indString) 824 825 def accept(self): 826 """ 827 Public slot to hide the dialog and set the result code to Accepted. 828 """ 829 self.cw.shutdown() 830 super().accept() 831 832 def reject(self): 833 """ 834 Public slot to hide the dialog and set the result code to Rejected. 835 """ 836 self.cw.shutdown() 837 super().reject() 838 839 840class QRegularExpressionWizardWindow(E5MainWindow): 841 """ 842 Main window class for the standalone dialog. 843 """ 844 def __init__(self, parent=None): 845 """ 846 Constructor 847 848 @param parent reference to the parent widget (QWidget) 849 """ 850 super().__init__(parent) 851 self.cw = QRegularExpressionWizardWidget(self, fromEric=False) 852 size = self.cw.size() 853 self.setCentralWidget(self.cw) 854 self.resize(size) 855 self.setWindowTitle(self.cw.windowTitle()) 856 857 self.setStyle( 858 Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) 859 860 self.cw.buttonBox.accepted.connect(self.close) 861 self.cw.buttonBox.rejected.connect(self.close) 862 863 def closeEvent(self, evt): 864 """ 865 Protected method handling the close event. 866 867 @param evt close event (QCloseEvent) 868 """ 869 self.cw.shutdown() 870 super().closeEvent(evt) 871