source: trunk/macosx/MessageWindowController.m @ 13471

Last change on this file since 13471 was 13471, checked in by livings124, 10 years ago

revert MessageWindowController? changes

  • Property svn:keywords set to Date Rev Author Id
File size: 20.7 KB
Line 
1/******************************************************************************
2 * $Id: MessageWindowController.m 13471 2012-09-06 03:25:52Z livings124 $
3 *
4 * Copyright (c) 2006-2012 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 "Controller.h"
27#import "NSApplicationAdditions.h"
28#import "NSMutableArrayAdditions.h"
29#import "NSStringAdditions.h"
30#import <transmission.h>
31#import <utils.h>
32
33#define LEVEL_ERROR 0
34#define LEVEL_INFO  1
35#define LEVEL_DEBUG 2
36
37#define UPDATE_SECONDS  0.75
38
39@interface MessageWindowController (Private)
40
41- (void) resizeColumn;
42- (BOOL) shouldIncludeMessageForFilter: (NSString *) filterString message: (NSDictionary *) message;
43- (void) updateListForFilter;
44- (NSString *) stringForMessage: (NSDictionary *) message;
45
46@end
47
48@implementation MessageWindowController
49
50- (id) init
51{
52    return [super initWithWindowNibName: @"MessageWindow"];
53}
54
55- (void) awakeFromNib
56{
57    NSWindow * window = [self window];
58    [window setFrameAutosaveName: @"MessageWindowFrame"];
59    [window setFrameUsingName: @"MessageWindowFrame"];
60   
61    if ([NSApp isOnLionOrBetter])
62        [window setRestorationClass: [self class]];
63   
64    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(resizeColumn)
65        name: NSTableViewColumnDidResizeNotification object: fMessageTable];
66   
67    [window setContentBorderThickness: NSMinY([[fMessageTable enclosingScrollView] frame]) forEdge: NSMinYEdge];
68   
69    [[self window] setTitle: NSLocalizedString(@"Message Log", "Message window -> title")];
70   
71    //set images and text for popup button items
72    [[fLevelButton itemAtIndex: LEVEL_ERROR] setTitle: NSLocalizedString(@"Error", "Message window -> level string")];
73    [[fLevelButton itemAtIndex: LEVEL_INFO] setTitle: NSLocalizedString(@"Info", "Message window -> level string")];
74    [[fLevelButton itemAtIndex: LEVEL_DEBUG] setTitle: NSLocalizedString(@"Debug", "Message window -> level string")];
75   
76    const CGFloat levelButtonOldWidth = NSWidth([fLevelButton frame]);
77    [fLevelButton sizeToFit];
78   
79    //set table column text
80    [[[fMessageTable tableColumnWithIdentifier: @"Date"] headerCell] setTitle: NSLocalizedString(@"Date",
81        "Message window -> table column")];
82    [[[fMessageTable tableColumnWithIdentifier: @"Name"] headerCell] setTitle: NSLocalizedString(@"Process",
83        "Message window -> table column")];
84    [[[fMessageTable tableColumnWithIdentifier: @"Message"] headerCell] setTitle: NSLocalizedString(@"Message",
85        "Message window -> table column")];
86   
87    //set and size buttons
88    [fSaveButton setTitle: [NSLocalizedString(@"Save", "Message window -> save button") stringByAppendingEllipsis]];
89    [fSaveButton sizeToFit];
90   
91    NSRect saveButtonFrame = [fSaveButton frame];
92    saveButtonFrame.size.width += 10.0;
93    saveButtonFrame.origin.x += NSWidth([fLevelButton frame]) - levelButtonOldWidth;
94    [fSaveButton setFrame: saveButtonFrame];
95   
96    const CGFloat oldClearButtonWidth = [fClearButton frame].size.width;
97   
98    [fClearButton setTitle: NSLocalizedString(@"Clear", "Message window -> save button")];
99    [fClearButton sizeToFit];
100   
101    NSRect clearButtonFrame = [fClearButton frame];
102    clearButtonFrame.size.width = MAX(clearButtonFrame.size.width + 10.0, saveButtonFrame.size.width);
103    clearButtonFrame.origin.x -= NSWidth(clearButtonFrame) - oldClearButtonWidth;
104    [fClearButton setFrame: clearButtonFrame];
105   
106    [[fFilterField cell] setPlaceholderString: NSLocalizedString(@"Filter", "Message window -> filter field")];
107    NSRect filterButtonFrame = [fFilterField frame];
108    filterButtonFrame.origin.x -= NSWidth(clearButtonFrame) - oldClearButtonWidth;
109    [fFilterField setFrame: filterButtonFrame];
110   
111    fAttributes = [[[[[fMessageTable tableColumnWithIdentifier: @"Message"] dataCell] attributedStringValue]
112                    attributesAtIndex: 0 effectiveRange: NULL] retain];
113   
114    //select proper level in popup button
115    switch ([[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"])
116    {
117        case TR_MSG_ERR:
118            [fLevelButton selectItemAtIndex: LEVEL_ERROR];
119            break;
120        case TR_MSG_INF:
121            [fLevelButton selectItemAtIndex: LEVEL_INFO];
122            break;
123        case TR_MSG_DBG:
124            [fLevelButton selectItemAtIndex: LEVEL_DEBUG];
125            break;
126        default: //safety
127            [[NSUserDefaults standardUserDefaults] setInteger: TR_MSG_ERR forKey: @"MessageLevel"];
128            [fLevelButton selectItemAtIndex: LEVEL_ERROR];
129    }
130   
131    fMessages = [[NSMutableArray alloc] init];
132    fDisplayedMessages = [[NSMutableArray alloc] init];
133   
134    fLock = [[NSLock alloc] init];
135}
136
137- (void) dealloc
138{
139    [[NSNotificationCenter defaultCenter] removeObserver: self];
140   
141    [fTimer invalidate];
142    [fLock release];
143   
144    [fMessages release];
145    [fDisplayedMessages release];
146   
147    [fAttributes release];
148   
149    [super dealloc];
150}
151
152- (void) windowDidBecomeKey: (NSNotification *) notification
153{
154    if (!fTimer)
155    {
156        fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_SECONDS target: self selector: @selector(updateLog:) userInfo: nil repeats: YES];
157        [self updateLog: nil];
158    }
159}
160
161- (void) windowWillClose: (id)sender
162{
163    [fTimer invalidate];
164    fTimer = nil;
165}
166
167+ (void) restoreWindowWithIdentifier: (NSString *) identifier state: (NSCoder *) state completionHandler: (void (^)(NSWindow *, NSError *)) completionHandler
168{
169    NSAssert1([identifier isEqualToString: @"MessageWindow"], @"Trying to restore unexpected identifier %@", identifier);
170   
171    NSWindow * window = [[(Controller *)[NSApp delegate] messageWindowController] window];
172    completionHandler(window, nil);
173}
174
175- (void) window: (NSWindow *) window didDecodeRestorableState: (NSCoder *) coder
176{
177    [fTimer invalidate];
178    fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_SECONDS target: self selector: @selector(updateLog:) userInfo: nil repeats: YES];
179    [self updateLog: nil];
180}
181
182- (void) updateLog: (NSTimer *) timer
183{
184    tr_msg_list * messages;
185    if ((messages = tr_getQueuedMessages()) == NULL)
186        return;
187   
188    [fLock lock];
189   
190    static NSUInteger currentIndex = 0;
191   
192    NSScroller * scroller = [[fMessageTable enclosingScrollView] verticalScroller];
193    const BOOL shouldScroll = currentIndex == 0 || [scroller floatValue] == 1.0 || [scroller isHidden]
194                                || [scroller knobProportion] == 1.0;
195   
196    const NSInteger maxLevel = [[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"];
197    NSString * filterString = [fFilterField stringValue];
198   
199    BOOL changed = NO;
200   
201    for (tr_msg_list * currentMessage = messages; currentMessage != NULL; currentMessage = currentMessage->next)
202    {
203        NSString * name = currentMessage->name != NULL ? [NSString stringWithUTF8String: currentMessage->name]
204                            : [[NSProcessInfo processInfo] processName];
205       
206        NSString * file = [[[NSString stringWithUTF8String: currentMessage->file] lastPathComponent] stringByAppendingFormat: @":%d",
207                            currentMessage->line];
208       
209        NSDictionary * message  = [NSDictionary dictionaryWithObjectsAndKeys:
210                                    [NSString stringWithUTF8String: currentMessage->message], @"Message",
211                                    [NSDate dateWithTimeIntervalSince1970: currentMessage->when], @"Date",
212                                    [NSNumber numberWithUnsignedInteger: currentIndex++], @"Index", //more accurate when sorting by date
213                                    [NSNumber numberWithInteger: currentMessage->level], @"Level",
214                                    name, @"Name",
215                                    file, @"File", nil];
216       
217        [fMessages addObject: message];
218       
219        if (currentMessage->level <= maxLevel && [self shouldIncludeMessageForFilter: filterString message: message])
220        {
221            [fDisplayedMessages addObject: message];
222            changed = YES;
223        }
224    }
225   
226    if ([fMessages count] > TR_MAX_MSG_LOG)
227    {
228        const NSUInteger oldCount = [fDisplayedMessages count];
229       
230        NSIndexSet * removeIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fMessages count]-TR_MAX_MSG_LOG)];
231        NSArray * itemsToRemove = [fMessages objectsAtIndexes: removeIndexes];
232       
233        [fMessages removeObjectsAtIndexes: removeIndexes];
234        [fDisplayedMessages removeObjectsInArray: itemsToRemove];
235       
236        changed |= oldCount > [fDisplayedMessages count];
237    }
238   
239    if (changed)
240    {
241        [fDisplayedMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
242       
243        [fMessageTable reloadData];
244        if (shouldScroll)
245            [fMessageTable scrollRowToVisible: [fMessageTable numberOfRows]-1];
246    }
247   
248    [fLock unlock];
249   
250    tr_freeMessageList(messages);
251}
252
253- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
254{
255    return [fDisplayedMessages count];
256}
257
258- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
259{
260    NSString * ident = [column identifier];
261    NSDictionary * message = [fDisplayedMessages objectAtIndex: row];
262
263    if ([ident isEqualToString: @"Date"])
264        return [message objectForKey: @"Date"];
265    else if ([ident isEqualToString: @"Level"])
266    {
267        const NSInteger level = [[message objectForKey: @"Level"] integerValue];
268        switch (level)
269        {
270            case TR_MSG_ERR:
271                return [NSImage imageNamed: @"RedDot"];
272            case TR_MSG_INF:
273                return [NSImage imageNamed: @"YellowDot"];
274            case TR_MSG_DBG:
275                return [NSImage imageNamed: @"PurpleDot"];
276            default:
277                NSAssert1(NO, @"Unknown message log level: %ld", level);
278                return nil;
279        }
280    }
281    else if ([ident isEqualToString: @"Name"])
282        return [message objectForKey: @"Name"];
283    else
284        return [message objectForKey: @"Message"];
285}
286
287#warning don't cut off end
288- (CGFloat) tableView: (NSTableView *) tableView heightOfRow: (NSInteger) row
289{
290    NSString * message = [[fDisplayedMessages objectAtIndex: row] objectForKey: @"Message"];
291   
292    NSTableColumn * column = [tableView tableColumnWithIdentifier: @"Message"];
293    const CGFloat count = floorf([message sizeWithAttributes: fAttributes].width / [column width]);
294   
295    return [tableView rowHeight] * (count + 1.0);
296}
297
298- (void) tableView: (NSTableView *) tableView sortDescriptorsDidChange: (NSArray *) oldDescriptors
299{
300    [fDisplayedMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
301    [fMessageTable reloadData];
302}
303
304- (NSString *) tableView: (NSTableView *) tableView toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
305                tableColumn: (NSTableColumn *) column row: (NSInteger) row mouseLocation: (NSPoint) mouseLocation
306{
307    NSDictionary * message = [fDisplayedMessages objectAtIndex: row];
308    return [message objectForKey: @"File"];
309}
310
311- (void) copy: (id) sender
312{
313    NSIndexSet * indexes = [fMessageTable selectedRowIndexes];
314    NSMutableArray * messageStrings = [NSMutableArray arrayWithCapacity: [indexes count]];
315   
316    for (NSDictionary * message in [fDisplayedMessages objectsAtIndexes: indexes])
317        [messageStrings addObject: [self stringForMessage: message]];
318   
319    NSString * messageString = [messageStrings componentsJoinedByString: @"\n"];
320   
321    NSPasteboard * pb = [NSPasteboard generalPasteboard];
322    [pb clearContents];
323    [pb writeObjects: [NSArray arrayWithObject: messageString]];
324}
325
326- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
327{
328    SEL action = [menuItem action];
329   
330    if (action == @selector(copy:))
331        return [fMessageTable numberOfSelectedRows] > 0;
332   
333    return YES;
334}
335
336- (void) changeLevel: (id) sender
337{
338    NSInteger level;
339    switch ([fLevelButton indexOfSelectedItem])
340    {
341        case LEVEL_ERROR:
342            level = TR_MSG_ERR;
343            break;
344        case LEVEL_INFO:
345            level = TR_MSG_INF;
346            break;
347        case LEVEL_DEBUG:
348            level = TR_MSG_DBG;
349            break;
350        default:
351            NSAssert1(NO, @"Unknown message log level: %ld", [fLevelButton indexOfSelectedItem]);
352    }
353   
354    if ([[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"] == level)
355        return;
356   
357    [[NSUserDefaults standardUserDefaults] setInteger: level forKey: @"MessageLevel"];
358   
359    [fLock lock];
360   
361    [self updateListForFilter];
362   
363    [fLock unlock];
364}
365
366- (void) changeFilter: (id) sender
367{
368    [fLock lock];
369   
370    [self updateListForFilter];
371   
372    [fLock unlock];
373}
374
375- (void) clearLog: (id) sender
376{
377    [fLock lock];
378   
379    [fMessages removeAllObjects];
380   
381    const BOOL onLion = [NSApp isOnLionOrBetter];
382   
383    if (onLion)
384        [fMessageTable beginUpdates];
385   
386    if (onLion)
387        [fMessageTable removeRowsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fDisplayedMessages count])] withAnimation: NSTableViewAnimationSlideLeft];
388    [fDisplayedMessages removeAllObjects];
389   
390    if (onLion)
391        [fMessageTable endUpdates];
392    else
393        [fMessageTable reloadData];
394   
395    [fLock unlock];
396}
397
398- (void) writeToFile: (id) sender
399{
400    NSSavePanel * panel = [NSSavePanel savePanel];
401    [panel setAllowedFileTypes: [NSArray arrayWithObject: @"txt"]];
402    [panel setCanSelectHiddenExtension: YES];
403   
404    [panel setNameFieldStringValue: NSLocalizedString(@"untitled", "Save log panel -> default file name")];
405   
406    [panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
407        if (result == NSFileHandlingPanelOKButton)
408        {
409            //make the array sorted by date
410            NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey: @"Index" ascending: YES];
411            NSArray * descriptors = [[NSArray alloc] initWithObjects: descriptor, nil];
412            NSArray * sortedMessages = [fDisplayedMessages sortedArrayUsingDescriptors: descriptors];
413            [descriptors release];
414           
415            //create the text to output
416            NSMutableArray * messageStrings = [NSMutableArray arrayWithCapacity: [sortedMessages count]];
417            for (NSDictionary * message in sortedMessages)
418                [messageStrings addObject: [self stringForMessage: message]];
419           
420            NSString * fileString = [messageStrings componentsJoinedByString: @"\n"];
421           
422            if (![fileString writeToFile: [[panel URL] path] atomically: YES encoding: NSUTF8StringEncoding error: nil])
423            {
424                NSAlert * alert = [[NSAlert alloc] init];
425                [alert addButtonWithTitle: NSLocalizedString(@"OK", "Save log alert panel -> button")];
426                [alert setMessageText: NSLocalizedString(@"Log Could Not Be Saved", "Save log alert panel -> title")];
427                [alert setInformativeText: [NSString stringWithFormat:
428                                            NSLocalizedString(@"There was a problem creating the file \"%@\".",
429                                                              "Save log alert panel -> message"), [[[panel URL] path] lastPathComponent]]];
430                [alert setAlertStyle: NSWarningAlertStyle];
431               
432                [alert runModal];
433                [alert release];
434            }
435        }
436    }];
437}
438
439@end
440
441@implementation MessageWindowController (Private)
442
443- (void) resizeColumn
444{
445    [fMessageTable noteHeightOfRowsWithIndexesChanged: [NSIndexSet indexSetWithIndexesInRange:
446                    NSMakeRange(0, [fMessageTable numberOfRows])]];
447}
448
449- (BOOL) shouldIncludeMessageForFilter: (NSString *) filterString message: (NSDictionary *) message
450{
451    if ([filterString isEqualToString: @""])
452        return YES;
453   
454    const NSStringCompareOptions searchOptions = NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch;
455    return [[message objectForKey: @"Name"] rangeOfString: filterString options: searchOptions].location != NSNotFound
456            || [[message objectForKey: @"Message"] rangeOfString: filterString options: searchOptions].location != NSNotFound;
457}
458
459- (void) updateListForFilter
460{
461    const NSInteger level = [[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"];
462    NSString * filterString = [fFilterField stringValue];
463   
464    NSIndexSet * indexes = [fMessages indexesOfObjectsWithOptions: NSEnumerationConcurrent passingTest: ^BOOL(id message, NSUInteger idx, BOOL * stop) {
465        return [[(NSDictionary *)message objectForKey: @"Level"] integerValue] <= level && [self shouldIncludeMessageForFilter: filterString message: message];
466    }];
467   
468    NSArray * tempMessages = [[fMessages objectsAtIndexes: indexes] sortedArrayUsingDescriptors: [fMessageTable sortDescriptors]];
469   
470    const BOOL onLion = [NSApp isOnLionOrBetter];
471   
472    if (onLion)
473        [fMessageTable beginUpdates];
474   
475    //figure out which rows were added/moved
476    NSUInteger currentIndex = 0, totalCount = 0;
477    NSMutableArray * itemsToAdd = [NSMutableArray array];
478    NSMutableIndexSet * itemsToAddIndexes = [NSMutableIndexSet indexSet];
479   
480    for (NSDictionary * message in tempMessages)
481    {
482        const NSUInteger previousIndex = [fDisplayedMessages indexOfObject: message inRange: NSMakeRange(currentIndex, [fDisplayedMessages count]-currentIndex)];
483        if (previousIndex == NSNotFound)
484        {
485            [itemsToAdd addObject: message];
486            [itemsToAddIndexes addIndex: totalCount];
487        }
488        else
489        {
490            if (previousIndex != currentIndex)
491            {
492                [fDisplayedMessages moveObjectAtIndex: previousIndex toIndex: currentIndex];
493                if (onLion)
494                    [fMessageTable moveRowAtIndex: previousIndex toIndex: currentIndex];
495            }
496            ++currentIndex;
497        }
498       
499        ++totalCount;
500    }
501   
502    //remove trailing items - those are the unused
503    if (currentIndex < [fDisplayedMessages count])
504    {
505        const NSRange removeRange = NSMakeRange(currentIndex, [fDisplayedMessages count]-currentIndex);
506        [fDisplayedMessages removeObjectsInRange: removeRange];
507        if (onLion)
508            [fMessageTable removeRowsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: removeRange] withAnimation: NSTableViewAnimationSlideDown];
509    }
510   
511    //add new items
512    [fDisplayedMessages insertObjects: itemsToAdd atIndexes: itemsToAddIndexes];
513    if (onLion)
514        [fMessageTable insertRowsAtIndexes: itemsToAddIndexes withAnimation: NSTableViewAnimationSlideUp];
515   
516    if (onLion)
517        [fMessageTable endUpdates];
518    else
519    {
520        [fMessageTable reloadData];
521       
522        if ([fDisplayedMessages count] > 0)
523            [fMessageTable deselectAll: self];
524    }
525   
526    NSAssert2([fDisplayedMessages isEqualToArray: tempMessages], @"Inconsistency between message arrays! %@ %@", fDisplayedMessages, tempMessages);
527}
528
529- (NSString *) stringForMessage: (NSDictionary *) message
530{
531    NSString * levelString;
532    const NSInteger level = [[message objectForKey: @"Level"] integerValue];
533    switch (level)
534    {
535        case TR_MSG_ERR:
536            levelString = NSLocalizedString(@"Error", "Message window -> level");
537            break;
538        case TR_MSG_INF:
539            levelString = NSLocalizedString(@"Info", "Message window -> level");
540            break;
541        case TR_MSG_DBG:
542            levelString = NSLocalizedString(@"Debug", "Message window -> level");
543            break;
544        default:
545            NSAssert1(NO, @"Unknown message log level: %ld", level);
546    }
547   
548    return [NSString stringWithFormat: @"%@ %@ [%@] %@: %@", [message objectForKey: @"Date"],
549            [message objectForKey: @"File"], levelString,
550            [message objectForKey: @"Name"], [message objectForKey: @"Message"], nil];
551}
552
553@end
Note: See TracBrowser for help on using the repository browser.