15 |
15 |
16 |
16 |
17 from PyQt5.QtCore import pyqtSlot, Qt, QTimer |
17 from PyQt5.QtCore import pyqtSlot, Qt, QTimer |
18 from PyQt5.QtGui import QCursor, QBrush |
18 from PyQt5.QtGui import QCursor, QBrush |
19 from PyQt5.QtWidgets import ( |
19 from PyQt5.QtWidgets import ( |
20 QTreeWidget, QApplication, QTreeWidgetItem, QAbstractItemView, QWidget, |
20 QApplication, QTreeWidgetItem, QAbstractItemView, QWidget, QMenu |
21 QVBoxLayout, QLabel, QMenu |
|
22 ) |
21 ) |
23 |
22 |
24 import Preferences |
23 import Preferences |
|
24 |
|
25 from .Ui_PythonDisViewer import Ui_PythonDisViewer |
25 |
26 |
26 |
27 |
27 class PythonDisViewerModes(enum.Enum): |
28 class PythonDisViewerModes(enum.Enum): |
28 """ |
29 """ |
29 Class implementing the disassembly viewer operation modes. |
30 Class implementing the disassembly viewer operation modes. |
30 """ |
31 """ |
31 SourceDisassemblyMode = 0 |
32 SourceDisassemblyMode = 0 |
32 TracebackMode = 1 |
33 TracebackMode = 1 |
33 |
34 |
34 |
35 |
35 class PythonDisViewer(QWidget): |
36 class PythonDisViewer(QWidget, Ui_PythonDisViewer): |
36 """ |
37 """ |
37 Class implementing a widget to visualize the Python Disassembly for some |
38 Class implementing a widget to visualize the Python Disassembly for some |
38 Python sources. |
39 Python sources. |
39 """ |
40 """ |
40 StartLineRole = Qt.UserRole |
41 StartLineRole = Qt.UserRole |
41 EndLineRole = Qt.UserRole + 1 |
42 EndLineRole = Qt.UserRole + 1 |
|
43 CodeInfoRole = Qt.UserRole + 2 |
42 |
44 |
43 def __init__(self, viewmanager, |
45 def __init__(self, viewmanager, |
44 mode=PythonDisViewerModes.SourceDisassemblyMode, |
46 mode=PythonDisViewerModes.SourceDisassemblyMode, |
45 parent=None): |
47 parent=None): |
46 """ |
48 """ |
76 self.__mode = mode |
66 self.__mode = mode |
77 |
67 |
78 self.__editor = None |
68 self.__editor = None |
79 self.__source = "" |
69 self.__source = "" |
80 |
70 |
81 self.__disWidget.setHeaderLabels( |
71 self.disWidget.setHeaderLabels( |
82 [self.tr("Line"), self.tr("Offset"), self.tr("Operation"), |
72 [self.tr("Line"), self.tr("Offset"), self.tr("Operation"), |
83 self.tr("Parameters"), self.tr("Interpreted Parameters")]) |
73 self.tr("Parameters"), self.tr("Interpreted Parameters")]) |
84 self.__disWidget.setSortingEnabled(False) |
74 self.codeInfoWidget.setHeaderLabels( |
85 self.__disWidget.setSelectionBehavior(QAbstractItemView.SelectRows) |
75 [self.tr("Key"), self.tr("Value")]) |
86 self.__disWidget.setSelectionMode(QAbstractItemView.SingleSelection) |
76 |
87 self.__disWidget.setAlternatingRowColors(True) |
77 self.__disMenu = QMenu(self.disWidget) |
88 |
78 if self.__mode == PythonDisViewerModes.SourceDisassemblyMode: |
89 self.__menu = QMenu(self.__disWidget) |
79 self.__codeInfoAct = self.__disMenu.addAction( |
90 self.__menu.addAction(self.tr('Expand All'), self.__expandAll) |
80 self.tr("Show Code Info"), self.__showCodeInfo) |
91 self.__menu.addAction(self.tr('Collapse All'), self.__collapseAll) |
81 self.__disMenu.addSeparator() |
92 |
82 self.__disMenu.addAction( |
93 self.__disWidget.setContextMenuPolicy(Qt.CustomContextMenu) |
83 self.tr('Expand All'), self.__expandAllDis) |
94 self.__disWidget.customContextMenuRequested.connect( |
84 self.__disMenu.addAction( |
95 self.__contextMenuRequested) |
85 self.tr('Collapse All'), self.__collapseAllDis) |
|
86 |
|
87 self.__codeInfoMenu = QMenu(self.codeInfoWidget) |
|
88 if self.__mode == PythonDisViewerModes.SourceDisassemblyMode: |
|
89 self.__codeInfoMenu.addAction( |
|
90 self.tr("Hide"), self.codeInfoWidget.hide) |
|
91 self.__codeInfoMenu.addAction( |
|
92 self.tr('Expand All'), self.__expandAllCodeInfo) |
|
93 self.__codeInfoMenu.addAction( |
|
94 self.tr('Collapse All'), self.__collapseAllCodeInfo) |
96 |
95 |
97 self.__errorColor = QBrush( |
96 self.__errorColor = QBrush( |
98 Preferences.getPython("DisViewerErrorColor")) |
97 Preferences.getPython("DisViewerErrorColor")) |
99 self.__currentInstructionColor = QBrush( |
98 self.__currentInstructionColor = QBrush( |
100 Preferences.getPython("DisViewerCurrentColor")) |
99 Preferences.getPython("DisViewerCurrentColor")) |
101 self.__jumpTargetColor = QBrush( |
100 self.__jumpTargetColor = QBrush( |
102 Preferences.getPython("DisViewerLabeledColor")) |
101 Preferences.getPython("DisViewerLabeledColor")) |
103 |
102 |
104 self.__disWidget.itemClicked.connect(self.__disItemClicked) |
103 self.disWidget.itemClicked.connect(self.__disItemClicked) |
105 self.__disWidget.itemCollapsed.connect(self.__resizeColumns) |
104 self.disWidget.itemCollapsed.connect(self.__resizeDisColumns) |
106 self.__disWidget.itemExpanded.connect(self.__resizeColumns) |
105 self.disWidget.itemExpanded.connect(self.__resizeDisColumns) |
|
106 self.disWidget.customContextMenuRequested.connect( |
|
107 self.__disContextMenuRequested) |
|
108 |
|
109 self.codeInfoWidget.itemCollapsed.connect(self.__resizeCodeInfoColumns) |
|
110 self.codeInfoWidget.itemExpanded.connect(self.__resizeCodeInfoColumns) |
|
111 self.codeInfoWidget.customContextMenuRequested.connect( |
|
112 self.__codeInfoContextMenuRequested) |
107 |
113 |
108 if self.__mode == PythonDisViewerModes.SourceDisassemblyMode: |
114 if self.__mode == PythonDisViewerModes.SourceDisassemblyMode: |
109 self.__vm.disViewerStateChanged.connect( |
115 self.__vm.disViewerStateChanged.connect( |
110 self.__disViewerStateChanged) |
116 self.__disViewerStateChanged) |
|
117 |
|
118 self.codeInfoWidget.hide() |
111 |
119 |
112 self.hide() |
120 self.hide() |
|
121 |
113 elif self.__mode == PythonDisViewerModes.TracebackMode: |
122 elif self.__mode == PythonDisViewerModes.TracebackMode: |
114 self.__styleLabels() |
123 self.__styleLabels() |
115 |
124 |
116 def __contextMenuRequested(self, coord): |
125 def __disContextMenuRequested(self, coord): |
117 """ |
126 """ |
118 Private slot to show the context menu. |
127 Private slot to show the context menu of the disassembly widget. |
119 |
128 |
120 @param coord position of the mouse pointer |
129 @param coord position of the mouse pointer |
121 @type QPoint |
130 @type QPoint |
122 """ |
131 """ |
123 coord = self.__disWidget.mapToGlobal(coord) |
132 if self.__mode == PythonDisViewerModes.SourceDisassemblyMode: |
124 self.__menu.popup(coord) |
133 itm = self.disWidget.itemAt(coord) |
|
134 self.__codeInfoAct.setEnabled(bool(itm.data(0, self.CodeInfoRole))) |
|
135 self.disWidget.setCurrentItem(itm) |
|
136 |
|
137 if self.disWidget.topLevelItemCount() > 0: |
|
138 # don't show context menu on empty list |
|
139 coord = self.disWidget.mapToGlobal(coord) |
|
140 self.__disMenu.popup(coord) |
125 |
141 |
126 def __editorChanged(self, editor): |
142 def __editorChanged(self, editor): |
127 """ |
143 """ |
128 Private slot to handle a change of the current editor. |
144 Private slot to handle a change of the current editor. |
129 |
145 |
243 self.__loadDIS() |
259 self.__loadDIS() |
244 else: |
260 else: |
245 self.hide() |
261 self.hide() |
246 self.__editor = None |
262 self.__editor = None |
247 |
263 |
248 def __expandAll(self): |
264 def __expandAllDis(self): |
249 """ |
265 """ |
250 Private slot to expand all items. |
266 Private slot to expand all items of the disassembly widget. |
251 """ |
267 """ |
252 block = self.__disWidget.blockSignals(True) |
268 block = self.disWidget.blockSignals(True) |
253 self.__disWidget.expandAll() |
269 self.disWidget.expandAll() |
254 self.__disWidget.blockSignals(block) |
270 self.disWidget.blockSignals(block) |
255 self.__resizeColumns() |
271 self.__resizeDisColumns() |
256 |
272 |
257 def __collapseAll(self): |
273 def __collapseAllDis(self): |
258 """ |
274 """ |
259 Private slot to collapse all items. |
275 Private slot to collapse all items of the disassembly widget. |
260 """ |
276 """ |
261 block = self.__disWidget.blockSignals(True) |
277 block = self.disWidget.blockSignals(True) |
262 self.__disWidget.collapseAll() |
278 self.disWidget.collapseAll() |
263 self.__disWidget.blockSignals(block) |
279 self.disWidget.blockSignals(block) |
264 self.__resizeColumns() |
280 self.__resizeDisColumns() |
265 |
281 |
266 def __createErrorItem(self, error): |
282 def __createErrorItem(self, error): |
267 """ |
283 """ |
268 Private method to create a top level error item. |
284 Private method to create a top level error item. |
269 |
285 |
270 @param error error message |
286 @param error error message |
271 @type str |
287 @type str |
272 @return generated item |
288 @return generated item |
273 @rtype QTreeWidgetItem |
289 @rtype QTreeWidgetItem |
274 """ |
290 """ |
275 itm = QTreeWidgetItem(self.__disWidget, [error]) |
291 itm = QTreeWidgetItem(self.disWidget, [error]) |
276 itm.setFirstColumnSpanned(True) |
292 itm.setFirstColumnSpanned(True) |
277 itm.setForeground(0, self.__errorColor) |
293 itm.setForeground(0, self.__errorColor) |
278 return itm |
294 return itm |
279 |
295 |
280 def __createTitleItem(self, title, line, parentItem): |
296 def __createTitleItem(self, title, line, parentItem): |
378 ) |
394 ) |
379 else: |
395 else: |
380 endLine = itm.data(0, self.StartLineRole) |
396 endLine = itm.data(0, self.StartLineRole) |
381 itm.setData(0, self.EndLineRole, endLine) |
397 itm.setData(0, self.EndLineRole, endLine) |
382 |
398 |
|
399 def __createCodeInfo(self, co): |
|
400 """ |
|
401 Private method to create a dictionary containing the code info data. |
|
402 |
|
403 @param co reference to the code object to generate the info for |
|
404 @type code |
|
405 @return dictionary containing the code info data |
|
406 @rtype dict |
|
407 """ |
|
408 return { |
|
409 "name": co.co_name, |
|
410 "filename": co.co_filename, |
|
411 "argcount": co.co_argcount, |
|
412 "posonlyargcount": co.co_posonlyargcount, |
|
413 "kwonlyargcount": co.co_kwonlyargcount, |
|
414 "nlocals": co.co_nlocals, |
|
415 "stacksize": co.co_stacksize, |
|
416 "flags": dis.pretty_flags(co.co_flags), |
|
417 "consts": co.co_consts, |
|
418 "names": co.co_names, |
|
419 "varnames": co.co_varnames, |
|
420 "freevars": co.co_freevars, |
|
421 "cellvars": co.co_cellvars, |
|
422 } |
|
423 |
383 def __loadDIS(self): |
424 def __loadDIS(self): |
384 """ |
425 """ |
385 Private method to generate the Disassembly from the source of the |
426 Private method to generate the Disassembly from the source of the |
386 current editor and visualize it. |
427 current editor and visualize it. |
387 """ |
428 """ |
484 self.__createInstructionItem( |
525 self.__createInstructionItem( |
485 instr, lastStartItem, lasti=lasti) |
526 instr, lastStartItem, lasti=lasti) |
486 if lastStartItem: |
527 if lastStartItem: |
487 self.__updateItemEndLine(lastStartItem) |
528 self.__updateItemEndLine(lastStartItem) |
488 |
529 |
489 QTimer.singleShot(0, self.__resizeColumns) |
530 QTimer.singleShot(0, self.__resizeDisColumns) |
490 |
531 |
491 self.__disWidget.blockSignals(block) |
532 self.disWidget.blockSignals(block) |
492 self.setUpdatesEnabled(True) |
533 self.setUpdatesEnabled(True) |
493 |
534 |
494 if lasti: |
535 if lasti: |
495 lastInstructions = self.__disWidget.findItems( |
536 lastInstructions = self.disWidget.findItems( |
496 "{0:d}".format(lasti), |
537 "{0:d}".format(lasti), |
497 Qt.MatchFixedString | Qt.MatchRecursive, 1) |
538 Qt.MatchFixedString | Qt.MatchRecursive, 1) |
498 if lastInstructions: |
539 if lastInstructions: |
499 self.__disWidget.scrollToItem( |
540 self.disWidget.scrollToItem( |
500 lastInstructions[0], |
541 lastInstructions[0], |
501 QAbstractItemView.PositionAtCenter) |
542 QAbstractItemView.PositionAtCenter) |
502 |
543 |
503 def __resizeColumns(self): |
544 if "codeInfo" in disassembly: |
504 """ |
545 self.__showCodeInfoData(disassembly["codeInfo"]) |
505 Private method to resize the columns to suitable values. |
546 |
506 """ |
547 def __resizeDisColumns(self): |
507 for col in range(self.__disWidget.columnCount()): |
548 """ |
508 self.__disWidget.resizeColumnToContents(col) |
549 Private method to resize the columns of the disassembly widget to |
|
550 suitable values. |
|
551 """ |
|
552 for col in range(self.disWidget.columnCount()): |
|
553 self.disWidget.resizeColumnToContents(col) |
509 |
554 |
510 def resizeEvent(self, evt): |
555 def resizeEvent(self, evt): |
511 """ |
556 """ |
512 Protected method to handle resize events. |
557 Protected method to handle resize events. |
513 |
558 |
514 @param evt resize event |
559 @param evt resize event |
515 @type QResizeEvent |
560 @type QResizeEvent |
516 """ |
561 """ |
517 # just adjust the sizes of the columns |
562 # just adjust the sizes of the columns |
518 self.__resizeColumns() |
563 self.__resizeDisColumns() |
|
564 self.__resizeCodeInfoColumns() |
519 |
565 |
520 def __clearSelection(self): |
566 def __clearSelection(self): |
521 """ |
567 """ |
522 Private method to clear all selected items. |
568 Private method to clear all selected items. |
523 """ |
569 """ |
524 for itm in self.__disWidget.selectedItems(): |
570 for itm in self.disWidget.selectedItems(): |
525 itm.setSelected(False) |
571 itm.setSelected(False) |
526 |
572 |
527 def __selectChildren(self, itm, lineno): |
573 def __selectChildren(self, itm, lineno): |
528 """ |
574 """ |
529 Private method to select children of the given item covering the given |
575 Private method to select children of the given item covering the given |
671 def __styleLabels(self): |
721 def __styleLabels(self): |
672 """ |
722 """ |
673 Private method to style the info labels iaw. selected colors. |
723 Private method to style the info labels iaw. selected colors. |
674 """ |
724 """ |
675 # current instruction |
725 # current instruction |
676 self.__currentInfoLabel.setStyleSheet( |
726 self.currentInfoLabel.setStyleSheet( |
677 "QLabel {{ color : {0}; }}".format( |
727 "QLabel {{ color : {0}; }}".format( |
678 self.__currentInstructionColor.color().name() |
728 self.__currentInstructionColor.color().name() |
679 ) |
729 ) |
680 ) |
730 ) |
681 font = self.__currentInfoLabel.font() |
731 font = self.currentInfoLabel.font() |
682 font.setItalic(True) |
732 font.setItalic(True) |
683 self.__currentInfoLabel.setFont(font) |
733 self.currentInfoLabel.setFont(font) |
684 |
734 |
685 # labeled instruction |
735 # labeled instruction |
686 self.__labeledInfoLabel.setStyleSheet( |
736 self.labeledInfoLabel.setStyleSheet( |
687 "QLabel {{ color : {0}; }}".format( |
737 "QLabel {{ color : {0}; }}".format( |
688 self.__jumpTargetColor.color().name() |
738 self.__jumpTargetColor.color().name() |
689 ) |
739 ) |
690 ) |
740 ) |
691 font = self.__labeledInfoLabel.font() |
741 font = self.labeledInfoLabel.font() |
692 font.setBold(True) |
742 font.setBold(True) |
693 self.__labeledInfoLabel.setFont(font) |
743 self.labeledInfoLabel.setFont(font) |
694 |
744 |
695 @pyqtSlot() |
745 @pyqtSlot() |
696 def clear(self): |
746 def clear(self): |
697 """ |
747 """ |
698 Public method to clear the display. |
748 Public method to clear the display. |
699 """ |
749 """ |
700 self.__disWidget.clear() |
750 self.disWidget.clear() |
|
751 |
|
752 def __showCodeInfo(self): |
|
753 """ |
|
754 Private slot handling the context menu action to show code info. |
|
755 """ |
|
756 itm = self.disWidget.currentItem() |
|
757 codeInfo = itm.data(0, self.CodeInfoRole) |
|
758 if codeInfo: |
|
759 self.codeInfoWidget.show() |
|
760 self.__showCodeInfoData(codeInfo) |
|
761 |
|
762 def __showCodeInfoData(self, codeInfo): |
|
763 """ |
|
764 Private method to show the passed code info data. |
|
765 |
|
766 @param codeInfo dictionary containing the code info data |
|
767 @type dict |
|
768 """ |
|
769 def createCodeInfoItems(title, infoList): |
|
770 """ |
|
771 Function to create code info items for the given list. |
|
772 |
|
773 @param title title string for the list |
|
774 @type str |
|
775 @param infoList list of info strings |
|
776 @type list of str |
|
777 """ |
|
778 parent = QTreeWidgetItem(self.codeInfoWidget, |
|
779 [title, str(len(infoList))]) |
|
780 # TODO: make this a configuration item |
|
781 parent.setExpanded(False) |
|
782 |
|
783 for index, value in enumerate(infoList): |
|
784 itm = QTreeWidgetItem(parent, [str(index), str(value)]) |
|
785 itm.setTextAlignment(0, Qt.AlignRight) |
|
786 |
|
787 self.codeInfoWidget.clear() |
|
788 |
|
789 if codeInfo: |
|
790 QTreeWidgetItem(self.codeInfoWidget, [ |
|
791 self.tr("Name"), codeInfo["name"]]) |
|
792 QTreeWidgetItem(self.codeInfoWidget, [ |
|
793 self.tr("Filename"), codeInfo["filename"]]) |
|
794 QTreeWidgetItem(self.codeInfoWidget, [ |
|
795 self.tr("Argument Count"), str(codeInfo["argcount"])]) |
|
796 QTreeWidgetItem(self.codeInfoWidget, [ |
|
797 self.tr("Positional-only Arguments"), |
|
798 str(codeInfo["posonlyargcount"])]) |
|
799 QTreeWidgetItem(self.codeInfoWidget, [ |
|
800 self.tr("Keyword-only arguments"), |
|
801 str(codeInfo["kwonlyargcount"])]) |
|
802 QTreeWidgetItem(self.codeInfoWidget, [ |
|
803 self.tr("Number of Locals"), str(codeInfo["nlocals"])]) |
|
804 QTreeWidgetItem(self.codeInfoWidget, [ |
|
805 self.tr("Stack Size"), str(codeInfo["stacksize"])]) |
|
806 QTreeWidgetItem(self.codeInfoWidget, [ |
|
807 self.tr("Flags"), codeInfo["flags"]]) |
|
808 if codeInfo["consts"]: |
|
809 createCodeInfoItems(self.tr("Constants"), |
|
810 codeInfo["consts"]) |
|
811 if codeInfo["names"]: |
|
812 createCodeInfoItems(self.tr("Names"), |
|
813 codeInfo["names"]) |
|
814 if codeInfo["varnames"]: |
|
815 createCodeInfoItems(self.tr("Variable Names"), |
|
816 codeInfo["varnames"]) |
|
817 if codeInfo["freevars"]: |
|
818 createCodeInfoItems(self.tr("Free Variables"), |
|
819 codeInfo["freevars"]) |
|
820 if codeInfo["cellvars"]: |
|
821 createCodeInfoItems(self.tr("Cell Variables"), |
|
822 codeInfo["cellvars"]) |
|
823 |
|
824 QTimer.singleShot(0, self.__resizeCodeInfoColumns) |
|
825 |
|
826 def __resizeCodeInfoColumns(self): |
|
827 """ |
|
828 Private method to resize the columns of the code info widget to |
|
829 suitable values. |
|
830 """ |
|
831 for col in range(self.codeInfoWidget.columnCount()): |
|
832 self.codeInfoWidget.resizeColumnToContents(col) |
|
833 |
|
834 def __expandAllCodeInfo(self): |
|
835 """ |
|
836 Private slot to expand all items of the code info widget. |
|
837 """ |
|
838 block = self.codeInfoWidget.blockSignals(True) |
|
839 self.codeInfoWidget.expandAll() |
|
840 self.codeInfoWidget.blockSignals(block) |
|
841 self.__resizeCodeInfoColumns() |
|
842 |
|
843 def __collapseAllCodeInfo(self): |
|
844 """ |
|
845 Private slot to collapse all items of the code info widget. |
|
846 """ |
|
847 block = self.codeInfoWidget.blockSignals(True) |
|
848 self.codeInfoWidget.collapseAll() |
|
849 self.codeInfoWidget.blockSignals(block) |
|
850 self.__resizeCodeInfoColumns() |
|
851 |
|
852 def __codeInfoContextMenuRequested(self, coord): |
|
853 """ |
|
854 Private slot to show the context menu of the code info widget. |
|
855 |
|
856 @param coord position of the mouse pointer |
|
857 @type QPoint |
|
858 """ |
|
859 if self.disWidget.topLevelItemCount() > 0: |
|
860 # don't show context menu on empty list |
|
861 coord = self.codeInfoWidget.mapToGlobal(coord) |
|
862 self.__codeInfoMenu.popup(coord) |