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/>.
26 from PyQt4
.QtCore
import Qt
27 from PyQt4
.QtGui
import ( QSizePolicy
, QFrame
, QTreeWidget
, QToolButton
,
28 QTreeWidgetItem
, QHeaderView
, QVBoxLayout
,
29 QLabel
, QWidget
, QAbstractItemView
, QMenu
,
30 QSpacerItem
, QHBoxLayout
, QPalette
, QCursor
)
31 from ui
.itemdelegates
import NoOutlineHeightDelegate
32 from utils
.pixmapcache
import PixmapCache
33 from utils
.globals import GlobalData
34 from utils
.settings
import Settings
38 class StackFrameItem( QTreeWidgetItem
):
39 " Single stack frame item data structure "
41 def __init__( self
, fileName
, lineNumber
, funcName
, frameNumber
):
43 shortened
= os
.path
.basename( fileName
) + ":" + str( lineNumber
)
44 full
= fileName
+ ":" + str( lineNumber
)
46 self
.__lineNumber
= lineNumber
47 QTreeWidgetItem
.__init
__( self
, [ "", shortened
, funcName
, fileName
] )
49 self
.__isCurrent
= False
50 self
.__frameNumber
= frameNumber
52 for index
in xrange( 4 ):
53 self
.setToolTip( index
, full
)
56 def setCurrent( self
, value
):
57 """ Mark the current stack frame with an icon if so """
58 self
.__isCurrent
= value
60 self
.setIcon( 0, PixmapCache().getIcon( 'currentframe.png' ) )
62 self
.setIcon( 0, PixmapCache().getIcon( 'empty.png' ) )
65 def getFrameNumber( self
):
66 " Provides the frame number "
67 return self
.__frameNumber
69 def getFilename( self
):
70 """ Provides the full project filename """
71 return str( self
.text( 3 ) )
73 def getLineNumber( self
):
74 " Provides the line number "
75 return self
.__lineNumber
77 def isCurrent( self
):
78 " True if the project is current "
79 return self
.__isCurrent
84 class StackViewer( QWidget
):
85 " Implements the stack viewer for a debugger "
87 def __init__( self
, debugger
, parent
= None ):
88 QWidget
.__init
__( self
, parent
)
90 self
.__debugger
= debugger
91 self
.currentStack
= None
93 self
.__createPopupMenu
()
96 if Settings().showStackViewer
== False:
97 self
.__onShowHide
( True )
100 def __createPopupMenu( self
):
101 " Creates the popup menu "
102 self
.__framesMenu
= QMenu()
103 self
.__setCurrentMenuItem
= self
.__framesMenu
.addAction(
104 "Set current (single click)", self
.__onSetCurrent
)
105 self
.__jumpMenuItem
= self
.__framesMenu
.addAction(
106 "Set current and jump to the source (double click)",
107 self
.__onSetCurrentAndJump
)
110 def __createLayout( self
):
111 " Creates the widget layout "
113 verticalLayout
= QVBoxLayout( self
)
114 verticalLayout
.setContentsMargins( 0, 0, 0, 0 )
115 verticalLayout
.setSpacing( 0 )
117 self
.headerFrame
= QFrame()
118 self
.headerFrame
.setFrameStyle( QFrame
.StyledPanel
)
119 self
.headerFrame
.setAutoFillBackground( True )
120 headerPalette
= self
.headerFrame
.palette()
121 headerBackground
= headerPalette
.color( QPalette
.Background
)
122 headerBackground
.setRgb( min( headerBackground
.red() + 30, 255 ),
123 min( headerBackground
.green() + 30, 255 ),
124 min( headerBackground
.blue() + 30, 255 ) )
125 headerPalette
.setColor( QPalette
.Background
, headerBackground
)
126 self
.headerFrame
.setPalette( headerPalette
)
127 self
.headerFrame
.setFixedHeight( 24 )
129 self
.__stackLabel
= QLabel( "Stack" )
131 expandingSpacer
= QSpacerItem( 10, 10, QSizePolicy
.Expanding
)
132 fixedSpacer
= QSpacerItem( 3, 3 )
134 self
.__showHideButton
= QToolButton()
135 self
.__showHideButton
.setAutoRaise( True )
136 self
.__showHideButton
.setIcon( PixmapCache().getIcon( 'less.png' ) )
137 self
.__showHideButton
.setFixedSize( 20, 20 )
138 self
.__showHideButton
.setToolTip( "Hide frames list" )
139 self
.__showHideButton
.setFocusPolicy( Qt
.NoFocus
)
140 self
.__showHideButton
.clicked
.connect( self
.__onShowHide
)
142 headerLayout
= QHBoxLayout()
143 headerLayout
.setContentsMargins( 0, 0, 0, 0 )
144 headerLayout
.addSpacerItem( fixedSpacer
)
145 headerLayout
.addWidget( self
.__stackLabel
)
146 headerLayout
.addSpacerItem( expandingSpacer
)
147 headerLayout
.addWidget( self
.__showHideButton
)
148 self
.headerFrame
.setLayout( headerLayout
)
150 self
.__framesList
= QTreeWidget( self
)
151 self
.__framesList
.setSortingEnabled( False )
152 # I might not need that because of two reasons:
153 # - the window has no focus
154 # - the window has custom current indicator
155 # self.__framesList.setAlternatingRowColors( True )
156 self
.__framesList
.setRootIsDecorated( False )
157 self
.__framesList
.setItemsExpandable( False )
158 self
.__framesList
.setUniformRowHeights( True )
159 self
.__framesList
.setSelectionMode( QAbstractItemView
.NoSelection
)
160 self
.__framesList
.setSelectionBehavior( QAbstractItemView
.SelectRows
)
161 self
.__framesList
.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
162 self
.__framesList
.setFocusPolicy( Qt
.NoFocus
)
163 self
.__framesList
.setContextMenuPolicy( Qt
.CustomContextMenu
)
165 self
.__framesList
.itemClicked
.connect( self
.__onFrameClicked
)
166 self
.__framesList
.itemDoubleClicked
.connect( self
.__onFrameDoubleClicked
)
167 self
.__framesList
.customContextMenuRequested
.connect( self
.__showContextMenu
)
169 self
.__framesList
.setHeaderLabels( [ "", "File:line", "Function",
172 verticalLayout
.addWidget( self
.headerFrame
)
173 verticalLayout
.addWidget( self
.__framesList
)
176 def __onShowHide( self
, startup
= False ):
177 " Triggered when show/hide button is clicked "
178 if startup
or self
.__framesList
.isVisible():
179 self
.__framesList
.setVisible( False )
180 self
.__showHideButton
.setIcon( PixmapCache().getIcon( 'more.png' ) )
181 self
.__showHideButton
.setToolTip( "Show frames list" )
183 self
.__minH
= self
.minimumHeight()
184 self
.__maxH
= self
.maximumHeight()
186 self
.setMinimumHeight( self
.headerFrame
.height() )
187 self
.setMaximumHeight( self
.headerFrame
.height() )
189 Settings().showStackViewer
= False
191 self
.__framesList
.setVisible( True )
192 self
.__showHideButton
.setIcon( PixmapCache().getIcon( 'less.png' ) )
193 self
.__showHideButton
.setToolTip( "Hide frames list" )
195 self
.setMinimumHeight( self
.__minH
)
196 self
.setMaximumHeight( self
.__maxH
)
198 Settings().showStackViewer
= True
202 " Clears the content "
203 self
.__framesList
.clear()
204 self
.currentStack
= None
205 self
.__stackLabel
.setText( "Stack" )
208 def __resizeColumns( self
):
209 " Resize the files list columns "
210 self
.__framesList
.header().setStretchLastSection( True )
211 self
.__framesList
.header().resizeSections(
212 QHeaderView
.ResizeToContents
)
213 self
.__framesList
.header().resizeSection( 0, 22 )
214 self
.__framesList
.header().setResizeMode( 0, QHeaderView
.Fixed
)
218 def populate( self
, stack
):
219 " Sets the new call stack and selects the first item in it "
222 self
.currentStack
= stack
223 self
.currentFrame
= 0
227 # This is when an exception comes
231 item
= StackFrameItem( s
[ 0 ], s
[ 1 ], funcName
, frameNumber
)
232 self
.__framesList
.addTopLevelItem( item
)
234 self
.__resizeColumns
()
235 self
.__framesList
.topLevelItem( 0 ).setCurrent( True )
236 self
.__stackLabel
.setText( "Stack (total: " +
237 str( len( stack
) ) + ")" )
240 def getFrameNumber( self
):
241 " Provides the current frame number "
242 return self
.currentFrame
244 def __onFrameClicked( self
, item
, column
):
245 " Triggered when a frame is clicked "
249 # Hide the current indicator
250 self
.__framesList
.topLevelItem( self
.currentFrame
).setCurrent( False )
252 # Show the new indicator
253 self
.currentFrame
= item
.getFrameNumber()
254 for index
in xrange( self
.__framesList
.topLevelItemCount() ):
255 item
= self
.__framesList
.topLevelItem( index
)
256 if item
.getFrameNumber() == self
.currentFrame
:
257 item
.setCurrent( True )
258 self
.__debugger
.remoteClientVariables( 1, self
.currentFrame
) # globals
259 self
.__debugger
.remoteClientVariables( 0, self
.currentFrame
) # locals
262 def __onFrameDoubleClicked( self
, item
, column
):
263 " Triggered when a frame is double clicked "
264 # The frame has been switched already because the double click
265 # signal always comes after the single click one
266 fileName
= item
.getFilename()
267 lineNumber
= item
.getLineNumber()
269 editorsManager
= GlobalData().mainWindow
.editorsManager()
270 editorsManager
.openFile( fileName
, lineNumber
)
271 editor
= editorsManager
.currentWidget().getEditor()
272 editor
.gotoLine( lineNumber
)
273 editorsManager
.currentWidget().setFocus()
276 def __showContextMenu( self
, coord
):
277 " Shows the frames list context menu "
278 self
.__contextItem
= self
.__framesList
.itemAt( coord
)
279 if self
.__contextItem
is not None:
280 self
.__setCurrentMenuItem
.setEnabled(
281 not self
.__contextItem
.isCurrent() )
282 self
.__framesMenu
.popup( QCursor
.pos() )
285 def __onSetCurrent( self
):
286 " Context menu item handler "
287 self
.__onFrameClicked
( self
.__contextItem
, 0 )
290 def __onSetCurrentAndJump( self
):
291 " Context menu item handler "
292 self
.__onFrameClicked
( self
.__contextItem
, 0 )
293 self
.__onFrameDoubleClicked
( self
.__contextItem
, 0 )
296 def switchControl( self
, isInIDE
):
297 " Switches the UI depending where the control flow is "
298 self
.__framesList
.setEnabled( isInIDE
)