You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

335 lines
10 KiB

/*****************************************************************************
* VLCLogWindowController.m: Log message window controller
*****************************************************************************
* Copyright (C) 2004-2013 VLC authors and VideoLAN
*
* Authors: Felix Paul Kühne <fkuehne at videolan dot org>
* Pierre d'Herbemont <pdherbemont # videolan org>
* Derk-Jan Hartman <hartman at videolan.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#import "VLCLogWindowController.h"
#import <vlc_common.h>
#import "main/VLCMain.h"
#import "windows/logging/VLCLogMessage.h"
@interface VLCLogWindowController () <NSWindowDelegate>
/* This array stores messages that are managed by the arrayController */
@property (retain) NSMutableArray *messagesArray;
/* This array stores messages before they are added to the messagesArray on refresh */
@property (retain) NSMutableArray *messageBuffer;
/* We do not want to refresh the table for every message, as that would be very frequent when
* there are a lot of messages, therefore we use a timer to refresh the table with new data
* from the messageBuffer every now and then, which is much more efficient and still fast
* enough for a good user experience
*/
@property (retain) NSTimer *refreshTimer;
- (void)addMessage:(VLCLogMessage *)message;
@end
/*
* MsgCallback: Callback triggered by the core once a new debug message is
* ready to be displayed. We store everything in a NSArray in our Cocoa part
* of this file.
*/
static void MsgCallback(void *data, int type, const vlc_log_t *item, const char *format, va_list ap)
{
@autoreleasepool {
char *msg;
VLCLogWindowController *controller = (__bridge VLCLogWindowController*)data;
if (vasprintf(&msg, format, ap) == -1) {
return;
}
[controller addMessage:[VLCLogMessage logMessage:msg
type:type
info:item]];
free(msg);
}
}
static const struct vlc_logger_operations log_ops = { MsgCallback, NULL };
@implementation VLCLogWindowController
- (id)init
{
self = [super initWithWindowNibName:@"LogMessageWindow"];
if (self) {
_messagesArray = [[NSMutableArray alloc] initWithCapacity:500];
_messageBuffer = [[NSMutableArray alloc] initWithCapacity:100];
}
return self;
}
- (void)dealloc
{
if (getIntf())
vlc_LogSet( vlc_object_instance(getIntf()), NULL, NULL );
}
- (void)windowDidLoad
{
[self.window setExcludedFromWindowsMenu:YES];
[self.window setDelegate:self];
[self.window setTitle:_NS("Messages")];
#define setupButton(target, title, desc) \
target.accessibilityTitle = title; \
target.accessibilityLabel = desc; \
[target setToolTip:desc];
setupButton(_saveButton,
_NS("Save log"),
_NS("Save the debug log to a file"));
setupButton(_refreshButton,
_NS("Refresh log"),
_NS("Refresh the log output"));
setupButton(_clearButton,
_NS("Clear log"),
_NS("Clear the log output"));
setupButton(_toggleDetailsButton,
_NS("Toggle details"),
_NS("Show/hide details about a log message"));
#undef setupButton
}
- (void)showWindow:(id)sender
{
// Do nothing if window is already visible
if ([self.window isVisible]) {
return [super showWindow:sender];
}
// Subscribe to LibVLCCore's messages
vlc_LogSet(vlc_object_instance(getIntf()), &log_ops, (__bridge void*)self);
_refreshTimer = [NSTimer scheduledTimerWithTimeInterval:0.3
target:self
selector:@selector(appendMessageBuffer)
userInfo:nil
repeats:YES];
return [super showWindow:sender];
}
- (void)windowWillClose:(NSNotification *)notification
{
// Unsubscribe from LibVLCCore's messages
vlc_LogSet( vlc_object_instance(getIntf()), NULL, NULL );
// Remove all messages
[self clearMessageBuffer];
[self clearMessageTable];
// Invalidate timer
[_refreshTimer invalidate];
_refreshTimer = nil;
}
#pragma mark -
#pragma mark Delegate methods
/*
* Called when a row is added to the table
* We use this to set the correct background color for the row, depending on the
* message type.
*/
- (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
{
// Initialize background colors
static NSDictionary *colors = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
colors = @{
@(VLC_MSG_INFO): [NSColor colorWithCalibratedRed:0.65 green:0.91 blue:1.0 alpha:0.7],
@(VLC_MSG_ERR) : [NSColor colorWithCalibratedRed:1.0 green:0.49 blue:0.45 alpha:0.5],
@(VLC_MSG_WARN): [NSColor colorWithCalibratedRed:1.0 green:0.88 blue:0.45 alpha:0.7],
@(VLC_MSG_DBG) : [NSColor colorWithCalibratedRed:0.96 green:0.96 blue:0.96 alpha:0.5]
};
});
// Lookup color for message type
VLCLogMessage *message = [[_arrayController arrangedObjects] objectAtIndex:row];
rowView.backgroundColor = [colors objectForKey:@(message.type)];
}
- (void)splitViewDidResizeSubviews:(NSNotification *)notification
{
if ([_splitView isSubviewCollapsed:_detailView]) {
[_toggleDetailsButton setState:NSOffState];
} else {
[_toggleDetailsButton setState:NSOnState];
}
}
#pragma mark -
#pragma mark UI actions
/* Save debug log to file action
*/
- (IBAction)saveDebugLog:(id)sender
{
NSSavePanel * saveFolderPanel = [[NSSavePanel alloc] init];
[saveFolderPanel setCanSelectHiddenExtension: NO];
[saveFolderPanel setCanCreateDirectories: YES];
[saveFolderPanel setAllowedFileTypes: [NSArray arrayWithObject:@"txt"]];
[saveFolderPanel setNameFieldStringValue:[NSString stringWithFormat: _NS("VLC Debug Log (%s).txt"), VERSION_MESSAGE]];
[saveFolderPanel beginSheetModalForWindow: self.window completionHandler:^(NSInteger returnCode) {
if (returnCode != NSModalResponseOK) {
return;
}
NSMutableString *string = [[NSMutableString alloc] init];
for (VLCLogMessage *message in self->_messagesArray) {
[string appendFormat:@"%@\r\n", message.fullMessage];
}
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
if ([data writeToFile:[[saveFolderPanel URL] path] atomically:YES] == NO)
msg_Warn(getIntf(), "Error while saving the debug log");
}];
}
/* Clear log action
*/
- (IBAction)clearLog:(id)sender
{
// Unregister handler
vlc_LogSet(vlc_object_instance(getIntf()), NULL, NULL);
// Remove all messages
[self clearMessageBuffer];
[self clearMessageTable];
// Reregister handler, to write new header to log
vlc_LogSet(vlc_object_instance(getIntf()), &log_ops, (__bridge void*)self);
}
/* Refresh log action
*/
- (IBAction)refreshLog:(id)sender
{
[self appendMessageBuffer];
[_messageTable scrollToEndOfDocument:self];
}
/* Show/Hide details action
*/
- (IBAction)toggleDetails:(id)sender
{
if ([_splitView isSubviewCollapsed:_detailView]) {
[_detailView setHidden:NO];
} else {
[_detailView setHidden:YES];
}
}
/* Called when the user hits CMD + C or copy is clicked in the edit menu
*/
- (void) copy:(id)sender {
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard clearContents];
for (VLCLogMessage *message in [_arrayController selectedObjects]) {
[pasteBoard writeObjects:@[message.fullMessage]];
}
}
#pragma mark -
#pragma mark UI validation
/* Validate the copy menu item
*/
- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
SEL theAction = [anItem action];
if (theAction == @selector(copy:)) {
if ([[_arrayController selectedObjects] count] > 0) {
return YES;
}
return NO;
}
/* Indicate that we handle the validation method,
* even if we don’t implement the action
*/
return YES;
}
#pragma mark -
#pragma mark Data handling
/**
Adds a message to the messageBuffer, it does not has to be called from the main thread, as
items are only added to the messageArray on refresh.
*/
- (void)addMessage:(VLCLogMessage *)message
{
if (!message)
return;
@synchronized (_messageBuffer) {
[_messageBuffer addObject:message];
}
}
/**
Clears the message buffer
*/
- (void)clearMessageBuffer
{
@synchronized (_messageBuffer) {
[_messageBuffer removeAllObjects];
}
}
/**
Clears all messages in the message table by removing all items from the messagesArray
*/
- (void)clearMessageTable
{
[self willChangeValueForKey:@"messagesArray"];
[_messagesArray removeAllObjects];
[self didChangeValueForKey:@"messagesArray"];}
/**
Appends all messages from the buffer to the messagesArray and clears the buffer
*/
- (void)appendMessageBuffer
{
static const NSUInteger limit = 1000000;
[self willChangeValueForKey:@"messagesArray"];
@synchronized (_messageBuffer) {
[_messagesArray addObjectsFromArray:_messageBuffer];
[_messageBuffer removeAllObjects];
}
if ([_messagesArray count] > limit) {
[_messagesArray removeObjectsInRange:NSMakeRange(0, _messagesArray.count - limit)];
}
[self didChangeValueForKey:@"messagesArray"];
}
@end