source: trunk/macosx/MessageWindowController.m @ 10026

Last change on this file since 10026 was 10026, checked in by livings124, 12 years ago

add a missing removeObserver: to the message window controller

the last commit ensured that the drag overlay window resized when not window, which led to inaccurate sizes when exposé-d

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