source: trunk/macosx/MessageWindowController.m @ 9703

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

one more tweak

  • Property svn:keywords set to Date Rev Author Id
File size: 16.4 KB
Line 
1/******************************************************************************
2 * $Id: MessageWindowController.m 9703 2009-12-10 05:28:45Z 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 currentIndex = [fMessages count] > 0 ? ([[[fMessages lastObject] objectForKey: @"Index"] unsignedIntegerValue] + 1) : 0;
162   
163    NSScroller * scroller = [[fMessageTable enclosingScrollView] verticalScroller];
164    const BOOL shouldScroll = currentIndex == 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: currentIndex++], @"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 ([fMessages count] > TR_MAX_MSG_LOG)
196    {
197        NSIndexSet * removeIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fMessages count]-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.