Bug fix: closing the file
[codimension.git] / src / debugger / bpointviewer.py
blob033aa9924426660d910b95003b9529f2b3d53f62
2 # -*- coding: utf-8 -*-
4 # codimension - graphics python two-way code editor and analyzer
5 # Copyright (C) 2010-2012 Sergey Satskiy <sergey.satskiy@gmail.com>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 # $Id$
23 " Break points viewer "
25 import logging
26 from PyQt4.QtCore import Qt, SIGNAL, QSize
27 from PyQt4.QtGui import ( QSizePolicy, QFrame, QTreeView,
28 QHeaderView, QVBoxLayout, QSortFilterProxyModel,
29 QLabel, QWidget, QAbstractItemView, QMenu,
30 QSpacerItem, QHBoxLayout, QPalette, QCursor,
31 QItemSelectionModel, QDialog, QToolBar, QAction )
32 from ui.itemdelegates import NoOutlineHeightDelegate
33 from utils.pixmapcache import PixmapCache
34 from utils.globals import GlobalData
35 from utils.settings import Settings
36 from utils.project import CodimensionProject
37 from editbreakpoint import BreakpointEditDialog
38 from breakpoint import Breakpoint
39 from bputils import getBreakpointLines
42 class BreakPointView( QTreeView ):
43 " Breakpoint viewer widget "
45 def __init__( self, parent, bpointsModel ):
46 QTreeView.__init__( self, parent )
48 self.__model = None
49 self.setModel( bpointsModel )
51 self.setItemsExpandable( False )
52 self.setRootIsDecorated( False )
53 self.setAlternatingRowColors( True )
54 self.setUniformRowHeights( True )
55 self.setSelectionMode( QAbstractItemView.SingleSelection )
56 self.setSelectionBehavior( QAbstractItemView.SelectRows )
57 self.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
59 self.setContextMenuPolicy( Qt.CustomContextMenu )
60 self.customContextMenuRequested.connect( self.__showContextMenu )
61 self.doubleClicked.connect( self.__doubleClicked )
63 self.__createPopupMenus()
64 return
66 def setModel( self, model ):
67 " Sets the breakpoint model "
68 self.__model = model
70 self.sortingModel = QSortFilterProxyModel()
71 self.sortingModel.setSourceModel( self.__model )
72 QTreeView.setModel( self, self.sortingModel )
74 header = self.header()
75 header.setSortIndicator( 0, Qt.AscendingOrder )
76 header.setSortIndicatorShown( True )
77 header.setClickable( True )
79 self.setSortingEnabled( True )
80 self.layoutDisplay()
81 return
83 def layoutDisplay( self ):
84 " Performs the layout operation "
85 self.__resizeColumns()
86 self.__resort()
87 return
89 def __resizeColumns( self ):
90 " Resizes the view when items get added, edited or deleted "
91 self.header().resizeSections( QHeaderView.ResizeToContents )
92 self.header().setStretchLastSection( True )
93 return
95 def __resort( self ):
96 " Resorts the tree "
97 self.model().sort( self.header().sortIndicatorSection(),
98 self.header().sortIndicatorOrder() )
99 return
101 def toSourceIndex( self, index ):
102 " Converts an index to a source index "
103 return self.sortingModel.mapToSource( index )
105 def __fromSourceIndex( self, sindex ):
106 " Convert a source index to an index "
107 return self.sortingModel.mapFromSource( sindex )
109 def __setRowSelected( self, index, selected = True ):
110 " Selects a row "
111 if not index.isValid():
112 return
114 if selected:
115 flags = QItemSelectionModel.SelectionFlags(
116 QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows )
117 else:
118 flags = QItemSelectionModel.SelectionFlags(
119 QItemSelectionModel.Deselect | QItemSelectionModel.Rows )
120 self.selectionModel().select( index, flags )
121 return
123 def __createPopupMenus( self ):
124 " Generate the popup menu "
125 self.menu = QMenu()
126 self.__editAct = self.menu.addAction(
127 PixmapCache().getIcon( 'bpprops.png' ),
128 "Edit...", self.__editBreak )
129 self.__jumpToCodeAct = self.menu.addAction(
130 PixmapCache().getIcon( 'gotoline.png' ),
131 "Jump to code", self.__showSource )
132 self.menu.addSeparator()
133 self.__enableAct = self.menu.addAction(
134 PixmapCache().getIcon( 'bpenable.png' ),
135 "Enable", self.enableBreak )
136 self.__enableAllAct = self.menu.addAction(
137 PixmapCache().getIcon( 'bpenableall.png' ),
138 "Enable all", self.enableAllBreaks )
139 self.menu.addSeparator()
140 self.__disableAct = self.menu.addAction(
141 PixmapCache().getIcon( 'bpdisable.png' ),
142 "Disable", self.disableBreak )
143 self.__disableAllAct = self.menu.addAction(
144 PixmapCache().getIcon( 'bpdisableall.png' ),
145 "Disable all", self.disableAllBreaks )
146 self.menu.addSeparator()
147 self.__delAct = self.menu.addAction(
148 PixmapCache().getIcon( 'bpdel.png' ),
149 "Delete", self.deleteBreak )
150 self.__delAllAct = self.menu.addAction(
151 PixmapCache().getIcon( 'bpdelall.png' ),
152 "Delete all", self.deleteAllBreaks )
153 return
155 def __showContextMenu( self, coord ):
156 " Shows the context menu "
157 index = self.currentIndex()
158 if not index.isValid():
159 return
160 sindex = self.toSourceIndex( index )
161 if not sindex.isValid():
162 return
163 bp = self.__model.getBreakPointByIndex( sindex )
164 if not bp:
165 return
167 enableCount, disableCount = self.__model.getCounts()
169 self.__editAct.setEnabled( True )
170 self.__enableAct.setEnabled( not bp.isEnabled() )
171 self.__disableAct.setEnabled( bp.isEnabled() )
172 self.__jumpToCodeAct.setEnabled( True )
173 self.__delAct.setEnabled( True )
174 self.__enableAllAct.setEnabled( disableCount > 0 )
175 self.__disableAllAct.setEnabled( enableCount > 0 )
176 self.__delAllAct.setEnabled( enableCount + disableCount > 0 )
178 self.menu.popup( QCursor.pos() )
179 return
181 def __doubleClicked( self, index ):
182 " Handles the double clicked signal "
183 if not index.isValid():
184 return
186 sindex = self.toSourceIndex( index )
187 if not sindex.isValid():
188 return
190 # Jump to the code
191 bpoint = self.__model.getBreakPointByIndex( sindex )
192 fileName = bpoint.getAbsoluteFileName()
193 line = bpoint.getLineNumber()
194 self.jumpToCode( fileName, line )
195 return
197 def jumpToCode( self, fileName, line ):
198 " Jumps to the source code "
199 editorsManager = GlobalData().mainWindow.editorsManager()
200 editorsManager.openFile( fileName, line )
201 editor = editorsManager.currentWidget().getEditor()
202 editor.gotoLine( line )
203 editorsManager.currentWidget().setFocus()
204 return
206 def __editBreak( self ):
207 " Handle the edit breakpoint context menu entry "
208 index = self.currentIndex()
209 if index.isValid():
210 self.__editBreakpoint( index )
211 return
213 def __editBreakpoint( self, index ):
214 " Edits a breakpoint "
215 sindex = self.toSourceIndex( index )
216 if sindex.isValid():
217 bp = self.__model.getBreakPointByIndex( sindex )
218 if not bp:
219 return
221 dlg = BreakpointEditDialog( bp )
222 if dlg.exec_() == QDialog.Accepted:
223 newBpoint = dlg.getData()
224 if newBpoint == bp:
225 return
226 self.__model.setBreakPointByIndex( sindex, newBpoint )
227 self.layoutDisplay()
228 return
230 def __setBpEnabled( self, index, enabled ):
231 " Sets the enabled status of a breakpoint "
232 sindex = self.toSourceIndex( index )
233 if sindex.isValid():
234 self.__model.setBreakPointEnabledByIndex( sindex, enabled )
235 return
237 def enableBreak( self ):
238 " Handles the enable breakpoint context menu entry "
239 index = self.currentIndex()
240 self.__setBpEnabled( index, True )
241 self.__resizeColumns()
242 self.__resort()
243 return
245 def enableAllBreaks( self ):
246 " Handles the enable all breakpoints context menu entry "
247 index = self.model().index( 0, 0 )
248 while index.isValid():
249 self.__setBpEnabled( index, True )
250 index = self.indexBelow( index )
251 self.__resizeColumns()
252 self.__resort()
253 return
255 def disableBreak( self ):
256 " Handles the disable breakpoint context menu entry "
257 index = self.currentIndex()
258 self.__setBpEnabled( index, False )
259 self.__resizeColumns()
260 self.__resort()
261 return
263 def disableAllBreaks( self ):
264 " Handles the disable all breakpoints context menu entry "
265 index = self.model().index( 0, 0 )
266 while index.isValid():
267 self.__setBpEnabled( index, False )
268 index = self.indexBelow( index )
269 self.__resizeColumns()
270 self.__resort()
271 return
273 def deleteBreak( self ):
274 " Handles the delete breakpoint context menu entry "
275 index = self.currentIndex()
276 sindex = self.toSourceIndex( index )
277 if sindex.isValid():
278 self.__model.deleteBreakPointByIndex( sindex )
279 return
281 def deleteAllBreaks( self ):
282 " Handles the delete all breakpoints context menu entry "
283 self.__model.deleteAll()
284 return
286 def __showSource( self ):
287 " Handles the goto context menu entry "
288 index = self.currentIndex()
289 self.__doubleClicked( index )
290 return
292 def highlightBreakpoint( self, fn, lineno ):
293 " Handles the clientLine signal "
294 sindex = self.__model.getBreakPointIndex( fn, lineno )
295 if sindex.isValid():
296 return
298 index = self.__fromSourceIndex( sindex )
299 if index.isValid():
300 self.__clearSelection()
301 self.__setRowSelected( index, True )
302 return
304 def __getSelectedItemsCount( self ):
305 " Provides the count of items selected "
306 count = len( self.selectedIndexes() ) / ( self.__model.columnCount() - 1 )
307 # column count is 1 greater than selectable
308 return count
310 def selectionChanged( self, selected, deselected ):
311 " The slot is called when the selection has changed "
312 if selected.indexes():
313 self.emit( SIGNAL( 'selectionChanged' ),
314 selected.indexes()[ 0 ] )
315 else:
316 self.emit( SIGNAL( 'selectionChanged' ), None )
317 QTreeView.selectionChanged( self, selected, deselected )
318 return
322 class BreakPointViewer( QWidget ):
323 " Implements the break point viewer for a debugger "
325 def __init__( self, parent, bpointsModel ):
326 QWidget.__init__( self, parent )
328 self.__currentItem = None
329 self.__createLayout( bpointsModel )
331 GlobalData().project.projectChanged.connect( self.__onProjectChanged )
332 self.connect( GlobalData().project, SIGNAL( 'projectAboutToUnload' ),
333 self.__onProjectAboutToUnload )
334 self.connect( self.bpointsList,
335 SIGNAL( "selectionChanged" ),
336 self.__onSelectionChanged )
337 self.connect( bpointsModel,
338 SIGNAL( 'BreakpoinsChanged' ),
339 self.__onModelChanged )
340 return
342 def setFocus( self ):
343 " Sets the widget focus "
344 self.bpointsList.setFocus()
345 return
347 def __createLayout( self, bpointsModel ):
348 " Creates the widget layout "
350 verticalLayout = QVBoxLayout( self )
351 verticalLayout.setContentsMargins( 0, 0, 0, 0 )
352 verticalLayout.setSpacing( 0 )
354 self.headerFrame = QFrame()
355 self.headerFrame.setFrameStyle( QFrame.StyledPanel )
356 self.headerFrame.setAutoFillBackground( True )
357 headerPalette = self.headerFrame.palette()
358 headerBackground = headerPalette.color( QPalette.Background )
359 headerBackground.setRgb( min( headerBackground.red() + 30, 255 ),
360 min( headerBackground.green() + 30, 255 ),
361 min( headerBackground.blue() + 30, 255 ) )
362 headerPalette.setColor( QPalette.Background, headerBackground )
363 self.headerFrame.setPalette( headerPalette )
364 self.headerFrame.setFixedHeight( 24 )
366 self.__breakpointLabel = QLabel( "Breakpoints" )
368 fixedSpacer = QSpacerItem( 3, 3 )
370 headerLayout = QHBoxLayout()
371 headerLayout.setContentsMargins( 0, 0, 0, 0 )
372 headerLayout.addSpacerItem( fixedSpacer )
373 headerLayout.addWidget( self.__breakpointLabel )
374 self.headerFrame.setLayout( headerLayout )
376 self.bpointsList = BreakPointView( self, bpointsModel )
378 self.__editButton = QAction(
379 PixmapCache().getIcon( 'bpprops.png' ),
380 "Edit breakpoint properties", self )
381 self.__editButton.triggered.connect( self.__onEdit )
382 self.__editButton.setEnabled( False )
384 self.__jumpToCodeButton = QAction(
385 PixmapCache().getIcon( 'gotoline.png' ),
386 "Jump to the code", self )
387 self.__jumpToCodeButton.triggered.connect( self.__onJumpToCode )
388 self.__jumpToCodeButton.setEnabled( False )
390 self.__enableButton = QAction(
391 PixmapCache().getIcon( 'bpenable.png' ),
392 "Enable selected breakpoint", self )
393 self.__enableButton.triggered.connect( self.__onEnableDisable )
394 self.__enableButton.setEnabled( False )
396 self.__disableButton = QAction(
397 PixmapCache().getIcon( 'bpdisable.png' ),
398 "Disable selected breakpoint", self )
399 self.__disableButton.triggered.connect( self.__onEnableDisable )
400 self.__disableButton.setEnabled( False )
402 self.__enableAllButton = QAction(
403 PixmapCache().getIcon( 'bpenableall.png' ),
404 "Enable all the breakpoint", self )
405 self.__enableAllButton.triggered.connect( self.__onEnableAll )
406 self.__enableAllButton.setEnabled( False )
408 self.__disableAllButton = QAction(
409 PixmapCache().getIcon( 'bpdisableall.png' ),
410 "Disable all the breakpoint", self )
411 self.__disableAllButton.triggered.connect( self.__onDisableAll )
412 self.__disableAllButton.setEnabled( False )
414 self.__delButton = QAction(
415 PixmapCache().getIcon( 'delitem.png' ),
416 "Delete selected breakpoint", self )
417 self.__delButton.triggered.connect( self.__onDel )
418 self.__delButton.setEnabled( False )
420 self.__delAllButton = QAction(
421 PixmapCache().getIcon( 'bpdelall.png' ),
422 "Delete all the breakpoint", self )
423 self.__delAllButton.triggered.connect( self.__onDelAll )
424 self.__delAllButton.setEnabled( False )
427 # Toolbar
428 self.toolbar = QToolBar()
429 self.toolbar.setOrientation( Qt.Horizontal )
430 self.toolbar.setMovable( False )
431 self.toolbar.setAllowedAreas( Qt.TopToolBarArea )
432 self.toolbar.setIconSize( QSize( 16, 16 ) )
433 self.toolbar.setFixedHeight( 28 )
434 self.toolbar.setContentsMargins( 0, 0, 0, 0 )
435 self.toolbar.addAction( self.__editButton )
436 self.toolbar.addAction( self.__jumpToCodeButton )
437 fixedSpacer2 = QWidget()
438 fixedSpacer2.setFixedWidth( 5 )
439 self.toolbar.addWidget( fixedSpacer2 )
440 self.toolbar.addAction( self.__enableButton )
441 self.toolbar.addAction( self.__enableAllButton )
442 fixedSpacer3 = QWidget()
443 fixedSpacer3.setFixedWidth( 5 )
444 self.toolbar.addWidget( fixedSpacer3 )
445 self.toolbar.addAction( self.__disableButton )
446 self.toolbar.addAction( self.__disableAllButton )
447 expandingSpacer = QWidget()
448 expandingSpacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )
449 fixedSpacer4 = QWidget()
450 fixedSpacer4.setFixedWidth( 5 )
451 self.toolbar.addWidget( fixedSpacer4 )
452 self.toolbar.addWidget( expandingSpacer )
453 self.toolbar.addAction( self.__delButton )
454 fixedSpacer5 = QWidget()
455 fixedSpacer5.setFixedWidth( 5 )
456 self.toolbar.addWidget( fixedSpacer5 )
457 self.toolbar.addAction( self.__delAllButton )
459 verticalLayout.addWidget( self.headerFrame )
460 verticalLayout.addWidget( self.toolbar )
461 verticalLayout.addWidget( self.bpointsList )
462 return
464 def clear( self ):
465 " Clears the content "
466 self.__onDelAll()
467 self.__updateBreakpointsLabel()
468 self.__currentItem = None
469 return
471 def __updateBreakpointsLabel( self ):
472 " Updates the breakpoints header label "
473 enableCount, \
474 disableCount = self.bpointsList.model().sourceModel().getCounts()
475 total = enableCount + disableCount
476 if total > 0:
477 self.__breakpointLabel.setText( "Breakpoints (total: " +
478 str( total ) + ")" )
479 else:
480 self.__breakpointLabel.setText( "Breakpoints" )
481 return
483 def __onProjectChanged( self, what ):
484 " Triggered when a project is changed "
485 if what != CodimensionProject.CompleteProject:
486 return
488 self.clear()
489 model = self.bpointsList.model().sourceModel()
490 project = GlobalData().project
491 if project.isLoaded():
492 bpoints = project.breakpoints
493 else:
494 bpoints = Settings().breakpoints
496 for bp in bpoints:
497 newBpoint = Breakpoint()
498 try:
499 if not newBpoint.deserialize( bp ):
500 # Non valid
501 continue
502 except:
503 continue
504 # Need to check if it still points to a breakable line
505 line = newBpoint.getLineNumber()
506 fileName = newBpoint.getAbsoluteFileName()
507 breakableLines = getBreakpointLines( fileName, None, True )
508 if breakableLines is not None and line in breakableLines:
509 model.addBreakpoint( newBpoint )
510 else:
511 logging.warning( "Breakpoint at " + fileName + ":" +
512 str( line ) + " does not point to a breakable "
513 "line anymore (the file is invalid or was "
514 "modified outside of the "
515 "IDE etc.). The breakpoint is deleted." )
516 return
518 def __onProjectAboutToUnload( self ):
519 " Triggered before the project is unloaded "
520 self.__serializeBreakpoints()
521 return
523 def __serializeBreakpoints( self ):
524 " Saves the breakpoints into a file "
525 model = self.bpointsList.model().sourceModel()
527 project = GlobalData().project
528 if project.isLoaded():
529 project.setBreakpoints( model.serialize() )
530 else:
531 Settings().breakpoints = model.serialize()
532 return
534 def __onSelectionChanged( self, index ):
535 " Triggered when the current item is changed "
536 if index is None:
537 self.__currentItem = None
538 else:
539 srcModel = self.bpointsList.model().sourceModel()
540 sindex = self.bpointsList.toSourceIndex( index )
541 self.__currentItem = srcModel.getBreakPointByIndex( sindex )
542 self.__updateButtons()
543 return
545 def __updateButtons( self ):
546 " Updates the buttons status "
548 enableCount, \
549 disableCount = self.bpointsList.model().sourceModel().getCounts()
551 if self.__currentItem is None:
552 self.__editButton.setEnabled( False )
553 self.__enableButton.setEnabled( False )
554 self.__disableButton.setEnabled( False )
555 self.__jumpToCodeButton.setEnabled( False )
556 self.__delButton.setEnabled( False )
557 else:
558 self.__editButton.setEnabled( True )
559 self.__enableButton.setEnabled( not self.__currentItem.isEnabled() )
560 self.__disableButton.setEnabled( self.__currentItem.isEnabled() )
561 self.__jumpToCodeButton.setEnabled( True )
562 self.__delButton.setEnabled( True )
564 self.__enableAllButton.setEnabled( disableCount > 0 )
565 self.__disableAllButton.setEnabled( enableCount > 0 )
566 self.__delAllButton.setEnabled( enableCount + disableCount > 0 )
567 return
569 def __onEnableDisable( self ):
570 " Triggered when a breakpoint should be enabled/disabled "
571 if self.__currentItem is None:
572 return
574 if self.__currentItem.isEnabled():
575 self.bpointsList.disableBreak()
576 else:
577 self.bpointsList.enableBreak()
578 return
580 def __onEdit( self ):
581 " Triggered when a breakpoint should be edited "
582 if self.__currentItem is None:
583 return
585 dlg = BreakpointEditDialog( self.__currentItem )
586 if dlg.exec_() == QDialog.Accepted:
587 newBpoint = dlg.getData()
588 if newBpoint == self.__currentItem:
589 return
590 model = self.bpointsList.model().sourceModel()
591 index = model.getBreakPointIndex( self.__currentItem.getAbsoluteFileName(),
592 self.__currentItem.getLineNumber() )
593 model.setBreakPointByIndex( index, newBpoint )
594 self.bpointsList.layoutDisplay()
595 return
597 def __onJumpToCode( self ):
598 " Triggered when should jump to source "
599 if self.__currentItem is None:
600 return
601 self.bpointsList.jumpToCode( self.__currentItem.getAbsoluteFileName(),
602 self.__currentItem.getLineNumber() )
603 return
605 def __onEnableAll( self ):
606 " Triggered when all the breakpoints should be enabled "
607 self.bpointsList.enableAllBreaks()
608 return
610 def __onDisableAll( self ):
611 " Triggered when all the breakpoints should be disabled "
612 self.bpointsList.disableAllBreaks()
613 return
615 def __onDel( self ):
616 " Triggered when a breakpoint should be deleted "
617 if self.__currentItem is None:
618 return
619 self.bpointsList.deleteBreak()
620 return
622 def __onDelAll( self ):
623 " Triggered when all the breakpoints should be deleted "
624 self.bpointsList.deleteAllBreaks()
625 return
627 def __onModelChanged( self ):
628 " Triggered when something has changed in any of the breakpoints "
629 self.__updateBreakpointsLabel()
630 self.__updateButtons()
631 self.bpointsList.layoutDisplay()
633 self.__serializeBreakpoints()
634 return