1/* GSMemoryPanel.m -*-objc-*- 2 3 A GNUstep panel for tracking memory leaks. 4 5 Copyright (C) 2000, 2002 Free Software Foundation, Inc. 6 7 Author: Nicola Pero <nicola@brainstorm.co.uk> 8 Date: 2000, 2002 9 10 This file is part of the GNUstep GUI Library. 11 12 This library is free software; you can redistribute it and/or 13 modify it under the terms of the GNU Lesser General Public 14 License as published by the Free Software Foundation; either 15 version 2 of the License, or (at your option) any later version. 16 17 This library is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public 23 License along with this library; see the file COPYING.LIB. 24 If not, see <http://www.gnu.org/licenses/> or write to the 25 Free Software Foundation, 51 Franklin Street, Fifth Floor, 26 Boston, MA 02110-1301, USA. 27*/ 28 29#import <Foundation/NSArray.h> 30#import <Foundation/NSDebug.h> 31#import <Foundation/NSString.h> 32#import <Foundation/NSValue.h> 33#import "AppKit/NSButton.h" 34#import "AppKit/NSScrollView.h" 35#import "AppKit/NSTableColumn.h" 36#import "AppKit/NSTableView.h" 37#import "GNUstepGUI/GSMemoryPanel.h" 38#import "GNUstepGUI/GSHbox.h" 39#import "GNUstepGUI/GSVbox.h" 40 41enum { 42 OrderByClassName, 43 OrderByCount, 44 OrderByTotal, 45 OrderByPeak 46}; 47 48static inline NSComparisonResult 49invertComparison (NSComparisonResult comparison) 50{ 51 /* invert comparison */ 52 if (comparison == NSOrderedAscending) 53 { 54 comparison = NSOrderedDescending; 55 } 56 else if (comparison == NSOrderedDescending) 57 { 58 comparison = NSOrderedAscending; 59 } 60 return comparison; 61} 62 63/* 64 * Internal private class used in reordering entries. 65 * 66 */ 67@interface GSMemoryPanelEntry : NSObject 68{ 69 NSString *string; 70 NSNumber *count; 71 NSNumber *total; 72 NSNumber *peak; 73} 74- (id) initWithString: (NSString *)aString 75 count: (int)aCount 76 total: (int)aTotal 77 peak: (int)aPeak; 78- (NSString *) string; 79- (NSNumber *) count; 80- (NSNumber *) total; 81- (NSNumber *) peak; 82- (NSComparisonResult) compareByTotal: (GSMemoryPanelEntry *)aEntry; 83- (NSComparisonResult) compareByCount: (GSMemoryPanelEntry *)aEntry; 84- (NSComparisonResult) compareByPeak: (GSMemoryPanelEntry *)aEntry; 85- (NSComparisonResult) compareByClassName: (GSMemoryPanelEntry *)aEntry; 86@end 87 88@implementation GSMemoryPanelEntry 89 90- (id) initWithString: (NSString *)aString 91 count: (int)aCount 92 total: (int)aTotal 93 peak: (int)aPeak 94{ 95 ASSIGN (string, aString); 96 ASSIGN (count, [NSNumber numberWithInt: aCount]); 97 ASSIGN (total, [NSNumber numberWithInt: aTotal]); 98 ASSIGN (peak, [NSNumber numberWithInt: aPeak]); 99 return self; 100} 101 102- (void) dealloc 103{ 104 RELEASE (string); 105 RELEASE (count); 106 RELEASE (total); 107 RELEASE (peak); 108 [super dealloc]; 109} 110 111- (NSString *) string 112{ 113 return string; 114} 115 116- (NSNumber *) count 117{ 118 return count; 119} 120 121- (NSNumber *) total 122{ 123 return total; 124} 125 126- (NSNumber *) peak 127{ 128 return peak; 129} 130 131- (NSComparisonResult) compareByCount: (GSMemoryPanelEntry *)aEntry 132{ 133 NSComparisonResult comparison = [count compare: aEntry->count]; 134 135 return invertComparison (comparison); 136} 137 138- (NSComparisonResult) compareByTotal: (GSMemoryPanelEntry *)aEntry 139{ 140 NSComparisonResult comparison = [total compare: aEntry->total]; 141 142 return invertComparison (comparison); 143} 144 145- (NSComparisonResult) compareByPeak: (GSMemoryPanelEntry *)aEntry 146{ 147 NSComparisonResult comparison = [peak compare: aEntry->peak]; 148 149 return invertComparison (comparison); 150} 151 152- (NSComparisonResult) compareByClassName: (GSMemoryPanelEntry *)aEntry 153{ 154 return [string compare: aEntry->string]; 155} 156 157@end 158 159/* 160 * The Memory Panel code 161 */ 162 163static GSMemoryPanel *sharedGSMemoryPanel = nil; 164 165@implementation GSMemoryPanel 166+ (id) sharedMemoryPanel 167{ 168 if (sharedGSMemoryPanel == nil) 169 { 170 sharedGSMemoryPanel = [GSMemoryPanel new]; 171 } 172 173 return sharedGSMemoryPanel; 174} 175 176+ (void) update: (id)sender 177{ 178 [[self sharedMemoryPanel] update: sender]; 179} 180 181- (id) init 182{ 183 NSRect winFrame; 184 NSTableColumn *classColumn; 185 NSTableColumn *countColumn; 186 NSTableColumn *totalColumn; 187 NSTableColumn *peakColumn; 188 NSScrollView *scrollView; 189 GSVbox *vbox; 190 GSHbox *hbox; 191 NSButton *button; 192 193 /* Activate debugging of allocation. */ 194 GSDebugAllocationActive (YES); 195 196 hbox = [GSHbox new]; 197 [hbox setDefaultMinXMargin: 5]; 198 [hbox setBorder: 5]; 199 [hbox setAutoresizingMask: NSViewWidthSizable]; 200 201 /* Button updating the table. */ 202 button = [NSButton new]; 203 [button setBordered: YES]; 204 [button setButtonType: NSMomentaryPushButton]; 205 [button setTitle: @"Update"]; 206 [button setImagePosition: NSNoImage]; 207 [button setTarget: self]; 208 [button setAction: @selector(update:)]; 209 [button setAutoresizingMask: NSViewMaxXMargin]; 210 [button sizeToFit]; 211 [button setTag: 1]; 212 213 [hbox addView: button]; 214 RELEASE (button); 215 216 /* Button taking snapshot of the table. */ 217 button = [NSButton new]; 218 [button setBordered: YES]; 219 [button setButtonType: NSMomentaryPushButton]; 220 [button setTitle: @"Snapshot"]; 221 [button setImagePosition: NSNoImage]; 222 [button setTarget: self]; 223 [button setAction: @selector(snapshot:)]; 224 [button setAutoresizingMask: NSViewMinXMargin]; 225 [button sizeToFit]; 226 [button setTag: 2]; 227 228 [hbox addView: button]; 229 RELEASE (button); 230 231 classColumn = [[NSTableColumn alloc] initWithIdentifier: @"Class"]; 232 [classColumn setEditable: NO]; 233 [[classColumn headerCell] setStringValue: @"Class Name"]; 234 [classColumn setMinWidth: 200]; 235 236 countColumn = [[NSTableColumn alloc] initWithIdentifier: @"Count"]; 237 [countColumn setEditable: NO]; 238 [[countColumn headerCell] setStringValue: @"Current"]; 239 [countColumn setMinWidth: 50]; 240 241 totalColumn = [[NSTableColumn alloc] initWithIdentifier: @"Total"]; 242 [totalColumn setEditable: NO]; 243 [[totalColumn headerCell] setStringValue: @"Total"]; 244 [totalColumn setMinWidth: 50]; 245 246 peakColumn = [[NSTableColumn alloc] initWithIdentifier: @"Peak"]; 247 [peakColumn setEditable: NO]; 248 [[peakColumn headerCell] setStringValue: @"Peak"]; 249 [peakColumn setMinWidth: 50]; 250 251 table = [[NSTableView alloc] initWithFrame: NSMakeRect (0, 0, 300, 300)]; 252 [table addTableColumn: classColumn]; 253 RELEASE (classColumn); 254 [table addTableColumn: countColumn]; 255 RELEASE (countColumn); 256 [table addTableColumn: totalColumn]; 257 RELEASE (totalColumn); 258 [table addTableColumn: peakColumn]; 259 RELEASE (peakColumn); 260 [table setDataSource: self]; 261 [table setDelegate: self]; 262 [table setDoubleAction: @selector (reorder:)]; 263 264 scrollView = [[NSScrollView alloc] 265 initWithFrame: NSMakeRect (0, 0, 350, 300)]; 266 [scrollView setDocumentView: table]; 267 [scrollView setHasHorizontalScroller: YES]; 268 [scrollView setHasVerticalScroller: YES]; 269 [scrollView setBorderType: NSBezelBorder]; 270 [scrollView setAutoresizingMask: (NSViewWidthSizable | NSViewHeightSizable)]; 271 [table sizeToFit]; 272 RELEASE (table); 273 274 vbox = [GSVbox new]; 275 [vbox setDefaultMinYMargin: 5]; 276 [vbox setBorder: 5]; 277 [vbox addView: hbox enablingYResizing: NO]; 278 RELEASE (hbox); 279 [vbox addView: scrollView]; 280 RELEASE (scrollView); 281 282 /* FIXME - should actually autosave the memory panel position and frame ! */ 283 winFrame.size = [vbox frame].size; 284 winFrame.origin = NSMakePoint (100, 200); 285 286 self = [super initWithContentRect: winFrame 287 styleMask: (NSTitledWindowMask 288 | NSClosableWindowMask 289 | NSMiniaturizableWindowMask 290 | NSResizableWindowMask) 291 backing: NSBackingStoreBuffered 292 defer: NO]; 293 if (nil == self) 294 return nil; 295 296 array = [NSMutableArray new]; 297 orderingBy = @selector(compareByCount:); 298 299 [self setReleasedWhenClosed: NO]; 300 [self setContentView: vbox]; 301 RELEASE (vbox); 302 [self setTitle: @"Memory Panel"]; 303 304 return self; 305} 306 307- (void) dealloc 308{ 309 RELEASE(array); 310 [super dealloc]; 311} 312 313- (NSInteger) numberOfRowsInTableView: (NSTableView *)aTableView 314{ 315 return [array count]; 316} 317 318- (id) tableView: (NSTableView *)aTableView 319 objectValueForTableColumn: (NSTableColumn *)aTableColumn 320 row:(NSInteger)rowIndex 321{ 322 GSMemoryPanelEntry *entry = [array objectAtIndex: rowIndex]; 323 id identifier = [aTableColumn identifier]; 324 325 if ([identifier isEqual: @"Class"]) 326 { 327 return [entry string]; 328 } 329 else if ([identifier isEqual: @"Count"]) 330 { 331 return [entry count]; 332 } 333 else if ([identifier isEqual: @"Total"]) 334 { 335 return [entry total]; 336 } 337 else if ([identifier isEqual: @"Peak"]) 338 { 339 return [entry peak]; 340 } 341 342 NSLog (@"Hi, I am a bug in your table view"); 343 344 return @""; 345} 346 347- (void) snapshot: (id)sender 348{ 349 GSMemoryPanel *snapshot = [GSMemoryPanel new]; 350 351 [snapshot setTitle: 352 [NSString stringWithFormat: @"Memory Snapshot at %@", [NSDate date]]]; 353 [[[snapshot contentView] viewWithTag: 1] removeFromSuperview]; 354 [[[snapshot contentView] viewWithTag: 2] removeFromSuperview]; 355 [snapshot setReleasedWhenClosed: YES]; 356 [snapshot makeKeyAndOrderFront: self]; 357 [snapshot update: self]; 358} 359 360- (void) update: (id)sender 361{ 362 Class *classList = GSDebugAllocationClassList (); 363 Class *pointer; 364 GSMemoryPanelEntry *entry; 365 int i, count, total, peak; 366 NSString *className; 367 368 pointer = classList; 369 i = 0; 370 [array removeAllObjects]; 371 while (pointer[i] != NULL) 372 { 373 className = NSStringFromClass (pointer[i]); 374 count = GSDebugAllocationCount (pointer[i]); 375 total = GSDebugAllocationTotal (pointer[i]); 376 peak = GSDebugAllocationPeak (pointer[i]); 377 378 /* Insert into array */ 379 entry = [[GSMemoryPanelEntry alloc] 380 initWithString: className 381 count: count 382 total: total 383 peak: peak]; 384 [array addObject: entry]; 385 RELEASE (entry); 386 i++; 387 } 388 NSZoneFree(NSDefaultMallocZone(), classList); 389 390 [array sortUsingSelector: orderingBy]; 391 392 [table reloadData]; 393} 394 395- (void) reorder: (id)sender 396{ 397 int selectedColumn = [table clickedColumn]; 398 NSArray *tableColumns = [table tableColumns]; 399 id identifier; 400 SEL newOrderingBy = @selector(compareByCount:); 401 402 if (selectedColumn == -1) 403 { 404 return; 405 } 406 407 identifier 408 = [(NSTableColumn*)[tableColumns objectAtIndex: selectedColumn] identifier]; 409 410 if ([identifier isEqual: @"Class"]) 411 { 412 newOrderingBy = @selector(compareByClassName:); 413 } 414 else if ([identifier isEqual: @"Count"]) 415 { 416 newOrderingBy = @selector(compareByCount:); 417 } 418 else if ([identifier isEqual: @"Total"]) 419 { 420 newOrderingBy = @selector(compareByTotal:); 421 } 422 else if ([identifier isEqual: @"Peak"]) 423 { 424 newOrderingBy = @selector(compareByPeak:); 425 } 426 427 if (newOrderingBy == orderingBy) 428 { 429 return; 430 } 431 else 432 { 433 orderingBy = newOrderingBy; 434 [array sortUsingSelector: orderingBy]; 435 [table reloadData]; 436 } 437} 438 439@end 440 441@implementation NSApplication (memoryPanel) 442 443- (void) orderFrontSharedMemoryPanel: (id)sender 444{ 445 GSMemoryPanel *memoryPanel; 446 447 memoryPanel = [GSMemoryPanel sharedMemoryPanel]; 448 [memoryPanel update: self]; 449 [memoryPanel orderFront: self]; 450} 451 452@end 453