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")];
108 [self.window setLevel:NSModalPanelWindowLevel];
110 #define setupButton(target, title, desc) \
111 [target accessibilitySetOverrideValue:title \
112 forAttribute:NSAccessibilityTitleAttribute]; \
113 [target accessibilitySetOverrideValue:desc \
114 forAttribute:NSAccessibilityDescriptionAttribute]; \
115 [target setToolTip:desc];
117 setupButton(_saveButton,
119 _NS("Click to save the debug log to a file."));
120 setupButton(_refreshButton,
122 _NS("Click to frefresh the log output."));
123 setupButton(_clearButton,
125 _NS("Click to clear the log output."));
126 setupButton(_toggleDetailsButton,
127 _NS("Toggle details"),
128 _NS("Click to show/hide details about a log message."));
133 - (void)showWindow:(id)sender
135 // Do nothing if window is already visible
136 if ([self.window isVisible]) {
137 return [super showWindow:sender];
140 // Subscribe to LibVLCCore's messages
141 vlc_LogSet(getIntf()->obj.libvlc, MsgCallback, (__bridge void*)self);
142 _refreshTimer = [NSTimer scheduledTimerWithTimeInterval:0.3
144 selector:@selector(appendMessageBuffer)
147 return [super showWindow:sender];
150 - (void)windowWillClose:(NSNotification *)notification
152 // Unsubscribe from LibVLCCore's messages
153 vlc_LogSet( getIntf()->obj.libvlc, NULL, NULL );
155 // Remove all messages
156 [self clearMessageBuffer];
157 [self clearMessageTable];
160 [_refreshTimer invalidate];
165 #pragma mark Delegate methods
168 * Called when a row is added to the table
169 * We use this to set the correct background color for the row, depending on the
172 - (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
174 // Initialize background colors
175 static NSDictionary *colors = nil;
176 static dispatch_once_t onceToken;
177 dispatch_once(&onceToken, ^{
179 @"info" : [NSColor colorWithCalibratedRed:0.65 green:0.91 blue:1.0 alpha:0.7],
180 @"error" : [NSColor colorWithCalibratedRed:1.0 green:0.49 blue:0.45 alpha:0.5],
181 @"warning" : [NSColor colorWithCalibratedRed:1.0 green:0.88 blue:0.45 alpha:0.7],
182 @"debug" : [NSColor colorWithCalibratedRed:0.96 green:0.96 blue:0.96 alpha:0.5]
186 // Lookup color for message type
187 NSDictionary *message = [[_arrayController arrangedObjects] objectAtIndex:row];
188 rowView.backgroundColor = [colors objectForKey:[message objectForKey:@"type"]];
191 - (void)splitViewDidResizeSubviews:(NSNotification *)notification
193 if ([_splitView isSubviewCollapsed:_detailView]) {
194 [_toggleDetailsButton setState:NSOffState];
196 [_toggleDetailsButton setState:NSOnState];
201 #pragma mark UI actions
203 /* Save debug log to file action
205 - (IBAction)saveDebugLog:(id)sender
207 NSSavePanel * saveFolderPanel = [[NSSavePanel alloc] init];
209 [saveFolderPanel setCanSelectHiddenExtension: NO];
210 [saveFolderPanel setCanCreateDirectories: YES];
211 [saveFolderPanel setAllowedFileTypes: [NSArray arrayWithObject:@"txt"]];
212 [saveFolderPanel setNameFieldStringValue:[NSString stringWithFormat: _NS("VLC Debug Log (%s).txt"), VERSION_MESSAGE]];
213 [saveFolderPanel beginSheetModalForWindow: self.window completionHandler:^(NSInteger returnCode) {
214 if (returnCode != NSOKButton) {
217 NSMutableString *string = [[NSMutableString alloc] init];
219 for (NSDictionary *line in _messagesArray) {
220 NSString *message = [NSString stringWithFormat:@"%@ %@ %@\n",
221 [line objectForKey:@"component"],
222 [line objectForKey:@"type"],
223 [line objectForKey:@"message"]];
224 [string appendString:message];
226 NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
227 if ([data writeToFile: [[saveFolderPanel URL] path] atomically: YES] == NO)
228 msg_Warn(getIntf(), "Error while saving the debug log");
234 - (IBAction)clearLog:(id)sender
236 // Unregister handler
237 vlc_LogSet(getIntf()->obj.libvlc, NULL, NULL);
239 // Remove all messages
240 [self clearMessageBuffer];
241 [self clearMessageTable];
243 // Reregister handler, to write new header to log
244 vlc_LogSet(getIntf()->obj.libvlc, MsgCallback, (__bridge void*)self);
247 /* Refresh log action
249 - (IBAction)refreshLog:(id)sender
251 [self appendMessageBuffer];
252 [_messageTable scrollToEndOfDocument:self];
255 /* Show/Hide details action
257 - (IBAction)toggleDetails:(id)sender
259 if ([_splitView isSubviewCollapsed:_detailView]) {
260 [_detailView setHidden:NO];
262 [_detailView setHidden:YES];
266 /* Called when the user hits CMD + C or copy is clicked in the edit menu
268 - (void) copy:(id)sender {
269 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
270 [pasteBoard clearContents];
271 for (NSDictionary *line in [_arrayController selectedObjects]) {
272 NSString *message = [NSString stringWithFormat:@"%@ %@ %@",
273 [line objectForKey:@"component"],
274 [line objectForKey:@"type"],
275 [line objectForKey:@"message"]];
276 [pasteBoard writeObjects:@[message]];
281 #pragma mark UI validation
283 /* Validate the copy menu item
285 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
287 SEL theAction = [anItem action];
289 if (theAction == @selector(copy:)) {
290 if ([[_arrayController selectedObjects] count] > 0) {
295 /* Indicate that we handle the validation method,
296 * even if we don’t implement the action
302 #pragma mark Data handling
305 Adds a message to the messageBuffer, it does not has to be called from the main thread, as
306 items are only added to the messageArray on refresh.
308 - (void)addMessage:(NSDictionary *)messageDict
310 @synchronized (_messageBuffer) {
311 [_messageBuffer addObject:messageDict];
316 Clears the message buffer
318 - (void)clearMessageBuffer
320 @synchronized (_messageBuffer) {
321 [_messageBuffer removeAllObjects];
326 Clears all messages in the message table by removing all items from the arrayController
328 - (void)clearMessageTable
330 NSRange range = NSMakeRange(0, [[_arrayController arrangedObjects] count]);
331 [_arrayController removeObjectsAtArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:range]];
335 Appends all messages from the buffer to the arrayController and clears the buffer
337 - (void)appendMessageBuffer
339 if ([_messagesArray count] > 1000000) {
340 [_messagesArray removeObjectsInRange:NSMakeRange(0, 2)];
342 @synchronized (_messageBuffer) {
343 [_arrayController addObjects:_messageBuffer];
344 [_messageBuffer removeAllObjects];