HexEdit/HexEditMainWindow.py

Sat, 16 Jan 2016 16:53:31 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 16 Jan 2016 16:53:31 +0100
changeset 4666
bc52ef526e11
parent 4665
30d4972ab403
child 4669
d37bfc9ca3b9
permissions
-rw-r--r--

Generated the source docu for the hex editor.

# -*- coding: utf-8 -*-

# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the hex editor main window.
"""

from __future__ import unicode_literals

import os

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QFile, QFileInfo, QSize, \
    QCoreApplication
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QWhatsThis, QLabel, QWidget, QVBoxLayout, \
    QDialog, QAction

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

import Preferences


# TODO: füge Goto Aktion inkl. Eingabewidget hinzu
class HexEditMainWindow(E5MainWindow):
    """
    Class implementing the web browser main window.
    
    @signal editorClosed() emitted after the window was requested to close down
    """
    editorClosed = pyqtSignal()
    
    windows = []
    
    def __init__(self, fileName="", parent=None, fromEric=False, project=None):
        """
        Constructor
        
        @param fileName name of a file to load on startup (string)
        @param parent parent widget of this window (QWidget)
        @keyparam fromEric flag indicating whether it was called from within
            eric6 (boolean)
        @keyparam project reference to the project object (Project)
        """
        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"))
        
        if not self.__fromEric:
            self.setStyle(Preferences.getUI("Style"),
                          Preferences.getUI("StyleSheet"))
        
        self.__editor = HexEditWidget()
        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)
        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():
            s = QSize(600, 500)
            self.resize(s)
        else:
            self.restoreGeometry(g)
        
        self.__initActions()
        self.__initMenus()
        self.__initToolbars()
        self.__createStatusBar()
        
        self.__class__.windows.append(self)
        
        state = Preferences.getHexEditor("HexEditorState")
        self.restoreState(state)
        
        self.__editor.currentAddressChanged.connect(self.__showAddress)
        self.__editor.currentSizeChanged.connect(self.__showSize)
        self.__editor.dataChanged.connect(self.__modificationChanged)
        self.__editor.overwriteModeChanged.connect(self.__showEditMode)
        self.__editor.readOnlyChanged.connect(self.__showReadOnlyMode)
        self.__editor.readOnlyChanged.connect(self.__checkActions)
        
        self.preferencesChanged()
        self.__editor.setOverwriteMode(
            Preferences.getHexEditor("OpenInOverwriteMode"))
        
        self.__project = project
        self.__lastOpenPath = ""
        self.__lastSavePath = ""
        
        self.__setCurrentFile("")
        if fileName:
            self.__loadHexFile(fileName)
        
        self.__checkActions()
    
    def __initActions(self):
        """
        Private method to define the user interface actions.
        """
        # list of all actions
        self.__actions = []
        
        self.__initFileActions()
        self.__initEditActions()
        self.__initHelpActions()
        if not self.__fromEric:
            self.__initConfigActions()
        
    def __initFileActions(self):
        """
        Private method to define the file related user interface actions.
        """
        self.newWindowAct = E5Action(
            self.tr('New Window'),
            UI.PixmapCache.getIcon("newWindow.png"),
            self.tr('New &Window'),
            0, 0, self, 'hexEditor_file_new_window')
        self.newWindowAct.setStatusTip(self.tr(
            'Open a binary file for editing in a new hex editor window'))
        self.newWindowAct.setWhatsThis(self.tr(
            """<b>New Window</b>"""
            """<p>This opens a binary file for editing in a new hex editor"""
            """ window.</p>"""
        ))
        self.newWindowAct.triggered.connect(self.__openHexFileNewWindow)
        self.__actions.append(self.newWindowAct)
        
        # correct texts will be set later
        self.openAct = E5Action(
            self.tr('Open'),
            UI.PixmapCache.getIcon("open.png"),
            self.tr('&Open...'),
            QKeySequence(self.tr("Ctrl+O", "File|Open")),
            0, self, 'hexEditor_file_open')
        self.openAct.triggered.connect(self.__openHexFile)
        self.__actions.append(self.openAct)
        
        # correct texts will be set later
        self.openReadOnlyAct = E5Action(
            "", "",
            0, 0, self, 'hexEditor_file_open_read_only')
        self.openReadOnlyAct.triggered.connect(self.__openHexFileReadOnly)
        self.__actions.append(self.openReadOnlyAct)
        
        self.saveAct = E5Action(
            self.tr('Save'),
            UI.PixmapCache.getIcon("fileSave.png"),
            self.tr('&Save'),
            QKeySequence(self.tr("Ctrl+S", "File|Save")),
            0, self, 'hexEditor_file_save')
        self.saveAct.setStatusTip(self.tr('Save the current binary file'))
        self.saveAct.setWhatsThis(self.tr(
            """<b>Save File</b>"""
            """<p>Save the contents of the hex editor window.</p>"""
        ))
        self.saveAct.triggered.connect(self.__saveHexFile)
        self.__actions.append(self.saveAct)
        
        self.saveAsAct = E5Action(
            self.tr('Save As'),
            UI.PixmapCache.getIcon("fileSaveAs.png"),
            self.tr('Save &As...'),
            QKeySequence(self.tr("Shift+Ctrl+S", "File|Save As")),
            0, self, 'hexEditor_file_save_as')
        self.saveAsAct.setStatusTip(
            self.tr('Save the current binary data to a new file'))
        self.saveAsAct.setWhatsThis(self.tr(
            """<b>Save As...</b>"""
            """<p>Saves the current binary data to a new file.</p>"""
        ))
        self.saveAsAct.triggered.connect(self.__saveHexFileAs)
        self.__actions.append(self.saveAsAct)
        
        self.saveReadableAct = E5Action(
            self.tr('Save As Readable'),
            self.tr('Save As &Readable...'),
            0, 0, self, 'hexEditor_file_save_readable')
        self.saveReadableAct.setStatusTip(
            self.tr('Save the current binary data to a new file in a readable'
                    ' format'))
        self.saveReadableAct.setWhatsThis(self.tr(
            """<b>Save As Readable...</b>"""
            """<p>Saves the current binary data to a new file in a readable"""
            """ format.</p>"""
        ))
        self.saveReadableAct.triggered.connect(self.__saveHexFileReadable)
        self.__actions.append(self.saveReadableAct)
        
        self.closeAct = E5Action(
            self.tr('Close'),
            UI.PixmapCache.getIcon("close.png"),
            self.tr('&Close'),
            QKeySequence(self.tr("Ctrl+W", "File|Close")),
            0, self, 'hexEditor_file_close')
        self.closeAct.setStatusTip(self.tr(
            'Close the current hex editor window'))
        self.closeAct.setWhatsThis(self.tr(
            """<b>Close</b>"""
            """<p>Closes the current hex editor window.</p>"""
        ))
        self.closeAct.triggered.connect(self.close)
        self.__actions.append(self.closeAct)
        
        self.closeAllAct = E5Action(
            self.tr('Close All'),
            self.tr('Close &All'),
            0, 0, self, 'hexEditor_file_close_all')
        self.closeAllAct.setStatusTip(self.tr(
            'Close all hex editor windows'))
        self.closeAllAct.setWhatsThis(self.tr(
            """<b>Close All</b>"""
            """<p>Closes all hex editor windows.</p>"""
        ))
        self.closeAllAct.triggered.connect(self.__closeAll)
        self.__actions.append(self.closeAllAct)
        
        self.closeOthersAct = E5Action(
            self.tr('Close Others'),
            self.tr('Close Others'),
            0, 0, self, 'hexEditor_file_close_others')
        self.closeOthersAct.setStatusTip(self.tr(
            'Close all hex other editor windows'))
        self.closeOthersAct.setWhatsThis(self.tr(
            """<b>Close Others</b>"""
            """<p>Closes all other hex editor windows.</p>"""
        ))
        self.closeOthersAct.triggered.connect(self.__closeOthers)
        self.__actions.append(self.closeOthersAct)
        
        self.exitAct = E5Action(
            self.tr('Quit'),
            UI.PixmapCache.getIcon("exit.png"),
            self.tr('&Quit'),
            QKeySequence(self.tr("Ctrl+Q", "File|Quit")),
            0, self, 'hexEditor_file_quit')
        self.exitAct.setStatusTip(self.tr('Quit the hex editor'))
        self.exitAct.setWhatsThis(self.tr(
            """<b>Quit</b>"""
            """<p>Quit the hex editor.</p>"""
        ))
        if not self.__fromEric:
            self.exitAct.triggered.connect(self.__closeAll)
        self.__actions.append(self.exitAct)
    
    def __initEditActions(self):
        """
        Private method to create the Edit actions.
        """
        self.undoAct = E5Action(
            self.tr('Undo'),
            UI.PixmapCache.getIcon("editUndo.png"),
            self.tr('&Undo'),
            QKeySequence(self.tr("Ctrl+Z", "Edit|Undo")),
            QKeySequence(self.tr("Alt+Backspace", "Edit|Undo")),
            self, 'hexEditor_edit_undo')
        self.undoAct.setStatusTip(self.tr('Undo the last change'))
        self.undoAct.setWhatsThis(self.tr(
            """<b>Undo</b>"""
            """<p>Undo the last change done.</p>"""
        ))
        self.undoAct.triggered.connect(self.__editor.undo)
        self.__actions.append(self.undoAct)
        
        self.redoAct = E5Action(
            self.tr('Redo'),
            UI.PixmapCache.getIcon("editRedo.png"),
            self.tr('&Redo'),
            QKeySequence(self.tr("Ctrl+Shift+Z", "Edit|Redo")),
            0, self, 'hexEditor_edit_redo')
        self.redoAct.setStatusTip(self.tr('Redo the last change'))
        self.redoAct.setWhatsThis(self.tr(
            """<b>Redo</b>"""
            """<p>Redo the last change done.</p>"""
        ))
        self.redoAct.triggered.connect(self.__editor.redo)
        self.__actions.append(self.redoAct)
        
        self.revertAct = E5Action(
            self.tr('Revert to last saved state'),
            self.tr('Re&vert to last saved state'),
            QKeySequence(self.tr("Ctrl+Y", "Edit|Revert")),
            0,
            self, 'hexEditor_edit_revert')
        self.revertAct.setStatusTip(self.tr('Revert to last saved state'))
        self.revertAct.setWhatsThis(self.tr(
            """<b>Revert to last saved state</b>"""
            """<p>Undo all changes up to the last saved state of the"""
            """ editor.</p>"""
        ))
        self.revertAct.triggered.connect(self.__editor.revertToUnmodified)
        self.__actions.append(self.revertAct)
        
        self.cutAct = E5Action(
            self.tr('Cut'),
            UI.PixmapCache.getIcon("editCut.png"),
            self.tr('Cu&t'),
            QKeySequence(self.tr("Ctrl+X", "Edit|Cut")),
            QKeySequence(self.tr("Shift+Del", "Edit|Cut")),
            self, 'hexEditor_edit_cut')
        self.cutAct.setStatusTip(self.tr('Cut the selection'))
        self.cutAct.setWhatsThis(self.tr(
            """<b>Cut</b>"""
            """<p>Cut the selected binary area to the clipboard.</p>"""
        ))
        self.cutAct.triggered.connect(self.__editor.cut)
        self.__actions.append(self.cutAct)
        
        self.copyAct = E5Action(
            self.tr('Copy'),
            UI.PixmapCache.getIcon("editCopy.png"),
            self.tr('&Copy'),
            QKeySequence(self.tr("Ctrl+C", "Edit|Copy")),
            QKeySequence(self.tr("Ctrl+Ins", "Edit|Copy")),
            self, 'hexEditor_edit_copy')
        self.copyAct.setStatusTip(self.tr('Copy the selection'))
        self.copyAct.setWhatsThis(self.tr(
            """<b>Copy</b>"""
            """<p>Copy the selected binary area to the clipboard.</p>"""
        ))
        self.copyAct.triggered.connect(self.__editor.copy)
        self.__actions.append(self.copyAct)
        
        self.pasteAct = E5Action(
            self.tr('Paste'),
            UI.PixmapCache.getIcon("editPaste.png"),
            self.tr('&Paste'),
            QKeySequence(self.tr("Ctrl+V", "Edit|Paste")),
            QKeySequence(self.tr("Shift+Ins", "Edit|Paste")),
            self, 'hexEditor_edit_paste')
        self.pasteAct.setStatusTip(self.tr('Paste the clipboard contents'))
        self.pasteAct.setWhatsThis(self.tr(
            """<b>Paste</b>"""
            """<p>Paste the clipboard contents.</p>"""
        ))
        self.pasteAct.triggered.connect(self.__editor.paste)
        self.__actions.append(self.pasteAct)
        
        self.selectAllAct = E5Action(
            self.tr('Select All'),
            UI.PixmapCache.getIcon("editSelectAll.png"),
            self.tr('&Select All'),
            QKeySequence(self.tr("Ctrl+A", "Edit|Select All")),
            0,
            self, 'hexEditor_edit_select_all')
        self.selectAllAct.setStatusTip(self.tr(
            'Select the complete binary data'))
        self.selectAllAct.setWhatsThis(self.tr(
            """<b>Select All</b>"""
            """<p>Selects the complete binary data.</p>"""
        ))
        self.selectAllAct.triggered.connect(self.__editor.selectAll)
        self.__actions.append(self.selectAllAct)
        
        self.deselectAllAct = E5Action(
            self.tr('Deselect all'),
            self.tr('&Deselect all'),
            QKeySequence(self.tr("Alt+Ctrl+A", "Edit|Deselect all")),
            0,
            self, 'hexEditor_edit_deselect_all')
        self.deselectAllAct.setStatusTip(self.tr('Deselect all binary data'))
        self.deselectAllAct.setWhatsThis(self.tr(
            """<b>Deselect All</b>"""
            """<p>Deselect all all binary data.</p>"""
        ))
        self.deselectAllAct.triggered.connect(self.__editor.deselectAll)
        self.__actions.append(self.deselectAllAct)
        
        self.saveSelectionReadableAct = E5Action(
            self.tr('Save Selection Readable'),
            self.tr('Save Selection Readable...'),
            0, 0, self, 'hexEditor_edit_selection_save_readable')
        self.saveSelectionReadableAct.setStatusTip(
            self.tr('Save the binary data of the current selection to a file'
                    ' in a readable format'))
        self.saveSelectionReadableAct.setWhatsThis(self.tr(
            """<b>Save Selection Readable...</b>"""
            """<p>Saves the binary data of the current selection to a file"""
            """ in a readable format.</p>"""
        ))
        self.saveSelectionReadableAct.triggered.connect(
            self.__saveSelectionReadable)
        self.__actions.append(self.saveSelectionReadableAct)
        
        self.readonlyAct = E5Action(
            self.tr('Set Read Only'),
            self.tr('Set Read Only'),
            0, 0, self, 'hexEditor_edit_readonly', True)
        self.readonlyAct.setStatusTip(self.tr(
            'Change the edit mode to read only'))
        self.readonlyAct.setWhatsThis(self.tr(
            """<b>Set Read Only</b>"""
            """<p>This changes the edit mode to read only (i.e. to view"""
            """ mode).</p>"""
        ))
        self.readonlyAct.setChecked(False)
        self.readonlyAct.toggled[bool].connect(self.__editor.setReadOnly)
        self.__editor.readOnlyChanged.connect(self.readonlyAct.setChecked)
        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 data'))
        self.searchAct.setWhatsThis(self.tr(
            """<b>Search</b>"""
            """<p>Search for some data. A dialog is shown to enter the"""
            """ data to search for in various formats.</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'))
        self.searchNextAct.setWhatsThis(self.tr(
            """<b>Search next</b>"""
            """<p>Search the next occurrence of some data. The previously"""
            """ entered search data 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'))
        self.searchPrevAct.setWhatsThis(self.tr(
            """<b>Search previous</b>"""
            """<p>Search the previous occurrence of some data. The"""
            """ previously entered search data 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 data'))
        self.replaceAct.setWhatsThis(self.tr(
            """<b>Replace</b>"""
            """<p>Search for some data and replace it."""
            """ A dialog is shown to enter the data to search for and the"""
            """ replacement data in various formats.</p>"""
        ))
        self.replaceAct.triggered.connect(self.__replace)
        self.__actions.append(self.replaceAct)
        
        self.redoAct.setEnabled(False)
        self.__editor.canRedoChanged.connect(self.redoAct.setEnabled)
        
        self.undoAct.setEnabled(False)
        self.__editor.canUndoChanged.connect(self.undoAct.setEnabled)
        
        self.revertAct.setEnabled(False)
        self.__editor.dataChanged.connect(self.revertAct.setEnabled)
        
        self.cutAct.setEnabled(False)
        self.copyAct.setEnabled(False)
        self.saveSelectionReadableAct.setEnabled(False)
        self.__editor.selectionAvailable.connect(self.__checkActions)
        self.__editor.selectionAvailable.connect(self.copyAct.setEnabled)
        self.__editor.selectionAvailable.connect(
            self.saveSelectionReadableAct.setEnabled)
    
    def __initHelpActions(self):
        """
        Private method to create the Help actions.
        """
        self.aboutAct = E5Action(
            self.tr('About'),
            self.tr('&About'),
            0, 0, self, 'hexEditor_help_about')
        self.aboutAct.setStatusTip(self.tr(
            'Display information about this software'))
        self.aboutAct.setWhatsThis(self.tr(
            """<b>About</b>"""
            """<p>Display some information about this software.</p>"""))
        self.aboutAct.triggered.connect(self.__about)
        self.__actions.append(self.aboutAct)
        
        self.aboutQtAct = E5Action(
            self.tr('About Qt'),
            self.tr('About &Qt'),
            0, 0, self, 'hexEditor_help_about_qt')
        self.aboutQtAct.setStatusTip(
            self.tr('Display information about the Qt toolkit'))
        self.aboutQtAct.setWhatsThis(self.tr(
            """<b>About Qt</b>"""
            """<p>Display some information about the Qt toolkit.</p>"""
        ))
        self.aboutQtAct.triggered.connect(self.__aboutQt)
        self.__actions.append(self.aboutQtAct)
        
        self.whatsThisAct = E5Action(
            self.tr('What\'s This?'),
            UI.PixmapCache.getIcon("whatsThis.png"),
            self.tr('&What\'s This?'),
            QKeySequence(self.tr("Shift+F1", "Help|What's This?'")),
            0, self, 'hexEditor_help_whats_this')
        self.whatsThisAct.setStatusTip(self.tr('Context sensitive help'))
        self.whatsThisAct.setWhatsThis(self.tr(
            """<b>Display context sensitive help</b>"""
            """<p>In What's This? mode, the mouse cursor shows an arrow"""
            """ with a question mark, and you can click on the interface"""
            """ elements to get a short description of what they do and"""
            """ how to use them. In dialogs, this feature can be accessed"""
            """ using the context help button in the titlebar.</p>"""
        ))
        self.whatsThisAct.triggered.connect(self.__whatsThis)
        self.__actions.append(self.whatsThisAct)
    
    def __initConfigActions(self):
        """
        Private method to create the Settings actions.
        """
        self.prefAct = E5Action(
            self.tr('Preferences'),
            UI.PixmapCache.getIcon("configure.png"),
            self.tr('&Preferences...'),
            0, 0, self, 'hexEditor_settings_preferences')
        self.prefAct.setStatusTip(self.tr(
            'Set the prefered configuration'))
        self.prefAct.setWhatsThis(self.tr(
            """<b>Preferences</b>"""
            """<p>Set the configuration items of the application"""
            """ with your prefered values.</p>"""
        ))
        self.prefAct.triggered.connect(self.__showPreferences)
        self.prefAct.setMenuRole(QAction.PreferencesRole)
        self.__actions.append(self.prefAct)
    
    def __setReadOnlyActionTexts(self):
        """
        Private method to switch the 'Open Read Only' action between
        'read only' and 'read write'.
        """
        if Preferences.getHexEditor("OpenReadOnly"):
            self.openAct.setStatusTip(self.tr(
                'Open a binary file for viewing'))
            self.openAct.setWhatsThis(self.tr(
                """<b>Open File</b>"""
                """<p>This opens a binary file for viewing (i.e. in read"""
                """ only mode). It pops up a file selection dialog.</p>"""
            ))
            
            self.openReadOnlyAct.setText(self.tr('Open for Editing...'))
            self.openReadOnlyAct.setIconText(self.tr('Open for Editing'))
            self.openReadOnlyAct.setStatusTip(self.tr(
                'Open a binary file for editing'))
            self.openReadOnlyAct.setWhatsThis(self.tr(
                """<b>Open for Editing</b>"""
                """<p>This opens a binary file for editing."""
                """ It pops up a file selection dialog.</p>"""
            ))
        else:
            self.openAct.setStatusTip(self.tr(
                'Open a binary file for editing'))
            self.openAct.setWhatsThis(self.tr(
                """<b>Open File</b>"""
                """<p>This opens a binary file for editing."""
                """ It pops up a file selection dialog.</p>"""
            ))
            
            self.openReadOnlyAct.setText(self.tr('Open Read Only...'))
            self.openReadOnlyAct.setIconText(self.tr('Open Read Only'))
            self.openReadOnlyAct.setStatusTip(self.tr(
                'Open a binary file for viewing'))
            self.openReadOnlyAct.setWhatsThis(self.tr(
                """<b>Open Read Only</b>"""
                """<p>This opens a binary file for viewing (i.e. in read"""
                """ only mode). It pops up a file selection dialog.</p>"""
            ))
    
    def __initMenus(self):
        """
        Private method to create the menus.
        """
        mb = self.menuBar()
        
        menu = mb.addMenu(self.tr('&File'))
        menu.setTearOffEnabled(True)
        menu.addAction(self.newWindowAct)
        menu.addAction(self.openAct)
        menu.addAction(self.openReadOnlyAct)
        menu.addSeparator()
        menu.addAction(self.saveAct)
        menu.addAction(self.saveAsAct)
        menu.addAction(self.saveReadableAct)
        menu.addSeparator()
        menu.addAction(self.closeAct)
        menu.addAction(self.closeOthersAct)
        if self.__fromEric:
            menu.addAction(self.closeAllAct)
        else:
            menu.addSeparator()
            menu.addAction(self.exitAct)
        
        menu = mb.addMenu(self.tr("&Edit"))
        menu.setTearOffEnabled(True)
        menu.addAction(self.undoAct)
        menu.addAction(self.redoAct)
        menu.addAction(self.revertAct)
        menu.addSeparator()
        menu.addAction(self.cutAct)
        menu.addAction(self.copyAct)
        menu.addAction(self.pasteAct)
        menu.addSeparator()
        menu.addAction(self.selectAllAct)
        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)
        
        if not self.__fromEric:
            menu = mb.addMenu(self.tr("Se&ttings"))
            menu.setTearOffEnabled(True)
            menu.addAction(self.prefAct)
        
        mb.addSeparator()
        
        menu = mb.addMenu(self.tr("&Help"))
        menu.addAction(self.aboutAct)
        menu.addAction(self.aboutQtAct)
        menu.addSeparator()
        menu.addAction(self.whatsThisAct)
    
    def __initToolbars(self):
        """
        Private method to create the toolbars.
        """
        filetb = self.addToolBar(self.tr("File"))
        filetb.setObjectName("FileToolBar")
        filetb.setIconSize(UI.Config.ToolBarIconSize)
        filetb.addAction(self.newWindowAct)
        filetb.addAction(self.openAct)
        filetb.addSeparator()
        filetb.addAction(self.saveAct)
        filetb.addAction(self.saveAsAct)
        filetb.addSeparator()
        filetb.addAction(self.closeAct)
        if not self.__fromEric:
            filetb.addAction(self.exitAct)
        
        edittb = self.addToolBar(self.tr("Edit"))
        edittb.setObjectName("EditToolBar")
        edittb.setIconSize(UI.Config.ToolBarIconSize)
        edittb.addAction(self.undoAct)
        edittb.addAction(self.redoAct)
        edittb.addSeparator()
        edittb.addAction(self.cutAct)
        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)
        
        if not self.__fromEric:
            settingstb = self.addToolBar(self.tr("Settings"))
            settingstb.setObjectName("SettingsToolBar")
            settingstb.setIconSize(UI.Config.ToolBarIconSize)
            settingstb.addAction(self.prefAct)
        
        helptb = self.addToolBar(self.tr("Help"))
        helptb.setObjectName("HelpToolBar")
        helptb.setIconSize(UI.Config.ToolBarIconSize)
        helptb.addAction(self.whatsThisAct)
    
    def __createStatusBar(self):
        """
        Private method to initialize the status bar.
        """
        self.__statusBar = self.statusBar()
        self.__statusBar.setSizeGripEnabled(True)
        
        self.__sbEditMode = QLabel(self.__statusBar)
        self.__statusBar.addPermanentWidget(self.__sbEditMode)
        self.__sbEditMode.setWhatsThis(self.tr(
            """<p>This part of the status bar displays the edit mode.</p>"""
        ))
        
        self.__sbReadOnly = QLabel(self.__statusBar)
        self.__statusBar.addPermanentWidget(self.__sbReadOnly)
        self.__sbReadOnly.setWhatsThis(self.tr(
            """<p>This part of the status bar displays the read"""
            """ only mode.</p>"""
        ))

        self.__sbAddress = QLabel(self.__statusBar)
        self.__statusBar.addPermanentWidget(self.__sbAddress)
        self.__sbAddress.setWhatsThis(self.tr(
            """<p>This part of the status bar displays the cursor"""
            """ address.</p>"""
        ))
        
        self.__showEditMode(self.__editor.overwriteMode())
        self.__showReadOnlyMode(self.__editor.isReadOnly())
        
        self.__sbSize = QLabel(self.__statusBar)
        self.__statusBar.addPermanentWidget(self.__sbSize)
        self.__sbSize.setWhatsThis(self.tr(
            """<p>This part of the status bar displays the size of the"""
            """ binary data.</p>"""
        ))
    
    @pyqtSlot(int)
    def __showAddress(self, address):
        """
        Private slot to show the address of the cursor position.
        
        @param address address of the cursor
        @type int
        """
        self.__sbAddress.setText(self.tr("Address: {0:#{1}x}").format(
            address, self.__editor.addressWidth()))
    
    @pyqtSlot(bool)
    def __showReadOnlyMode(self, on):
        """
        Private slot to show the read only mode.
        
        @param on flag indicating the read only state
        @type bool
        """
        self.__sbReadOnly.setText(self.tr("ro") if on else self.tr("rw"))
    
    @pyqtSlot(bool)
    def __showEditMode(self, overwrite):
        """
        Private slot to show the edit mode.
        
        @param overwrite flag indicating overwrite mode
        @type bool
        """
        self.__sbEditMode.setText(
            self.tr("Overwrite") if overwrite else self.tr("Insert"))
    
    @pyqtSlot(int)
    def __showSize(self, size):
        """
        Private slot to show the binary data size.
        
        @param size size of the binary data
        @type int
        """
        self.__sbSize.setText(self.tr("Size: {0:n}").format(size))
    
    def closeEvent(self, evt):
        """
        Protected event handler for the close event.
        
        @param evt reference to the close event
            <br />This event is simply accepted after the history has been
            saved and all window references have been deleted.
        @type QCloseEvent
        """
        if self.__maybeSave():
            state = self.saveState()
            Preferences.setHexEditor("HexEditorState", state)

            Preferences.setGeometry("HexEditorGeometry", self.saveGeometry())
            
            try:
                if self.__fromEric or len(self.__class__.windows) > 1:
                    del self.__class__.windows[
                        self.__class__.windows.index(self)]
            except ValueError:
                pass
            
            if not self.__fromEric:
                Preferences.syncPreferences()
            
            evt.accept()
            self.editorClosed.emit()
        else:
            evt.ignore()
    
    def __openHexFileNewWindow(self):
        """
        Private slot called to open a binary file in new hex editor window.
        """
        if not self.__lastOpenPath:
            if self.__project and self.__project.isOpen():
                self.__lastOpenPath = self.__project.getProjectPath()
        
        fileName = E5FileDialog.getOpenFileName(
            self,
            self.tr("Open binary file in new window"),
            self.__lastOpenPath,
            self.tr("All Files (*)"))
        if fileName:
            he = HexEditMainWindow(fileName=fileName,
                                   parent=self.parent(),
                                   fromEric=self.__fromEric,
                                   project=self.__project)
            he.setRecentPaths("", self.__lastSavePath)
            he.show()
    
    def __maybeSave(self):
        """
        Private method to ask the user to save the file, if it was modified.
        
        @return flag indicating, if it is ok to continue
        @rtype bool
        """
        if self.__editor.isModified():
            ret = E5MessageBox.okToClearData(
                self,
                self.tr("eric6 Hex Editor"),
                self.tr("""The loaded file has unsaved changes."""),
                self.__saveHexFile)
            if not ret:
                return False
        return True
    
    def __loadHexFile(self, fileName):
        """
        Private method to load a binary file.
        
        @param fileName name of the binary file to load
        @type str
        """
        file = QFile(fileName)
        if not file.exists():
            E5MessageBox.warning(
                self, self.tr("eric6 Hex Editor"),
                self.tr("The file '{0}' does not exist.")
                .format(fileName))
            return
        
        if not file.open(QFile.ReadOnly):
            E5MessageBox.warning(
                self, self.tr("eric6 Hex Editor"),
                self.tr("Cannot read file '{0}:\n{1}.")
                .format(fileName, file.errorString()))
            return
        
        data = file.readAll()
        file.close()
        
        self.__lastOpenPath = os.path.dirname(fileName)
        self.__editor.setData(data)
        self.__setCurrentFile(fileName)
        
        self.__editor.setReadOnly(Preferences.getHexEditor("OpenReadOnly"))
    
    def __openHexFile(self):
        """
        Private slot to open a binary file.
        """
        if self.__maybeSave():
            if not self.__lastOpenPath:
                if self.__project and self.__project.isOpen():
                    self.__lastOpenPath = self.__project.getProjectPath()
            
            fileName = E5FileDialog.getOpenFileName(
                self,
                self.tr("Open binary file"),
                self.__lastOpenPath,
                self.tr("All Files (*)"))
            if fileName:
                self.__loadHexFile(fileName)
        self.__checkActions()
    
    def __openHexFileReadOnly(self):
        """
        Private slot to open a binary file in read only mode.
        """
        self.__openHexFile()
        self.__editor.setReadOnly(not Preferences.getHexEditor("OpenReadOnly"))
        self.__checkActions()
    
    def __saveHexFile(self):
        """
        Private method to save a binary file.
        
        @return flag indicating success
        @rtype bool
        """
        if not self.__fileName:
            ok = self.__saveHexFileAs()
        else:
            ok = self.__saveHexDataFile(self.__fileName)
        
        if ok:
            self.__editor.undoStack().setClean()
        
        return ok
    
    def __saveHexFileAs(self):
        """
        Private method to save the data to a new file.
        
        @return flag indicating success
        @rtype bool
        """
        if not self.__lastSavePath:
            if self.__project and self.__project.isOpen():
                self.__lastSavePath = self.__project.getProjectPath()
        if not self.__lastSavePath and self.__lastOpenPath:
            self.__lastSavePath = self.__lastOpenPath
        
        fileName = E5FileDialog.getSaveFileName(
            self,
            self.tr("Save binary file"),
            self.__lastSavePath,
            self.tr("All Files (*)"),
            E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
        if not fileName:
            return False
        
        if QFileInfo(fileName).exists():
            res = E5MessageBox.yesNo(
                self,
                self.tr("Save binary file"),
                self.tr("<p>The file <b>{0}</b> already exists."
                        " Overwrite it?</p>").format(fileName),
                icon=E5MessageBox.Warning)
            if not res:
                return False
        
        self.__lastSavePath = os.path.dirname(fileName)
        
        return self.__saveHexDataFile(fileName)
    
    def __saveHexDataFile(self, fileName):
        """
        Private method to save the binary data to a file.
        
        @param fileName name of the file to write to
        @type str
        @return flag indicating success
        @rtype bool
        """
        file = QFile(fileName)
        if not file.open(QFile.WriteOnly):
            E5MessageBox.warning(
                self, self.tr("eric6 Hex Editor"),
                self.tr("Cannot write file '{0}:\n{1}.")
                .format(fileName, file.errorString()))
        
            self.__checkActions()
            
            return False
        
        res = file.write(self.__editor.data()) != -1
        file.close()
        
        if not res:
            E5MessageBox.warning(
                self, self.tr("eric6 Hex Editor"),
                self.tr("Cannot write file '{0}:\n{1}.")
                .format(fileName, file.errorString()))
        
            self.__checkActions()
            
            return False
        
        self.__editor.setModified(False, setCleanState=True)
        
        self.__setCurrentFile(fileName)
        self.__statusBar.showMessage(self.tr("File saved"), 2000)
        
        self.__checkActions()
        
        return True
    
    def __saveHexFileReadable(self, selectionOnly=False):
        """
        Private method to save the binary data in readable format.
        
        @param selectionOnly flag indicating to save the selection only
        @type bool
        """
        savePath = self.__lastSavePath
        if not savePath:
            if self.__project and self.__project.isOpen():
                savePath = self.__project.getProjectPath()
        if not savePath and self.__lastOpenPath:
            savePath = self.__lastOpenPath
        
        fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
            self,
            self.tr("Save to readable file"),
            savePath,
            self.tr("Text Files (*.txt);;All Files (*)"),
            self.tr("Text Files (*.txt)"),
            E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
        if not fileName:
            return
        
        ext = QFileInfo(fileName).suffix()
        if not ext:
            ex = selectedFilter.split("(*")[1].split(")")[0]
            if ex:
                fileName += ex
        if QFileInfo(fileName).exists():
            res = E5MessageBox.yesNo(
                self,
                self.tr("Save to readable file"),
                self.tr("<p>The file <b>{0}</b> already exists."
                        " Overwrite it?</p>").format(fileName),
                icon=E5MessageBox.Warning)
            if not res:
                return
        
        file = QFile(fileName)
        if not file.open(QFile.WriteOnly):
            E5MessageBox.warning(
                self, self.tr("eric6 Hex Editor"),
                self.tr("Cannot write file '{0}:\n{1}.")
                .format(fileName, file.errorString()))
            return
        
        if selectionOnly:
            readableData = self.__editor.selectionToReadableString()
        else:
            readableData = self.__editor.toReadableString()
        res = file.write(readableData.encode("latin1")) != -1
        file.close()
        
        if not res:
            E5MessageBox.warning(
                self, self.tr("eric6 Hex Editor"),
                self.tr("Cannot write file '{0}:\n{1}.")
                .format(fileName, file.errorString()))
            return
        
        self.__statusBar.showMessage(self.tr("File saved"), 2000)
    
    def __saveSelectionReadable(self):
        """
        Private method to save the data of the current selection in readable
        format.
        """
        self.__saveHexFileReadable(selectionOnly=True)
    
    def __closeAll(self):
        """
        Private slot to close all windows.
        """
        self.__closeOthers()
        self.close()
    
    def __closeOthers(self):
        """
        Private slot to close all other windows.
        """
        for win in self.__class__.windows[:]:
            if win != self:
                win.close()

    def __setCurrentFile(self, fileName):
        """
        Private method to register the file name of the current file.
        
        @param fileName name of the file to register
        @type str
        """
        self.__fileName = fileName
        
        if not self.__fileName:
            shownName = self.tr("Untitled")
        else:
            shownName = self.__strippedName(self.__fileName)
        
        self.setWindowTitle(self.tr("{0}[*] - {1}")
                            .format(shownName, self.tr("Hex Editor")))
        
        self.setWindowModified(self.__editor.isModified())
    
    def __strippedName(self, fullFileName):
        """
        Private method to return the filename part of the given path.
        
        @param fullFileName full pathname of the given file
        @type str
        @return filename part
        @rtype str
        """
        return QFileInfo(fullFileName).fileName()
    
    def setRecentPaths(self, openPath, savePath):
        """
        Public method to set the last open and save paths.
        
        @param openPath least recently used open path
        @type str
        @param savePath least recently used save path
        @type str
        """
        if openPath:
            self.__lastOpenPath = openPath
        if savePath:
            self.__lastSavePath = savePath
    
    @pyqtSlot()
    def __checkActions(self):
        """
        Private slot to check some actions for their enable/disable status.
        """
        self.saveAct.setEnabled(self.__editor.isModified())
        
        self.cutAct.setEnabled(not self.__editor.isReadOnly() and
                               self.__editor.hasSelection())
        self.pasteAct.setEnabled(not self.__editor.isReadOnly())
        self.replaceAct.setEnabled(not self.__editor.isReadOnly())
    
    @pyqtSlot(bool)
    def __modificationChanged(self, m):
        """
        Private slot to handle the dataChanged signal.
        
        @param m modification status
        @type bool
        """
        self.setWindowModified(m)
        self.__checkActions()
    
    def __about(self):
        """
        Private slot to show a little About message.
        """
        E5MessageBox.about(
            self, self.tr("About eric6 Hex Editor"),
            self.tr("The eric6 Hex Editor is a simple editor component"
                    " to edit binary files."))
    
    def __aboutQt(self):
        """
        Private slot to handle the About Qt dialog.
        """
        E5MessageBox.aboutQt(self, "eric6 Hex Editor")
    
    def __whatsThis(self):
        """
        Private slot called in to enter Whats This mode.
        """
        QWhatsThis.enterWhatsThisMode()
        
    def __search(self):
        """
        Private method to handle the search action.
        """
        self.__replaceWidget.hide()
        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()
        if self.__editor.hasSelection():
            txt = self.__editor.selectionToHexString()
        else:
            txt = ""
        self.__replaceWidget.show(txt)
    
    def preferencesChanged(self):
        """
        Public method to (re-)read the various settings.
        """
        self.__editor.setAddressWidth(
            Preferences.getHexEditor("AddressAreaWidth"))
        self.__editor.setAddressArea(
            Preferences.getHexEditor("ShowAddressArea"))
        self.__editor.setAsciiArea(
            Preferences.getHexEditor("ShowAsciiArea"))
        self.__editor.setHighlighting(
            Preferences.getHexEditor("HighlightChanges"))
        self.__editor.setHighlightColors(
            Preferences.getHexEditor("HighlightingForeGround"),
            Preferences.getHexEditor("HighlightingBackGround"))
        self.__editor.setSelectionColors(
            Preferences.getHexEditor("SelectionForeGround"),
            Preferences.getHexEditor("SelectionBackGround"))
        self.__editor.setAddressAreaColors(
            Preferences.getHexEditor("AddressAreaForeGround"),
            Preferences.getHexEditor("AddressAreaBackGround"))
        self.__editor.setFont(
            Preferences.getHexEditor("Font"))
        
        self.__setReadOnlyActionTexts()
    
    def __showPreferences(self):
        """
        Private slot to set the preferences.
        """
        from Preferences.ConfigurationDialog import ConfigurationDialog
        dlg = ConfigurationDialog(
            None, 'Configuration', True, fromEric=True,
            displayMode=ConfigurationDialog.HexEditorMode)
        dlg.preferencesChanged.connect(
            self.__preferencesChangedByLocalPreferencesDialog)
        dlg.show()
        dlg.showConfigurationPageByName("hexEditorPage")
        dlg.exec_()
        QCoreApplication.processEvents()
        if dlg.result() == QDialog.Accepted:
            dlg.setPreferences()
            Preferences.syncPreferences()
            self.__preferencesChangedByLocalPreferencesDialog()
    
    def __preferencesChangedByLocalPreferencesDialog(self):
        """
        Private slot to handle preferences changes by our local dialog.
        """
        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
        @rtype list of tuples of (int, str)
        """
        assert key in ['search', 'replace']
        
        return self.__srHistory[key]

eric ide

mercurial