1 /*****************************************************************************
2 * VLCDebugMessageWindowController.m: Mac OS X interface crash reporter
3 *****************************************************************************
4 * Copyright (C) 2004-2013 VLC authors and VideoLAN
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>
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.
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.
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 *****************************************************************************/
26 #import "VLCDebugMessageWindowController.h"
28 #import <vlc_common.h>
30 @interface VLCDebugMessageWindowController () <NSWindowDelegate>
32 /* This array stores messages that are managed by the arrayController */
33 @property (retain) NSMutableArray *messagesArray;
35 /* This array stores messages before they are added to the messagesArray on refresh */
36 @property (retain) NSMutableArray *messageBuffer;
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
43 @property (retain) NSTimer *refreshTimer;
45 - (void)addMessage:(NSDictionary *)message;
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
54 static void MsgCallback(void *data, int type, const vlc_log_t *item, const char *format, va_list ap)
59 VLCDebugMessageWindowController *controller = (__bridge VLCDebugMessageWindowController*)data;
60 static NSString *types[4] = { @"info", @"error", @"warning", @"debug" };
62 if (vasprintf(&msg, format, ap) == -1) {
66 if (!item->psz_module || !msg) {
71 NSString *position = [NSString stringWithFormat:@"%s:%i", item->file, item->line];
73 NSDictionary *messageDict = @{
74 @"type" : types[type],
75 @"message" : toNSStr(msg),
76 @"component" : toNSStr(item->psz_module),
77 @"position" : position,
78 @"func" : toNSStr(item->func)
80 [controller addMessage:messageDict];
85 @implementation VLCDebugMessageWindowController
89 self = [super initWithWindowNibName:@"LogMessageWindow"];
91 _messagesArray = [[NSMutableArray alloc] initWithCapacity:500];
92 _messageBuffer = [[NSMutableArray alloc] initWithCapacity:100];
100 vlc_LogSet( getIntf()->obj.libvlc, NULL, NULL );
103 - (void)windowDidLoad
105 [self.window setExcludedFromWindowsMenu:YES];
106 [self.window setDelegate:self];
107 [self.window setTitle:_NS("Messages")];
109 #define setupButton(target, title, desc) \
110 [target accessibilitySetOverrideValue:title \
111 forAttribute:NSAccessibilityTitleAttribute]; \
112 [target accessibilitySetOverrideValue:desc \
113 forAttribute:NSAccessibilityDescriptionAttribute]; \
114 [target setToolTip:desc];
116 setupButton(_saveButton,
118 _NS("Click to save the debug log to a file."));
119 setupButton(_refreshButton,
121 _NS("Click to frefresh the log output."));
122 setupButton(_clearButton,
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."));
132 - (void)showWindow:(id)sender
134 // Do nothing if window is already visible
135 if ([self.window isVisible]) {
136 return [super showWindow:sender];
139 // Subscribe to LibVLCCore's messages
140 vlc_LogSet(getIntf()->obj.libvlc, MsgCallback, (__bridge void*)self);
141 _refreshTimer = [NSTimer scheduledTimerWithTimeInterval:0.3
143 selector:@selector(appendMessageBuffer)
146 return [super showWindow:sender];
149 - (void)windowWillClose:(NSNotification *)notification
151 // Unsubscribe from LibVLCCore's messages
152 vlc_LogSet( getIntf()->obj.libvlc, NULL, NULL );
154 // Remove all messages
155 [self clearMessageBuffer];
156 [self clearMessageTable];
159 [_refreshTimer invalidate];
164 #pragma mark Delegate methods
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
171 - (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
173 // Initialize background colors
174 static NSDictionary *colors = nil;
175 static dispatch_once_t onceToken;
176 dispatch_once(&onceToken, ^{
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]
185 // Lookup color for message type
186 NSDictionary *message = [[_arrayController arrangedObjects] objectAtIndex:row];
187 rowView.backgroundColor = [colors objectForKey:[message objectForKey:@"type"]];
190 - (void)splitViewDidResizeSubviews:(NSNotification *)notification
192 if ([_splitView isSubviewCollapsed:_detailView]) {
193 [_toggleDetailsButton setState:NSOffState];
195 [_toggleDetailsButton setState:NSOnState];
200 #pragma mark UI actions
202 /* Save debug log to file action
204 - (IBAction)saveDebugLog:(id)sender
206 NSSavePanel * saveFolderPanel = [[NSSavePanel alloc] init];
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) {
216 NSMutableString *string = [[NSMutableString alloc] init];
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];
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");
233 - (IBAction)clearLog:(id)sender
235 // Unregister handler
236 vlc_LogSet(getIntf()->obj.libvlc, NULL, NULL);
238 // Remove all messages
239 [self clearMessageBuffer];
240 [self clearMessageTable];
242 // Reregister handler, to write new header to log
243 vlc_LogSet(getIntf()->obj.libvlc, MsgCallback, (__bridge void*)self);
246 /* Refresh log action
248 - (IBAction)refreshLog:(id)sender
250 [self appendMessageBuffer];
251 [_messageTable scrollToEndOfDocument:self];
254 /* Show/Hide details action
256 - (IBAction)toggleDetails:(id)sender
258 if ([_splitView isSubviewCollapsed:_detailView]) {
259 [_detailView setHidden:NO];
261 [_detailView setHidden:YES];
265 /* Called when the user hits CMD + C or copy is clicked in the edit menu
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]];
280 #pragma mark UI validation
282 /* Validate the copy menu item
284 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
286 SEL theAction = [anItem action];
288 if (theAction == @selector(copy:)) {
289 if ([[_arrayController selectedObjects] count] > 0) {
294 /* Indicate that we handle the validation method,
295 * even if we don’t implement the action
301 #pragma mark Data handling
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.
307 - (void)addMessage:(NSDictionary *)messageDict
309 @synchronized (_messageBuffer) {
310 [_messageBuffer addObject:messageDict];
315 Clears the message buffer
317 - (void)clearMessageBuffer
319 @synchronized (_messageBuffer) {
320 [_messageBuffer removeAllObjects];
325 Clears all messages in the message table by removing all items from the arrayController
327 - (void)clearMessageTable
329 NSRange range = NSMakeRange(0, [[_arrayController arrangedObjects] count]);
330 [_arrayController removeObjectsAtArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:range]];
334 Appends all messages from the buffer to the arrayController and clears the buffer
336 - (void)appendMessageBuffer
338 if ([_messagesArray count] > 1000000) {
339 [_messagesArray removeObjectsInRange:NSMakeRange(0, 2)];
341 @synchronized (_messageBuffer) {
342 [_arrayController addObjects:_messageBuffer];
343 [_messageBuffer removeAllObjects];