source: trunk/macosx/MessageWindowController.m @ 9325

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

add an extra check when determining if the message window should be scrolled to the bottom

  • Property svn:keywords set to Date Rev Author Id
File size: 14.4 KB
Line 
1/******************************************************************************
2 * $Id: MessageWindowController.m 9325 2009-10-19 02:44:04Z 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 "NSStringAdditions.h"
27#import <transmission.h>
28
29#define LEVEL_ERROR 0
30#define LEVEL_INFO  1
31#define LEVEL_DEBUG 2
32
33#define UPDATE_SECONDS  0.6
34#define MAX_MESSAGES    8000
35
36@interface MessageWindowController (Private)
37
38- (void) resizeColumn;
39- (NSString *) stringForMessage: (NSDictionary *) message;
40
41@end
42
43@implementation MessageWindowController
44
45- (id) init
46{
47    return [super initWithWindowNibName: @"MessageWindow"];
48}
49
50- (void) dealloc
51{
52    [fTimer invalidate];
53    [fMessages release];
54   
55    [fAttributes release];
56   
57    [super dealloc];
58}
59
60- (void) awakeFromNib
61{
62    NSWindow * window = [self window];
63    [window setFrameAutosaveName: @"MessageWindowFrame"];
64    [window setFrameUsingName: @"MessageWindowFrame"];
65   
66    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(resizeColumn)
67        name: @"NSTableViewColumnDidResizeNotification" object: fMessageTable];
68   
69    [window setContentBorderThickness: [[fMessageTable enclosingScrollView] frame].origin.y forEdge: NSMinYEdge];
70   
71    //initially sort peer table by date
72    if ([[fMessageTable sortDescriptors] count] == 0)
73        [fMessageTable setSortDescriptors: [NSArray arrayWithObject: [[fMessageTable tableColumnWithIdentifier: @"Date"]
74                                            sortDescriptorPrototype]]];
75   
76    [[self window] setTitle: NSLocalizedString(@"Message Log", "Message window -> title")];
77   
78    //set images and text for popup button items
79    [[fLevelButton itemAtIndex: LEVEL_ERROR] setTitle: NSLocalizedString(@"Error", "Message window -> level string")];
80    [[fLevelButton itemAtIndex: LEVEL_INFO] setTitle: NSLocalizedString(@"Info", "Message window -> level string")];
81    [[fLevelButton itemAtIndex: LEVEL_DEBUG] setTitle: NSLocalizedString(@"Debug", "Message window -> level string")];
82   
83    //set table column text
84    [[[fMessageTable tableColumnWithIdentifier: @"Date"] headerCell] setTitle: NSLocalizedString(@"Date",
85        "Message window -> table column")];
86    [[[fMessageTable tableColumnWithIdentifier: @"Name"] headerCell] setTitle: NSLocalizedString(@"Process",
87        "Message window -> table column")];
88    [[[fMessageTable tableColumnWithIdentifier: @"Message"] headerCell] setTitle: NSLocalizedString(@"Message",
89        "Message window -> table column")];
90   
91    //set and size buttons
92    [fSaveButton setTitle: [NSLocalizedString(@"Save", "Message window -> save button") stringByAppendingEllipsis]];
93    [fSaveButton sizeToFit];
94   
95    NSRect saveButtonFrame = [fSaveButton frame];
96    saveButtonFrame.size.width += 10.0;
97    [fSaveButton setFrame: saveButtonFrame];
98   
99    float oldClearButtonWidth = [fClearButton frame].size.width;
100   
101    [fClearButton setTitle: NSLocalizedString(@"Clear", "Message window -> save button")];
102    [fClearButton sizeToFit];
103   
104    NSRect clearButtonFrame = [fClearButton frame];
105    clearButtonFrame.size.width = MAX(clearButtonFrame.size.width + 10.0, saveButtonFrame.size.width);
106    clearButtonFrame.origin.x -= clearButtonFrame.size.width - oldClearButtonWidth;
107    [fClearButton setFrame: clearButtonFrame];
108   
109    //select proper level in popup button
110    switch (tr_getMessageLevel())
111    {
112        case TR_MSG_ERR:
113            [fLevelButton selectItemAtIndex: LEVEL_ERROR];
114            break;
115        case TR_MSG_INF:
116            [fLevelButton selectItemAtIndex: LEVEL_INFO];
117            break;
118        case TR_MSG_DBG:
119            [fLevelButton selectItemAtIndex: LEVEL_DEBUG];
120    }
121   
122    fMessages = [[NSMutableArray alloc] init];
123}
124
125- (void) windowDidBecomeKey: (NSNotification *) notification
126{
127    if (!fTimer)
128        fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_SECONDS target: self
129                    selector: @selector(updateLog:) userInfo: nil repeats: YES];
130    [self updateLog: nil];
131}
132
133- (void) windowWillClose: (id)sender
134{
135    [fTimer invalidate];
136    fTimer = nil;
137}
138
139- (void) updateLog: (NSTimer *) timer
140{
141    tr_msg_list * messages;
142    if ((messages = tr_getQueuedMessages()) == NULL)
143        return;
144   
145    static NSUInteger currentIndex = 0;
146   
147    NSScroller * scroller = [[fMessageTable enclosingScrollView] verticalScroller];
148    const BOOL shouldScroll = currentIndex == 0 || [scroller floatValue] == 1.0 || [scroller isHidden]
149                                || [scroller knobProportion] == 1.0;
150   
151    for (tr_msg_list * currentMessage = messages; currentMessage != NULL; currentMessage = currentMessage->next)
152    {
153        NSString * name = currentMessage->name != NULL ? [NSString stringWithUTF8String: currentMessage->name]
154                            : [[NSProcessInfo processInfo] processName];
155       
156        NSDictionary * message  = [NSDictionary dictionaryWithObjectsAndKeys:
157                                    [NSString stringWithUTF8String: currentMessage->message], @"Message",
158                                    [NSDate dateWithTimeIntervalSince1970: currentMessage->when], @"Date",
159                                    [NSNumber numberWithUnsignedInteger: currentIndex++], @"Index", //more accurate when sorting by date
160                                    [NSNumber numberWithInteger: currentMessage->level], @"Level",
161                                    name, @"Name",
162                                    [NSString stringWithUTF8String: currentMessage->file], @"File",
163                                    [NSNumber numberWithInteger: currentMessage->line], @"Line", nil];
164                               
165        [fMessages addObject: message];
166    }
167   
168    tr_freeMessageList(messages);
169   
170    NSUInteger total = [fMessages count];
171    if (total > MAX_MESSAGES)
172    {
173        //remove the oldest
174        NSSortDescriptor * descriptor = [[[NSSortDescriptor alloc] initWithKey: @"Index" ascending: YES] autorelease];
175        [fMessages sortUsingDescriptors: [NSArray arrayWithObject: descriptor]];
176       
177        [fMessages removeObjectsInRange: NSMakeRange(0, total-MAX_MESSAGES)];
178       
179        [fMessageTable noteHeightOfRowsWithIndexesChanged: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, MAX_MESSAGES)]];
180        total = MAX_MESSAGES;
181    }
182   
183    [fMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
184   
185    [fMessageTable reloadData];
186    if (shouldScroll)
187        [fMessageTable scrollRowToVisible: total-1];
188}
189
190- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
191{
192    return [fMessages count];
193}
194
195- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
196{
197    NSString * ident = [column identifier];
198    NSDictionary * message = [fMessages objectAtIndex: row];
199
200    if ([ident isEqualToString: @"Date"])
201        return [message objectForKey: @"Date"];
202    else if ([ident isEqualToString: @"Level"])
203    {
204        const NSInteger level = [[message objectForKey: @"Level"] integerValue];
205        switch (level)
206        {
207            case TR_MSG_ERR:
208                return [NSImage imageNamed: @"RedDot.png"];
209            case TR_MSG_INF:
210                return [NSImage imageNamed: @"YellowDot.png"];
211            case TR_MSG_DBG:
212                return [NSImage imageNamed: @"PurpleDot.png"];
213            default:
214                NSAssert1(NO, @"Unknown message log level: %d", level);
215                return nil;
216        }
217    }
218    else if ([ident isEqualToString: @"Name"])
219        return [message objectForKey: @"Name"];
220    else
221        return [message objectForKey: @"Message"];
222}
223
224#warning don't cut off end
225- (CGFloat) tableView: (NSTableView *) tableView heightOfRow: (NSInteger) row
226{
227    NSTableColumn * column = [tableView tableColumnWithIdentifier: @"Message"];
228   
229    if (!fAttributes)
230        fAttributes = [[[[column dataCell] attributedStringValue] attributesAtIndex: 0 effectiveRange: NULL] retain];
231   
232    const CGFloat count = floorf([[[fMessages objectAtIndex: row] objectForKey: @"Message"] sizeWithAttributes: fAttributes].width
233                                / [column width]);
234    return [tableView rowHeight] * (count + 1.0);
235}
236
237- (void) tableView: (NSTableView *) tableView sortDescriptorsDidChange: (NSArray *) oldDescriptors
238{
239    [fMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
240    [fMessageTable reloadData];
241}
242
243- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
244                tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
245{
246    NSDictionary * message = [fMessages objectAtIndex: row];
247    return [NSString stringWithFormat: @"%@:%@", [[message objectForKey: @"File"] lastPathComponent], [message objectForKey: @"Line"]];
248}
249
250- (void) copy: (id) sender
251{
252    NSPasteboard * pb = [NSPasteboard generalPasteboard];
253    [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType] owner: self];
254   
255    NSIndexSet * indexes = [fMessageTable selectedRowIndexes];
256    NSMutableArray * messageStrings = [NSMutableArray arrayWithCapacity: [indexes count]];
257   
258    for (NSDictionary * message in [fMessages objectsAtIndexes: indexes])
259        [messageStrings addObject: [self stringForMessage: message]];
260   
261    [pb setString: [messageStrings componentsJoinedByString: @"\n"] forType: NSStringPboardType];
262}
263
264- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
265{
266    SEL action = [menuItem action];
267   
268    if (action == @selector(copy:))
269        return [fMessageTable numberOfSelectedRows] > 0;
270   
271    return YES;
272}
273
274- (void) changeLevel: (id) sender
275{
276    [self updateLog: nil];
277   
278    NSInteger level;
279    switch ([fLevelButton indexOfSelectedItem])
280    {
281        case LEVEL_ERROR:
282            level = TR_MSG_ERR;
283            break;
284        case LEVEL_INFO:
285            level = TR_MSG_INF;
286            break;
287        case LEVEL_DEBUG:
288            level = TR_MSG_DBG;
289            break;
290    }
291   
292    tr_setMessageLevel(level);
293    [[NSUserDefaults standardUserDefaults] setInteger: level forKey: @"MessageLevel"];
294}
295
296- (void) clearLog: (id) sender
297{
298    [fMessages removeAllObjects];
299    [fMessageTable reloadData];
300}
301
302- (void) writeToFile: (id) sender
303{
304    //make the array sorted by date
305    NSSortDescriptor * descriptor = [[[NSSortDescriptor alloc] initWithKey: @"Index" ascending: YES] autorelease];
306    NSArray * descriptors = [[NSArray alloc] initWithObjects: descriptor, nil];
307    NSArray * sortedMessages = [fMessages sortedArrayUsingDescriptors: descriptors];
308    [descriptors release];
309   
310    //create the text to output
311    NSMutableArray * messageStrings = [NSMutableArray arrayWithCapacity: [fMessages count]];
312    for (NSDictionary * message in sortedMessages)
313        [messageStrings addObject: [self stringForMessage: message]];
314   
315    NSString * fileString = [[messageStrings componentsJoinedByString: @"\n"] retain];
316   
317    NSSavePanel * panel = [NSSavePanel savePanel];
318    [panel setRequiredFileType: @"txt"];
319    [panel setCanSelectHiddenExtension: YES];
320   
321    [panel beginSheetForDirectory: nil file: NSLocalizedString(@"untitled", "Save log panel -> default file name")
322            modalForWindow: [self window] modalDelegate: self
323            didEndSelector: @selector(writeToFileSheetClosed:returnCode:contextInfo:) contextInfo: fileString];
324}
325
326- (void) writeToFileSheetClosed: (NSSavePanel *) panel returnCode: (NSInteger) code contextInfo: (NSString *) string
327{
328    if (code == NSOKButton)
329    {
330        if (![string writeToFile: [panel filename] atomically: YES encoding: NSUTF8StringEncoding error: nil])
331        {
332            NSAlert * alert = [[NSAlert alloc] init];
333            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Save log alert panel -> button")];
334            [alert setMessageText: NSLocalizedString(@"Log Could Not Be Saved", "Save log alert panel -> title")];
335            [alert setInformativeText: [NSString stringWithFormat:
336                    NSLocalizedString(@"There was a problem creating the file \"%@\".",
337                    "Save log alert panel -> message"), [[panel filename] lastPathComponent]]];
338            [alert setAlertStyle: NSWarningAlertStyle];
339           
340            [alert runModal];
341            [alert release];
342        }
343    }
344   
345    [string release];
346}
347
348@end
349
350@implementation MessageWindowController (Private)
351
352- (void) resizeColumn
353{
354    [fMessageTable noteHeightOfRowsWithIndexesChanged: [NSIndexSet indexSetWithIndexesInRange:
355                    NSMakeRange(0, [fMessageTable numberOfRows])]];
356}
357
358- (NSString *) stringForMessage: (NSDictionary *) message
359{
360    NSString * level;
361    switch ([[message objectForKey: @"Level"] integerValue])
362    {
363        case TR_MSG_ERR:
364            level = NSLocalizedString(@"Error", "Message window -> level");
365            break;
366        case TR_MSG_INF:
367            level = NSLocalizedString(@"Info", "Message window -> level");
368            break;
369        case TR_MSG_DBG:
370            level = NSLocalizedString(@"Debug", "Message window -> level");
371            break;
372        default:
373            level = @"";
374    }
375   
376    return [NSString stringWithFormat: @"%@ %@:%@ [%@] %@: %@", [message objectForKey: @"Date"],
377            [[message objectForKey: @"File"] lastPathComponent], [message objectForKey: @"Line"], level,
378            [message objectForKey: @"Name"], [message objectForKey: @"Message"], nil];
379}
380
381@end
Note: See TracBrowser for help on using the repository browser.