We previously were leaking all over the place due to improper use of memory managemen...
[macgdbp.git] / Source / DebuggerWindowController.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2002 - 2007, Blue Static <http://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 #import "DebuggerWindowController.h"
18 #import "DebuggerConnection.h"
19 #import "NSXMLElementAdditions.h"
20 #import "AppDelegate.h"
21
22 @interface DebuggerWindowController (Private)
23
24 - (void)updateSourceViewer;
25
26 @end
27
28 @implementation DebuggerWindowController
29
30 /**
31 * Initializes the window controller and sets the connection
32 */
33 - (id)initWithConnection: (DebuggerConnection *)cnx
34 {
35 if (self = [super initWithWindowNibName: @"Debugger"])
36 {
37 _connection = cnx;
38 _expandedRegisters = [[NSMutableArray alloc] init];
39 }
40 return self;
41 }
42
43 /**
44 * Before the display get's comfortable, set up the NSTextView to scroll horizontally
45 */
46 - (void)awakeFromNib
47 {
48 // set up the scroller for the source viewer
49 [_sourceViewer setMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
50 [[_sourceViewer textContainer] setContainerSize: NSMakeSize(FLT_MAX, FLT_MAX)];
51 [[_sourceViewer textContainer] setWidthTracksTextView: NO];
52 [_sourceViewer setHorizontallyResizable: YES];
53 [_sourceViewerScroller setHasHorizontalScroller: YES];
54 [_sourceViewerScroller display];
55 }
56
57 /**
58 * Called when the window is going to be closed so we can clean up all of our stuff
59 */
60 - (void)windowWillClose: (NSNotification *)aNotification
61 {
62 [_connection windowDidClose];
63 }
64
65 /**
66 * Release object members
67 */
68 - (void)dealloc
69 {
70 [_expandedRegisters release];
71
72 [super dealloc];
73 }
74
75 /**
76 * Sets the status and clears any error message
77 */
78 - (void)setStatus: (NSString *)status
79 {
80 [_error setHidden: YES];
81 [_status setStringValue: status];
82 [[self window] setTitle: [NSString stringWithFormat: @"GDBp @ %@:%d/%@", [_connection remoteHost], [_connection port], [_connection session]]];
83
84 [_stepInButton setEnabled: NO];
85 [_stepOutButton setEnabled: NO];
86 [_stepOverButton setEnabled: NO];
87 [_runButton setEnabled: NO];
88
89 if ([_connection isConnected])
90 {
91 if ([status isEqualToString: @"Starting"])
92 {
93 [_stepInButton setEnabled: YES];
94 [_runButton setEnabled: YES];
95 }
96 }
97 }
98
99 /**
100 * Sets the status to be "Error" and then displays the error message
101 */
102 - (void)setError: (NSString *)error
103 {
104 [_error setStringValue: error];
105 [self setStatus: @"Error"];
106 [_error setHidden: NO];
107 }
108
109 /**
110 * Sets the root node element of the stacktrace
111 */
112 - (void)setStack: (NSArray *)stack
113 {
114 if (_stack != nil)
115 {
116 [_stack release];
117 }
118
119 _stack = stack;
120 [_stack retain];
121
122 if ([_stack count] > 1)
123 {
124 [_stepOutButton setEnabled: YES];
125 }
126 [_stepInButton setEnabled: YES];
127 [_stepOverButton setEnabled: YES];
128 [_runButton setEnabled: YES];
129
130 [self updateSourceViewer];
131 }
132
133 /**
134 * Sets the stack root element so that the NSOutlineView can display it
135 */
136 - (void)setRegister: (NSXMLDocument *)elm
137 {
138 /*
139 [_registerController willChangeValueForKey: @"rootElement.children"];
140 [_registerController unbind: @"contentArray"];
141 [_registerController bind: @"contentArray" toObject: elm withKeyPath: @"rootElement.children" options: nil];
142 [_registerController didChangeValueForKey: @"rootElement.children"];
143 */
144 // XXX: Doing anything short of this will cause bindings to crash spectacularly for no reason whatsoever, and
145 // in seemingly arbitrary places. The class that crashes is _NSKeyValueObservationInfoCreateByRemoving.
146 // http://boredzo.org/blog/archives/2006-01-29/have-you-seen-this-crash says that this means nothing is
147 // being observed, but I doubt that he was using an NSOutlineView which seems to be one f!cking piece of
148 // sh!t when used with NSTreeController. http://www.cocoadev.com/index.pl?NSTreeControllerBugOrDeveloperError
149 // was the inspiration for this fix (below) but the author says that inserting does not work too well, but
150 // that's okay for us as we just need to replace the entire thing.
151 [_registerController setContent: nil];
152 [_registerController setContent: [[elm rootElement] children]];
153
154 for (int i = 0; i < [_registerView numberOfRows]; i++)
155 {
156 int index = [_expandedRegisters indexOfObject: [[[_registerView itemAtRow: i] observedObject] variable]];
157 if (index != NSNotFound)
158 {
159 [_registerView expandItem: [_registerView itemAtRow: i]];
160 }
161 }
162 }
163
164 /**
165 * Forwards the message to run script execution to the connection
166 */
167 - (IBAction)run: (id)sender
168 {
169 [_connection run];
170 }
171
172 /**
173 * Forwards the message to "step in" to the connection
174 */
175 - (IBAction)stepIn: (id)sender
176 {
177 [_connection stepIn];
178 }
179
180 /**
181 * Forwards the message to "step out" to the connection
182 */
183 - (IBAction)stepOut: (id)sender
184 {
185 [_connection stepOut];
186 }
187
188 /**
189 * Forwards the message to "step over" to the connection
190 */
191 - (IBAction)stepOver: (id)sender
192 {
193 [_connection stepOver];
194 }
195
196 /**
197 * NSTableView delegate method that informs the controller that the stack selection did change and that
198 * we should update the source viewer
199 */
200 - (void)tableViewSelectionDidChange: (NSNotification *)notif
201 {
202 NSLog(@"selection changed");
203 [self updateSourceViewer];
204 }
205 /**
206 * Does the actual updating of the source viewer by reading in the file
207 */
208 - (void)updateSourceViewer
209 {
210 int selection = [_stackController selectionIndex];
211 if (selection == NSNotFound)
212 {
213 [_sourceViewer setString: @""];
214 return;
215 }
216
217 // get the filename and then set the text
218 NSString *filename = [[_stack objectAtIndex: selection] valueForKey: @"filename"];
219 filename = [[NSURL URLWithString: filename] path];
220 NSString *text = [NSString stringWithContentsOfFile: filename];
221 [_sourceViewer setString: text];
222
223 // go through the document until we find the NSRange for the line we want
224 int destination = [[[_stack objectAtIndex: selection] valueForKey: @"lineno"] intValue];
225 int rangeIndex = 0;
226 for (int line = 0; line < destination; line++)
227 {
228 rangeIndex = NSMaxRange([text lineRangeForRange: NSMakeRange(rangeIndex, 0)]);
229 }
230
231 // now get the true start/end markers for it
232 unsigned lineStart, lineEnd;
233 [text getLineStart: &lineStart end: NULL contentsEnd: &lineEnd forRange: NSMakeRange(rangeIndex - 1, 0)];
234 NSRange lineRange = NSMakeRange(lineStart, lineEnd - lineStart);
235
236 // colorize it so the user knows which line we're on in the stack
237 [[_sourceViewer textStorage] setAttributes: [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: [NSColor redColor], [NSColor yellowColor], nil]
238 forKeys: [NSArray arrayWithObjects: NSForegroundColorAttributeName, NSBackgroundColorAttributeName, nil]]
239 range: lineRange];
240 [_sourceViewer scrollRangeToVisible: [text lineRangeForRange: NSMakeRange(lineStart, lineEnd - lineStart)]];
241
242 // make sure the font stays Monaco
243 [_sourceViewer setFont: [NSFont fontWithName: @"Monaco" size: 10.0]];
244 }
245
246 /**
247 * Called whenver an item is expanded. This allows us to determine if we need to fetch deeper
248 */
249 - (void)outlineViewItemDidExpand: (NSNotification *)notif
250 {
251 // XXX: This very well may break because NSTreeController sends us a _NSArrayControllerTreeNode object
252 // which is presumably private, and thus this is not a reliable method for getting the object. But
253 // we damn well need it, so f!ck the rules and we're using it. <rdar://problem/5387001>
254 id notifObj = [[notif userInfo] objectForKey: @"NSObject"];
255 NSXMLElement *obj = [notifObj observedObject];
256
257 // we're not a leaf but have no children. this must be beyond our depth, so go make us deeper
258 if (![obj isLeaf] && [[obj children] count] < 1)
259 {
260 [_connection getProperty: [[obj attributeForName: @"fullname"] stringValue] forElement: notifObj];
261 }
262
263 [_expandedRegisters addObject: [obj variable]];
264 }
265
266 /**
267 * Called when an item was collapsed. This allows us to remove it from the list of expanded items
268 */
269 - (void)outlineViewItemDidCollapse: (NSNotification *)notif
270 {
271 [_expandedRegisters removeObject: [[[[notif userInfo] objectForKey: @"NSObject"] observedObject] variable]];
272 }
273
274 /**
275 * Updates the register view by reinserting a given node back into the outline view
276 */
277 - (void)addChildren: (NSArray *)children toNode: (id)node
278 {
279 // XXX: this may break like in outlineViewItemDidExpand: <rdar://problem/5387001>
280 NSIndexPath *masterPath = [node indexPath];
281 for (int i = 0; i < [children count]; i++)
282 {
283 [_registerController insertObject: [children objectAtIndex: i] atArrangedObjectIndexPath: [masterPath indexPathByAddingIndex: i]];
284 }
285
286 [_registerController rearrangeObjects];
287 }
288
289 @end