Started to implement a Python Disassembly Viewer showing the byte code generated from a Python source file loaded in an editor pane.

Sat, 19 Sep 2020 19:04:21 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 19 Sep 2020 19:04:21 +0200
changeset 7704
9251c4dc4f7a
parent 7703
1f800f8295ea
child 7705
90a9aefd4253

Started to implement a Python Disassembly Viewer showing the byte code generated from a Python source file loaded in an editor pane.

docs/changelog file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
eric6/UI/PythonAstViewer.py file | annotate | diff | comparison | revisions
eric6/UI/PythonDisViewer.py file | annotate | diff | comparison | revisions
eric6/UI/UserInterface.py file | annotate | diff | comparison | revisions
eric6/ViewManager/ViewManager.py file | annotate | diff | comparison | revisions
eric6/icons/breeze-dark/disassembly.svg file | annotate | diff | comparison | revisions
eric6/icons/breeze-light/disassembly.svg file | annotate | diff | comparison | revisions
--- a/docs/changelog	Thu Sep 17 19:16:18 2020 +0200
+++ b/docs/changelog	Sat Sep 19 19:04:21 2020 +0200
@@ -5,6 +5,9 @@
 - Editor
   -- added an outline widget showing the structure of the editor source code
      and allowing to navigate in the code
+- Python Disassembly Viewer
+  -- added a tool to visualize the Python byte code generated from a Python
+     source file
 - Third Party packages
   -- updated Pygments to 2.7.0
   -- updated coverage.py to 5.3.0
--- a/eric6.e4p	Thu Sep 17 19:16:18 2020 +0200
+++ b/eric6.e4p	Sat Sep 19 19:04:21 2020 +0200
@@ -1276,6 +1276,7 @@
     <Source>eric6/UI/Previewers/PreviewerQSS.py</Source>
     <Source>eric6/UI/Previewers/__init__.py</Source>
     <Source>eric6/UI/PythonAstViewer.py</Source>
+    <Source>eric6/UI/PythonDisViewer.py</Source>
     <Source>eric6/UI/SearchWidget.py</Source>
     <Source>eric6/UI/SplashScreen.py</Source>
     <Source>eric6/UI/SymbolsWidget.py</Source>
--- a/eric6/UI/PythonAstViewer.py	Thu Sep 17 19:16:18 2020 +0200
+++ b/eric6/UI/PythonAstViewer.py	Sat Sep 19 19:04:21 2020 +0200
@@ -8,7 +8,6 @@
 sources.
 """
 
-
 import ast
 
 from PyQt5.QtCore import pyqtSlot, Qt, QTimer
@@ -106,10 +105,10 @@
             if editor.isModified():
                 # reload the source
                 QTimer.singleShot(0, self.__loadAST)
-            else:
-                # highlight the corresponding entry
-                QTimer.singleShot(0, self.__selectItemForEditorSelection)
-                QTimer.singleShot(0, self.__grabFocus)
+            
+            # highlight the corresponding entry
+            QTimer.singleShot(0, self.__selectItemForEditorSelection)
+            QTimer.singleShot(0, self.__grabFocus)
     
     def __lastEditorClosed(self):
         """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/UI/PythonDisViewer.py	Sat Sep 19 19:04:21 2020 +0200
@@ -0,0 +1,426 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a widget to visualize the Python Disassembly for some
+Python sources.
+"""
+
+import os
+import dis
+
+from PyQt5.QtCore import pyqtSlot, Qt, QTimer
+from PyQt5.QtGui import QCursor, QBrush
+from PyQt5.QtWidgets import (
+    QTreeWidget, QApplication, QTreeWidgetItem, QAbstractItemView, QWidget,
+    QVBoxLayout, QLabel
+)
+
+
+class PythonDisViewer(QWidget):
+    """
+    Class implementing a widget to visualize the Python Disassembly for some
+    Python sources.
+    """
+    StartLineRole = Qt.UserRole
+    EndLineRole = Qt.UserRole + 1
+    
+    def __init__(self, viewmanager, parent=None):
+        """
+        Constructor
+        
+        @param viewmanager reference to the viewmanager object
+        @type ViewManager
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(PythonDisViewer, self).__init__(parent)
+        
+        self.__layout = QVBoxLayout(self)
+        self.setLayout(self.__layout)
+        self.__disWidget = QTreeWidget(self)
+        self.__layout.addWidget(self.__disWidget)
+        self.__layout.setContentsMargins(0, 0, 0, 0)
+        
+        self.__infoLabel = QLabel(self.tr(
+            "italic: current instruction\n"
+            "bold: labelled instruction"
+        ))
+        self.__layout.addWidget(self.__infoLabel)
+        
+        self.__vm = viewmanager
+        self.__vmConnected = False
+        
+        self.__editor = None
+        self.__source = ""
+        
+        self.__disWidget.setHeaderLabels(
+            [self.tr("Line"), self.tr("Offset"), self.tr("Operation"),
+             self.tr("Parameters"), self.tr("Interpreted Parameters")])
+        self.__disWidget.setSortingEnabled(False)
+        self.__disWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
+        self.__disWidget.setSelectionMode(QAbstractItemView.SingleSelection)
+        self.__disWidget.setAlternatingRowColors(True)
+        
+        self.__disWidget.itemClicked.connect(self.__disItemClicked)
+        
+        self.__vm.disViewerStateChanged.connect(self.__disViewerStateChanged)
+        
+        self.hide()
+    
+    def __editorChanged(self, editor):
+        """
+        Private slot to handle a change of the current editor.
+        
+        @param editor reference to the current editor
+        @type Editor
+        """
+        if editor is not self.__editor:
+            if self.__editor:
+                self.__editor.clearAllHighlights()
+            self.__editor = editor
+            if self.__editor:
+                self.__loadDIS()
+    
+    def __editorSaved(self, editor):
+        """
+        Private slot to reload the Disassembly after the connected editor was
+        saved.
+        
+        @param editor reference to the editor that performed a save action
+        @type Editor
+        """
+        if editor and editor is self.__editor:
+            self.__loadDIS()
+    
+    def __editorDoubleClicked(self, editor, pos, buttons):
+        """
+        Private slot to handle a mouse button double click in the editor.
+        
+        @param editor reference to the editor, that emitted the signal
+        @type Editor
+        @param pos position of the double click
+        @type QPoint
+        @param buttons mouse buttons that were double clicked
+        @type Qt.MouseButtons
+        """
+        if editor is self.__editor and buttons == Qt.LeftButton:
+            if editor.isModified():
+                # reload the source
+                QTimer.singleShot(0, self.__loadDIS)
+            
+            # highlight the corresponding entry
+##            QTimer.singleShot(0, self.__selectItemForEditorSelection)
+            QTimer.singleShot(0, self.__grabFocus)
+    
+    def __lastEditorClosed(self):
+        """
+        Private slot to handle the last editor closed signal of the view
+        manager.
+        """
+        self.hide()
+    
+    def show(self):
+        """
+        Public slot to show the DIS viewer.
+        """
+        super(PythonDisViewer, self).show()
+        
+        if not self.__vmConnected:
+            self.__vm.editorChangedEd.connect(self.__editorChanged)
+            self.__vm.editorSavedEd.connect(self.__editorSaved)
+            self.__vm.editorDoubleClickedEd.connect(self.__editorDoubleClicked)
+            self.__vmConnected = True
+    
+    def hide(self):
+        """
+        Public slot to hide the DIS viewer.
+        """
+        super(PythonDisViewer, self).hide()
+        
+        if self.__editor:
+            self.__editor.clearAllHighlights()
+        
+        if self.__vmConnected:
+            self.__vm.editorChangedEd.disconnect(self.__editorChanged)
+            self.__vm.editorSavedEd.disconnect(self.__editorSaved)
+            self.__vm.editorDoubleClickedEd.disconnect(
+                self.__editorDoubleClicked)
+            self.__vmConnected = False
+    
+    def shutdown(self):
+        """
+        Public method to perform shutdown actions.
+        """
+        self.__editor = None
+    
+    def __disViewerStateChanged(self, on):
+        """
+        Private slot to toggle the display of the Disassembly viewer.
+        
+        @param on flag indicating to show the Disassembly
+        @type bool
+        """
+        editor = self.__vm.activeWindow()
+        if on and editor and editor.isPyFile():
+            if editor is not self.__editor:
+                self.__editor = editor
+            self.show()
+            self.__loadDIS()
+        else:
+            self.hide()
+            self.__editor = None
+    
+    def __createErrorItem(self, error):
+        """
+        Private method to create a top level error item.
+        
+        @param error error message
+        @type str
+        @return generated item
+        @rtype QTreeWidgetItem
+        """
+        itm = QTreeWidgetItem(self.__disWidget, [error])
+        itm.setFirstColumnSpanned(True)
+        itm.setForeground(0, QBrush(Qt.red))
+        return itm
+    
+    def __createTitleItem(self, title, line):
+        """
+        Private method to create a title item.
+        
+        @param title titel string for the item
+        @type str
+        @param line start line of the titled disassembly
+        @type int
+        @return generated item
+        @rtype QTreeWidgetItem
+        """
+        itm = QTreeWidgetItem(self.__disWidget, [title])
+        itm.setFirstColumnSpanned(True)
+        itm.setExpanded(True)
+        
+        itm.setData(0, self.StartLineRole, line)
+        itm.setData(0, self.EndLineRole, line)
+        
+        return itm
+    
+    def __createInstructionItem(self, instr, parent, lasti=-1):
+        """
+        Private method to create an item for the given instruction.
+        
+        @param instr instruction the item should be based on
+        @type dis.Instruction
+        @param parent reference to the parent item
+        @type QTreeWidgetItem
+        @param lasti index of the instruction of a traceback
+        @type int
+        @return generated item
+        @rtype QTreeWidgetItem
+        """
+        fields = []
+        # Column: Source code line number (right aligned)
+        if instr.starts_line:
+            fields.append("{0:d}".format(instr.starts_line))
+        else:
+            fields.append("")
+        # Column: Instruction offset from start of code sequence
+        # (right aligned)
+        fields.append("{0:d}".format(instr.offset))
+        # Column: Opcode name
+        fields.append(instr.opname)
+        # Column: Opcode argument (right aligned)
+        if instr.arg is not None:
+            fields.append(repr(instr.arg))
+            # Column: Opcode argument details
+            if instr.argrepr:
+                fields.append('(' + instr.argrepr + ')')
+        
+        itm = QTreeWidgetItem(parent, fields)
+        for col in (0, 1, 3):
+            itm.setTextAlignment(col, Qt.AlignRight)
+        font = itm.font(0)
+        if instr.offset == lasti:
+            font.setItalic(True)
+        if instr.is_jump_target:
+            font.setBold(True)
+        for col in range(itm.columnCount()):
+            itm.setFont(col, font)
+        
+        itm.setExpanded(True)
+        
+        if instr.starts_line:
+            itm.setData(0, self.StartLineRole, instr.starts_line)
+            itm.setData(0, self.EndLineRole, instr.starts_line)
+        else:
+            # get line from parent (= start line)
+            lineno = parent.data(0, self.StartLineRole)
+            itm.setData(0, self.StartLineRole, lineno)
+            itm.setData(0, self.EndLineRole, lineno)
+        return itm
+    
+    def __updateItemEndLine(self, itm):
+        """
+        Private method to update an items end line based on its children.
+        
+        @param itm reference to the item to be updated
+        @type QTreeWidgetItem
+        """
+        if itm.childCount():
+            endLine = max(
+                itm.child(index).data(0, self.EndLineRole)
+                for index in range(itm.childCount())
+            )
+        else:
+            endLine = itm.data(0, self.StartLineRole)
+        itm.setData(0, self.EndLineRole, endLine)
+    
+    def __loadDIS(self):
+        """
+        Private method to generate the Disassembly from the source of the
+        current editor and visualize it.
+        """
+        if not self.__editor:
+            return
+        
+        self.__disWidget.clear()
+        self.__editor.clearAllHighlights()
+        
+        if not self.__editor.isPyFile():
+            self.__createErrorItem(self.tr(
+                "The current editor text does not contain Python source."
+            ))
+            return
+        
+        source = self.__editor.text()
+        if not source.strip():
+            # empty editor or white space only
+            return
+        
+        filename = self.__editor.getFileName()
+        if not filename:
+            filename = "<dis>"
+        
+        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
+        try:
+            codeObject = self.__tryCompile(source, filename)
+        except Exception as exc:
+            codeObject = None
+            self.__createErrorItem(str(exc))
+        
+        if codeObject:
+            self.setUpdatesEnabled(False)
+            
+            self.__disassembleObject(codeObject, self.__disWidget, filename)
+            QTimer.singleShot(0, self.__resizeColumns)
+            
+            self.setUpdatesEnabled(True)
+        
+        QApplication.restoreOverrideCursor()
+        
+        self.__grabFocus()
+    
+    def __resizeColumns(self):
+        """
+        Private method to resize the columns to suitable values.
+        """
+        for col in range(self.__disWidget.columnCount()):
+            self.__disWidget.resizeColumnToContents(col)
+    
+    def resizeEvent(self, evt):
+        """
+        Protected method to handle resize events.
+        
+        @param evt resize event
+        @type QResizeEvent
+        """
+        # just adjust the sizes of the columns
+        self.__resizeColumns()
+    
+    def __grabFocus(self):
+        """
+        Private method to grab the input focus.
+        """
+        self.__disWidget.setFocus(Qt.OtherFocusReason)
+    
+    @pyqtSlot(QTreeWidgetItem, int)
+    def __disItemClicked(self, itm, column):
+        """
+        Private slot handling a user click on a Disassembly node item.
+        
+        @param itm reference to the clicked item
+        @type QTreeWidgetItem
+        @param column column number of the click
+        @type int
+        """
+        self.__editor.clearAllHighlights()
+        
+        if itm is not None:
+            startLine = itm.data(0, self.StartLineRole)
+            endLine = itm.data(0, self.EndLineRole)
+            
+            self.__editor.gotoLine(startLine, firstVisible=True,
+                                   expand=True)
+            self.__editor.setHighlight(startLine - 1, 0, endLine, -1)
+    
+    def __tryCompile(self, source, name):
+        """
+        Private method to attempt to compile the given source, first as an
+        expression and then as a statement if the first approach fails.
+        
+        @param source source code string to be compiled
+        @type str
+        @param name name of the file containing the source
+        @type str
+        @return compiled code
+        @rtype code object
+        """
+        try:
+            c = compile(source, name, 'eval')
+        except SyntaxError:
+            c = compile(source, name, 'exec')
+        return c
+    
+    def __disassembleObject(self, co, parentItem, name="", lasti=-1):
+        """
+        Private method to disassemble the given code object recursively.
+        
+        @param co code object to be disassembled
+        @type code object
+        @param parentItem reference to the parent item
+        @type QTreeWidget or QTreeWidgetItem
+        @param name name of the code object
+        @type str
+        @param lasti index of the instruction of a traceback
+        @type int
+        """
+        if co.co_name == "<module>":
+            title = (
+                self.tr("Disassembly of module '{0}'")
+                .format(os.path.basename(co.co_filename))
+            )
+        else:
+            title = (
+                self.tr("Disassembly of code object '{0}'")
+                .format(co.co_name)
+            )
+        titleItem = self.__createTitleItem(title, co.co_firstlineno)
+        lastStartItem = None
+        for instr in dis.get_instructions(co):
+            if instr.starts_line:
+                if lastStartItem:
+                    self.__updateItemEndLine(lastStartItem)
+                lastStartItem = self.__createInstructionItem(
+                    instr, titleItem, lasti=lasti)
+            else:
+                self.__createInstructionItem(instr, lastStartItem, lasti=lasti)
+        if lastStartItem:
+            self.__updateItemEndLine(lastStartItem)
+        
+        self.__updateItemEndLine(titleItem)
+        
+        for x in co.co_consts:
+            if hasattr(x, 'co_code'):
+                self.__disassembleObject(x, self.__disWidget, lasti=lasti)
--- a/eric6/UI/UserInterface.py	Thu Sep 17 19:16:18 2020 +0200
+++ b/eric6/UI/UserInterface.py	Sat Sep 19 19:04:21 2020 +0200
@@ -790,6 +790,12 @@
         self.__astViewer = PythonAstViewer(self.viewmanager, splitter)
         splitter.addWidget(self.__astViewer)
         
+        # Create DIS viewer
+        logging.debug("Creating Python Disassembly Viewer")
+        from .PythonDisViewer import PythonDisViewer
+        self.__disViewer = PythonDisViewer(self.viewmanager, splitter)
+        splitter.addWidget(self.__disViewer)
+        
         # Create layout with toolbox windows embedded in dock windows
         if self.__layoutType == "Toolboxes":
             logging.debug("Creating toolboxes...")
--- a/eric6/ViewManager/ViewManager.py	Thu Sep 17 19:16:18 2020 +0200
+++ b/eric6/ViewManager/ViewManager.py	Sat Sep 19 19:04:21 2020 +0200
@@ -119,6 +119,8 @@
         preview state
     @signal astViewerStateChanged(bool) emitted to signal a change in the
         AST viewer state
+    @signal disViewerStateChanged(bool) emitted to signal a change in the
+        DIS viewer state
     @signal editorLanguageChanged(Editor) emitted to signal a change of an
         editor's language
     @signal editorTextChanged(Editor) emitted to signal a change of an
@@ -147,6 +149,7 @@
     syntaxerrorToggled = pyqtSignal(Editor)
     previewStateChanged = pyqtSignal(bool)
     astViewerStateChanged = pyqtSignal(bool)
+    disViewerStateChanged = pyqtSignal(bool)
     editorLanguageChanged = pyqtSignal(Editor)
     editorTextChanged = pyqtSignal(Editor)
     editorLineChanged = pyqtSignal(str, int)
@@ -3784,6 +3787,23 @@
         self.astViewerAct.toggled[bool].connect(self.__astViewer)
         self.viewActions.append(self.astViewerAct)
         
+        self.disViewerAct = E5Action(
+            QCoreApplication.translate('ViewManager', 'Python DIS Viewer'),
+            UI.PixmapCache.getIcon("disassembly"),
+            QCoreApplication.translate('ViewManager', 'Python DIS Viewer'),
+            0, 0, self, 'vm_python_dis_viewer', True)
+        self.disViewerAct.setStatusTip(QCoreApplication.translate(
+            'ViewManager', 'Show the Disassembly for the current Python file'))
+        self.disViewerAct.setWhatsThis(QCoreApplication.translate(
+            'ViewManager',
+            """<b>Python DIS Viewer</b>"""
+            """<p>This opens the a tree view of the Disassembly of the"""
+            """ current Python source file.</p>"""
+        ))
+        self.disViewerAct.setChecked(False)
+        self.disViewerAct.toggled[bool].connect(self.__disViewer)
+        self.viewActions.append(self.disViewerAct)
+        
         self.viewActGrp.setEnabled(False)
         self.viewFoldActGrp.setEnabled(False)
         self.unhighlightAct.setEnabled(False)
@@ -3794,6 +3814,7 @@
         self.prevSplitAct.setEnabled(False)
         self.previewAct.setEnabled(True)
         self.astViewerAct.setEnabled(False)
+        self.disViewerAct.setEnabled(False)
         self.newDocumentViewAct.setEnabled(False)
         self.newDocumentSplitViewAct.setEnabled(False)
         
@@ -3815,6 +3836,7 @@
         menu.addSeparator()
         menu.addAction(self.previewAct)
         menu.addAction(self.astViewerAct)
+        menu.addAction(self.disViewerAct)
         menu.addSeparator()
         menu.addAction(self.unhighlightAct)
         menu.addSeparator()
@@ -3848,6 +3870,7 @@
         tb.addSeparator()
         tb.addAction(self.previewAct)
         tb.addAction(self.astViewerAct)
+        tb.addAction(self.disViewerAct)
         tb.addSeparator()
         tb.addAction(self.newDocumentViewAct)
         if self.canSplit():
@@ -6211,6 +6234,14 @@
         """
         self.astViewerStateChanged.emit(checked)
     
+    def __disViewer(self, checked):
+        """
+        Private slot to handle a change of the DIS Viewer selection state.
+        
+        @param checked state of the action (boolean)
+        """
+        self.disViewerStateChanged.emit(checked)
+    
     ##################################################################
     ## Below are the action methods for the macro menu
     ##################################################################
@@ -6683,6 +6714,7 @@
         self.splitOrientationAct.setEnabled(False)
         self.previewAct.setEnabled(True)
         self.astViewerAct.setEnabled(False)
+        self.disViewerAct.setEnabled(False)
         self.macroActGrp.setEnabled(False)
         self.bookmarkActGrp.setEnabled(False)
         self.__enableSpellingActions()
@@ -6704,6 +6736,9 @@
         # hide the AST Viewer via its action
         self.astViewerAct.setChecked(False)
         
+        # hide the DIS Viewer via its action
+        self.disViewerAct.setChecked(False)
+        
     def __editorOpened(self):
         """
         Private slot to handle the editorOpened signal.
@@ -6729,6 +6764,7 @@
         self.bookmarkActGrp.setEnabled(True)
         self.__enableSpellingActions()
         self.astViewerAct.setEnabled(True)
+        self.disViewerAct.setEnabled(True)
         
         # activate the autosave timer
         if (
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/breeze-dark/disassembly.svg	Sat Sep 19 19:04:21 2020 +0200
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   viewBox="0 0 5.8208332 5.8208332"
+   version="1.1"
+   id="svg8"
+   sodipodi:docname="disassembly.svg"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.4"
+     inkscape:cx="16.8675"
+     inkscape:cy="11.853478"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1080"
+     inkscape:window-x="240"
+     inkscape:window-y="254"
+     inkscape:window-maximized="0" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Ebene 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-39.800894,-71.083631)">
+    <g
+       id="g1417"
+       transform="matrix(0.99851053,0,0,1.0220192,0.06122203,-1.6214429)"
+       style="stroke:#eff0f1;stroke-opacity:1">
+      <rect
+         y="72.577782"
+         x="41.295048"
+         height="0.98044246"
+         width="0.98044252"
+         id="rect1368"
+         style="fill:none;fill-opacity:1;stroke:#eff0f1;stroke-width:0.34247419;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path1370"
+         d="m 40.197749,72.404262 0.01509,-0.873434 0.860208,0.01679"
+         style="fill:none;stroke:#eff0f1;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         transform="matrix(0.97374363,0,0,0.98302048,1.055057,1.2316379)"
+         id="g1375"
+         style="stroke:#eff0f1;stroke-opacity:1">
+        <path
+           style="fill:none;stroke:#eff0f1;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 40.258855,71.530033 0.900391,0.885964"
+           id="path1372"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cc" />
+      </g>
+    </g>
+    <g
+       transform="matrix(0,0.99851053,-1.0220192,0,118.3268,31.343956)"
+       id="g1417-9"
+       style="stroke:#eff0f1;stroke-opacity:1">
+      <rect
+         y="72.577782"
+         x="41.295048"
+         height="0.98044246"
+         width="0.98044252"
+         id="rect1368-1"
+         style="fill:none;fill-opacity:1;stroke:#eff0f1;stroke-width:0.34247419;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path1370-2"
+         d="m 40.197749,72.404262 0.01509,-0.873434 0.860208,0.01679"
+         style="fill:none;stroke:#eff0f1;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         transform="matrix(0.97374363,0,0,0.98302048,1.055057,1.2316379)"
+         id="g1375-7"
+         style="stroke:#eff0f1;stroke-opacity:1">
+        <path
+           style="fill:none;stroke:#eff0f1;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 40.258855,71.530033 0.900391,0.885964"
+           id="path1372-0"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cc" />
+      </g>
+    </g>
+    <g
+       id="g1417-6"
+       transform="matrix(-0.99854218,0,0,-1.0203583,85.364322,149.48925)"
+       style="stroke:#eff0f1;stroke-opacity:1">
+      <rect
+         y="72.577782"
+         x="41.295048"
+         height="0.98044246"
+         width="0.98044252"
+         id="rect1368-18"
+         style="fill:none;fill-opacity:1;stroke:#eff0f1;stroke-width:0.34247419;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path1370-7"
+         d="m 40.197749,72.404262 0.01509,-0.873434 0.860208,0.01679"
+         style="fill:none;stroke:#eff0f1;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         transform="matrix(0.97374363,0,0,0.98302048,1.055057,1.2316379)"
+         id="g1375-9"
+         style="stroke:#eff0f1;stroke-opacity:1">
+        <path
+           style="fill:none;stroke:#eff0f1;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 40.258855,71.530033 0.900391,0.885964"
+           id="path1372-2"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cc" />
+      </g>
+    </g>
+    <g
+       transform="matrix(0,-0.99688788,1.0220516,0,-32.905006,116.57743)"
+       id="g1417-9-0"
+       style="stroke:#eff0f1;stroke-opacity:1">
+      <rect
+         y="72.577782"
+         x="41.295048"
+         height="0.98044246"
+         width="0.98044252"
+         id="rect1368-1-2"
+         style="fill:none;fill-opacity:1;stroke:#eff0f1;stroke-width:0.34247419;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path1370-2-3"
+         d="m 40.197749,72.404262 0.01509,-0.873434 0.860208,0.01679"
+         style="fill:none;stroke:#eff0f1;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         transform="matrix(0.97374363,0,0,0.98302048,1.055057,1.2316379)"
+         id="g1375-7-7"
+         style="stroke:#eff0f1;stroke-opacity:1">
+        <path
+           style="fill:none;stroke:#eff0f1;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 40.258855,71.530033 0.900391,0.885964"
+           id="path1372-0-5"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cc" />
+      </g>
+    </g>
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/breeze-light/disassembly.svg	Sat Sep 19 19:04:21 2020 +0200
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   viewBox="0 0 5.8208332 5.8208332"
+   version="1.1"
+   id="svg8"
+   sodipodi:docname="disassembly.svg"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.4"
+     inkscape:cx="16.8675"
+     inkscape:cy="11.853478"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1080"
+     inkscape:window-x="240"
+     inkscape:window-y="254"
+     inkscape:window-maximized="0" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Ebene 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-39.800894,-71.083631)">
+    <g
+       id="g1417"
+       transform="matrix(0.99851053,0,0,1.0220192,0.06122203,-1.6214429)"
+       style="stroke:#232629;stroke-opacity:1">
+      <rect
+         y="72.577782"
+         x="41.295048"
+         height="0.98044246"
+         width="0.98044252"
+         id="rect1368"
+         style="fill:none;fill-opacity:1;stroke:#232629;stroke-width:0.34247419;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path1370"
+         d="m 40.197749,72.404262 0.01509,-0.873434 0.860208,0.01679"
+         style="fill:none;stroke:#232629;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         transform="matrix(0.97374363,0,0,0.98302048,1.055057,1.2316379)"
+         id="g1375"
+         style="stroke:#232629;stroke-opacity:1">
+        <path
+           style="fill:none;stroke:#232629;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 40.258855,71.530033 0.900391,0.885964"
+           id="path1372"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cc" />
+      </g>
+    </g>
+    <g
+       transform="matrix(0,0.99851053,-1.0220192,0,118.3268,31.343956)"
+       id="g1417-9"
+       style="stroke:#232629;stroke-opacity:1">
+      <rect
+         y="72.577782"
+         x="41.295048"
+         height="0.98044246"
+         width="0.98044252"
+         id="rect1368-1"
+         style="fill:none;fill-opacity:1;stroke:#232629;stroke-width:0.34247419;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path1370-2"
+         d="m 40.197749,72.404262 0.01509,-0.873434 0.860208,0.01679"
+         style="fill:none;stroke:#232629;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         transform="matrix(0.97374363,0,0,0.98302048,1.055057,1.2316379)"
+         id="g1375-7"
+         style="stroke:#232629;stroke-opacity:1">
+        <path
+           style="fill:none;stroke:#232629;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 40.258855,71.530033 0.900391,0.885964"
+           id="path1372-0"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cc" />
+      </g>
+    </g>
+    <g
+       id="g1417-6"
+       transform="matrix(-0.99854218,0,0,-1.0203583,85.364322,149.48925)"
+       style="stroke:#232629;stroke-opacity:1">
+      <rect
+         y="72.577782"
+         x="41.295048"
+         height="0.98044246"
+         width="0.98044252"
+         id="rect1368-18"
+         style="fill:none;fill-opacity:1;stroke:#232629;stroke-width:0.34247419;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path1370-7"
+         d="m 40.197749,72.404262 0.01509,-0.873434 0.860208,0.01679"
+         style="fill:none;stroke:#232629;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         transform="matrix(0.97374363,0,0,0.98302048,1.055057,1.2316379)"
+         id="g1375-9"
+         style="stroke:#232629;stroke-opacity:1">
+        <path
+           style="fill:none;stroke:#232629;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 40.258855,71.530033 0.900391,0.885964"
+           id="path1372-2"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cc" />
+      </g>
+    </g>
+    <g
+       transform="matrix(0,-0.99688788,1.0220516,0,-32.905006,116.57743)"
+       id="g1417-9-0"
+       style="stroke:#232629;stroke-opacity:1">
+      <rect
+         y="72.577782"
+         x="41.295048"
+         height="0.98044246"
+         width="0.98044252"
+         id="rect1368-1-2"
+         style="fill:none;fill-opacity:1;stroke:#232629;stroke-width:0.34247419;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path1370-2-3"
+         d="m 40.197749,72.404262 0.01509,-0.873434 0.860208,0.01679"
+         style="fill:none;stroke:#232629;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         transform="matrix(0.97374363,0,0,0.98302048,1.055057,1.2316379)"
+         id="g1375-7-7"
+         style="stroke:#232629;stroke-opacity:1">
+        <path
+           style="fill:none;stroke:#232629;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 40.258855,71.530033 0.900391,0.885964"
+           id="path1372-0-5"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cc" />
+      </g>
+    </g>
+  </g>
+</svg>

eric ide

mercurial