source: trunk/macosx/MessageWindowController.m @ 4660

Last change on this file since 4660 was 4660, checked in by livings124, 14 years ago

fix autoscrolling in message window when it's full; trivial changes to peer string encoding

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