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