Improved the search/replace history handling of the hex editor and added input validators.

Mon, 11 Jan 2016 19:46:19 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 11 Jan 2016 19:46:19 +0100
changeset 4659
2863d05e83c6
parent 4658
d760763dcc4a
child 4660
9096e8a2df54

Improved the search/replace history handling of the hex editor and added input validators.

HexEdit/HexEditMainWindow.py file | annotate | diff | comparison | revisions
HexEdit/HexEditReplaceWidget.ui file | annotate | diff | comparison | revisions
HexEdit/HexEditSearchReplaceWidget.py file | annotate | diff | comparison | revisions
HexEdit/HexEditSearchWidget.ui file | annotate | diff | comparison | revisions
HexEdit/HexEditWidget.py file | annotate | diff | comparison | revisions
--- a/HexEdit/HexEditMainWindow.py	Mon Jan 11 17:39:04 2016 +0100
+++ b/HexEdit/HexEditMainWindow.py	Mon Jan 11 19:46:19 2016 +0100
@@ -53,6 +53,15 @@
         super(HexEditMainWindow, self).__init__(parent)
         self.setObjectName("eric6_hex_editor")
         
+        self.__srHistory = {
+            "search": [],
+            # list of recent searches (tuple of format type index and
+            # search term)
+            "replace": [],
+            # list of recent replaces (tuple of format type index and
+            # replace term
+        }
+        
         self.__fromEric = fromEric
         self.setWindowIcon(UI.PixmapCache.getIcon("hexEditor.png"))
         
@@ -61,8 +70,10 @@
                           Preferences.getUI("StyleSheet"))
         
         self.__editor = HexEditWidget()
-        self.__searchWidget = HexEditSearchReplaceWidget(self.__editor, False)
-        self.__replaceWidget = HexEditSearchReplaceWidget(self.__editor, True)
+        self.__searchWidget = HexEditSearchReplaceWidget(
+            self.__editor, self, False)
+        self.__replaceWidget = HexEditSearchReplaceWidget(
+            self.__editor, self, True)
         cw = QWidget()
         layout = QVBoxLayout(cw)
         layout.setContentsMargins(1, 1, 1, 1)
@@ -1192,14 +1203,22 @@
         Private method to handle the search action.
         """
         self.__replaceWidget.hide()
-        self.__searchWidget.show()
+        if self.__editor.hasSelection():
+            txt = self.__editor.selectionToHexString()
+        else:
+            txt = ""
+        self.__searchWidget.show(txt)
         
     def __replace(self):
         """
         Private method to handle the replace action.
         """
         self.__searchWidget.hide()
-        self.__replaceWidget.show()
+        if self.__editor.hasSelection():
+            txt = self.__editor.selectionToHexString()
+        else:
+            txt = ""
+        self.__replaceWidget.show(txt)
     
     def preferencesChanged(self):
         """
@@ -1252,3 +1271,16 @@
         """
         for hexEditor in HexEditMainWindow.windows:
             hexEditor.preferencesChanged()
+    
+    def getSRHistory(self, key):
+        """
+        Public method to get the search or replace history list.
+        
+        @param key name of list to return
+        @type str (must be 'search' or 'replace')
+        @return the requested history list
+        @type list of tuples of (int, str)
+        """
+        assert key in ['search', 'replace']
+        
+        return self.__srHistory[key]
--- a/HexEdit/HexEditReplaceWidget.ui	Mon Jan 11 17:39:04 2016 +0100
+++ b/HexEdit/HexEditReplaceWidget.ui	Mon Jan 11 19:46:19 2016 +0100
@@ -54,16 +54,6 @@
      <property name="toolTip">
       <string>Select the data format of the find data field</string>
      </property>
-     <item>
-      <property name="text">
-       <string>Hex</string>
-      </property>
-     </item>
-     <item>
-      <property name="text">
-       <string>Text</string>
-      </property>
-     </item>
     </widget>
    </item>
    <item row="0" column="3">
@@ -117,16 +107,6 @@
      <property name="toolTip">
       <string>Select the data format of the replace data field</string>
      </property>
-     <item>
-      <property name="text">
-       <string>Hex</string>
-      </property>
-     </item>
-     <item>
-      <property name="text">
-       <string>Text</string>
-      </property>
-     </item>
     </widget>
    </item>
    <item row="1" column="3">
--- a/HexEdit/HexEditSearchReplaceWidget.py	Mon Jan 11 17:39:04 2016 +0100
+++ b/HexEdit/HexEditSearchReplaceWidget.py	Mon Jan 11 19:46:19 2016 +0100
@@ -9,7 +9,8 @@
 
 from __future__ import unicode_literals
 
-from PyQt5.QtCore import pyqtSlot, Qt, QByteArray
+from PyQt5.QtCore import pyqtSlot, Qt, QByteArray, QRegExp
+from PyQt5.QtGui import QRegExpValidator
 from PyQt5.QtWidgets import QWidget
 
 from E5Gui.E5Action import E5Action
@@ -18,19 +19,20 @@
 import UI.PixmapCache
 
 
-# TODO: make the histories containing tuples with format index and text
-# TODO: add more format types (Dec, Oct, Bin)
-# TODO: add input validators to limit the find/replace input (use QRegExpValidator)
+# TODO: add more format types (Dec, Oct, Bin, UTF-8)
+# TODO: change the text of the combo when the format changes
 class HexEditSearchReplaceWidget(QWidget):
     """
     Class implementing a search and replace widget for the hex editor.
     """
-    def __init__(self, editor, replace=False, parent=None):
+    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
+        @type HexEditMainWindow
         @param replace flag indicating a replace widget
         @type bool
         @param parent reference to the parent widget
@@ -41,10 +43,15 @@
         self.__replace = replace
         self.__editor = editor
         
-        self.__findHistory = []
+        self.__formatAndValidators = [
+            (self.tr("Hex"), QRegExpValidator((QRegExp("[0-9a-f]*")))),
+            (self.tr("Text"), None),
+        ]
+        
+        self.__findHistory = mainWindow.getSRHistory("search")
         if replace:
             from .Ui_HexEditReplaceWidget import Ui_HexEditReplaceWidget
-            self.__replaceHistory = []
+            self.__replaceHistory = mainWindow.getSRHistory("replace")
             self.__ui = Ui_HexEditReplaceWidget()
         else:
             from .Ui_HexEditSearchWidget import Ui_HexEditSearchWidget
@@ -65,6 +72,11 @@
             self.__ui.replaceAllButton.setIcon(
                 UI.PixmapCache.getIcon("editReplaceAll.png"))
         
+        for format, validator in self.__formatAndValidators:
+            self.__ui.findFormatCombo.addItem(format)
+        if replace:
+            for format, validator in self.__formatAndValidators:
+                self.__ui.replaceFormatCombo.addItem(format)
         self.__ui.findtextCombo.setCompleter(None)
         self.__ui.findtextCombo.lineEdit().returnPressed.connect(
             self.__findByReturnPressed)
@@ -91,11 +103,25 @@
         
         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:
+            self.__ui.findtextCombo.setValidator(
+                self.__formatAndValidators[idx][1])
+    
+    @pyqtSlot(str)
     def on_findtextCombo_editTextChanged(self, txt):
         """
         Private slot to enable/disable the find buttons.
         
-        @param txt text of the find text combo (string)
+        @param txt text of the find text combo
+        @type str
         """
         if not txt:
             self.__ui.findNextButton.setEnabled(False)
@@ -116,6 +142,18 @@
                 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
+        """
+        if idx >= 0:
+            formatIndex = self.__ui.findtextCombo.itemData(idx)
+            self.__ui.findFormatCombo.setCurrentIndex(formatIndex)
+    
     def __getContent(self, replace=False):
         """
         Private method to get the contents of the find/replace combo as
@@ -145,11 +183,13 @@
         
         # This moves any previous occurrence of this statement to the head
         # of the list and updates the combobox
-        if txt in history:
-            history.remove(txt)
-        history.insert(0, txt)
+        historyEntry = (idx, txt)
+        if historyEntry in history:
+            history.remove(historyEntry)
+        history.insert(0, historyEntry)
         textCombo.clear()
-        textCombo.addItems(history)
+        for index, text in history:
+            textCombo.addItem(text, index)
         
         return ba, txt
     
@@ -218,6 +258,30 @@
             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:
+            self.__ui.replacetextCombo.setValidator(
+                self.__formatAndValidators[idx][1])
+    
+    @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
+        """
+        if idx >= 0:
+            formatIndex = self.__ui.replacetextCombo.itemData(idx)
+            self.__ui.replaceFormatCombo.setCurrentIndex(formatIndex)
 
     @pyqtSlot()
     def on_replaceButton_clicked(self):
@@ -239,7 +303,7 @@
         Private method to replace one occurrence of data.
         
         @param searchNext flag indicating to search for the next occurrence
-        (boolean).
+        @type bool
         """
         # Check enabled status due to dual purpose usage of this method
         if not self.__ui.replaceButton.isEnabled() and \
@@ -300,12 +364,16 @@
         """
         Private method to display this widget in find mode.
         
-        @param text text to be shown in the findtext edit (string)
+        @param text hex encoded text to be shown in the findtext edit
+        @type str
         """
         self.__replace = False
         
         self.__ui.findtextCombo.clear()
-        self.__ui.findtextCombo.addItems(self.__findHistory)
+        for index, txt in self.__findHistory:
+            self.__ui.findtextCombo.addItem(txt, index)
+        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()
@@ -318,19 +386,26 @@
         """
         Private slot to display this widget in replace mode.
         
-        @param text text to be shown in the findtext edit
+        @param text hex encoded text to be shown in the findtext edit
+        @type str
         """
         self.__replace = True
         
         self.__ui.findtextCombo.clear()
-        self.__ui.findtextCombo.addItems(self.__findHistory)
+        for index, txt in self.__findHistory:
+            self.__ui.findtextCombo.addItem(txt, index)
+        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()
-        self.__ui.replacetextCombo.addItems(self.__replaceHistory)
+        for index, txt in self.__replaceHistory:
+            self.__ui.replacetextCombo.addItem(txt, index)
+        self.__ui.replaceFormatCombo.setCurrentIndex(0)    # 0 is always Hex
+        self.on_replaceFormatCombo_currentIndexChanged(0)
         self.__ui.replacetextCombo.setEditText('')
         
         self.__havefound = True
@@ -340,7 +415,8 @@
         """
         Public slot to show the widget.
         
-        @param text text to be shown in the findtext edit (string)
+        @param text hex encoded text to be shown in the findtext edit
+        @type str
         """
         if self.__replace:
             self.__showReplace(text)
@@ -361,7 +437,8 @@
         """
         Protected slot to handle key press events.
         
-        @param event reference to the key press event (QKeyEvent)
+        @param event reference to the key press event
+        @type QKeyEvent
         """
         if event.key() == Qt.Key_Escape:
             self.close()
--- a/HexEdit/HexEditSearchWidget.ui	Mon Jan 11 17:39:04 2016 +0100
+++ b/HexEdit/HexEditSearchWidget.ui	Mon Jan 11 19:46:19 2016 +0100
@@ -54,16 +54,6 @@
      <property name="toolTip">
       <string>Select the data format of the find data field</string>
      </property>
-     <item>
-      <property name="text">
-       <string>Hex</string>
-      </property>
-     </item>
-     <item>
-      <property name="text">
-       <string>Text</string>
-      </property>
-     </item>
     </widget>
    </item>
    <item>
--- a/HexEdit/HexEditWidget.py	Mon Jan 11 17:39:04 2016 +0100
+++ b/HexEdit/HexEditWidget.py	Mon Jan 11 19:46:19 2016 +0100
@@ -750,6 +750,17 @@
         if not modified and setCleanState:
             self.__undoStack.setClean()
     
+    def selectionToHexString(self):
+        """
+        Public method to get a hexadecimal representation of the selection.
+        
+        @return hexadecimal representation of the selection
+        @rtype str
+        """
+        byteArray = self.__chunks.data(self.getSelectionBegin(),
+                                       self.__getSelectionLength())
+        return self.__toHex(byteArray).decode(encoding="ascii")
+    
     def selectionToReadableString(self):
         """
         Public method to get a formatted representation of the selection.

eric ide

mercurial