source: trunk/macosx/MessageWindowController.m @ 9701

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

calculate the message attributes when opening the window, to avoid constant checking for nil

  • Property svn:keywords set to Date Rev Author Id
File size: 16.3 KB
Line 
1/******************************************************************************
2 * $Id: MessageWindowController.m 9701 2009-12-10 05:16:30Z 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    fAttributes = [[[[[fMessageTable tableColumnWithIdentifier: @"Message"] dataCell] attributedStringValue]
114                    attributesAtIndex: 0 effectiveRange: NULL] retain];
115   
116    //select proper level in popup button
117    switch ([[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"])
118    {
119        case TR_MSG_ERR:
120            [fLevelButton selectItemAtIndex: LEVEL_ERROR];
121            break;
122        case TR_MSG_INF:
123            [fLevelButton selectItemAtIndex: LEVEL_INFO];
124            break;
125        case TR_MSG_DBG:
126            [fLevelButton selectItemAtIndex: LEVEL_DEBUG];
127            break;
128        default: //safety
129            [[NSUserDefaults standardUserDefaults] setInteger: TR_MSG_ERR forKey: @"MessageLevel"];
130            [fLevelButton selectItemAtIndex: LEVEL_ERROR];
131    }
132   
133    fMessages = [[NSMutableArray alloc] init];
134    fDisplayedMessages = [[NSMutableArray alloc] init];
135   
136    fLock = [[NSLock alloc] init];
137}
138
139- (void) windowDidBecomeKey: (NSNotification *) notification
140{
141    if (!fTimer)
142        fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_SECONDS target: self
143                    selector: @selector(updateLog:) userInfo: nil repeats: YES];
144    [self updateLog: nil];
145}
146
147- (void) windowWillClose: (id)sender
148{
149    [fTimer invalidate];
150    fTimer = nil;
151}
152
153- (void) updateLog: (NSTimer *) timer
154{
155    tr_msg_list * messages;
156    if ((messages = tr_getQueuedMessages()) == NULL)
157        return;
158   
159    [fLock lock];
160   
161    NSUInteger currentCount = [fMessages count];
162   
163    NSScroller * scroller = [[fMessageTable enclosingScrollView] verticalScroller];
164    const BOOL shouldScroll = currentCount == 0 || [scroller floatValue] == 1.0 || [scroller isHidden]
165                                || [scroller knobProportion] == 1.0;
166   
167    const NSInteger maxLevel = [[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"];
168    BOOL changed = NO;
169   
170    for (tr_msg_list * currentMessage = messages; currentMessage != NULL; currentMessage = currentMessage->next)
171    {
172        NSString * name = currentMessage->name != NULL ? [NSString stringWithUTF8String: currentMessage->name]
173                            : [[NSProcessInfo processInfo] processName];
174       
175        NSString * file = [[[NSString stringWithUTF8String: currentMessage->file] lastPathComponent] stringByAppendingFormat: @":%d",
176                            currentMessage->line];
177       
178        NSDictionary * message  = [NSDictionary dictionaryWithObjectsAndKeys:
179                                    [NSString stringWithUTF8String: currentMessage->message], @"Message",
180                                    [NSDate dateWithTimeIntervalSince1970: currentMessage->when], @"Date",
181                                    [NSNumber numberWithUnsignedInteger: currentCount++], @"Index", //more accurate when sorting by date
182                                    [NSNumber numberWithInteger: currentMessage->level], @"Level",
183                                    name, @"Name",
184                                    file, @"File", nil];
185       
186        [fMessages addObject: message];
187       
188        if (currentMessage->level <= maxLevel)
189        {
190            [fDisplayedMessages addObject: message];
191            changed = YES;
192        }
193    }
194   
195    if (currentCount > TR_MAX_MSG_LOG)
196    {
197        NSIndexSet * removeIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, currentCount-TR_MAX_MSG_LOG)];
198        NSArray * itemsToRemove = [fMessages objectsAtIndexes: removeIndexes];
199       
200        [fMessages removeObjectsAtIndexes: removeIndexes];
201        [fDisplayedMessages removeObjectsInArray: itemsToRemove];
202        changed = YES;
203    }
204   
205    if (changed)
206    {
207        [fDisplayedMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
208       
209        [fMessageTable reloadData];
210        if (shouldScroll)
211            [fMessageTable scrollRowToVisible: [fMessageTable numberOfRows]-1];
212    }
213   
214    [fLock unlock];
215   
216    tr_freeMessageList(messages);
217}
218
219- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
220{
221    return [fDisplayedMessages count];
222}
223
224- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
225{
226    NSString * ident = [column identifier];
227    NSDictionary * message = [fDisplayedMessages objectAtIndex: row];
228
229    if ([ident isEqualToString: @"Date"])
230        return [message objectForKey: @"Date"];
231    else if ([ident isEqualToString: @"Level"])
232    {
233        const NSInteger level = [[message objectForKey: @"Level"] integerValue];
234        switch (level)
235        {
236            case TR_MSG_ERR:
237                return [NSImage imageNamed: @"RedDot.png"];
238            case TR_MSG_INF:
239                return [NSImage imageNamed: @"YellowDot.png"];
240            case TR_MSG_DBG:
241                return [NSImage imageNamed: @"PurpleDot.png"];
242            default:
243                NSAssert1(NO, @"Unknown message log level: %d", level);
244                return nil;
245        }
246    }
247    else if ([ident isEqualToString: @"Name"])
248        return [message objectForKey: @"Name"];
249    else
250        return [message objectForKey: @"Message"];
251}
252
253#warning don't cut off end
254- (CGFloat) tableView: (NSTableView *) tableView heightOfRow: (NSInteger) row
255{
256    NSString * message = [[fDisplayedMessages objectAtIndex: row] objectForKey: @"Message"];
257   
258    NSTableColumn * column = [tableView tableColumnWithIdentifier: @"Message"];
259    const CGFloat count = floorf([message sizeWithAttributes: fAttributes].width / [column width]);
260    return [tableView rowHeight] * (count + 1.0);
261}
262
263- (void) tableView: (NSTableView *) tableView sortDescriptorsDidChange: (NSArray *) oldDescriptors
264{
265    [fDisplayedMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
266    [fMessageTable reloadData];
267}
268
269- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
270                tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
271{
272    NSDictionary * message = [fDisplayedMessages objectAtIndex: row];
273    return [message objectForKey: @"File"];
274}
275
276- (void) copy: (id) sender
277{
278    NSIndexSet * indexes = [fMessageTable selectedRowIndexes];
279    NSMutableArray * messageStrings = [NSMutableArray arrayWithCapacity: [indexes count]];
280   
281    for (NSDictionary * message in [fDisplayedMessages objectsAtIndexes: indexes])
282        [messageStrings addObject: [self stringForMessage: message]];
283   
284    NSString * messageString = [messageStrings componentsJoinedByString: @"\n"];
285   
286    NSPasteboard * pb = [NSPasteboard generalPasteboard];
287    if ([NSApp isOnSnowLeopardOrBetter])
288    {
289        [pb clearContents];
290        [pb writeObjects: [NSArray arrayWithObject: messageString]];
291    }
292    else
293    {
294        [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType] owner: nil];
295        [pb setString: messageString forType: NSStringPboardType];
296    }
297}
298
299- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
300{
301    SEL action = [menuItem action];
302   
303    if (action == @selector(copy:))
304        return [fMessageTable numberOfSelectedRows] > 0;
305   
306    return YES;
307}
308
309- (void) changeLevel: (id) sender
310{
311    NSInteger level;
312    switch ([fLevelButton indexOfSelectedItem])
313    {
314        case LEVEL_ERROR:
315            level = TR_MSG_ERR;
316            break;
317        case LEVEL_INFO:
318            level = TR_MSG_INF;
319            break;
320        case LEVEL_DEBUG:
321            level = TR_MSG_DBG;
322            break;
323        default:
324            NSAssert1(NO, @"Unknown message log level: %d", [fLevelButton indexOfSelectedItem]);
325    }
326   
327    if ([[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"] == level)
328        return;
329   
330    [fLock lock];
331   
332    [[NSUserDefaults standardUserDefaults] setInteger: level forKey: @"MessageLevel"];
333   
334    if (level == TR_MSG_DBG) //all messages at this level
335        [fDisplayedMessages setArray: fMessages];
336    else
337    {
338        [fDisplayedMessages removeAllObjects];
339        for (NSDictionary * message in fMessages)
340            if ([[message objectForKey: @"Level"] integerValue] <= level)
341                [fDisplayedMessages addObject: message];
342    }
343   
344    [fDisplayedMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
345   
346    [fMessageTable reloadData];
347   
348    if ([fDisplayedMessages count] > 0)
349    {
350        [fMessageTable deselectAll: self];
351        [fMessageTable scrollRowToVisible: [fMessageTable numberOfRows]-1];
352    }
353   
354    [fLock unlock];
355}
356
357- (void) clearLog: (id) sender
358{
359    [fLock lock];
360   
361    [fMessages removeAllObjects];
362    [fDisplayedMessages removeAllObjects];
363    [fMessageTable reloadData];
364   
365    [fLock unlock];
366}
367
368- (void) writeToFile: (id) sender
369{
370    //make the array sorted by date
371    NSSortDescriptor * descriptor = [[[NSSortDescriptor alloc] initWithKey: @"Index" ascending: YES] autorelease];
372    NSArray * descriptors = [[NSArray alloc] initWithObjects: descriptor, nil];
373    NSArray * sortedMessages = [[fDisplayedMessages sortedArrayUsingDescriptors: descriptors] retain];
374    [descriptors release];
375   
376    NSSavePanel * panel = [NSSavePanel savePanel];
377    [panel setRequiredFileType: @"txt"];
378    [panel setCanSelectHiddenExtension: YES];
379   
380    [panel beginSheetForDirectory: nil file: NSLocalizedString(@"untitled", "Save log panel -> default file name")
381            modalForWindow: [self window] modalDelegate: self
382            didEndSelector: @selector(writeToFileSheetClosed:returnCode:contextInfo:) contextInfo: sortedMessages];
383}
384
385- (void) writeToFileSheetClosed: (NSSavePanel *) panel returnCode: (NSInteger) code contextInfo: (NSArray *) messages
386{
387    if (code == NSOKButton)
388    {
389        //create the text to output
390        NSMutableArray * messageStrings = [NSMutableArray arrayWithCapacity: [messages count]];
391        for (NSDictionary * message in messages)
392            [messageStrings addObject: [self stringForMessage: message]];
393   
394        NSString * fileString = [messageStrings componentsJoinedByString: @"\n"];
395       
396        if (![fileString writeToFile: [panel filename] atomically: YES encoding: NSUTF8StringEncoding error: nil])
397        {
398            NSAlert * alert = [[NSAlert alloc] init];
399            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Save log alert panel -> button")];
400            [alert setMessageText: NSLocalizedString(@"Log Could Not Be Saved", "Save log alert panel -> title")];
401            [alert setInformativeText: [NSString stringWithFormat:
402                    NSLocalizedString(@"There was a problem creating the file \"%@\".",
403                    "Save log alert panel -> message"), [[panel filename] lastPathComponent]]];
404            [alert setAlertStyle: NSWarningAlertStyle];
405           
406            [alert runModal];
407            [alert release];
408        }
409    }
410   
411    [messages release];
412}
413
414@end
415
416@implementation MessageWindowController (Private)
417
418- (void) resizeColumn
419{
420    [fMessageTable noteHeightOfRowsWithIndexesChanged: [NSIndexSet indexSetWithIndexesInRange:
421                    NSMakeRange(0, [fMessageTable numberOfRows])]];
422}
423
424- (NSString *) stringForMessage: (NSDictionary *) message
425{
426    NSString * levelString;
427    const NSInteger level = [[message objectForKey: @"Level"] integerValue];
428    switch (level)
429    {
430        case TR_MSG_ERR:
431            levelString = NSLocalizedString(@"Error", "Message window -> level");
432            break;
433        case TR_MSG_INF:
434            levelString = NSLocalizedString(@"Info", "Message window -> level");
435            break;
436        case TR_MSG_DBG:
437            levelString = NSLocalizedString(@"Debug", "Message window -> level");
438            break;
439        default:
440            NSAssert1(NO, @"Unknown message log level: %d", level);
441    }
442   
443    return [NSString stringWithFormat: @"%@ %@ [%@] %@: %@", [message objectForKey: @"Date"],
444            [message objectForKey: @"File"], levelString,
445            [message objectForKey: @"Name"], [message objectForKey: @"Message"], nil];
446}
447
448@end
Note: See TracBrowser for help on using the repository browser.