diff -r e9e7eca7efee -r bf71ee032bb4 src/eric7/HexEdit/HexEditSearchReplaceWidget.py --- a/src/eric7/HexEdit/HexEditSearchReplaceWidget.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/HexEdit/HexEditSearchReplaceWidget.py Wed Jul 13 14:55:47 2022 +0200 @@ -21,10 +21,11 @@ """ Class implementing a search and replace widget for the hex editor. """ + def __init__(self, editor, mainWindow, replace=False, parent=None): """ Constructor - + @param editor reference to the hex editor widget @type HexEditWidget @param mainWindow reference to the main window @@ -35,54 +36,61 @@ @type QWidget """ super().__init__(parent) - + self.__replace = replace self.__editor = editor - + # keep this in sync with the logic in __getContent() self.__formatAndValidators = { - "hex": (self.tr("Hex"), QRegularExpressionValidator( - QRegularExpression("[0-9a-f]*"))), - "dec": (self.tr("Dec"), QRegularExpressionValidator( - QRegularExpression("[0-9]*"))), - "oct": (self.tr("Oct"), QRegularExpressionValidator( - QRegularExpression("[0-7]*"))), - "bin": (self.tr("Bin"), QRegularExpressionValidator( - QRegularExpression("[01]*"))), + "hex": ( + self.tr("Hex"), + QRegularExpressionValidator(QRegularExpression("[0-9a-f]*")), + ), + "dec": ( + self.tr("Dec"), + QRegularExpressionValidator(QRegularExpression("[0-9]*")), + ), + "oct": ( + self.tr("Oct"), + QRegularExpressionValidator(QRegularExpression("[0-7]*")), + ), + "bin": ( + self.tr("Bin"), + QRegularExpressionValidator(QRegularExpression("[01]*")), + ), "iso-8859-1": (self.tr("Text"), None), # text as latin-1/iso-8859-1 "utf-8": (self.tr("UTF-8"), None), # text as utf-8 } formatOrder = ["hex", "dec", "oct", "bin", "iso-8859-1", "utf-8"] - + self.__currentFindFormat = "" self.__currentReplaceFormat = "" - + self.__findHistory = mainWindow.getSRHistory("search") if replace: from .Ui_HexEditReplaceWidget import Ui_HexEditReplaceWidget + self.__replaceHistory = mainWindow.getSRHistory("replace") self.__ui = Ui_HexEditReplaceWidget() else: from .Ui_HexEditSearchWidget import Ui_HexEditSearchWidget + self.__ui = Ui_HexEditSearchWidget() self.__ui.setupUi(self) - + self.__ui.closeButton.setIcon(UI.PixmapCache.getIcon("close")) - self.__ui.findPrevButton.setIcon( - UI.PixmapCache.getIcon("1leftarrow")) - self.__ui.findNextButton.setIcon( - UI.PixmapCache.getIcon("1rightarrow")) - + self.__ui.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow")) + self.__ui.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow")) + if replace: - self.__ui.replaceButton.setIcon( - UI.PixmapCache.getIcon("editReplace")) + self.__ui.replaceButton.setIcon(UI.PixmapCache.getIcon("editReplace")) self.__ui.replaceSearchButton.setIcon( - UI.PixmapCache.getIcon("editReplaceSearch")) - self.__ui.replaceAllButton.setIcon( - UI.PixmapCache.getIcon("editReplaceAll")) - + UI.PixmapCache.getIcon("editReplaceSearch") + ) + self.__ui.replaceAllButton.setIcon(UI.PixmapCache.getIcon("editReplaceAll")) + for dataFormat in formatOrder: formatStr, validator = self.__formatAndValidators[dataFormat] self.__ui.findFormatCombo.addItem(formatStr, dataFormat) @@ -90,60 +98,70 @@ for dataFormat in formatOrder: formatStr, validator = self.__formatAndValidators[dataFormat] self.__ui.replaceFormatCombo.addItem(formatStr, dataFormat) - + self.__ui.findtextCombo.setCompleter(None) self.__ui.findtextCombo.lineEdit().returnPressed.connect( - self.__findByReturnPressed) + self.__findByReturnPressed + ) if replace: self.__ui.replacetextCombo.setCompleter(None) self.__ui.replacetextCombo.lineEdit().returnPressed.connect( - self.on_replaceButton_clicked) - + self.on_replaceButton_clicked + ) + self.findNextAct = EricAction( - self.tr('Find Next'), - self.tr('Find Next'), - 0, 0, self, 'hexEditor_search_widget_find_next') + self.tr("Find Next"), + self.tr("Find Next"), + 0, + 0, + self, + "hexEditor_search_widget_find_next", + ) self.findNextAct.triggered.connect(self.on_findNextButton_clicked) self.findNextAct.setEnabled(False) self.__ui.findtextCombo.addAction(self.findNextAct) - + self.findPrevAct = EricAction( - self.tr('Find Prev'), - self.tr('Find Prev'), - 0, 0, self, 'hexEditor_search_widget_find_prev') + self.tr("Find Prev"), + self.tr("Find Prev"), + 0, + 0, + self, + "hexEditor_search_widget_find_prev", + ) self.findPrevAct.triggered.connect(self.on_findPrevButton_clicked) self.findPrevAct.setEnabled(False) self.__ui.findtextCombo.addAction(self.findPrevAct) - + self.__havefound = False - + @pyqtSlot(int) def on_findFormatCombo_currentIndexChanged(self, idx): """ Private slot to handle a selection from the find format. - + @param idx index of the selected entry @type int """ if idx >= 0: findFormat = self.__ui.findFormatCombo.itemData(idx) - + if findFormat != self.__currentFindFormat: txt = self.__ui.findtextCombo.currentText() - newTxt = self.__convertText( - txt, self.__currentFindFormat, findFormat) + newTxt = self.__convertText(txt, self.__currentFindFormat, findFormat) self.__currentFindFormat = findFormat - + self.__ui.findtextCombo.setValidator( - self.__formatAndValidators[findFormat][1]) - + self.__formatAndValidators[findFormat][1] + ) + self.__ui.findtextCombo.setEditText(newTxt) - + @pyqtSlot(str) def on_findtextCombo_editTextChanged(self, txt): """ Private slot to enable/disable the find buttons. - + @param txt text of the find text combo @type str """ @@ -165,12 +183,12 @@ self.__ui.replaceButton.setEnabled(False) self.__ui.replaceSearchButton.setEnabled(False) self.__ui.replaceAllButton.setEnabled(True) - + @pyqtSlot(int) def on_findtextCombo_activated(self, idx): """ Private slot to handle a selection from the find history. - + @param idx index of the selected entry @type int """ @@ -178,12 +196,12 @@ formatIndex = self.__ui.findtextCombo.itemData(idx) if formatIndex is not None: self.__ui.findFormatCombo.setCurrentIndex(formatIndex) - + def __getContent(self, replace=False): """ Private method to get the contents of the find/replace combo as a bytearray. - + @param replace flag indicating to retrieve the replace contents @type bool @return search or replace term as text and binary data @@ -197,12 +215,12 @@ textCombo = self.__ui.findtextCombo formatCombo = self.__ui.findFormatCombo history = self.__findHistory - + txt = textCombo.currentText() idx = formatCombo.currentIndex() findFormat = formatCombo.itemData(idx) ba = self.__text2bytearray(txt, findFormat) - + # This moves any previous occurrence of this statement to the head # of the list and updates the combobox historyEntry = (idx, txt) @@ -212,27 +230,27 @@ textCombo.clear() for index, text in history: textCombo.addItem(text, index) - + return ba, txt - + @pyqtSlot() def on_findNextButton_clicked(self): """ Private slot to find the next occurrence. """ self.findPrevNext(False) - + @pyqtSlot() def on_findPrevButton_clicked(self): """ Private slot to find the previous occurrence. """ self.findPrevNext(True) - + def findPrevNext(self, prev=False): """ Public slot to find the next occurrence of the search term. - + @param prev flag indicating a backwards search @type bool @return flag indicating a successful search @@ -241,41 +259,41 @@ if not self.__havefound or not self.__ui.findtextCombo.currentText(): self.show() return False - + self.__findBackwards = prev ba, txt = self.__getContent() - + idx = -1 if len(ba) > 0: startIndex = self.__editor.cursorPosition() // 2 if prev: if ( - self.__editor.hasSelection() and - startIndex == self.__editor.getSelectionEnd() + self.__editor.hasSelection() + and startIndex == self.__editor.getSelectionEnd() ): # skip to the selection start startIndex = self.__editor.getSelectionBegin() idx = self.__editor.lastIndexOf(ba, startIndex) else: if ( - self.__editor.hasSelection() and - startIndex == self.__editor.getSelectionBegin() - 1 + self.__editor.hasSelection() + and startIndex == self.__editor.getSelectionBegin() - 1 ): # skip to the selection end startIndex = self.__editor.getSelectionEnd() idx = self.__editor.indexOf(ba, startIndex) - + if idx >= 0: if self.__replace: self.__ui.replaceButton.setEnabled(True) self.__ui.replaceSearchButton.setEnabled(True) else: EricMessageBox.information( - self, self.windowTitle(), - self.tr("'{0}' was not found.").format(txt)) - + self, self.windowTitle(), self.tr("'{0}' was not found.").format(txt) + ) + return idx >= 0 - + def __findByReturnPressed(self): """ Private slot to handle a return pressed in the find combo. @@ -284,34 +302,36 @@ self.findPrevNext(True) else: self.findPrevNext(False) - + @pyqtSlot(int) def on_replaceFormatCombo_currentIndexChanged(self, idx): """ Private slot to handle a selection from the replace format. - + @param idx index of the selected entry @type int """ if idx >= 0: replaceFormat = self.__ui.replaceFormatCombo.itemData(idx) - + if replaceFormat != self.__currentReplaceFormat: txt = self.__ui.replacetextCombo.currentText() newTxt = self.__convertText( - txt, self.__currentReplaceFormat, replaceFormat) + txt, self.__currentReplaceFormat, replaceFormat + ) self.__currentReplaceFormat = replaceFormat - + self.__ui.replacetextCombo.setValidator( - self.__formatAndValidators[replaceFormat][1]) - + self.__formatAndValidators[replaceFormat][1] + ) + self.__ui.replacetextCombo.setEditText(newTxt) - + @pyqtSlot(int) def on_replacetextCombo_activated(self, idx): """ Private slot to handle a selection from the replace history. - + @param idx index of the selected entry @type int """ @@ -326,7 +346,7 @@ Private slot to replace one occurrence of data. """ self.__doReplace(False) - + @pyqtSlot() def on_replaceSearchButton_clicked(self): """ @@ -334,49 +354,49 @@ one. """ self.__doReplace(True) - + def __doReplace(self, searchNext): """ Private method to replace one occurrence of data. - + @param searchNext flag indicating to search for the next occurrence @type bool """ # Check enabled status due to dual purpose usage of this method if ( - not self.__ui.replaceButton.isEnabled() and - not self.__ui.replaceSearchButton.isEnabled() + not self.__ui.replaceButton.isEnabled() + and not self.__ui.replaceSearchButton.isEnabled() ): return - + fba, ftxt = self.__getContent(False) rba, rtxt = self.__getContent(True) - + ok = False if self.__editor.hasSelection(): # we did a successful search before startIdx = self.__editor.getSelectionBegin() self.__editor.replaceByteArray(startIdx, len(fba), rba) - + if searchNext: ok = self.findPrevNext(self.__findBackwards) - + if not ok: self.__ui.replaceButton.setEnabled(False) self.__ui.replaceSearchButton.setEnabled(False) - + @pyqtSlot() def on_replaceAllButton_clicked(self): """ Private slot to replace all occurrences of data. """ replacements = 0 - + cursorPosition = self.__editor.cursorPosition() - + fba, ftxt = self.__getContent(False) rba, rtxt = self.__getContent(True) - + idx = 0 while idx >= 0: idx = self.__editor.indexOf(fba, idx) @@ -384,76 +404,78 @@ self.__editor.replaceByteArray(idx, len(fba), rba) idx += len(rba) replacements += 1 - + if replacements: EricMessageBox.information( - self, self.windowTitle(), - self.tr("Replaced {0} occurrences.") - .format(replacements)) + self, + self.windowTitle(), + self.tr("Replaced {0} occurrences.").format(replacements), + ) else: EricMessageBox.information( - self, self.windowTitle(), - self.tr("Nothing replaced because '{0}' was not found.") - .format(ftxt)) - + self, + self.windowTitle(), + self.tr("Nothing replaced because '{0}' was not found.").format(ftxt), + ) + self.__editor.setCursorPosition(cursorPosition) self.__editor.ensureVisible() - - def __showFind(self, text=''): + + def __showFind(self, text=""): """ Private method to display this widget in find mode. - + @param text hex encoded text to be shown in the findtext edit @type str """ self.__replace = False - + self.__ui.findtextCombo.clear() for index, txt in self.__findHistory: self.__ui.findtextCombo.addItem(txt, index) - self.__ui.findFormatCombo.setCurrentIndex(0) # 0 is always Hex + self.__ui.findFormatCombo.setCurrentIndex(0) # 0 is always Hex self.on_findFormatCombo_currentIndexChanged(0) self.__ui.findtextCombo.setEditText(text) self.__ui.findtextCombo.lineEdit().selectAll() self.__ui.findtextCombo.setFocus() self.on_findtextCombo_editTextChanged(text) - + self.__havefound = True self.__findBackwards = False - - def __showReplace(self, text=''): + + def __showReplace(self, text=""): """ Private slot to display this widget in replace mode. - + @param text hex encoded text to be shown in the findtext edit @type str """ self.__replace = True - + self.__ui.findtextCombo.clear() for index, txt in self.__findHistory: self.__ui.findtextCombo.addItem(txt, index) - self.__ui.findFormatCombo.setCurrentIndex(0) # 0 is always Hex + self.__ui.findFormatCombo.setCurrentIndex(0) # 0 is always Hex self.on_findFormatCombo_currentIndexChanged(0) self.__ui.findtextCombo.setEditText(text) self.__ui.findtextCombo.lineEdit().selectAll() self.__ui.findtextCombo.setFocus() self.on_findtextCombo_editTextChanged(text) - + self.__ui.replacetextCombo.clear() for index, txt in self.__replaceHistory: self.__ui.replacetextCombo.addItem(txt, index) - self.__ui.replaceFormatCombo.setCurrentIndex(0) # 0 is always Hex + self.__ui.replaceFormatCombo.setCurrentIndex(0) # 0 is always Hex self.on_replaceFormatCombo_currentIndexChanged(0) - self.__ui.replacetextCombo.setEditText('') - + self.__ui.replacetextCombo.setEditText("") + self.__havefound = True self.__findBackwards = False - - def show(self, text=''): + + def show(self, text=""): """ Public slot to show the widget. - + @param text hex encoded text to be shown in the findtext edit @type str """ @@ -463,7 +485,7 @@ self.__showFind(text) super().show() self.activateWindow() - + @pyqtSlot() def on_closeButton_clicked(self): """ @@ -475,17 +497,17 @@ def keyPressEvent(self, event): """ Protected slot to handle key press events. - + @param event reference to the key press event @type QKeyEvent """ if event.key() == Qt.Key.Key_Escape: self.close() - + def __convertText(self, txt, oldFormat, newFormat): """ Private method to convert text from one format into another. - + @param txt text to be converted @type str @param oldFormat current format of the text @@ -498,16 +520,16 @@ if txt and oldFormat and newFormat and oldFormat != newFormat: # step 1: convert the text to a byte array using the old format byteArray = self.__text2bytearray(txt, oldFormat) - + # step 2: convert the byte array to text using the new format txt = self.__bytearray2text(byteArray, newFormat) - + return txt - + def __int2bytearray(self, value): """ Private method to convert an integer to a byte array. - + @param value value to be converted @type int @return byte array for the given value @@ -517,13 +539,13 @@ while value > 0: value, modulus = divmod(value, 256) ba.insert(0, modulus) - + return ba - + def __bytearray2int(self, array): """ Private method to convert a byte array to an integer value. - + @param array byte array to be converted @type bytearray @return integer value of the given array @@ -532,13 +554,13 @@ value = 0 for b in array: value = value * 256 + b - + return value - + def __text2bytearray(self, txt, dataFormat): """ Private method to convert a text to a byte array. - + @param txt text to be converted @type str @param dataFormat format of the text @@ -550,27 +572,26 @@ """ if dataFormat not in self.__formatAndValidators.keys(): raise ValueError("Bad value for 'dataFormat' parameter.") - - if dataFormat == "hex": # hex format - ba = bytearray(QByteArray.fromHex( - bytes(txt, encoding="ascii"))) - elif dataFormat == "dec": # decimal format + + if dataFormat == "hex": # hex format + ba = bytearray(QByteArray.fromHex(bytes(txt, encoding="ascii"))) + elif dataFormat == "dec": # decimal format ba = self.__int2bytearray(int(txt, 10)) - elif dataFormat == "oct": # octal format + elif dataFormat == "oct": # octal format ba = self.__int2bytearray(int(txt, 8)) - elif dataFormat == "bin": # binary format + elif dataFormat == "bin": # binary format ba = self.__int2bytearray(int(txt, 2)) - elif dataFormat == "iso-8859-1": # latin-1/iso-8859-1 text + elif dataFormat == "iso-8859-1": # latin-1/iso-8859-1 text ba = bytearray(txt, encoding="iso-8859-1") - elif dataFormat == "utf-8": # utf-8 text + elif dataFormat == "utf-8": # utf-8 text ba = bytearray(txt, encoding="utf-8") - + return ba - + def __bytearray2text(self, array, dataFormat): """ Private method to convert a byte array to a text. - + @param array byte array to be converted @type bytearray @param dataFormat format of the text @@ -582,18 +603,18 @@ """ if dataFormat not in self.__formatAndValidators.keys(): raise ValueError("Bad value for 'dataFormat' parameter.") - - if dataFormat == "hex": # hex format + + if dataFormat == "hex": # hex format txt = "{0:x}".format(self.__bytearray2int(array)) - elif dataFormat == "dec": # decimal format + elif dataFormat == "dec": # decimal format txt = "{0:d}".format(self.__bytearray2int(array)) - elif dataFormat == "oct": # octal format + elif dataFormat == "oct": # octal format txt = "{0:o}".format(self.__bytearray2int(array)) - elif dataFormat == "bin": # binary format + elif dataFormat == "bin": # binary format txt = "{0:b}".format(self.__bytearray2int(array)) - elif dataFormat == "iso-8859-1": # latin-1/iso-8859-1 text + elif dataFormat == "iso-8859-1": # latin-1/iso-8859-1 text txt = str(array, encoding="iso-8859-1") - elif dataFormat == "utf-8": # utf-8 text + elif dataFormat == "utf-8": # utf-8 text txt = str(array, encoding="utf-8", errors="replace") - + return txt