1# -*- coding: utf-8 -*- 2# 3# Copyright © Spyder Project Contributors 4# based loosley on pylintgui.py by Pierre Raybaut 5# Licensed under the terms of the MIT License 6# (see spyder/__init__.py for details) 7 8"""Breakpoint widget""" 9 10# pylint: disable=C0103 11# pylint: disable=R0903 12# pylint: disable=R0911 13# pylint: disable=R0201 14 15# Standard library imports 16import os.path as osp 17import sys 18 19# Third party imports 20from qtpy import API 21from qtpy.compat import to_qvariant 22from qtpy.QtCore import (QAbstractTableModel, QModelIndex, QTextCodec, Qt, 23 Signal) 24from qtpy.QtWidgets import (QItemDelegate, QMenu, QTableView, QVBoxLayout, 25 QWidget) 26 27# Local imports 28from spyder.config.base import get_translation 29from spyder.config.main import CONF 30from spyder.utils.qthelpers import add_actions, create_action 31 32# This is needed for testing this module as a stand alone script 33try: 34 _ = get_translation("breakpoints", "spyder_breakpoints") 35except KeyError as error: 36 import gettext 37 _ = gettext.gettext 38 39 40locale_codec = QTextCodec.codecForLocale() 41 42 43class BreakpointTableModel(QAbstractTableModel): 44 """ 45 Table model for breakpoints dictionary 46 47 """ 48 def __init__(self, parent, data): 49 QAbstractTableModel.__init__(self, parent) 50 if data is None: 51 data = {} 52 self._data = None 53 self.breakpoints = None 54 self.set_data(data) 55 56 def set_data(self, data): 57 """Set model data""" 58 self._data = data 59 keys = list(data.keys()) 60 self.breakpoints = [] 61 for key in keys: 62 bp_list = data[key] 63 if bp_list: 64 for item in data[key]: 65 self.breakpoints.append((key, item[0], item[1], "")) 66 self.reset() 67 68 def rowCount(self, qindex=QModelIndex()): 69 """Array row number""" 70 return len(self.breakpoints) 71 72 def columnCount(self, qindex=QModelIndex()): 73 """Array column count""" 74 return 4 75 76 def sort(self, column, order=Qt.DescendingOrder): 77 """Overriding sort method""" 78 if column == 0: 79 self.breakpoints.sort( 80 key=lambda breakpoint: breakpoint[1]) 81 self.breakpoints.sort( 82 key=lambda breakpoint: osp.basename(breakpoint[0])) 83 elif column == 1: 84 pass 85 elif column == 2: 86 pass 87 elif column == 3: 88 pass 89 self.reset() 90 91 def headerData(self, section, orientation, role=Qt.DisplayRole): 92 """Overriding method headerData""" 93 if role != Qt.DisplayRole: 94 return to_qvariant() 95 i_column = int(section) 96 if orientation == Qt.Horizontal: 97 headers = (_("File"), _("Line"), _("Condition"), "") 98 return to_qvariant( headers[i_column] ) 99 else: 100 return to_qvariant() 101 102 def get_value(self, index): 103 """Return current value""" 104 return self.breakpoints[index.row()][index.column()] 105 106 def data(self, index, role=Qt.DisplayRole): 107 """Return data at table index""" 108 if not index.isValid(): 109 return to_qvariant() 110 if role == Qt.DisplayRole: 111 if index.column() == 0: 112 value = osp.basename(self.get_value(index)) 113 return to_qvariant(value) 114 else: 115 value = self.get_value(index) 116 return to_qvariant(value) 117 elif role == Qt.TextAlignmentRole: 118 return to_qvariant(int(Qt.AlignLeft|Qt.AlignVCenter)) 119 elif role == Qt.ToolTipRole: 120 if index.column() == 0: 121 value = self.get_value(index) 122 return to_qvariant(value) 123 else: 124 return to_qvariant() 125 126 def reset(self): 127 self.beginResetModel() 128 self.endResetModel() 129 130 131class BreakpointDelegate(QItemDelegate): 132 def __init__(self, parent=None): 133 QItemDelegate.__init__(self, parent) 134 135 136class BreakpointTableView(QTableView): 137 edit_goto = Signal(str, int, str) 138 clear_breakpoint = Signal(str, int) 139 clear_all_breakpoints = Signal() 140 set_or_edit_conditional_breakpoint = Signal() 141 142 def __init__(self, parent, data): 143 QTableView.__init__(self, parent) 144 self.model = BreakpointTableModel(self, data) 145 self.setModel(self.model) 146 self.delegate = BreakpointDelegate(self) 147 self.setItemDelegate(self.delegate) 148 149 self.setup_table() 150 151 def setup_table(self): 152 """Setup table""" 153 self.horizontalHeader().setStretchLastSection(True) 154 self.adjust_columns() 155 self.columnAt(0) 156 # Sorting columns 157 self.setSortingEnabled(False) 158 self.sortByColumn(0, Qt.DescendingOrder) 159 160 def adjust_columns(self): 161 """Resize three first columns to contents""" 162 for col in range(3): 163 self.resizeColumnToContents(col) 164 165 def mouseDoubleClickEvent(self, event): 166 """Reimplement Qt method""" 167 index_clicked = self.indexAt(event.pos()) 168 if self.model.breakpoints: 169 filename = self.model.breakpoints[index_clicked.row()][0] 170 line_number_str = self.model.breakpoints[index_clicked.row()][1] 171 self.edit_goto.emit(filename, int(line_number_str), '') 172 if index_clicked.column()==2: 173 self.set_or_edit_conditional_breakpoint.emit() 174 175 def contextMenuEvent(self, event): 176 index_clicked = self.indexAt(event.pos()) 177 actions = [] 178 self.popup_menu = QMenu(self) 179 clear_all_breakpoints_action = create_action(self, 180 _("Clear breakpoints in all files"), 181 triggered=lambda: self.clear_all_breakpoints.emit()) 182 actions.append(clear_all_breakpoints_action) 183 if self.model.breakpoints: 184 filename = self.model.breakpoints[index_clicked.row()][0] 185 lineno = int(self.model.breakpoints[index_clicked.row()][1]) 186 # QAction.triggered works differently for PySide and PyQt 187 if not API == 'pyside': 188 clear_slot = lambda _checked, filename=filename, lineno=lineno: \ 189 self.clear_breakpoint.emit(filename, lineno) 190 edit_slot = lambda _checked, filename=filename, lineno=lineno: \ 191 (self.edit_goto.emit(filename, lineno, ''), 192 self.set_or_edit_conditional_breakpoint.emit()) 193 else: 194 clear_slot = lambda filename=filename, lineno=lineno: \ 195 self.clear_breakpoint.emit(filename, lineno) 196 edit_slot = lambda filename=filename, lineno=lineno: \ 197 (self.edit_goto.emit(filename, lineno, ''), 198 self.set_or_edit_conditional_breakpoint.emit()) 199 200 clear_breakpoint_action = create_action(self, 201 _("Clear this breakpoint"), 202 triggered=clear_slot) 203 actions.insert(0,clear_breakpoint_action) 204 205 edit_breakpoint_action = create_action(self, 206 _("Edit this breakpoint"), 207 triggered=edit_slot) 208 actions.append(edit_breakpoint_action) 209 add_actions(self.popup_menu, actions) 210 self.popup_menu.popup(event.globalPos()) 211 event.accept() 212 213 214class BreakpointWidget(QWidget): 215 """ 216 Breakpoint widget 217 """ 218 VERSION = '1.0.0' 219 clear_all_breakpoints = Signal() 220 set_or_edit_conditional_breakpoint = Signal() 221 clear_breakpoint = Signal(str, int) 222 edit_goto = Signal(str, int, str) 223 224 def __init__(self, parent): 225 QWidget.__init__(self, parent) 226 227 self.setWindowTitle("Breakpoints") 228 self.dictwidget = BreakpointTableView(self, 229 self._load_all_breakpoints()) 230 layout = QVBoxLayout() 231 layout.addWidget(self.dictwidget) 232 self.setLayout(layout) 233 self.dictwidget.clear_all_breakpoints.connect( 234 lambda: self.clear_all_breakpoints.emit()) 235 self.dictwidget.clear_breakpoint.connect( 236 lambda s1, lino: self.clear_breakpoint.emit(s1, lino)) 237 self.dictwidget.edit_goto.connect( 238 lambda s1, lino, s2: self.edit_goto.emit(s1, lino, s2)) 239 self.dictwidget.set_or_edit_conditional_breakpoint.connect( 240 lambda: self.set_or_edit_conditional_breakpoint.emit()) 241 242 def _load_all_breakpoints(self): 243 bp_dict = CONF.get('run', 'breakpoints', {}) 244 for filename in list(bp_dict.keys()): 245 if not osp.isfile(filename): 246 bp_dict.pop(filename) 247 return bp_dict 248 249 def get_data(self): 250 pass 251 252 def set_data(self): 253 bp_dict = self._load_all_breakpoints() 254 self.dictwidget.model.set_data(bp_dict) 255 self.dictwidget.adjust_columns() 256 self.dictwidget.sortByColumn(0, Qt.DescendingOrder) 257 258 259#============================================================================== 260# Tests 261#============================================================================== 262def test(): 263 """Run breakpoint widget test""" 264 from spyder.utils.qthelpers import qapplication 265 app = qapplication() 266 widget = BreakpointWidget(None) 267 widget.show() 268 sys.exit(app.exec_()) 269 270 271if __name__ == '__main__': 272 test() 273