macosx: Remove unused variable
[vlc.git] / modules / gui / macosx / VLCLogWindowController.m
1 /*****************************************************************************
2  * VLCLogWindowController.m: Log message window controller
3  *****************************************************************************
4  * Copyright (C) 2004-2013 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne at videolan dot org>
8  *          Pierre d'Herbemont <pdherbemont # videolan org>
9  *          Derk-Jan Hartman <hartman at videolan.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 #import "VLCLogWindowController.h"
27 #import "VLCMain.h"
28 #import <vlc_common.h>
29
30 @interface VLCLogWindowController () <NSWindowDelegate>
31
32 /* This array stores messages that are managed by the arrayController */
33 @property (retain) NSMutableArray *messagesArray;
34
35 /* This array stores messages before they are added to the messagesArray on refresh */
36 @property (retain) NSMutableArray *messageBuffer;
37
38 /* We do not want to refresh the table for every message, as that would be very frequent when
39  * there are a lot of messages, therefore we use a timer to refresh the table with new data
40  * from the messageBuffer every now and then, which is much more efficient and still fast
41  * enough for a good user experience
42  */
43 @property (retain) NSTimer        *refreshTimer;
44
45 - (void)addMessage:(NSDictionary *)message;
46
47 @end
48
49 /*
50  * MsgCallback: Callback triggered by the core once a new debug message is
51  * ready to be displayed. We store everything in a NSArray in our Cocoa part
52  * of this file.
53  */
54 static void MsgCallback(void *data, int type, const vlc_log_t *item, const char *format, va_list ap)
55 {
56     @autoreleasepool {
57         char *msg;
58         VLCLogWindowController *controller = (__bridge VLCLogWindowController*)data;
59         static NSString *types[4] = { @"info", @"error", @"warning", @"debug" };
60
61         if (vasprintf(&msg, format, ap) == -1) {
62             return;
63         }
64
65         if (!item->psz_module || !msg) {
66             free(msg);
67             return;
68         }
69
70         NSString *position = [NSString stringWithFormat:@"%s:%i", item->file, item->line];
71
72         NSDictionary *messageDict = @{
73                                       @"type"       : types[type],
74                                       @"message"    : toNSStr(msg),
75                                       @"component"  : toNSStr(item->psz_module),
76                                       @"position"   : position,
77                                       @"func"       : toNSStr(item->func)
78                                       };
79         [controller addMessage:messageDict];
80         free(msg);
81     }
82 }
83
84 @implementation VLCLogWindowController
85
86 - (id)init
87 {
88     self = [super initWithWindowNibName:@"LogMessageWindow"];
89     if (self) {
90         _messagesArray = [[NSMutableArray alloc] initWithCapacity:500];
91         _messageBuffer = [[NSMutableArray alloc] initWithCapacity:100];
92     }
93     return self;
94 }
95
96 - (void)dealloc
97 {
98     if (getIntf())
99         vlc_LogSet( getIntf()->obj.libvlc, NULL, NULL );
100 }
101
102 - (void)windowDidLoad
103 {
104     [self.window setExcludedFromWindowsMenu:YES];
105     [self.window setDelegate:self];
106     [self.window setTitle:_NS("Messages")];
107     [self.window setLevel:NSModalPanelWindowLevel];
108
109 #define setupButton(target, title, desc)                                              \
110     [target accessibilitySetOverrideValue:title                                       \
111                              forAttribute:NSAccessibilityTitleAttribute];             \
112     [target accessibilitySetOverrideValue:desc                                        \
113                              forAttribute:NSAccessibilityDescriptionAttribute];       \
114     [target setToolTip:desc];
115
116     setupButton(_saveButton,
117                 _NS("Save log"),
118                 _NS("Click to save the debug log to a file."));
119     setupButton(_refreshButton,
120                 _NS("Refresh log"),
121                 _NS("Click to frefresh the log output."));
122     setupButton(_clearButton,
123                 _NS("Clear log"),
124                 _NS("Click to clear the log output."));
125     setupButton(_toggleDetailsButton,
126                 _NS("Toggle details"),
127                 _NS("Click to show/hide details about a log message."));
128
129 #undef setupButton
130 }
131
132 - (void)showWindow:(id)sender
133 {
134     // Do nothing if window is already visible
135     if ([self.window isVisible]) {
136         return [super showWindow:sender];
137     }
138
139     // Subscribe to LibVLCCore's messages
140     vlc_LogSet(getIntf()->obj.libvlc, MsgCallback, (__bridge void*)self);
141     _refreshTimer = [NSTimer scheduledTimerWithTimeInterval:0.3
142                                                      target:self
143                                                    selector:@selector(appendMessageBuffer)
144                                                    userInfo:nil
145                                                     repeats:YES];
146     return [super showWindow:sender];
147 }
148
149 - (void)windowWillClose:(NSNotification *)notification
150 {
151     // Unsubscribe from LibVLCCore's messages
152     vlc_LogSet( getIntf()->obj.libvlc, NULL, NULL );
153
154     // Remove all messages
155     [self clearMessageBuffer];
156     [self clearMessageTable];
157
158     // Invalidate timer
159     [_refreshTimer invalidate];
160     _refreshTimer = nil;
161 }
162
163 #pragma mark -
164 #pragma mark Delegate methods
165
166 /*
167  * Called when a row is added to the table
168  * We use this to set the correct background color for the row, depending on the
169  * message type.
170  */
171 - (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
172 {
173     // Initialize background colors
174     static NSDictionary *colors = nil;
175     static dispatch_once_t onceToken;
176     dispatch_once(&onceToken, ^{
177         colors = @{
178                    @"info"     : [NSColor colorWithCalibratedRed:0.65 green:0.91 blue:1.0 alpha:0.7],
179                    @"error"    : [NSColor colorWithCalibratedRed:1.0 green:0.49 blue:0.45 alpha:0.5],
180                    @"warning"  : [NSColor colorWithCalibratedRed:1.0 green:0.88 blue:0.45 alpha:0.7],
181                    @"debug"    : [NSColor colorWithCalibratedRed:0.96 green:0.96 blue:0.96 alpha:0.5]
182                    };
183     });
184
185     // Lookup color for message type
186     NSDictionary *message = [[_arrayController arrangedObjects] objectAtIndex:row];
187     rowView.backgroundColor = [colors objectForKey:[message objectForKey:@"type"]];
188 }
189
190 - (void)splitViewDidResizeSubviews:(NSNotification *)notification
191 {
192     if ([_splitView isSubviewCollapsed:_detailView]) {
193         [_toggleDetailsButton setState:NSOffState];
194     } else {
195         [_toggleDetailsButton setState:NSOnState];
196     }
197 }
198
199 #pragma mark -
200 #pragma mark UI actions
201
202 /* Save debug log to file action
203  */
204 - (IBAction)saveDebugLog:(id)sender
205 {
206     NSSavePanel * saveFolderPanel = [[NSSavePanel alloc] init];
207
208     [saveFolderPanel setCanSelectHiddenExtension: NO];
209     [saveFolderPanel setCanCreateDirectories: YES];
210     [saveFolderPanel setAllowedFileTypes: [NSArray arrayWithObject:@"txt"]];
211     [saveFolderPanel setNameFieldStringValue:[NSString stringWithFormat: _NS("VLC Debug Log (%s).txt"), VERSION_MESSAGE]];
212     [saveFolderPanel beginSheetModalForWindow: self.window completionHandler:^(NSInteger returnCode) {
213         if (returnCode != NSOKButton) {
214             return;
215         }
216         NSMutableString *string = [[NSMutableString alloc] init];
217
218         for (NSDictionary *line in _messagesArray) {
219             NSString *message = [NSString stringWithFormat:@"%@ %@ %@\n",
220                                  [line objectForKey:@"component"],
221                                  [line objectForKey:@"type"],
222                                  [line objectForKey:@"message"]];
223             [string appendString:message];
224         }
225         NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
226         if ([data writeToFile: [[saveFolderPanel URL] path] atomically: YES] == NO)
227             msg_Warn(getIntf(), "Error while saving the debug log");
228     }];
229 }
230
231 /* Clear log action
232  */
233 - (IBAction)clearLog:(id)sender
234 {
235     // Unregister handler
236     vlc_LogSet(getIntf()->obj.libvlc, NULL, NULL);
237
238     // Remove all messages
239     [self clearMessageBuffer];
240     [self clearMessageTable];
241
242     // Reregister handler, to write new header to log
243     vlc_LogSet(getIntf()->obj.libvlc, MsgCallback, (__bridge void*)self);
244 }
245
246 /* Refresh log action
247  */
248 - (IBAction)refreshLog:(id)sender
249 {
250     [self appendMessageBuffer];
251     [_messageTable scrollToEndOfDocument:self];
252 }
253
254 /* Show/Hide details action
255  */
256 - (IBAction)toggleDetails:(id)sender
257 {
258     if ([_splitView isSubviewCollapsed:_detailView]) {
259         [_detailView setHidden:NO];
260     } else {
261         [_detailView setHidden:YES];
262     }
263 }
264
265 /* Called when the user hits CMD + C or copy is clicked in the edit menu
266  */
267 - (void) copy:(id)sender {
268     NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
269     [pasteBoard clearContents];
270     for (NSDictionary *line in [_arrayController selectedObjects]) {
271         NSString *message = [NSString stringWithFormat:@"%@ %@ %@",
272                              [line objectForKey:@"component"],
273                              [line objectForKey:@"type"],
274                              [line objectForKey:@"message"]];
275         [pasteBoard writeObjects:@[message]];
276     }
277 }
278
279 #pragma mark -
280 #pragma mark UI validation
281
282 /* Validate the copy menu item
283  */
284 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
285 {
286     SEL theAction = [anItem action];
287
288     if (theAction == @selector(copy:)) {
289         if ([[_arrayController selectedObjects] count] > 0) {
290             return YES;
291         }
292         return NO;
293     }
294     /* Indicate that we handle the validation method,
295      * even if we don’t implement the action
296      */
297     return YES;
298 }
299
300 #pragma mark -
301 #pragma mark Data handling
302
303 /** 
304  Adds a message to the messageBuffer, it does not has to be called from the main thread, as
305  items are only added to the messageArray on refresh.
306  */
307 - (void)addMessage:(NSDictionary *)messageDict
308 {
309     @synchronized (_messageBuffer) {
310         [_messageBuffer addObject:messageDict];
311     }
312 }
313
314 /**
315  Clears the message buffer
316  */
317 - (void)clearMessageBuffer
318 {
319     @synchronized (_messageBuffer) {
320         [_messageBuffer removeAllObjects];
321     }
322 }
323
324 /**
325  Clears all messages in the message table by removing all items from the arrayController
326  */
327 - (void)clearMessageTable
328 {
329     NSRange range = NSMakeRange(0, [[_arrayController arrangedObjects] count]);
330     [_arrayController removeObjectsAtArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:range]];
331 }
332
333 /**
334  Appends all messages from the buffer to the arrayController and clears the buffer
335  */
336 - (void)appendMessageBuffer
337 {
338     if ([_messagesArray count] > 1000000) {
339         [_messagesArray removeObjectsInRange:NSMakeRange(0, 2)];
340     }
341     @synchronized (_messageBuffer) {
342         [_arrayController addObjects:_messageBuffer];
343         [_messageBuffer removeAllObjects];
344     }
345 }
346
347 @end