Bug fix: closing the file
[codimension.git] / src / debugger / stackviewer.py
blob786bdb684d7f9b7b75b68ddbc540240c04707623
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 " Stack viewer "
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
35 import os.path
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 )
54 return
56 def setCurrent( self, value ):
57 """ Mark the current stack frame with an icon if so """
58 self.__isCurrent = value
59 if value:
60 self.setIcon( 0, PixmapCache().getIcon( 'currentframe.png' ) )
61 else:
62 self.setIcon( 0, PixmapCache().getIcon( 'empty.png' ) )
63 return
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
92 self.currentFrame = 0
93 self.__createPopupMenu()
94 self.__createLayout()
96 if Settings().showStackViewer == False:
97 self.__onShowHide( True )
98 return
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 )
108 return
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",
170 "Full path" ] )
172 verticalLayout.addWidget( self.headerFrame )
173 verticalLayout.addWidget( self.__framesList )
174 return
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
190 else:
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
199 return
201 def clear( self ):
202 " Clears the content "
203 self.__framesList.clear()
204 self.currentStack = None
205 self.__stackLabel.setText( "Stack" )
206 return
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 )
215 return
218 def populate( self, stack ):
219 " Sets the new call stack and selects the first item in it "
220 self.clear()
222 self.currentStack = stack
223 self.currentFrame = 0
224 frameNumber = 0
225 for s in stack:
226 if len( s ) == 2:
227 # This is when an exception comes
228 funcName = ""
229 else:
230 funcName = s[ 2 ]
231 item = StackFrameItem( s[ 0 ], s[ 1 ], funcName, frameNumber )
232 self.__framesList.addTopLevelItem( item )
233 frameNumber += 1
234 self.__resizeColumns()
235 self.__framesList.topLevelItem( 0 ).setCurrent( True )
236 self.__stackLabel.setText( "Stack (total: " +
237 str( len( stack ) ) + ")" )
238 return
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 "
246 if item.isCurrent():
247 return
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
260 return
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()
274 return
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() )
283 return
285 def __onSetCurrent( self ):
286 " Context menu item handler "
287 self.__onFrameClicked( self.__contextItem, 0 )
288 return
290 def __onSetCurrentAndJump( self ):
291 " Context menu item handler "
292 self.__onFrameClicked( self.__contextItem, 0 )
293 self.__onFrameDoubleClicked( self.__contextItem, 0 )
294 return
296 def switchControl( self, isInIDE ):
297 " Switches the UI depending where the control flow is "
298 self.__framesList.setEnabled( isInIDE )
299 return