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.
 
 
 
 
 
 

640 lines
23 KiB

/*****************************************************************************
* MainWindowTitle.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2011-2012 Felix Paul Kühne
* $Id$
*
* Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- 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 <vlc_common.h>
#import "VLCMain.h"
#import "MainWindowTitle.h"
#import "VLCCoreInteraction.h"
#import "CompatibilityFixes.h"
#import <SystemConfiguration/SystemConfiguration.h> // for the revealInFinder clone
/*****************************************************************************
* VLCMainWindowTitleView
*
* this is our title bar, which can do anything a title should do
* it relies on the VLCWindowButtonCell to display the correct traffic light
* states, since we can't capture the mouse-moved events here correctly
*****************************************************************************/
@interface VLCMainWindowTitleView()
{
NSImage *_redImage;
NSImage *_redHoverImage;
NSImage *_redOnClickImage;
NSImage * _yellowImage;
NSImage * _yellowHoverImage;
NSImage * _yellowOnClickImage;
NSImage * _greenImage;
NSImage * _greenHoverImage;
NSImage * _greenOnClickImage;
// yosemite fullscreen images
NSImage * _fullscreenImage;
NSImage * _fullscreenHoverImage;
NSImage * _fullscreenOnClickImage;
// old native fullscreen images
NSImage * _oldFullscreenImage;
NSImage * _oldFullscreenHoverImage;
NSImage * _oldFullscreenOnClickImage;
NSShadow * _windowTitleShadow;
NSDictionary * _windowTitleAttributesDictionary;
BOOL b_nativeFullscreenMode;
// state to determine correct image for green bubble
BOOL b_alt_pressed;
BOOL b_mouse_over;
}
@end
@implementation VLCMainWindowTitleView
- (id)init
{
self = [super init];
if (self) {
_windowTitleAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys: [NSColor whiteColor], NSForegroundColorAttributeName, [NSFont titleBarFontOfSize:12.0], NSFontAttributeName, nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
- (void)awakeFromNib
{
b_nativeFullscreenMode = var_InheritBool(getIntf(), "macosx-nativefullscreenmode");
if (!b_nativeFullscreenMode || OSX_YOSEMITE || OSX_EL_CAPITAN || OSX_SIERRA) {
[_fullscreenButton setHidden: YES];
}
[self setAutoresizesSubviews: YES];
[self setImagesLeft:imageFromRes(@"topbar-dark-left") middle: imageFromRes(@"topbar-dark-center-fill") right:imageFromRes(@"topbar-dark-right")];
[self loadButtonIcons];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(controlTintChanged:) name: NSControlTintDidChangeNotification object: nil];
}
- (void)controlTintChanged:(NSNotification *)notification
{
[self loadButtonIcons];
[_redButton setNeedsDisplay];
[_yellowButton setNeedsDisplay];
[_greenButton setNeedsDisplay];
}
- (void)informModifierPressed:(BOOL)b_is_altkey;
{
BOOL b_state_changed = b_alt_pressed != b_is_altkey;
b_alt_pressed = b_is_altkey;
if (b_state_changed) {
[self updateGreenButton];
}
}
- (NSImage *)getButtonImage:(NSString *)o_id
{
NSString *o_name = @"";
if (OSX_YOSEMITE || OSX_EL_CAPITAN) {
o_name = @"yosemite-";
} else { // OSX_LION, OSX_MOUNTAIN_LION, OSX_MAVERICKS
o_name = @"lion-";
}
o_name = [o_name stringByAppendingString:o_id];
if ([NSColor currentControlTint] != NSBlueControlTint) {
o_name = [o_name stringByAppendingString:@"-graphite"];
}
return [NSImage imageNamed:o_name];
}
- (void)loadButtonIcons
{
_redImage = [self getButtonImage:@"window-close"];
_redHoverImage = [self getButtonImage:@"window-close-over"];
_redOnClickImage = [self getButtonImage:@"window-close-on"];
_yellowImage = [self getButtonImage:@"window-minimize"];
_yellowHoverImage = [self getButtonImage:@"window-minimize-over"];
_yellowOnClickImage = [self getButtonImage:@"window-minimize-on"];
_greenImage = [self getButtonImage:@"window-zoom"];
_greenHoverImage = [self getButtonImage:@"window-zoom-over"];
_greenOnClickImage = [self getButtonImage:@"window-zoom-on"];
// these files are only available in the yosemite variant
if (OSX_YOSEMITE || OSX_EL_CAPITAN) {
_fullscreenImage = [self getButtonImage:@"window-fullscreen"];
_fullscreenHoverImage = [self getButtonImage:@"window-fullscreen-over"];
_fullscreenOnClickImage = [self getButtonImage:@"window-fullscreen-on"];
}
// old native fullscreen images are not available in graphite style
// thus they are loaded directly here
_oldFullscreenImage = [NSImage imageNamed:@"lion-window-fullscreen"];
_oldFullscreenOnClickImage = [NSImage imageNamed:@"lion-window-fullscreen-on"];
_oldFullscreenHoverImage = [NSImage imageNamed:@"lion-window-fullscreen-over"];
[_redButton setImage: _redImage];
[_redButton setAlternateImage: _redHoverImage];
[[_redButton cell] setShowsBorderOnlyWhileMouseInside: YES];
[[_redButton cell] setTag: 0];
[_yellowButton setImage: _yellowImage];
[_yellowButton setAlternateImage: _yellowHoverImage];
[[_yellowButton cell] setShowsBorderOnlyWhileMouseInside: YES];
[[_yellowButton cell] setTag: 1];
[self updateGreenButton];
[[_greenButton cell] setShowsBorderOnlyWhileMouseInside: YES];
[[_greenButton cell] setTag: 2];
[_fullscreenButton setImage: _oldFullscreenImage];
[_fullscreenButton setAlternateImage: _oldFullscreenHoverImage];
[[_fullscreenButton cell] setShowsBorderOnlyWhileMouseInside: YES];
[[_fullscreenButton cell] setTag: 3];
}
- (void)updateGreenButton
{
// default image for old version, or if native fullscreen is
// disabled on yosemite, or if alt key is pressed
if (!(OSX_YOSEMITE || OSX_EL_CAPITAN) || !b_nativeFullscreenMode || b_alt_pressed) {
if (b_mouse_over) {
[_greenButton setImage: _greenHoverImage];
[_greenButton setAlternateImage: _greenOnClickImage];
} else {
[_greenButton setImage: _greenImage];
[_greenButton setAlternateImage: _greenOnClickImage];
}
} else {
if (b_mouse_over) {
[_greenButton setImage: _fullscreenHoverImage];
[_greenButton setAlternateImage: _fullscreenOnClickImage];
} else {
[_greenButton setImage: _fullscreenImage];
[_greenButton setAlternateImage: _fullscreenOnClickImage];
}
}
}
- (BOOL)mouseDownCanMoveWindow
{
return YES;
}
- (IBAction)buttonAction:(id)sender
{
if (sender == _redButton)
[[self window] performClose: sender];
else if (sender == _yellowButton)
[[self window] miniaturize: sender];
else if (sender == _greenButton) {
if ((OSX_YOSEMITE || OSX_EL_CAPITAN) && b_nativeFullscreenMode && !b_alt_pressed) {
[[self window] toggleFullScreen:self];
} else {
[[self window] performZoom: sender];
}
} else if (sender == _fullscreenButton) {
// same action as native fs button
[[self window] toggleFullScreen:self];
} else
msg_Err(getIntf(), "unknown button action sender");
[self setWindowButtonOver: NO];
[self setWindowFullscreenButtonOver: NO];
}
- (void)setWindowTitle:(NSString *)title
{
if (!_windowTitleShadow) {
_windowTitleShadow = [[NSShadow alloc] init];
[_windowTitleShadow setShadowColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.5]];
[_windowTitleShadow setShadowOffset:NSMakeSize(0.0, -1.5)];
[_windowTitleShadow setShadowBlurRadius:0.5];
}
NSMutableAttributedString *attributedTitleString = [[NSMutableAttributedString alloc] initWithString:title attributes: _windowTitleAttributesDictionary];
NSUInteger i_titleLength = [title length];
[attributedTitleString addAttribute:NSShadowAttributeName value:_windowTitleShadow range:NSMakeRange(0, i_titleLength)];
[attributedTitleString setAlignment: NSCenterTextAlignment range:NSMakeRange(0, i_titleLength)];
[_titleLabel setAttributedStringValue:attributedTitleString];
}
- (void)setWindowButtonOver:(BOOL)b_value
{
b_mouse_over = b_value;
if (b_value) {
[_redButton setImage: _redHoverImage];
[_yellowButton setImage: _yellowHoverImage];
} else {
[_redButton setImage: _redImage];
[_yellowButton setImage: _yellowImage];
}
[self updateGreenButton];
}
- (void)setWindowFullscreenButtonOver:(BOOL)b_value
{
if (b_value)
[_fullscreenButton setImage: _oldFullscreenHoverImage];
else
[_fullscreenButton setImage: _oldFullscreenImage];
}
- (void)mouseDown:(NSEvent *)event
{
NSPoint ml = [self convertPoint: [event locationInWindow] fromView: self];
if (([[self window] frame].size.height - ml.y) <= 22. && [event clickCount] == 2) {
//Get settings from "System Preferences" > "Appearance" > "Double-click on windows title bar to minimize"
NSString *const MDAppleMiniaturizeOnDoubleClickKey = @"AppleMiniaturizeOnDoubleClick";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults addSuiteNamed:NSGlobalDomain];
if ([[userDefaults objectForKey:MDAppleMiniaturizeOnDoubleClickKey] boolValue])
[[self window] miniaturize:self];
}
[super mouseDown: event];
}
- (NSButton*)closeButton
{
return _redButton;
}
- (NSButton*)minimizeButton
{
return _yellowButton;
}
- (NSButton*)zoomButton
{
return _greenButton;
}
@end
/*****************************************************************************
* VLCWindowButtonCell
*
* since the title bar cannot fetch these mouse events (the more top-level
* NSButton is unable fetch them as well), we are using a subclass of the
* button cell to do so. It's set in the nib for the respective objects.
*****************************************************************************/
@implementation VLCWindowButtonCell
- (void)mouseEntered:(NSEvent *)theEvent
{
if ([self tag] == 3)
[(VLCMainWindowTitleView *)[[self controlView] superview] setWindowFullscreenButtonOver: YES];
else
[(VLCMainWindowTitleView *)[[self controlView] superview] setWindowButtonOver: YES];
}
- (void)mouseExited:(NSEvent *)theEvent
{
if ([self tag] == 3)
[(VLCMainWindowTitleView *)[[self controlView] superview] setWindowFullscreenButtonOver: NO];
else
[(VLCMainWindowTitleView *)[[self controlView] superview] setWindowButtonOver: NO];
}
/* accessibility stuff */
- (NSArray*)accessibilityAttributeNames {
NSArray *theAttributeNames = [super accessibilityAttributeNames];
id theControlView = [self controlView];
return ([theControlView respondsToSelector: @selector(extendedAccessibilityAttributeNames:)] ? [theControlView extendedAccessibilityAttributeNames: theAttributeNames] : theAttributeNames); // ask the cell's control view (i.e., the button) for additional attribute values
}
- (id)accessibilityAttributeValue: (NSString*)theAttributeName {
id theControlView = [self controlView];
if ([theControlView respondsToSelector: @selector(extendedAccessibilityAttributeValue:)]) {
id theValue = [theControlView extendedAccessibilityAttributeValue: theAttributeName];
if (theValue) {
return theValue; // if this is an extended attribute value we added, return that -- otherwise, fall back to super's implementation
}
}
return [super accessibilityAttributeValue: theAttributeName];
}
- (BOOL)accessibilityIsAttributeSettable: (NSString*)theAttributeName {
id theControlView = [self controlView];
if ([theControlView respondsToSelector: @selector(extendedAccessibilityIsAttributeSettable:)]) {
NSNumber *theValue = [theControlView extendedAccessibilityIsAttributeSettable: theAttributeName];
if (theValue)
return [theValue boolValue]; // same basic strategy we use in -accessibilityAttributeValue:
}
return [super accessibilityIsAttributeSettable: theAttributeName];
}
@end
/*****************************************************************************
* VLCResizeControl
*
* For Leopard and Snow Leopard, we need to emulate the resize control on the
* bottom right of the window, since it is gone by using the borderless window
* mask. A proper fix would be Lion-only.
*****************************************************************************/
@implementation VLCResizeControl
- (void)mouseDown:(NSEvent *)theEvent {
BOOL keepOn = YES;
while (keepOn) {
theEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask |
NSLeftMouseDraggedMask];
switch ([theEvent type]) {
case NSLeftMouseDragged:
{
NSRect windowFrame = [[self window] frame];
CGFloat deltaX, deltaY, oldOriginY;
deltaX = [theEvent deltaX];
deltaY = [theEvent deltaY];
oldOriginY = windowFrame.origin.y;
windowFrame.origin.y = (oldOriginY + windowFrame.size.height) - (windowFrame.size.height + deltaY);
windowFrame.size.width += deltaX;
windowFrame.size.height += deltaY;
NSSize winMinSize = [self window].minSize;
if (windowFrame.size.width < winMinSize.width)
windowFrame.size.width = winMinSize.width;
if (windowFrame.size.height < winMinSize.height) {
windowFrame.size.height = winMinSize.height;
windowFrame.origin.y = oldOriginY;
}
[[self window] setFrame: windowFrame display: YES animate: NO];
break;
}
break;
case NSLeftMouseUp:
keepOn = NO;
break;
default:
/* Ignore any other kind of event. */
break;
}
};
return;
}
@end
/*****************************************************************************
* VLCColorView
*
* since we are using a clear window color when using the black window
* style, some filling is needed behind the video and some other elements
*****************************************************************************/
@implementation VLCColorView
- (void)drawRect:(NSRect)rect {
[[NSColor blackColor] setFill];
NSRectFill(rect);
}
@end
/*****************************************************************************
* custom window buttons to support the accessibility stuff
*****************************************************************************/
@implementation VLCCustomWindowButtonPrototype
+ (Class)cellClass {
return [VLCWindowButtonCell class];
}
- (NSArray*)extendedAccessibilityAttributeNames: (NSArray*)theAttributeNames {
return ([theAttributeNames containsObject: NSAccessibilitySubroleAttribute] ? theAttributeNames : [theAttributeNames arrayByAddingObject: NSAccessibilitySubroleAttribute]); // run-of-the-mill button cells don't usually have a Subrole attribute, so we add that attribute
}
- (id)extendedAccessibilityAttributeValue: (NSString*)theAttributeName {
return nil;
}
- (NSNumber*)extendedAccessibilityIsAttributeSettable: (NSString*)theAttributeName {
return ([theAttributeName isEqualToString: NSAccessibilitySubroleAttribute] ? [NSNumber numberWithBool:NO] : nil); // make the Subrole attribute we added non-settable
}
- (void)accessibilityPerformAction: (NSString*)theActionName {
if ([theActionName isEqualToString: NSAccessibilityPressAction]) {
if ([self isEnabled])
[self performClick: nil];
} else
[super accessibilityPerformAction: theActionName];
}
@end
@implementation VLCCustomWindowCloseButton
- (id)extendedAccessibilityAttributeValue: (NSString*)theAttributeName {
return ([theAttributeName isEqualToString: NSAccessibilitySubroleAttribute] ? NSAccessibilityCloseButtonAttribute : nil);
}
@end
@implementation VLCCustomWindowMinimizeButton
- (id)extendedAccessibilityAttributeValue: (NSString*)theAttributeName {
return ([theAttributeName isEqualToString: NSAccessibilitySubroleAttribute] ? NSAccessibilityMinimizeButtonAttribute : nil);
}
@end
@implementation VLCCustomWindowZoomButton
- (id)extendedAccessibilityAttributeValue: (NSString*)theAttributeName {
return ([theAttributeName isEqualToString: NSAccessibilitySubroleAttribute] ? NSAccessibilityZoomButtonAttribute : nil);
}
@end
@implementation VLCCustomWindowFullscreenButton
- (id)extendedAccessibilityAttributeValue: (NSString*)theAttributeName {
return ([theAttributeName isEqualToString: NSAccessibilitySubroleAttribute] ? NSAccessibilityFullScreenButtonAttribute : nil);
}
@end
@interface VLCWindowTitleTextField()
{
NSMenu *_contextMenu;
}
@end
@implementation VLCWindowTitleTextField
- (void)showRightClickMenuWithEvent:(NSEvent *)o_event
{
NSURL * representedURL = [[self window] representedURL];
if (!representedURL)
return;
NSArray * pathComponents;
pathComponents = [representedURL pathComponents];
if (!pathComponents)
return;
_contextMenu = [[NSMenu alloc] initWithTitle: [[NSFileManager defaultManager] displayNameAtPath: [representedURL path]]];
NSUInteger count = [pathComponents count];
NSImage * icon;
NSMenuItem * currentItem;
NSMutableString * currentPath;
NSSize iconSize = NSMakeSize(16., 16.);
for (NSUInteger i = count - 1; i > 0; i--) {
currentPath = [NSMutableString stringWithCapacity:1024];
for (NSUInteger y = 0; y < i; y++)
[currentPath appendFormat: @"/%@", [pathComponents objectAtIndex:y + 1]];
[_contextMenu addItemWithTitle: [[NSFileManager defaultManager] displayNameAtPath: currentPath] action:@selector(revealInFinder:) keyEquivalent:@""];
currentItem = [_contextMenu itemAtIndex:[_contextMenu numberOfItems] - 1];
[currentItem setTarget: self];
icon = [[NSWorkspace sharedWorkspace] iconForFile:currentPath];
[icon setSize: iconSize];
[currentItem setImage: icon];
}
if ([[pathComponents objectAtIndex:1] isEqualToString:@"Volumes"]) {
/* we don't want to show the Volumes item, since the Cocoa does it neither */
currentItem = [_contextMenu itemWithTitle:[[NSFileManager defaultManager] displayNameAtPath: @"/Volumes"]];
if (currentItem)
[_contextMenu removeItem: currentItem];
} else {
/* we're on the boot drive, so add it since it isn't part of the components */
[_contextMenu addItemWithTitle: [[NSFileManager defaultManager] displayNameAtPath:@"/"] action:@selector(revealInFinder:) keyEquivalent:@""];
currentItem = [_contextMenu itemAtIndex: [_contextMenu numberOfItems] - 1];
icon = [[NSWorkspace sharedWorkspace] iconForFile:@"/"];
[icon setSize: iconSize];
[currentItem setImage: icon];
[currentItem setTarget: self];
}
/* add the computer item */
[_contextMenu addItemWithTitle:(NSString*)CFBridgingRelease(SCDynamicStoreCopyComputerName(NULL, NULL)) action:@selector(revealInFinder:) keyEquivalent:@""];
currentItem = [_contextMenu itemAtIndex: [_contextMenu numberOfItems] - 1];
icon = [NSImage imageNamed: NSImageNameComputer];
[icon setSize: iconSize];
[currentItem setImage: icon];
[currentItem setTarget: self];
// center the context menu similar to the white interface
CGFloat menuWidth = [_contextMenu size].width;
NSRect windowFrame = [[self window] frame];
NSPoint point;
CGFloat fullButtonWidth = 0.;
if([[VLCMain sharedInstance] nativeFullscreenMode])
fullButtonWidth = 20.;
// assumes 60 px for the window buttons
point.x = (windowFrame.size.width - 60. - fullButtonWidth) / 2. - menuWidth / 2. + 60. - 20.;
point.y = windowFrame.size.height + 1.;
if (point.x < 0)
point.x = 10;
NSEvent *fakeMouseEvent = [NSEvent mouseEventWithType:NSRightMouseDown
location:point
modifierFlags:0
timestamp:0
windowNumber:[[self window] windowNumber]
context:nil
eventNumber:0
clickCount:0
pressure:0];
[NSMenu popUpContextMenu: _contextMenu withEvent: fakeMouseEvent forView: [self superview]];
}
- (IBAction)revealInFinder:(id)sender
{
NSUInteger count = [_contextMenu numberOfItems];
NSUInteger selectedItem = [_contextMenu indexOfItem: sender];
if (selectedItem == count - 1) { // the fake computer item
[[NSWorkspace sharedWorkspace] selectFile: @"/" inFileViewerRootedAtPath: @""];
return;
}
NSURL * representedURL = [[self window] representedURL];
if (! representedURL)
return;
if (selectedItem == 0) { // the actual file, let's save time
[[NSWorkspace sharedWorkspace] selectFile: [representedURL path] inFileViewerRootedAtPath: [representedURL path]];
return;
}
NSArray * pathComponents;
pathComponents = [representedURL pathComponents];
if (!pathComponents)
return;
NSMutableString * currentPath;
currentPath = [NSMutableString stringWithCapacity:1024];
selectedItem = count - selectedItem;
/* fix for non-startup volumes */
if ([[pathComponents objectAtIndex:1] isEqualToString:@"Volumes"])
selectedItem += 1;
for (NSUInteger y = 1; y < selectedItem; y++)
[currentPath appendFormat: @"/%@", [pathComponents objectAtIndex:y]];
[[NSWorkspace sharedWorkspace] selectFile: currentPath inFileViewerRootedAtPath: currentPath];
}
- (void)rightMouseDown:(NSEvent *)o_event
{
if ([o_event type] == NSRightMouseDown)
[self showRightClickMenuWithEvent:o_event];
[super mouseDown: o_event];
}
@end