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