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/>.
23 " Break points viewer "
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
)
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
()
66 def setModel( self
, model
):
67 " Sets the breakpoint 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 )
83 def layoutDisplay( self
):
84 " Performs the layout operation "
85 self
.__resizeColumns
()
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 )
97 self
.model().sort( self
.header().sortIndicatorSection(),
98 self
.header().sortIndicatorOrder() )
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 ):
111 if not index
.isValid():
115 flags
= QItemSelectionModel
.SelectionFlags(
116 QItemSelectionModel
.ClearAndSelect | QItemSelectionModel
.Rows
)
118 flags
= QItemSelectionModel
.SelectionFlags(
119 QItemSelectionModel
.Deselect | QItemSelectionModel
.Rows
)
120 self
.selectionModel().select( index
, flags
)
123 def __createPopupMenus( self
):
124 " Generate the popup menu "
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
)
155 def __showContextMenu( self
, coord
):
156 " Shows the context menu "
157 index
= self
.currentIndex()
158 if not index
.isValid():
160 sindex
= self
.toSourceIndex( index
)
161 if not sindex
.isValid():
163 bp
= self
.__model
.getBreakPointByIndex( sindex
)
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() )
181 def __doubleClicked( self
, index
):
182 " Handles the double clicked signal "
183 if not index
.isValid():
186 sindex
= self
.toSourceIndex( index
)
187 if not sindex
.isValid():
191 bpoint
= self
.__model
.getBreakPointByIndex( sindex
)
192 fileName
= bpoint
.getAbsoluteFileName()
193 line
= bpoint
.getLineNumber()
194 self
.jumpToCode( fileName
, line
)
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()
206 def __editBreak( self
):
207 " Handle the edit breakpoint context menu entry "
208 index
= self
.currentIndex()
210 self
.__editBreakpoint
( index
)
213 def __editBreakpoint( self
, index
):
214 " Edits a breakpoint "
215 sindex
= self
.toSourceIndex( index
)
217 bp
= self
.__model
.getBreakPointByIndex( sindex
)
221 dlg
= BreakpointEditDialog( bp
)
222 if dlg
.exec_() == QDialog
.Accepted
:
223 newBpoint
= dlg
.getData()
226 self
.__model
.setBreakPointByIndex( sindex
, newBpoint
)
230 def __setBpEnabled( self
, index
, enabled
):
231 " Sets the enabled status of a breakpoint "
232 sindex
= self
.toSourceIndex( index
)
234 self
.__model
.setBreakPointEnabledByIndex( sindex
, enabled
)
237 def enableBreak( self
):
238 " Handles the enable breakpoint context menu entry "
239 index
= self
.currentIndex()
240 self
.__setBpEnabled
( index
, True )
241 self
.__resizeColumns
()
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
()
255 def disableBreak( self
):
256 " Handles the disable breakpoint context menu entry "
257 index
= self
.currentIndex()
258 self
.__setBpEnabled
( index
, False )
259 self
.__resizeColumns
()
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
()
273 def deleteBreak( self
):
274 " Handles the delete breakpoint context menu entry "
275 index
= self
.currentIndex()
276 sindex
= self
.toSourceIndex( index
)
278 self
.__model
.deleteBreakPointByIndex( sindex
)
281 def deleteAllBreaks( self
):
282 " Handles the delete all breakpoints context menu entry "
283 self
.__model
.deleteAll()
286 def __showSource( self
):
287 " Handles the goto context menu entry "
288 index
= self
.currentIndex()
289 self
.__doubleClicked
( index
)
292 def highlightBreakpoint( self
, fn
, lineno
):
293 " Handles the clientLine signal "
294 sindex
= self
.__model
.getBreakPointIndex( fn
, lineno
)
298 index
= self
.__fromSourceIndex
( sindex
)
300 self
.__clearSelection
()
301 self
.__setRowSelected
( index
, True )
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
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 ] )
316 self
.emit( SIGNAL( 'selectionChanged' ), None )
317 QTreeView
.selectionChanged( self
, selected
, deselected
)
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
)
342 def setFocus( self
):
343 " Sets the widget focus "
344 self
.bpointsList
.setFocus()
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 )
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
)
465 " Clears the content "
467 self
.__updateBreakpointsLabel
()
468 self
.__currentItem
= None
471 def __updateBreakpointsLabel( self
):
472 " Updates the breakpoints header label "
474 disableCount
= self
.bpointsList
.model().sourceModel().getCounts()
475 total
= enableCount
+ disableCount
477 self
.__breakpointLabel
.setText( "Breakpoints (total: " +
480 self
.__breakpointLabel
.setText( "Breakpoints" )
483 def __onProjectChanged( self
, what
):
484 " Triggered when a project is changed "
485 if what
!= CodimensionProject
.CompleteProject
:
489 model
= self
.bpointsList
.model().sourceModel()
490 project
= GlobalData().project
491 if project
.isLoaded():
492 bpoints
= project
.breakpoints
494 bpoints
= Settings().breakpoints
497 newBpoint
= Breakpoint()
499 if not newBpoint
.deserialize( bp
):
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
)
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." )
518 def __onProjectAboutToUnload( self
):
519 " Triggered before the project is unloaded "
520 self
.__serializeBreakpoints
()
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() )
531 Settings().breakpoints
= model
.serialize()
534 def __onSelectionChanged( self
, index
):
535 " Triggered when the current item is changed "
537 self
.__currentItem
= None
539 srcModel
= self
.bpointsList
.model().sourceModel()
540 sindex
= self
.bpointsList
.toSourceIndex( index
)
541 self
.__currentItem
= srcModel
.getBreakPointByIndex( sindex
)
542 self
.__updateButtons
()
545 def __updateButtons( self
):
546 " Updates the buttons status "
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 )
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 )
569 def __onEnableDisable( self
):
570 " Triggered when a breakpoint should be enabled/disabled "
571 if self
.__currentItem
is None:
574 if self
.__currentItem
.isEnabled():
575 self
.bpointsList
.disableBreak()
577 self
.bpointsList
.enableBreak()
580 def __onEdit( self
):
581 " Triggered when a breakpoint should be edited "
582 if self
.__currentItem
is None:
585 dlg
= BreakpointEditDialog( self
.__currentItem
)
586 if dlg
.exec_() == QDialog
.Accepted
:
587 newBpoint
= dlg
.getData()
588 if newBpoint
== self
.__currentItem
:
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()
597 def __onJumpToCode( self
):
598 " Triggered when should jump to source "
599 if self
.__currentItem
is None:
601 self
.bpointsList
.jumpToCode( self
.__currentItem
.getAbsoluteFileName(),
602 self
.__currentItem
.getLineNumber() )
605 def __onEnableAll( self
):
606 " Triggered when all the breakpoints should be enabled "
607 self
.bpointsList
.enableAllBreaks()
610 def __onDisableAll( self
):
611 " Triggered when all the breakpoints should be disabled "
612 self
.bpointsList
.disableAllBreaks()
616 " Triggered when a breakpoint should be deleted "
617 if self
.__currentItem
is None:
619 self
.bpointsList
.deleteBreak()
622 def __onDelAll( self
):
623 " Triggered when all the breakpoints should be deleted "
624 self
.bpointsList
.deleteAllBreaks()
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
()