source: trunk/macosx/MessageWindowController.m @ 9698

Last change on this file since 9698 was 9698, checked in by livings124, 13 years ago

reset the message index when clearing the log

  • Property svn:keywords set to Date Rev Author Id
File size: 16.3 KB
Line 
1/******************************************************************************
2 * $Id: MessageWindowController.m 9698 2009-12-10 04:44:21Z livings124 $
3 *
4 * Copyright (c) 2006-2009 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "MessageWindowController.h"
26#import "NSApplicationAdditions.h"
27#import "NSStringAdditions.h"
28#import <transmission.h>
29#import <utils.h>
30
31#define LEVEL_ERROR 0
32#define LEVEL_INFO  1
33#define LEVEL_DEBUG 2
34
35#define UPDATE_SECONDS  0.75
36
37@interface MessageWindowController (Private)
38
39- (void) resizeColumn;
40- (NSString *) stringForMessage: (NSDictionary *) message;
41
42@end
43
44@implementation MessageWindowController
45
46- (id) init
47{
48    return [super initWithWindowNibName: @"MessageWindow"];
49}
50
51- (void) dealloc
52{
53    [fTimer invalidate];
54    [fLock release];
55   
56    [fMessages release];
57    [fDisplayedMessages release];
58   
59    [fAttributes release];
60   
61    [super dealloc];
62}
63
64- (void) awakeFromNib
65{
66    NSWindow * window = [self window];
67    [window setFrameAutosaveName: @"MessageWindowFrame"];
68    [window setFrameUsingName: @"MessageWindowFrame"];
69   
70    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(resizeColumn)
71        name: @"NSTableViewColumnDidResizeNotification" object: fMessageTable];
72   
73    [window setContentBorderThickness: NSMinY([[fMessageTable enclosingScrollView] frame]) forEdge: NSMinYEdge];
74   
75    //initially sort peer table by date
76    if ([[fMessageTable sortDescriptors] count] == 0)
77        [fMessageTable setSortDescriptors: [NSArray arrayWithObject: [[fMessageTable tableColumnWithIdentifier: @"Date"]
78                                            sortDescriptorPrototype]]];
79   
80    [[self window] setTitle: NSLocalizedString(@"Message Log", "Message window -> title")];
81   
82    //set images and text for popup button items
83    [[fLevelButton itemAtIndex: LEVEL_ERROR] setTitle: NSLocalizedString(@"Error", "Message window -> level string")];
84    [[fLevelButton itemAtIndex: LEVEL_INFO] setTitle: NSLocalizedString(@"Info", "Message window -> level string")];
85    [[fLevelButton itemAtIndex: LEVEL_DEBUG] setTitle: NSLocalizedString(@"Debug", "Message window -> level string")];
86   
87    //set table column text
88    [[[fMessageTable tableColumnWithIdentifier: @"Date"] headerCell] setTitle: NSLocalizedString(@"Date",
89        "Message window -> table column")];
90    [[[fMessageTable tableColumnWithIdentifier: @"Name"] headerCell] setTitle: NSLocalizedString(@"Process",
91        "Message window -> table column")];
92    [[[fMessageTable tableColumnWithIdentifier: @"Message"] headerCell] setTitle: NSLocalizedString(@"Message",
93        "Message window -> table column")];
94   
95    //set and size buttons
96    [fSaveButton setTitle: [NSLocalizedString(@"Save", "Message window -> save button") stringByAppendingEllipsis]];
97    [fSaveButton sizeToFit];
98   
99    NSRect saveButtonFrame = [fSaveButton frame];
100    saveButtonFrame.size.width += 10.0;
101    [fSaveButton setFrame: saveButtonFrame];
102   
103    const CGFloat oldClearButtonWidth = [fClearButton frame].size.width;
104   
105    [fClearButton setTitle: NSLocalizedString(@"Clear", "Message window -> save button")];
106    [fClearButton sizeToFit];
107   
108    NSRect clearButtonFrame = [fClearButton frame];
109    clearButtonFrame.size.width = MAX(clearButtonFrame.size.width + 10.0, saveButtonFrame.size.width);
110    clearButtonFrame.origin.x -= clearButtonFrame.size.width - oldClearButtonWidth;
111    [fClearButton setFrame: clearButtonFrame];
112   
113    //select proper level in popup button
114    switch ([[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"])
115    {
116        case TR_MSG_ERR:
117            [fLevelButton selectItemAtIndex: LEVEL_ERROR];
118            break;
119        case TR_MSG_INF:
120            [fLevelButton selectItemAtIndex: LEVEL_INFO];
121            break;
122        case TR_MSG_DBG:
123            [fLevelButton selectItemAtIndex: LEVEL_DEBUG];
124            break;
125        default: //safety
126            [[NSUserDefaults standardUserDefaults] setInteger: TR_MSG_ERR forKey: @"MessageLevel"];
127            [fLevelButton selectItemAtIndex: LEVEL_ERROR];
128    }
129   
130    fMessages = [[NSMutableArray alloc] init];
131    fDisplayedMessages = [[NSMutableArray alloc] init];
132   
133    fLock = [[NSLock alloc] init];
134}
135
136- (void) windowDidBecomeKey: (NSNotification *) notification
137{
138    if (!fTimer)
139        fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_SECONDS target: self
140                    selector: @selector(updateLog:) userInfo: nil repeats: YES];
141    [self updateLog: nil];
142}
143
144- (void) windowWillClose: (id)sender
145{
146    [fTimer invalidate];
147    fTimer = nil;
148}
149
150- (void) updateLog: (NSTimer *) timer
151{
152    tr_msg_list * messages;
153    if ((messages = tr_getQueuedMessages()) == NULL)
154        return;
155   
156    [fLock lock];
157   
158    NSScroller * scroller = [[fMessageTable enclosingScrollView] verticalScroller];
159    const BOOL shouldScroll = fCurrentIndex == 0 || [scroller floatValue] == 1.0 || [scroller isHidden]
160                                || [scroller knobProportion] == 1.0;
161   
162    const NSInteger maxLevel = [[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"];
163    BOOL changed = NO;
164   
165    for (tr_msg_list * currentMessage = messages; currentMessage != NULL; currentMessage = currentMessage->next)
166    {
167        NSString * name = currentMessage->name != NULL ? [NSString stringWithUTF8String: currentMessage->name]
168                            : [[NSProcessInfo processInfo] processName];
169       
170        NSString * file = [[[NSString stringWithUTF8String: currentMessage->file] lastPathComponent] stringByAppendingFormat: @":%d",
171                            currentMessage->line];
172       
173        NSDictionary * message  = [NSDictionary dictionaryWithObjectsAndKeys:
174                                    [NSString stringWithUTF8String: currentMessage->message], @"Message",
175                                    [NSDate dateWithTimeIntervalSince1970: currentMessage->when], @"Date",
176                                    [NSNumber numberWithUnsignedInteger: fCurrentIndex++], @"Index", //more accurate when sorting by date
177                                    [NSNumber numberWithInteger: currentMessage->level], @"Level",
178                                    name, @"Name",
179                                    file, @"File", nil];
180       
181        [fMessages addObject: message];
182       
183        if (currentMessage->level <= maxLevel)
184        {
185            [fDisplayedMessages addObject: message];
186            changed = YES;
187        }
188    }
189   
190    if ([fMessages count] > TR_MAX_MSG_LOG)
191    {
192        NSIndexSet * removeIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fMessages count]-TR_MAX_MSG_LOG)];
193        NSArray * itemsToRemove = [fMessages objectsAtIndexes: removeIndexes];
194       
195        [fMessages removeObjectsAtIndexes: removeIndexes];
196        [fDisplayedMessages removeObjectsInArray: itemsToRemove];
197        changed = YES;
198    }
199   
200    if (changed)
201    {
202        [fDisplayedMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
203       
204        [fMessageTable reloadData];
205        if (shouldScroll)
206            [fMessageTable scrollRowToVisible: [fMessageTable numberOfRows]-1];
207    }
208   
209    [fLock unlock];
210   
211    tr_freeMessageList(messages);
212}
213
214- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
215{
216    return [fDisplayedMessages count];
217}
218
219- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
220{
221    NSString * ident = [column identifier];
222    NSDictionary * message = [fDisplayedMessages objectAtIndex: row];
223
224    if ([ident isEqualToString: @"Date"])
225        return [message objectForKey: @"Date"];
226    else if ([ident isEqualToString: @"Level"])
227    {
228        const NSInteger level = [[message objectForKey: @"Level"] integerValue];
229        switch (level)
230        {
231            case TR_MSG_ERR:
232                return [NSImage imageNamed: @"RedDot.png"];
233            case TR_MSG_INF:
234                return [NSImage imageNamed: @"YellowDot.png"];
235            case TR_MSG_DBG:
236                return [NSImage imageNamed: @"PurpleDot.png"];
237            default:
238                NSAssert1(NO, @"Unknown message log level: %d", level);
239                return nil;
240        }
241    }
242    else if ([ident isEqualToString: @"Name"])
243        return [message objectForKey: @"Name"];
244    else
245        return [message objectForKey: @"Message"];
246}
247
248#warning don't cut off end
249- (CGFloat) tableView: (NSTableView *) tableView heightOfRow: (NSInteger) row
250{
251    NSTableColumn * column = [tableView tableColumnWithIdentifier: @"Message"];
252   
253    if (!fAttributes)
254        fAttributes = [[[[column dataCell] attributedStringValue] attributesAtIndex: 0 effectiveRange: NULL] retain];
255   
256    NSString * message = [[fDisplayedMessages objectAtIndex: row] objectForKey: @"Message"];
257    const CGFloat count = floorf([message sizeWithAttributes: fAttributes].width / [column width]);
258    return [tableView rowHeight] * (count + 1.0);
259}
260
261- (void) tableView: (NSTableView *) tableView sortDescriptorsDidChange: (NSArray *) oldDescriptors
262{
263    [fDisplayedMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
264    [fMessageTable reloadData];
265}
266
267- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
268                tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
269{
270    NSDictionary * message = [fDisplayedMessages objectAtIndex: row];
271    return [message objectForKey: @"File"];
272}
273
274- (void) copy: (id) sender
275{
276    NSIndexSet * indexes = [fMessageTable selectedRowIndexes];
277    NSMutableArray * messageStrings = [NSMutableArray arrayWithCapacity: [indexes count]];
278   
279    for (NSDictionary * message in [fDisplayedMessages objectsAtIndexes: indexes])
280        [messageStrings addObject: [self stringForMessage: message]];
281   
282    NSString * messageString = [messageStrings componentsJoinedByString: @"\n"];
283   
284    NSPasteboard * pb = [NSPasteboard generalPasteboard];
285    if ([NSApp isOnSnowLeopardOrBetter])
286    {
287        [pb clearContents];
288        [pb writeObjects: [NSArray arrayWithObject: messageString]];
289    }
290    else
291    {
292        [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType] owner: nil];
293        [pb setString: messageString forType: NSStringPboardType];
294    }
295}
296
297- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
298{
299    SEL action = [menuItem action];
300   
301    if (action == @selector(copy:))
302        return [fMessageTable numberOfSelectedRows] > 0;
303   
304    return YES;
305}
306
307- (void) changeLevel: (id) sender
308{
309    NSInteger level;
310    switch ([fLevelButton indexOfSelectedItem])
311    {
312        case LEVEL_ERROR:
313            level = TR_MSG_ERR;
314            break;
315        case LEVEL_INFO:
316            level = TR_MSG_INF;
317            break;
318        case LEVEL_DEBUG:
319            level = TR_MSG_DBG;
320            break;
321        default:
322            NSAssert1(NO, @"Unknown message log level: %d", [fLevelButton indexOfSelectedItem]);
323    }
324   
325    if ([[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"] == level)
326        return;
327   
328    [fLock lock];
329   
330    [[NSUserDefaults standardUserDefaults] setInteger: level forKey: @"MessageLevel"];
331   
332    if (level == TR_MSG_DBG) //all messages at this level
333        [fDisplayedMessages setArray: fMessages];
334    else
335    {
336        [fDisplayedMessages removeAllObjects];
337        for (NSDictionary * message in fMessages)
338            if ([[message objectForKey: @"Level"] integerValue] <= level)
339                [fDisplayedMessages addObject: message];
340    }
341   
342    [fDisplayedMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
343   
344    [fMessageTable reloadData];
345   
346    if ([fDisplayedMessages count] > 0)
347    {
348        [fMessageTable deselectAll: self];
349        [fMessageTable scrollRowToVisible: [fMessageTable numberOfRows]-1];
350    }
351   
352    [fLock unlock];
353}
354
355- (void) clearLog: (id) sender
356{
357    [fLock lock];
358   
359    [fMessages removeAllObjects];
360    [fDisplayedMessages removeAllObjects];
361    [fMessageTable reloadData];
362    fCurrentIndex = 0;
363   
364    [fLock unlock];
365}
366
367- (void) writeToFile: (id) sender
368{
369    //make the array sorted by date
370    NSSortDescriptor * descriptor = [[[NSSortDescriptor alloc] initWithKey: @"Index" ascending: YES] autorelease];
371    NSArray * descriptors = [[NSArray alloc] initWithObjects: descriptor, nil];
372    NSArray * sortedMessages = [[fDisplayedMessages sortedArrayUsingDescriptors: descriptors] retain];
373    [descriptors release];
374   
375    NSSavePanel * panel = [NSSavePanel savePanel];
376    [panel setRequiredFileType: @"txt"];
377    [panel setCanSelectHiddenExtension: YES];
378   
379    [panel beginSheetForDirectory: nil file: NSLocalizedString(@"untitled", "Save log panel -> default file name")
380            modalForWindow: [self window] modalDelegate: self
381            didEndSelector: @selector(writeToFileSheetClosed:returnCode:contextInfo:) contextInfo: sortedMessages];
382}
383
384- (void) writeToFileSheetClosed: (NSSavePanel *) panel returnCode: (NSInteger) code contextInfo: (NSArray *) messages
385{
386    if (code == NSOKButton)
387    {
388        //create the text to output
389        NSMutableArray * messageStrings = [NSMutableArray arrayWithCapacity: [messages count]];
390        for (NSDictionary * message in messages)
391            [messageStrings addObject: [self stringForMessage: message]];
392   
393        NSString * fileString = [messageStrings componentsJoinedByString: @"\n"];
394       
395        if (![fileString writeToFile: [panel filename] atomically: YES encoding: NSUTF8StringEncoding error: nil])
396        {
397            NSAlert * alert = [[NSAlert alloc] init];
398            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Save log alert panel -> button")];
399            [alert setMessageText: NSLocalizedString(@"Log Could Not Be Saved", "Save log alert panel -> title")];
400            [alert setInformativeText: [NSString stringWithFormat:
401                    NSLocalizedString(@"There was a problem creating the file \"%@\".",
402                    "Save log alert panel -> message"), [[panel filename] lastPathComponent]]];
403            [alert setAlertStyle: NSWarningAlertStyle];
404           
405            [alert runModal];
406            [alert release];
407        }
408    }
409   
410    [messages release];
411}
412
413@end
414
415@implementation MessageWindowController (Private)
416
417- (void) resizeColumn
418{
419    [fMessageTable noteHeightOfRowsWithIndexesChanged: [NSIndexSet indexSetWithIndexesInRange:
420                    NSMakeRange(0, [fMessageTable numberOfRows])]];
421}
422
423- (NSString *) stringForMessage: (NSDictionary *) message
424{
425    NSString * levelString;
426    const NSInteger level = [[message objectForKey: @"Level"] integerValue];
427    switch (level)
428    {
429        case TR_MSG_ERR:
430            levelString = NSLocalizedString(@"Error", "Message window -> level");
431            break;
432        case TR_MSG_INF:
433            levelString = NSLocalizedString(@"Info", "Message window -> level");
434            break;
435        case TR_MSG_DBG:
436            levelString = NSLocalizedString(@"Debug", "Message window -> level");
437            break;
438        default:
439            NSAssert1(NO, @"Unknown message log level: %d", level);
440    }
441   
442    return [NSString stringWithFormat: @"%@ %@ [%@] %@: %@", [message objectForKey: @"Date"],
443            [message objectForKey: @"File"], levelString,
444            [message objectForKey: @"Name"], [message objectForKey: @"Message"], nil];
445}
446
447@end
Note: See TracBrowser for help on using the repository browser.