Added search and replace capability to the hex editor.

Sun, 10 Jan 2016 16:52:22 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 10 Jan 2016 16:52:22 +0100
changeset 4652
a88a2ba7a48a
parent 4651
7f3f276d3bf3
child 4653
e8b51747c48e

Added search and replace capability to the hex editor.

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
eric6.e4p file | annotate | diff | comparison | revisions
--- a/HexEdit/HexEditMainWindow.py	Sat Jan 09 19:44:31 2016 +0100
+++ b/HexEdit/HexEditMainWindow.py	Sun Jan 10 16:52:22 2016 +0100
@@ -13,13 +13,14 @@
 
 from PyQt5.QtCore import pyqtSignal, pyqtSlot, QFile, QFileInfo, QSize
 from PyQt5.QtGui import QKeySequence
-from PyQt5.QtWidgets import QWhatsThis, QLabel
+from PyQt5.QtWidgets import QWhatsThis, QLabel, QWidget, QVBoxLayout
 
 from E5Gui.E5Action import E5Action
 from E5Gui.E5MainWindow import E5MainWindow
 from E5Gui import E5FileDialog, E5MessageBox
 
 from .HexEditWidget import HexEditWidget
+from .HexEditSearchReplaceWidget import HexEditSearchReplaceWidget
 
 import UI.PixmapCache
 import UI.Config
@@ -59,7 +60,19 @@
                           Preferences.getUI("StyleSheet"))
         
         self.__editor = HexEditWidget()
-        self.setCentralWidget(self.__editor)
+        self.__searchWidget = HexEditSearchReplaceWidget(self.__editor, False)
+        self.__replaceWidget = HexEditSearchReplaceWidget(self.__editor, True)
+        cw = QWidget()
+        layout = QVBoxLayout(cw)
+        layout.setContentsMargins(1, 1, 1, 1)
+        layout.setSpacing(1)
+        layout.addWidget(self.__editor)
+        layout.addWidget(self.__searchWidget)
+        cw.setLayout(layout)
+        layout.addWidget(self.__replaceWidget)
+        self.__searchWidget.hide()
+        self.__replaceWidget.hide()
+        self.setCentralWidget(cw)
         
         g = Preferences.getGeometry("HexEditorGeometry")
         if g.isEmpty():
@@ -100,8 +113,6 @@
         
         self.__initFileActions()
         self.__initEditActions()
-##        self.__initViewActions()
-##        self.__initToolsActions()
         self.__initHelpActions()
         
     def __initFileActions(self):
@@ -406,6 +417,76 @@
         self.readonlyAct.toggled[bool].connect(self.__editor.setReadOnly)
         self.__actions.append(self.readonlyAct)
         
+        self.searchAct = E5Action(
+            self.tr('Search'),
+            UI.PixmapCache.getIcon("find.png"),
+            self.tr('&Search...'),
+            QKeySequence(self.tr("Ctrl+F", "Search|Search")),
+            0,
+            self, 'hexEditor_edit_search')
+        self.searchAct.setStatusTip(self.tr('Search for a text'))
+        self.searchAct.setWhatsThis(self.tr(
+            """<b>Search</b>"""
+            """<p>Search for some text in the current editor. A"""
+            """ dialog is shown to enter the searchtext and options"""
+            """ for the search.</p>"""
+        ))
+        self.searchAct.triggered.connect(self.__search)
+        self.__actions.append(self.searchAct)
+        
+        self.searchNextAct = E5Action(
+            self.tr('Search next'),
+            UI.PixmapCache.getIcon("findNext.png"),
+            self.tr('Search &next'),
+            QKeySequence(self.tr("F3", "Search|Search next")),
+            0,
+            self, 'hexEditor_edit_search_next')
+        self.searchNextAct.setStatusTip(self.tr(
+            'Search next occurrence of text'))
+        self.searchNextAct.setWhatsThis(self.tr(
+            """<b>Search next</b>"""
+            """<p>Search the next occurrence of some text in the current"""
+            """ editor. The previously entered searchtext and options are"""
+            """ reused.</p>"""
+        ))
+        self.searchNextAct.triggered.connect(self.__searchWidget.findPrevNext)
+        self.__actions.append(self.searchNextAct)
+        
+        self.searchPrevAct = E5Action(
+            self.tr('Search previous'),
+            UI.PixmapCache.getIcon("findPrev.png"),
+            self.tr('Search &previous'),
+            QKeySequence(self.tr("Shift+F3", "Search|Search previous")),
+            0,
+            self, 'hexEditor_edit_search_previous')
+        self.searchPrevAct.setStatusTip(self.tr(
+            'Search previous occurrence of text'))
+        self.searchPrevAct.setWhatsThis(self.tr(
+            """<b>Search previous</b>"""
+            """<p>Search the previous occurrence of some text in the current"""
+            """ editor. The previously entered searchtext and options are"""
+            """ reused.</p>"""
+        ))
+        self.searchPrevAct.triggered.connect(
+            lambda: self.__searchWidget.findPrevNext(True))
+        self.__actions.append(self.searchPrevAct)
+        
+        self.replaceAct = E5Action(
+            self.tr('Replace'),
+            self.tr('&Replace...'),
+            QKeySequence(self.tr("Ctrl+R", "Search|Replace")),
+            0,
+            self, 'hexEditor_edit_search_replace')
+        self.replaceAct.setStatusTip(self.tr('Replace some text'))
+        self.replaceAct.setWhatsThis(self.tr(
+            """<b>Replace</b>"""
+            """<p>Search for some text in the current editor and replace it."""
+            """ A dialog is shown to enter the searchtext, the replacement"""
+            """ text and options for the search and replace.</p>"""
+        ))
+        self.replaceAct.triggered.connect(self.__replace)
+        self.__actions.append(self.replaceAct)
+        
         self.redoAct.setEnabled(False)
         self.__editor.canRedoChanged.connect(self.redoAct.setEnabled)
         
@@ -508,6 +589,11 @@
         menu.addAction(self.deselectAllAct)
         menu.addAction(self.saveSelectionReadableAct)
         menu.addSeparator()
+        menu.addAction(self.searchAct)
+        menu.addAction(self.searchNextAct)
+        menu.addAction(self.searchPrevAct)
+        menu.addAction(self.replaceAct)
+        menu.addSeparator()
         menu.addAction(self.readonlyAct)
         
         mb.addSeparator()
@@ -545,6 +631,13 @@
         edittb.addAction(self.copyAct)
         edittb.addAction(self.pasteAct)
         
+        searchtb = self.addToolBar(self.tr("Find"))
+        searchtb.setObjectName("SearchToolBar")
+        searchtb.setIconSize(UI.Config.ToolBarIconSize)
+        searchtb.addAction(self.searchAct)
+        searchtb.addAction(self.searchNextAct)
+        searchtb.addAction(self.searchPrevAct)
+        
         helptb = self.addToolBar(self.tr("Help"))
         helptb.setObjectName("HelpToolBar")
         helptb.setIconSize(UI.Config.ToolBarIconSize)
@@ -1014,3 +1107,17 @@
         Private slot called in to enter Whats This mode.
         """
         QWhatsThis.enterWhatsThisMode()
+        
+    def __search(self):
+        """
+        Private method to handle the search action.
+        """
+        self.__replaceWidget.hide()
+        self.__searchWidget.show()
+        
+    def __replace(self):
+        """
+        Private method to handle the replace action.
+        """
+        self.__searchWidget.hide()
+        self.__replaceWidget.show()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HexEdit/HexEditReplaceWidget.ui	Sun Jan 10 16:52:22 2016 +0100
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>HexEditReplaceWidget</class>
+ <widget class="QWidget" name="HexEditReplaceWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>58</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Find and Replace</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <property name="leftMargin">
+    <number>1</number>
+   </property>
+   <property name="topMargin">
+    <number>1</number>
+   </property>
+   <property name="rightMargin">
+    <number>1</number>
+   </property>
+   <property name="bottomMargin">
+    <number>1</number>
+   </property>
+   <item row="0" column="0">
+    <widget class="QToolButton" name="closeButton">
+     <property name="toolTip">
+      <string>Press to close the window</string>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Find:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="2">
+    <widget class="QComboBox" name="findFormatCombo">
+     <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">
+    <widget class="QComboBox" name="findtextCombo">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>300</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="editable">
+      <bool>true</bool>
+     </property>
+     <property name="insertPolicy">
+      <enum>QComboBox::InsertAtTop</enum>
+     </property>
+     <property name="duplicatesEnabled">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="4">
+    <widget class="QToolButton" name="findPrevButton">
+     <property name="toolTip">
+      <string>Press to find the previous occurrence</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="5">
+    <widget class="QToolButton" name="findNextButton">
+     <property name="toolTip">
+      <string>Press to find the next occurrence</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Replace:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="2">
+    <widget class="QComboBox" name="replaceFormatCombo">
+     <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">
+    <widget class="QComboBox" name="replacetextCombo">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>300</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="editable">
+      <bool>true</bool>
+     </property>
+     <property name="insertPolicy">
+      <enum>QComboBox::InsertAtTop</enum>
+     </property>
+     <property name="duplicatesEnabled">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="4">
+    <widget class="QToolButton" name="replaceButton">
+     <property name="toolTip">
+      <string>Press to replace the selection</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="5">
+    <widget class="QToolButton" name="replaceSearchButton">
+     <property name="toolTip">
+      <string>Press to replace the selection and search for the next occurence</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="6">
+    <widget class="QToolButton" name="replaceAllButton">
+     <property name="toolTip">
+      <string>Press to replace all occurrences</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>findtextCombo</tabstop>
+  <tabstop>findFormatCombo</tabstop>
+  <tabstop>replacetextCombo</tabstop>
+  <tabstop>replaceFormatCombo</tabstop>
+  <tabstop>findPrevButton</tabstop>
+  <tabstop>findNextButton</tabstop>
+  <tabstop>replaceButton</tabstop>
+  <tabstop>replaceSearchButton</tabstop>
+  <tabstop>replaceAllButton</tabstop>
+  <tabstop>closeButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HexEdit/HexEditSearchReplaceWidget.py	Sun Jan 10 16:52:22 2016 +0100
@@ -0,0 +1,361 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a search and replace widget for the hex editor.
+"""
+
+from PyQt5.QtCore import pyqtSlot, Qt, QByteArray
+from PyQt5.QtWidgets import QWidget
+
+from E5Gui.E5Action import E5Action
+from E5Gui import E5MessageBox
+
+import UI.PixmapCache
+
+
+class HexEditSearchReplaceWidget(QWidget):
+    """
+    Class implementing a search and replace widget for the hex editor.
+    """
+    def __init__(self, editor, replace=False, parent=None):
+        """
+        Constructor
+        
+        @param editor reference to the hex editor widget
+        @type HexEditWidget
+        @param replace flag indicating a replace widget
+        @type bool
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(HexEditSearchReplaceWidget, self).__init__(parent)
+        
+        self.__replace = replace
+        self.__editor = editor
+        
+        self.__findHistory = []
+        if replace:
+            from .Ui_HexEditReplaceWidget import Ui_HexEditReplaceWidget
+            self.__replaceHistory = []
+            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.png"))
+        self.__ui.findPrevButton.setIcon(
+            UI.PixmapCache.getIcon("1leftarrow.png"))
+        self.__ui.findNextButton.setIcon(
+            UI.PixmapCache.getIcon("1rightarrow.png"))
+        
+        if replace:
+            self.__ui.replaceButton.setIcon(
+                UI.PixmapCache.getIcon("editReplace.png"))
+            self.__ui.replaceSearchButton.setIcon(
+                UI.PixmapCache.getIcon("editReplaceSearch.png"))
+            self.__ui.replaceAllButton.setIcon(
+                UI.PixmapCache.getIcon("editReplaceAll.png"))
+        
+        self.__ui.findtextCombo.setCompleter(None)
+        self.__ui.findtextCombo.lineEdit().returnPressed.connect(
+            self.__findByReturnPressed)
+        if replace:
+            self.__ui.replacetextCombo.setCompleter(None)
+            self.__ui.replacetextCombo.lineEdit().returnPressed.connect(
+                self.on_replaceButton_clicked)
+        
+        self.findNextAct = E5Action(
+            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 = E5Action(
+            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
+    
+    def on_findtextCombo_editTextChanged(self, txt):
+        """
+        Private slot to enable/disable the find buttons.
+        
+        @param txt text of the find text combo (string)
+        """
+        if not txt:
+            self.__ui.findNextButton.setEnabled(False)
+            self.findNextAct.setEnabled(False)
+            self.__ui.findPrevButton.setEnabled(False)
+            self.findPrevAct.setEnabled(False)
+            if self.__replace:
+                self.__ui.replaceButton.setEnabled(False)
+                self.__ui.replaceSearchButton.setEnabled(False)
+                self.__ui.replaceAllButton.setEnabled(False)
+        else:
+            self.__ui.findNextButton.setEnabled(True)
+            self.findNextAct.setEnabled(True)
+            self.__ui.findPrevButton.setEnabled(True)
+            self.findPrevAct.setEnabled(True)
+            if self.__replace:
+                self.__ui.replaceButton.setEnabled(False)
+                self.__ui.replaceSearchButton.setEnabled(False)
+                self.__ui.replaceAllButton.setEnabled(True)
+    
+    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
+        @rtype tuple of bytearray and str
+        """
+        if replace:
+            textCombo = self.__ui.replacetextCombo
+            formatCombo = self.__ui.replaceFormatCombo
+            history = self.__replaceHistory
+        else:
+            textCombo = self.__ui.findtextCombo
+            formatCombo = self.__ui.findFormatCombo
+            history = self.__findHistory
+        
+        txt = textCombo.currentText()
+        idx = formatCombo.currentIndex()
+        if idx == 0:        # hex format
+            ba = bytearray(QByteArray.fromHex(
+                bytes(txt, encoding="ascii")))
+        else:
+            ba = bytearray(txt, encoding="utf-8")
+        
+        # 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)
+        textCombo.clear()
+        textCombo.addItems(history)
+        
+        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
+        @rtype bool
+        """
+        if not self.__havefound or not self.__ui.findtextCombo.currentText():
+            self.show()
+            return
+        
+        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():
+                    # 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:
+                    # 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:
+            E5MessageBox.information(
+                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.
+        """
+        if self.__findBackwards:
+            self.findPrevNext(True)
+        else:
+            self.findPrevNext(False)
+
+    @pyqtSlot()
+    def on_replaceButton_clicked(self):
+        """
+        Private slot to replace one occurrence of data.
+        """
+        self.__doReplace(False)
+    
+    @pyqtSlot()
+    def on_replaceSearchButton_clicked(self):
+        """
+        Private slot to replace one occurrence of data and search for the next
+        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
+        (boolean).
+        """
+        # Check enabled status due to dual purpose usage of this method
+        if not self.__ui.replaceButton.isEnabled() and \
+           not self.__ui.replaceSearchButton.isEnabled():
+            return
+        
+        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(rba), 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)
+            if idx >= 0:
+                self.__editor.replaceByteArray(idx, len(rba), rba)
+                idx += len(rba)
+                replacements += 1
+        
+        if replacements:
+            E5MessageBox.information(
+                self, self.windowTitle(),
+                self.tr("Replaced {0} occurrences.")
+                .format(replacements))
+        else:
+            E5MessageBox.information(
+                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=''):
+        """
+        Private method to display this widget in find mode.
+        
+        @param text text to be shown in the findtext edit (string)
+        """
+        self.__replace = False
+        
+        self.__ui.findtextCombo.clear()
+        self.__ui.findtextCombo.addItems(self.__findHistory)
+        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=''):
+        """
+        Private slot to display this widget in replace mode.
+        
+        @param text text to be shown in the findtext edit
+        """
+        self.__replace = True
+        
+        self.__ui.findtextCombo.clear()
+        self.__ui.findtextCombo.addItems(self.__findHistory)
+        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)
+        self.__ui.replacetextCombo.setEditText('')
+        
+        self.__havefound = True
+        self.__findBackwards = False
+    
+    def show(self, text=''):
+        """
+        Public slot to show the widget.
+        
+        @param text text to be shown in the findtext edit (string)
+        """
+        if self.__replace:
+            self.__showReplace(text)
+        else:
+            self.__showFind(text)
+        super(HexEditSearchReplaceWidget, self).show()
+        self.activateWindow()
+    
+    @pyqtSlot()
+    def on_closeButton_clicked(self):
+        """
+        Private slot to close the widget.
+        """
+        self.__editor.setFocus(Qt.OtherFocusReason)
+        self.close()
+
+    def keyPressEvent(self, event):
+        """
+        Protected slot to handle key press events.
+        
+        @param event reference to the key press event (QKeyEvent)
+        """
+        if event.key() == Qt.Key_Escape:
+            self.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HexEdit/HexEditSearchWidget.ui	Sun Jan 10 16:52:22 2016 +0100
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>HexEditSearchWidget</class>
+ <widget class="QWidget" name="HexEditSearchWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>27</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Find</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <property name="leftMargin">
+    <number>1</number>
+   </property>
+   <property name="topMargin">
+    <number>1</number>
+   </property>
+   <property name="rightMargin">
+    <number>1</number>
+   </property>
+   <property name="bottomMargin">
+    <number>1</number>
+   </property>
+   <item>
+    <widget class="QToolButton" name="closeButton">
+     <property name="toolTip">
+      <string>Press to close the window</string>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Find:</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QComboBox" name="findFormatCombo">
+     <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>
+    <widget class="QComboBox" name="findtextCombo">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>300</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="editable">
+      <bool>true</bool>
+     </property>
+     <property name="insertPolicy">
+      <enum>QComboBox::InsertAtTop</enum>
+     </property>
+     <property name="duplicatesEnabled">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QToolButton" name="findPrevButton">
+     <property name="toolTip">
+      <string>Press to find the previous occurrence</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QToolButton" name="findNextButton">
+     <property name="toolTip">
+      <string>Press to find the next occurrence</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>findtextCombo</tabstop>
+  <tabstop>findFormatCombo</tabstop>
+  <tabstop>findPrevButton</tabstop>
+  <tabstop>findNextButton</tabstop>
+  <tabstop>closeButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- a/HexEdit/HexEditWidget.py	Sat Jan 09 19:44:31 2016 +0100
+++ b/HexEdit/HexEditWidget.py	Sun Jan 10 16:52:22 2016 +0100
@@ -94,7 +94,7 @@
         self.__readOnly = False
         # set read only mode on/off
         self.__cursorPosition = 0
-        # absolute positioin of cursor, 1 Byte == 2 tics
+        # absolute position of cursor, 1 Byte == 2 tics
         
         self.__addrDigits = 0
         self.__blink = True
@@ -732,7 +732,7 @@
         @return formatted representation of the selection
         @rtype str
         """
-        byteArray = self.__chunks.data(self.__getSelectionBegin(),
+        byteArray = self.__chunks.data(self.getSelectionBegin(),
                                        self.__getSelectionLength())
         return self.__toReadable(byteArray)
     
@@ -1039,7 +1039,7 @@
         """
         if not self.__readOnly:
             byteArray = self.__toHex(self.__chunks.data(
-                self.__getSelectionBegin(), self.__getSelectionLength()))
+                self.getSelectionBegin(), self.__getSelectionLength()))
             idx = 32
             while idx < len(byteArray):
                 byteArray.insert(idx, "\n")
@@ -1048,20 +1048,20 @@
             cb.setText(byteArray.decode(encoding="latin1"))
             if self.__overwriteMode:
                 length = self.__getSelectionLength()
-                self.replaceByteArray(self.__getSelectionBegin(), length,
+                self.replaceByteArray(self.getSelectionBegin(), length,
                                       bytearray(length))
             else:
-                self.remove(self.__getSelectionBegin(),
+                self.remove(self.getSelectionBegin(),
                             self.__getSelectionLength())
-            self.setCursorPosition(2 * self.__getSelectionBegin())
-            self.__resetSelection(2 * self.__getSelectionBegin())
+            self.setCursorPosition(2 * self.getSelectionBegin())
+            self.__resetSelection(2 * self.getSelectionBegin())
     
     def copy(self):
         """
         Public method to copy the selected bytes to the clipboard.
         """
         byteArray = self.__toHex(self.__chunks.data(
-            self.__getSelectionBegin(), self.__getSelectionLength()))
+            self.getSelectionBegin(), self.__getSelectionLength()))
         idx = 32
         while idx < len(byteArray):
             byteArray.insert(idx, "\n")
@@ -1083,7 +1083,7 @@
                 self.insertByteArray(self.__bPosCurrent, byteArray)
             self.setCursorPosition(
                 self.__cursorPosition + 2 * len(byteArray))
-            self.__resetSelection(2 * self.__getSelectionBegin())
+            self.__resetSelection(2 * self.getSelectionBegin())
     
     def deleteByte(self):
         """
@@ -1091,7 +1091,7 @@
         """
         if not self.__readOnly:
             if self.hasSelection():
-                self.__bPosCurrent = self.__getSelectionBegin()
+                self.__bPosCurrent = self.getSelectionBegin()
                 if self.__overwriteMode:
                     byteArray = bytearray(self.__getSelectionLength())
                     self.replaceByteArray(self.__bPosCurrent, len(byteArray),
@@ -1113,7 +1113,7 @@
         """
         if not self.__readOnly:
             if self.hasSelection():
-                self.__bPosCurrent = self.__getSelectionBegin()
+                self.__bPosCurrent = self.getSelectionBegin()
                 self.setCursorPosition(2 * self.__bPosCurrent)
                 if self.__overwriteMode:
                     byteArray = bytearray(self.__getSelectionLength())
@@ -1222,12 +1222,12 @@
                     if self.__overwriteMode:
                         length = self.__getSelectionLength()
                         self.replaceByteArray(
-                            self.__getSelectionBegin(), length,
+                            self.getSelectionBegin(), length,
                             bytearray(length))
                     else:
-                        self.remove(self.__getSelectionBegin(),
+                        self.remove(self.getSelectionBegin(),
                                     self.__getSelectionLength())
-                        self.__bPosCurrent = self.__getSelectionBegin()
+                        self.__bPosCurrent = self.getSelectionBegin()
                     self.setCursorPosition(2 * self.__bPosCurrent)
                     self.__resetSelection(2 * self.__bPosCurrent)
                 
@@ -1349,8 +1349,8 @@
                     painter.setPen(colStandard)
                     
                     posBa = self.__bPosFirst + bPosLine + colIdx
-                    if self.__getSelectionBegin() <= posBa and \
-                            self.__getSelectionEnd() > posBa:
+                    if self.getSelectionBegin() <= posBa and \
+                            self.getSelectionEnd() > posBa:
                         c = self.__selectionBrush.color()
                         painter.setPen(self.__selectionPen)
                     elif self.__highlighting:
@@ -1478,18 +1478,18 @@
         
         self.selectionAvailable.emit(True)
     
-    def __getSelectionBegin(self):
+    def getSelectionBegin(self):
         """
-        Private method to get the start of the selection.
+        Public method to get the start of the selection.
         
         @return selection start
         @rtype int
         """
         return self.__bSelectionBegin
     
-    def __getSelectionEnd(self):
+    def getSelectionEnd(self):
         """
-        Private method to get the end of the selection.
+        Public method to get the end of the selection.
         
         @return selection end
         @rtype int
--- a/eric6.e4p	Sat Jan 09 19:44:31 2016 +0100
+++ b/eric6.e4p	Sun Jan 10 16:52:22 2016 +0100
@@ -468,6 +468,7 @@
     <Source>Helpviewer/data/javascript_rc.py</Source>
     <Source>HexEdit/HexEditChunks.py</Source>
     <Source>HexEdit/HexEditMainWindow.py</Source>
+    <Source>HexEdit/HexEditSearchReplaceWidget.py</Source>
     <Source>HexEdit/HexEditUndoStack.py</Source>
     <Source>HexEdit/HexEditWidget.py</Source>
     <Source>HexEdit/__init__.py</Source>
@@ -1370,6 +1371,8 @@
     <Form>Helpviewer/WebPlugins/ClickToFlash/ClickToFlash.ui</Form>
     <Form>Helpviewer/WebPlugins/ClickToFlash/ClickToFlashWhitelistDialog.ui</Form>
     <Form>Helpviewer/ZoomManager/ZoomValuesDialog.ui</Form>
+    <Form>HexEdit/HexEditReplaceWidget.ui</Form>
+    <Form>HexEdit/HexEditSearchWidget.ui</Form>
     <Form>IconEditor/IconSizeDialog.ui</Form>
     <Form>MultiProject/AddProjectDialog.ui</Form>
     <Form>MultiProject/PropertiesDialog.ui</Form>

eric ide

mercurial