refactor: rename the atom directory to shell
This commit is contained in:
parent
4575a4aae3
commit
d7f07e8a80
631 changed files with 0 additions and 0 deletions
18
shell/browser/ui/cocoa/NSColor+Hex.h
Normal file
18
shell/browser/ui/cocoa/NSColor+Hex.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Created by Mathias Leppich on 03/02/14.
|
||||
// Copyright (c) 2014 Bit Bar. All rights reserved.
|
||||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface NSColor (Hex)
|
||||
- (NSString*)hexadecimalValue;
|
||||
- (NSString*)RGBAValue;
|
||||
+ (NSColor*)colorWithHexColorString:(NSString*)hex;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_
|
77
shell/browser/ui/cocoa/NSColor+Hex.mm
Normal file
77
shell/browser/ui/cocoa/NSColor+Hex.mm
Normal file
|
@ -0,0 +1,77 @@
|
|||
// Created by Mathias Leppich on 03/02/14.
|
||||
// Copyright (c) 2014 Bit Bar. All rights reserved.
|
||||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/NSColor+Hex.h"
|
||||
|
||||
@implementation NSColor (Hex)
|
||||
|
||||
- (NSString*)RGBAValue {
|
||||
double redFloatValue, greenFloatValue, blueFloatValue, alphaFloatValue;
|
||||
|
||||
NSColor* convertedColor =
|
||||
[self colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
|
||||
|
||||
if (convertedColor) {
|
||||
[convertedColor getRed:&redFloatValue
|
||||
green:&greenFloatValue
|
||||
blue:&blueFloatValue
|
||||
alpha:&alphaFloatValue];
|
||||
|
||||
int redIntValue = redFloatValue * 255.99999f;
|
||||
int greenIntValue = greenFloatValue * 255.99999f;
|
||||
int blueIntValue = blueFloatValue * 255.99999f;
|
||||
int alphaIntValue = alphaFloatValue * 255.99999f;
|
||||
|
||||
return
|
||||
[NSString stringWithFormat:@"%02x%02x%02x%02x", redIntValue,
|
||||
greenIntValue, blueIntValue, alphaIntValue];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString*)hexadecimalValue {
|
||||
double redFloatValue, greenFloatValue, blueFloatValue;
|
||||
|
||||
NSColor* convertedColor =
|
||||
[self colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
|
||||
|
||||
if (convertedColor) {
|
||||
[convertedColor getRed:&redFloatValue
|
||||
green:&greenFloatValue
|
||||
blue:&blueFloatValue
|
||||
alpha:NULL];
|
||||
|
||||
int redIntValue = redFloatValue * 255.99999f;
|
||||
int greenIntValue = greenFloatValue * 255.99999f;
|
||||
int blueIntValue = blueFloatValue * 255.99999f;
|
||||
|
||||
return [NSString stringWithFormat:@"#%02x%02x%02x", redIntValue,
|
||||
greenIntValue, blueIntValue];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSColor*)colorWithHexColorString:(NSString*)inColorString {
|
||||
unsigned colorCode = 0;
|
||||
|
||||
if (inColorString) {
|
||||
NSScanner* scanner = [NSScanner scannerWithString:inColorString];
|
||||
(void)[scanner scanHexInt:&colorCode]; // ignore error
|
||||
}
|
||||
|
||||
unsigned char redByte = (unsigned char)(colorCode >> 16);
|
||||
unsigned char greenByte = (unsigned char)(colorCode >> 8);
|
||||
unsigned char blueByte = (unsigned char)(colorCode); // masks off high bits
|
||||
|
||||
return [NSColor colorWithCalibratedRed:(CGFloat)redByte / 0xff
|
||||
green:(CGFloat)greenByte / 0xff
|
||||
blue:(CGFloat)blueByte / 0xff
|
||||
alpha:1.0];
|
||||
}
|
||||
|
||||
@end
|
17
shell/browser/ui/cocoa/NSString+ANSI.h
Normal file
17
shell/browser/ui/cocoa/NSString+ANSI.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Created by Kent Karlsson on 3/11/16.
|
||||
// Copyright (c) 2016 Bit Bar. All rights reserved.
|
||||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSString (ANSI)
|
||||
- (BOOL)containsANSICodes;
|
||||
- (NSMutableAttributedString*)attributedStringParsingANSICodes;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_
|
166
shell/browser/ui/cocoa/NSString+ANSI.mm
Normal file
166
shell/browser/ui/cocoa/NSString+ANSI.mm
Normal file
|
@ -0,0 +1,166 @@
|
|||
// Created by Kent Karlsson on 3/11/16.
|
||||
// Copyright (c) 2016 Bit Bar. All rights reserved.
|
||||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/NSColor+Hex.h"
|
||||
#include "atom/browser/ui/cocoa/NSString+ANSI.h"
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
|
||||
@implementation NSMutableDictionary (ANSI)
|
||||
|
||||
- (NSMutableDictionary*)modifyAttributesForANSICodes:(NSString*)codes {
|
||||
BOOL bold = NO;
|
||||
NSFont* font = self[NSFontAttributeName];
|
||||
|
||||
NSArray* codeArray = [codes componentsSeparatedByString:@";"];
|
||||
|
||||
for (NSString* codeString in codeArray) {
|
||||
int code = codeString.intValue;
|
||||
switch (code) {
|
||||
case 0:
|
||||
[self removeAllObjects];
|
||||
// remove italic and bold from font here
|
||||
if (font)
|
||||
self[NSFontAttributeName] = font;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
case 22:
|
||||
bold = (code == 1);
|
||||
break;
|
||||
|
||||
// case 3: italic
|
||||
// case 23: italic off
|
||||
// case 4: underlined
|
||||
// case 24: underlined off
|
||||
|
||||
case 30:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"7f7f7f" : @"000000"];
|
||||
break;
|
||||
case 31:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"cd0000" : @"ff0000"];
|
||||
break;
|
||||
case 32:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"00cd00" : @"00ff00"];
|
||||
break;
|
||||
case 33:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"cdcd00" : @"ffff00"];
|
||||
break;
|
||||
case 34:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"0000ee" : @"5c5cff"];
|
||||
break;
|
||||
case 35:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"cd00cd" : @"ff00ff"];
|
||||
break;
|
||||
case 36:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"00cdcd" : @"00ffff"];
|
||||
break;
|
||||
case 37:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"e5e5e5" : @"ffffff"];
|
||||
break;
|
||||
|
||||
case 39:
|
||||
[self removeObjectForKey:NSForegroundColorAttributeName];
|
||||
break;
|
||||
|
||||
case 40:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"7f7f7f"];
|
||||
break;
|
||||
case 41:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"cd0000"];
|
||||
break;
|
||||
case 42:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"00cd00"];
|
||||
break;
|
||||
case 43:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"cdcd00"];
|
||||
break;
|
||||
case 44:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"0000ee"];
|
||||
break;
|
||||
case 45:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"cd00cd"];
|
||||
break;
|
||||
case 46:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"00cdcd"];
|
||||
break;
|
||||
case 47:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"e5e5e5"];
|
||||
break;
|
||||
|
||||
case 49:
|
||||
[self removeObjectForKey:NSBackgroundColorAttributeName];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSString (ANSI)
|
||||
|
||||
- (BOOL)containsANSICodes {
|
||||
return [self rangeOfString:@"\033["].location != NSNotFound;
|
||||
}
|
||||
|
||||
- (NSMutableAttributedString*)attributedStringParsingANSICodes {
|
||||
NSMutableAttributedString* result = [[NSMutableAttributedString alloc] init];
|
||||
|
||||
base::scoped_nsobject<NSMutableDictionary> attributes(
|
||||
[[NSMutableDictionary alloc] init]);
|
||||
NSArray* parts = [self componentsSeparatedByString:@"\033["];
|
||||
[result appendAttributedString:[[[NSAttributedString alloc]
|
||||
initWithString:parts.firstObject
|
||||
attributes:nil] autorelease]];
|
||||
|
||||
for (NSString* part in
|
||||
[parts subarrayWithRange:NSMakeRange(1, parts.count - 1)]) {
|
||||
if (part.length == 0)
|
||||
continue;
|
||||
|
||||
NSArray* sequence = [part componentsSeparatedByString:@"m"];
|
||||
NSString* text = sequence.lastObject;
|
||||
|
||||
if (sequence.count < 2) {
|
||||
[result
|
||||
appendAttributedString:[[[NSAttributedString alloc]
|
||||
initWithString:text
|
||||
attributes:attributes] autorelease]];
|
||||
} else if (sequence.count >= 2) {
|
||||
text = [[sequence subarrayWithRange:NSMakeRange(1, sequence.count - 1)]
|
||||
componentsJoinedByString:@"m"];
|
||||
[attributes modifyAttributesForANSICodes:sequence[0]];
|
||||
[result
|
||||
appendAttributedString:[[[NSAttributedString alloc]
|
||||
initWithString:text
|
||||
attributes:attributes] autorelease]];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
43
shell/browser/ui/cocoa/atom_bundle_mover.h
Normal file
43
shell/browser/ui/cocoa/atom_bundle_mover.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_BUNDLE_MOVER_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_BUNDLE_MOVER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "native_mate/persistent_dictionary.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace ui {
|
||||
|
||||
namespace cocoa {
|
||||
|
||||
class AtomBundleMover {
|
||||
public:
|
||||
static bool Move(mate::Arguments* args);
|
||||
static bool IsCurrentAppInApplicationsFolder();
|
||||
|
||||
private:
|
||||
static bool IsInApplicationsFolder(NSString* bundlePath);
|
||||
static NSString* ContainingDiskImageDevice(NSString* bundlePath);
|
||||
static void Relaunch(NSString* destinationPath);
|
||||
static NSString* ShellQuotedString(NSString* string);
|
||||
static bool CopyBundle(NSString* srcPath, NSString* dstPath);
|
||||
static bool AuthorizedInstall(NSString* srcPath,
|
||||
NSString* dstPath,
|
||||
bool* canceled);
|
||||
static bool IsApplicationAtPathRunning(NSString* bundlePath);
|
||||
static bool DeleteOrTrash(NSString* path);
|
||||
static bool Trash(NSString* path);
|
||||
};
|
||||
|
||||
} // namespace cocoa
|
||||
|
||||
} // namespace ui
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_BUNDLE_MOVER_H_
|
425
shell/browser/ui/cocoa/atom_bundle_mover.mm
Normal file
425
shell/browser/ui/cocoa/atom_bundle_mover.mm
Normal file
|
@ -0,0 +1,425 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "atom/browser/ui/cocoa/atom_bundle_mover.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Security/Security.h>
|
||||
#import <dlfcn.h>
|
||||
#import <sys/mount.h>
|
||||
#import <sys/param.h>
|
||||
|
||||
#import "atom/browser/browser.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace ui {
|
||||
|
||||
namespace cocoa {
|
||||
|
||||
bool AtomBundleMover::Move(mate::Arguments* args) {
|
||||
// Path of the current bundle
|
||||
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
|
||||
|
||||
// Skip if the application is already in the Applications folder
|
||||
if (IsInApplicationsFolder(bundlePath))
|
||||
return true;
|
||||
|
||||
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||
|
||||
NSString* diskImageDevice = ContainingDiskImageDevice(bundlePath);
|
||||
|
||||
NSString* applicationsDirectory = [[NSSearchPathForDirectoriesInDomains(
|
||||
NSApplicationDirectory, NSLocalDomainMask, true) lastObject]
|
||||
stringByResolvingSymlinksInPath];
|
||||
NSString* bundleName = [bundlePath lastPathComponent];
|
||||
NSString* destinationPath =
|
||||
[applicationsDirectory stringByAppendingPathComponent:bundleName];
|
||||
|
||||
// Check if we can write to the applications directory
|
||||
// and then make sure that if the app already exists we can overwrite it
|
||||
bool needAuthorization =
|
||||
![fileManager isWritableFileAtPath:applicationsDirectory] |
|
||||
([fileManager fileExistsAtPath:destinationPath] &&
|
||||
![fileManager isWritableFileAtPath:destinationPath]);
|
||||
|
||||
// Activate app -- work-around for focus issues related to "scary file from
|
||||
// internet" OS dialog.
|
||||
if (![NSApp isActive]) {
|
||||
[NSApp activateIgnoringOtherApps:true];
|
||||
}
|
||||
|
||||
// Move to applications folder
|
||||
if (needAuthorization) {
|
||||
bool authorizationCanceled;
|
||||
|
||||
if (!AuthorizedInstall(bundlePath, destinationPath,
|
||||
&authorizationCanceled)) {
|
||||
if (authorizationCanceled) {
|
||||
// User rejected the authorization request
|
||||
args->ThrowError("User rejected the authorization request");
|
||||
return false;
|
||||
} else {
|
||||
args->ThrowError(
|
||||
"Failed to copy to applications directory even with authorization");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If a copy already exists in the Applications folder, put it in the Trash
|
||||
if ([fileManager fileExistsAtPath:destinationPath]) {
|
||||
// But first, make sure that it's not running
|
||||
if (IsApplicationAtPathRunning(destinationPath)) {
|
||||
// Give the running app focus and terminate myself
|
||||
[[NSTask
|
||||
launchedTaskWithLaunchPath:@"/usr/bin/open"
|
||||
arguments:[NSArray
|
||||
arrayWithObject:destinationPath]]
|
||||
waitUntilExit];
|
||||
atom::Browser::Get()->Quit();
|
||||
return true;
|
||||
} else {
|
||||
if (!Trash([applicationsDirectory
|
||||
stringByAppendingPathComponent:bundleName])) {
|
||||
args->ThrowError("Failed to delete existing application");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!CopyBundle(bundlePath, destinationPath)) {
|
||||
args->ThrowError(
|
||||
"Failed to copy current bundle to the applications folder");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Trash the original app. It's okay if this fails.
|
||||
// NOTE: This final delete does not work if the source bundle is in a network
|
||||
// mounted volume.
|
||||
// Calling rm or file manager's delete method doesn't work either. It's
|
||||
// unlikely to happen but it'd be great if someone could fix this.
|
||||
if (diskImageDevice == nil && !DeleteOrTrash(bundlePath)) {
|
||||
// Could not delete original but we just don't care
|
||||
}
|
||||
|
||||
// Relaunch.
|
||||
Relaunch(destinationPath);
|
||||
|
||||
// Launched from within a disk image? -- unmount (if no files are open after 5
|
||||
// seconds, otherwise leave it mounted).
|
||||
if (diskImageDevice) {
|
||||
NSString* script = [NSString
|
||||
stringWithFormat:@"(/bin/sleep 5 && /usr/bin/hdiutil detach %@) &",
|
||||
ShellQuotedString(diskImageDevice)];
|
||||
[NSTask launchedTaskWithLaunchPath:@"/bin/sh"
|
||||
arguments:[NSArray arrayWithObjects:@"-c", script,
|
||||
nil]];
|
||||
}
|
||||
|
||||
atom::Browser::Get()->Quit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AtomBundleMover::IsCurrentAppInApplicationsFolder() {
|
||||
return IsInApplicationsFolder([[NSBundle mainBundle] bundlePath]);
|
||||
}
|
||||
|
||||
bool AtomBundleMover::IsInApplicationsFolder(NSString* bundlePath) {
|
||||
// Check all the normal Application directories
|
||||
NSArray* applicationDirs = NSSearchPathForDirectoriesInDomains(
|
||||
NSApplicationDirectory, NSAllDomainsMask, true);
|
||||
for (NSString* appDir in applicationDirs) {
|
||||
if ([bundlePath hasPrefix:appDir])
|
||||
return true;
|
||||
}
|
||||
|
||||
// Also, handle the case that the user has some other Application directory
|
||||
// (perhaps on a separate data partition).
|
||||
if ([[bundlePath pathComponents] containsObject:@"Applications"])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NSString* AtomBundleMover::ContainingDiskImageDevice(NSString* bundlePath) {
|
||||
NSString* containingPath = [bundlePath stringByDeletingLastPathComponent];
|
||||
|
||||
struct statfs fs;
|
||||
if (statfs([containingPath fileSystemRepresentation], &fs) ||
|
||||
(fs.f_flags & MNT_ROOTFS))
|
||||
return nil;
|
||||
|
||||
NSString* device = [[NSFileManager defaultManager]
|
||||
stringWithFileSystemRepresentation:fs.f_mntfromname
|
||||
length:strlen(fs.f_mntfromname)];
|
||||
|
||||
NSTask* hdiutil = [[[NSTask alloc] init] autorelease];
|
||||
[hdiutil setLaunchPath:@"/usr/bin/hdiutil"];
|
||||
[hdiutil setArguments:[NSArray arrayWithObjects:@"info", @"-plist", nil]];
|
||||
[hdiutil setStandardOutput:[NSPipe pipe]];
|
||||
[hdiutil launch];
|
||||
[hdiutil waitUntilExit];
|
||||
|
||||
NSData* data =
|
||||
[[[hdiutil standardOutput] fileHandleForReading] readDataToEndOfFile];
|
||||
|
||||
NSDictionary* info =
|
||||
[NSPropertyListSerialization propertyListWithData:data
|
||||
options:NSPropertyListImmutable
|
||||
format:NULL
|
||||
error:NULL];
|
||||
|
||||
if (![info isKindOfClass:[NSDictionary class]])
|
||||
return nil;
|
||||
|
||||
NSArray* images = (NSArray*)[info objectForKey:@"images"];
|
||||
if (![images isKindOfClass:[NSArray class]])
|
||||
return nil;
|
||||
|
||||
for (NSDictionary* image in images) {
|
||||
if (![image isKindOfClass:[NSDictionary class]])
|
||||
return nil;
|
||||
|
||||
id systemEntities = [image objectForKey:@"system-entities"];
|
||||
if (![systemEntities isKindOfClass:[NSArray class]])
|
||||
return nil;
|
||||
|
||||
for (NSDictionary* systemEntity in systemEntities) {
|
||||
if (![systemEntity isKindOfClass:[NSDictionary class]])
|
||||
return nil;
|
||||
|
||||
NSString* devEntry = [systemEntity objectForKey:@"dev-entry"];
|
||||
if (![devEntry isKindOfClass:[NSString class]])
|
||||
return nil;
|
||||
|
||||
if ([devEntry isEqualToString:device])
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
bool AtomBundleMover::AuthorizedInstall(NSString* srcPath,
|
||||
NSString* dstPath,
|
||||
bool* canceled) {
|
||||
if (canceled)
|
||||
*canceled = false;
|
||||
|
||||
// Make sure that the destination path is an app bundle. We're essentially
|
||||
// running 'sudo rm -rf' so we really don't want to screw this up.
|
||||
if (![[dstPath pathExtension] isEqualToString:@"app"])
|
||||
return false;
|
||||
|
||||
// Do some more checks
|
||||
if ([[dstPath stringByTrimmingCharactersInSet:[NSCharacterSet
|
||||
whitespaceCharacterSet]]
|
||||
length] == 0)
|
||||
return false;
|
||||
if ([[srcPath stringByTrimmingCharactersInSet:[NSCharacterSet
|
||||
whitespaceCharacterSet]]
|
||||
length] == 0)
|
||||
return false;
|
||||
|
||||
int pid, status;
|
||||
AuthorizationRef myAuthorizationRef;
|
||||
|
||||
// Get the authorization
|
||||
OSStatus err =
|
||||
AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
|
||||
kAuthorizationFlagDefaults, &myAuthorizationRef);
|
||||
if (err != errAuthorizationSuccess)
|
||||
return false;
|
||||
|
||||
AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0};
|
||||
AuthorizationRights myRights = {1, &myItems};
|
||||
AuthorizationFlags myFlags = (AuthorizationFlags)(
|
||||
kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights |
|
||||
kAuthorizationFlagPreAuthorize);
|
||||
|
||||
err = AuthorizationCopyRights(myAuthorizationRef, &myRights, NULL, myFlags,
|
||||
NULL);
|
||||
if (err != errAuthorizationSuccess) {
|
||||
if (err == errAuthorizationCanceled && canceled)
|
||||
*canceled = true;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
static OSStatus (*security_AuthorizationExecuteWithPrivileges)(
|
||||
AuthorizationRef authorization, const char* pathToTool,
|
||||
AuthorizationFlags options, char* const* arguments,
|
||||
FILE** communicationsPipe) = NULL;
|
||||
if (!security_AuthorizationExecuteWithPrivileges) {
|
||||
// On 10.7, AuthorizationExecuteWithPrivileges is deprecated. We want to
|
||||
// still use it since there's no good alternative (without requiring code
|
||||
// signing). We'll look up the function through dyld and fail if it is no
|
||||
// longer accessible. If Apple removes the function entirely this will fail
|
||||
// gracefully. If they keep the function and throw some sort of exception,
|
||||
// this won't fail gracefully, but that's a risk we'll have to take for now.
|
||||
security_AuthorizationExecuteWithPrivileges = (OSStatus(*)(
|
||||
AuthorizationRef, const char*, AuthorizationFlags, char* const*,
|
||||
FILE**))dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges");
|
||||
}
|
||||
if (!security_AuthorizationExecuteWithPrivileges)
|
||||
goto fail;
|
||||
|
||||
// Delete the destination
|
||||
{
|
||||
char rf[] = "-rf";
|
||||
char* args[] = {rf, (char*)[dstPath fileSystemRepresentation], NULL};
|
||||
err = security_AuthorizationExecuteWithPrivileges(
|
||||
myAuthorizationRef, "/bin/rm", kAuthorizationFlagDefaults, args, NULL);
|
||||
if (err != errAuthorizationSuccess)
|
||||
goto fail;
|
||||
|
||||
// Wait until it's done
|
||||
pid = wait(&status);
|
||||
if (pid == -1 || !WIFEXITED(status))
|
||||
goto fail; // We don't care about exit status as the destination most
|
||||
// likely does not exist
|
||||
}
|
||||
|
||||
// Copy
|
||||
{
|
||||
char pR[] = "-pR";
|
||||
char* args[] = {pR, (char*)[srcPath fileSystemRepresentation],
|
||||
(char*)[dstPath fileSystemRepresentation], NULL};
|
||||
err = security_AuthorizationExecuteWithPrivileges(
|
||||
myAuthorizationRef, "/bin/cp", kAuthorizationFlagDefaults, args, NULL);
|
||||
if (err != errAuthorizationSuccess)
|
||||
goto fail;
|
||||
|
||||
// Wait until it's done
|
||||
pid = wait(&status);
|
||||
if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
|
||||
return true;
|
||||
|
||||
fail:
|
||||
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AtomBundleMover::CopyBundle(NSString* srcPath, NSString* dstPath) {
|
||||
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||
NSError* error = nil;
|
||||
|
||||
if ([fileManager copyItemAtPath:srcPath toPath:dstPath error:&error]) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
NSString* AtomBundleMover::ShellQuotedString(NSString* string) {
|
||||
return [NSString
|
||||
stringWithFormat:@"'%@'",
|
||||
[string stringByReplacingOccurrencesOfString:@"'"
|
||||
withString:@"'\\''"]];
|
||||
}
|
||||
|
||||
void AtomBundleMover::Relaunch(NSString* destinationPath) {
|
||||
// The shell script waits until the original app process terminates.
|
||||
// This is done so that the relaunched app opens as the front-most app.
|
||||
int pid = [[NSProcessInfo processInfo] processIdentifier];
|
||||
|
||||
// Command run just before running open /final/path
|
||||
NSString* preOpenCmd = @"";
|
||||
|
||||
NSString* quotedDestinationPath = ShellQuotedString(destinationPath);
|
||||
|
||||
// Before we launch the new app, clear xattr:com.apple.quarantine to avoid
|
||||
// duplicate "scary file from the internet" dialog.
|
||||
preOpenCmd = [NSString
|
||||
stringWithFormat:@"/usr/bin/xattr -d -r com.apple.quarantine %@",
|
||||
quotedDestinationPath];
|
||||
|
||||
NSString* script =
|
||||
[NSString stringWithFormat:
|
||||
@"(while /bin/kill -0 %d >&/dev/null; do /bin/sleep 0.1; "
|
||||
@"done; %@; /usr/bin/open %@) &",
|
||||
pid, preOpenCmd, quotedDestinationPath];
|
||||
|
||||
[NSTask
|
||||
launchedTaskWithLaunchPath:@"/bin/sh"
|
||||
arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
|
||||
}
|
||||
|
||||
bool AtomBundleMover::IsApplicationAtPathRunning(NSString* bundlePath) {
|
||||
bundlePath = [bundlePath stringByStandardizingPath];
|
||||
|
||||
for (NSRunningApplication* runningApplication in
|
||||
[[NSWorkspace sharedWorkspace] runningApplications]) {
|
||||
NSString* runningAppBundlePath =
|
||||
[[[runningApplication bundleURL] path] stringByStandardizingPath];
|
||||
if ([runningAppBundlePath isEqualToString:bundlePath]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AtomBundleMover::Trash(NSString* path) {
|
||||
bool result = false;
|
||||
|
||||
if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_8) {
|
||||
result = [[NSFileManager defaultManager]
|
||||
trashItemAtURL:[NSURL fileURLWithPath:path]
|
||||
resultingItemURL:NULL
|
||||
error:NULL];
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
result = [[NSWorkspace sharedWorkspace]
|
||||
performFileOperation:NSWorkspaceRecycleOperation
|
||||
source:[path stringByDeletingLastPathComponent]
|
||||
destination:@""
|
||||
files:[NSArray arrayWithObject:[path lastPathComponent]]
|
||||
tag:NULL];
|
||||
}
|
||||
|
||||
// As a last resort try trashing with AppleScript.
|
||||
// This allows us to trash the app in macOS Sierra even when the app is
|
||||
// running inside an app translocation image.
|
||||
if (!result) {
|
||||
auto* code = R"str(
|
||||
set theFile to POSIX file "%@"
|
||||
tell application "Finder"
|
||||
move theFile to trash
|
||||
end tell
|
||||
)str";
|
||||
NSAppleScript* appleScript = [[[NSAppleScript alloc]
|
||||
initWithSource:[NSString stringWithFormat:@(code), path]] autorelease];
|
||||
NSDictionary* errorDict = nil;
|
||||
NSAppleEventDescriptor* scriptResult =
|
||||
[appleScript executeAndReturnError:&errorDict];
|
||||
result = (scriptResult != nil);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AtomBundleMover::DeleteOrTrash(NSString* path) {
|
||||
NSError* error;
|
||||
|
||||
if ([[NSFileManager defaultManager] removeItemAtPath:path error:&error]) {
|
||||
return true;
|
||||
} else {
|
||||
return Trash(path);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cocoa
|
||||
|
||||
} // namespace ui
|
||||
|
||||
} // namespace atom
|
47
shell/browser/ui/cocoa/atom_inspectable_web_contents_view.h
Normal file
47
shell/browser/ui/cocoa/atom_inspectable_web_contents_view.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_BRY_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_BRY_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
|
||||
#include "ui/base/cocoa/base_view.h"
|
||||
|
||||
namespace atom {
|
||||
class InspectableWebContentsViewMac;
|
||||
}
|
||||
|
||||
using atom::InspectableWebContentsViewMac;
|
||||
|
||||
@interface AtomInspectableWebContentsView : BaseView <NSWindowDelegate> {
|
||||
@private
|
||||
atom::InspectableWebContentsViewMac* inspectableWebContentsView_;
|
||||
|
||||
base::scoped_nsobject<NSView> fake_view_;
|
||||
base::scoped_nsobject<NSWindow> devtools_window_;
|
||||
BOOL devtools_visible_;
|
||||
BOOL devtools_docked_;
|
||||
BOOL devtools_is_first_responder_;
|
||||
|
||||
DevToolsContentsResizingStrategy strategy_;
|
||||
}
|
||||
|
||||
- (instancetype)initWithInspectableWebContentsViewMac:
|
||||
(InspectableWebContentsViewMac*)view;
|
||||
- (void)removeObservers;
|
||||
- (void)notifyDevToolsFocused;
|
||||
- (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate;
|
||||
- (BOOL)isDevToolsVisible;
|
||||
- (BOOL)isDevToolsFocused;
|
||||
- (void)setIsDocked:(BOOL)docked activate:(BOOL)activate;
|
||||
- (void)setContentsResizingStrategy:
|
||||
(const DevToolsContentsResizingStrategy&)strategy;
|
||||
- (void)setTitle:(NSString*)title;
|
||||
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_BRY_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
269
shell/browser/ui/cocoa/atom_inspectable_web_contents_view.mm
Normal file
269
shell/browser/ui/cocoa/atom_inspectable_web_contents_view.mm
Normal file
|
@ -0,0 +1,269 @@
|
|||
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_inspectable_web_contents_view.h"
|
||||
|
||||
#include "atom/browser/ui/cocoa/event_dispatching_window.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_impl.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_view_mac.h"
|
||||
#include "content/public/browser/render_widget_host_view.h"
|
||||
#include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h"
|
||||
|
||||
@implementation AtomInspectableWebContentsView
|
||||
|
||||
- (instancetype)initWithInspectableWebContentsViewMac:
|
||||
(InspectableWebContentsViewMac*)view {
|
||||
self = [super init];
|
||||
if (!self)
|
||||
return nil;
|
||||
|
||||
inspectableWebContentsView_ = view;
|
||||
devtools_visible_ = NO;
|
||||
devtools_docked_ = NO;
|
||||
devtools_is_first_responder_ = NO;
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(viewDidBecomeFirstResponder:)
|
||||
name:kViewDidBecomeFirstResponder
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(parentWindowBecameMain:)
|
||||
name:NSWindowDidBecomeMainNotification
|
||||
object:nil];
|
||||
|
||||
if (inspectableWebContentsView_->inspectable_web_contents()->IsGuest()) {
|
||||
fake_view_.reset([[NSView alloc] init]);
|
||||
[fake_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
||||
[self addSubview:fake_view_];
|
||||
} else {
|
||||
auto* contents = inspectableWebContentsView_->inspectable_web_contents()
|
||||
->GetWebContents();
|
||||
auto* contentsView = contents->GetNativeView().GetNativeNSView();
|
||||
[contentsView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
||||
[self addSubview:contentsView];
|
||||
}
|
||||
|
||||
// See https://code.google.com/p/chromium/issues/detail?id=348490.
|
||||
[self setWantsLayer:YES];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)removeObservers {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
|
||||
[self adjustSubviews];
|
||||
}
|
||||
|
||||
- (IBAction)showDevTools:(id)sender {
|
||||
inspectableWebContentsView_->inspectable_web_contents()->ShowDevTools(true);
|
||||
}
|
||||
|
||||
- (void)notifyDevToolsFocused {
|
||||
if (inspectableWebContentsView_->GetDelegate())
|
||||
inspectableWebContentsView_->GetDelegate()->DevToolsFocused();
|
||||
}
|
||||
|
||||
- (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate {
|
||||
if (visible == devtools_visible_)
|
||||
return;
|
||||
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
auto* devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
devtools_visible_ = visible;
|
||||
if (devtools_docked_) {
|
||||
if (visible) {
|
||||
// Place the devToolsView under contentsView, notice that we didn't set
|
||||
// sizes for them until the setContentsResizingStrategy message.
|
||||
[self addSubview:devToolsView positioned:NSWindowBelow relativeTo:nil];
|
||||
[self adjustSubviews];
|
||||
|
||||
// Focus on web view.
|
||||
devToolsWebContents->RestoreFocus();
|
||||
} else {
|
||||
gfx::ScopedCocoaDisableScreenUpdates disabler;
|
||||
[devToolsView removeFromSuperview];
|
||||
[self adjustSubviews];
|
||||
}
|
||||
} else {
|
||||
if (visible) {
|
||||
if (activate) {
|
||||
[devtools_window_ makeKeyAndOrderFront:nil];
|
||||
} else {
|
||||
[devtools_window_ orderBack:nil];
|
||||
}
|
||||
} else {
|
||||
[devtools_window_ setDelegate:nil];
|
||||
[devtools_window_ close];
|
||||
devtools_window_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isDevToolsVisible {
|
||||
return devtools_visible_;
|
||||
}
|
||||
|
||||
- (BOOL)isDevToolsFocused {
|
||||
if (devtools_docked_) {
|
||||
return [[self window] isKeyWindow] && devtools_is_first_responder_;
|
||||
} else {
|
||||
return [devtools_window_ isKeyWindow];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsDocked:(BOOL)docked activate:(BOOL)activate {
|
||||
// Revert to no-devtools state.
|
||||
[self setDevToolsVisible:NO activate:NO];
|
||||
|
||||
// Switch to new state.
|
||||
devtools_docked_ = docked;
|
||||
if (!docked) {
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
auto styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
||||
NSMiniaturizableWindowMask | NSWindowStyleMaskResizable |
|
||||
NSTexturedBackgroundWindowMask |
|
||||
NSWindowStyleMaskUnifiedTitleAndToolbar;
|
||||
devtools_window_.reset([[EventDispatchingWindow alloc]
|
||||
initWithContentRect:NSMakeRect(0, 0, 800, 600)
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:YES]);
|
||||
[devtools_window_ setDelegate:self];
|
||||
[devtools_window_ setFrameAutosaveName:@"electron.devtools"];
|
||||
[devtools_window_ setTitle:@"Developer Tools"];
|
||||
[devtools_window_ setReleasedWhenClosed:NO];
|
||||
[devtools_window_ setAutorecalculatesContentBorderThickness:NO
|
||||
forEdge:NSMaxYEdge];
|
||||
[devtools_window_ setContentBorderThickness:24 forEdge:NSMaxYEdge];
|
||||
|
||||
NSView* contentView = [devtools_window_ contentView];
|
||||
devToolsView.frame = contentView.bounds;
|
||||
devToolsView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
|
||||
[contentView addSubview:devToolsView];
|
||||
}
|
||||
[self setDevToolsVisible:YES activate:activate];
|
||||
}
|
||||
|
||||
- (void)setContentsResizingStrategy:
|
||||
(const DevToolsContentsResizingStrategy&)strategy {
|
||||
strategy_.CopyFrom(strategy);
|
||||
[self adjustSubviews];
|
||||
}
|
||||
|
||||
- (void)adjustSubviews {
|
||||
if (![[self subviews] count])
|
||||
return;
|
||||
|
||||
if (![self isDevToolsVisible] || devtools_window_) {
|
||||
DCHECK_EQ(1u, [[self subviews] count]);
|
||||
NSView* contents = [[self subviews] objectAtIndex:0];
|
||||
[contents setFrame:[self bounds]];
|
||||
return;
|
||||
}
|
||||
|
||||
NSView* devToolsView = [[self subviews] objectAtIndex:0];
|
||||
NSView* contentsView = [[self subviews] objectAtIndex:1];
|
||||
|
||||
DCHECK_EQ(2u, [[self subviews] count]);
|
||||
|
||||
gfx::Rect new_devtools_bounds;
|
||||
gfx::Rect new_contents_bounds;
|
||||
ApplyDevToolsContentsResizingStrategy(
|
||||
strategy_, gfx::Size(NSSizeToCGSize([self bounds].size)),
|
||||
&new_devtools_bounds, &new_contents_bounds);
|
||||
[devToolsView setFrame:[self flipRectToNSRect:new_devtools_bounds]];
|
||||
[contentsView setFrame:[self flipRectToNSRect:new_contents_bounds]];
|
||||
}
|
||||
|
||||
- (void)setTitle:(NSString*)title {
|
||||
[devtools_window_ setTitle:title];
|
||||
}
|
||||
|
||||
- (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
if (!inspectable_web_contents || inspectable_web_contents->IsGuest())
|
||||
return;
|
||||
auto* webContents = inspectable_web_contents->GetWebContents();
|
||||
auto* webContentsView = webContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
NSView* view = [notification object];
|
||||
if ([[webContentsView subviews] containsObject:view]) {
|
||||
devtools_is_first_responder_ = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
if (!devToolsWebContents)
|
||||
return;
|
||||
auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
if ([[devToolsView subviews] containsObject:view]) {
|
||||
devtools_is_first_responder_ = YES;
|
||||
[self notifyDevToolsFocused];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parentWindowBecameMain:(NSNotification*)notification {
|
||||
NSWindow* parentWindow = [notification object];
|
||||
if ([self window] == parentWindow && devtools_docked_ &&
|
||||
devtools_is_first_responder_)
|
||||
[self notifyDevToolsFocused];
|
||||
}
|
||||
|
||||
#pragma mark - NSWindowDelegate
|
||||
|
||||
- (void)windowWillClose:(NSNotification*)notification {
|
||||
inspectableWebContentsView_->inspectable_web_contents()->CloseDevTools();
|
||||
}
|
||||
|
||||
- (void)windowDidBecomeMain:(NSNotification*)notification {
|
||||
content::WebContents* web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents()
|
||||
->GetDevToolsWebContents();
|
||||
if (!web_contents)
|
||||
return;
|
||||
|
||||
web_contents->RestoreFocus();
|
||||
|
||||
content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
|
||||
if (rwhv)
|
||||
rwhv->SetActive(true);
|
||||
|
||||
[self notifyDevToolsFocused];
|
||||
}
|
||||
|
||||
- (void)windowDidResignMain:(NSNotification*)notification {
|
||||
content::WebContents* web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents()
|
||||
->GetDevToolsWebContents();
|
||||
if (!web_contents)
|
||||
return;
|
||||
|
||||
web_contents->StoreFocus();
|
||||
|
||||
content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
|
||||
if (rwhv)
|
||||
rwhv->SetActive(false);
|
||||
}
|
||||
|
||||
@end
|
62
shell/browser/ui/cocoa/atom_menu_controller.h
Normal file
62
shell/browser/ui/cocoa/atom_menu_controller.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "base/strings/string16.h"
|
||||
|
||||
namespace atom {
|
||||
class AtomMenuModel;
|
||||
}
|
||||
|
||||
// A controller for the cross-platform menu model. The menu that's created
|
||||
// has the tag and represented object set for each menu item. The object is a
|
||||
// NSValue holding a pointer to the model for that level of the menu (to
|
||||
// allow for hierarchical menus). The tag is the index into that model for
|
||||
// that particular item. It is important that the model outlives this object
|
||||
// as it only maintains weak references.
|
||||
@interface AtomMenuController : NSObject <NSMenuDelegate> {
|
||||
@protected
|
||||
atom::AtomMenuModel* model_; // weak
|
||||
base::scoped_nsobject<NSMenu> menu_;
|
||||
BOOL isMenuOpen_;
|
||||
BOOL useDefaultAccelerator_;
|
||||
base::Callback<void()> closeCallback;
|
||||
}
|
||||
|
||||
@property(nonatomic, assign) atom::AtomMenuModel* model;
|
||||
|
||||
// Builds a NSMenu from the pre-built model (must not be nil). Changes made
|
||||
// to the contents of the model after calling this will not be noticed.
|
||||
- (id)initWithModel:(atom::AtomMenuModel*)model useDefaultAccelerator:(BOOL)use;
|
||||
|
||||
- (void)setCloseCallback:(const base::Callback<void()>&)callback;
|
||||
|
||||
// Populate current NSMenu with |model|.
|
||||
- (void)populateWithModel:(atom::AtomMenuModel*)model;
|
||||
|
||||
// Programmatically close the constructed menu.
|
||||
- (void)cancel;
|
||||
|
||||
// Access to the constructed menu if the complex initializer was used. If the
|
||||
// default initializer was used, then this will create the menu on first call.
|
||||
- (NSMenu*)menu;
|
||||
|
||||
// Whether the menu is currently open.
|
||||
- (BOOL)isMenuOpen;
|
||||
|
||||
// NSMenuDelegate methods this class implements. Subclasses should call super
|
||||
// if extending the behavior.
|
||||
- (void)menuWillOpen:(NSMenu*)menu;
|
||||
- (void)menuDidClose:(NSMenu*)menu;
|
||||
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_
|
384
shell/browser/ui/cocoa/atom_menu_controller.mm
Normal file
384
shell/browser/ui/cocoa/atom_menu_controller.mm
Normal file
|
@ -0,0 +1,384 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "atom/browser/ui/cocoa/atom_menu_controller.h"
|
||||
|
||||
#include "atom/browser/mac/atom_application.h"
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/task/post_task.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
#include "ui/base/accelerators/platform_accelerator_cocoa.h"
|
||||
#include "ui/base/l10n/l10n_util_mac.h"
|
||||
#include "ui/events/cocoa/cocoa_event_utils.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "ui/strings/grit/ui_strings.h"
|
||||
|
||||
using content::BrowserThread;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Role {
|
||||
SEL selector;
|
||||
const char* role;
|
||||
};
|
||||
Role kRolesMap[] = {
|
||||
{@selector(orderFrontStandardAboutPanel:), "about"},
|
||||
{@selector(hide:), "hide"},
|
||||
{@selector(hideOtherApplications:), "hideothers"},
|
||||
{@selector(unhideAllApplications:), "unhide"},
|
||||
{@selector(arrangeInFront:), "front"},
|
||||
{@selector(undo:), "undo"},
|
||||
{@selector(redo:), "redo"},
|
||||
{@selector(cut:), "cut"},
|
||||
{@selector(copy:), "copy"},
|
||||
{@selector(paste:), "paste"},
|
||||
{@selector(delete:), "delete"},
|
||||
{@selector(pasteAndMatchStyle:), "pasteandmatchstyle"},
|
||||
{@selector(selectAll:), "selectall"},
|
||||
{@selector(startSpeaking:), "startspeaking"},
|
||||
{@selector(stopSpeaking:), "stopspeaking"},
|
||||
{@selector(performMiniaturize:), "minimize"},
|
||||
{@selector(performClose:), "close"},
|
||||
{@selector(performZoom:), "zoom"},
|
||||
{@selector(terminate:), "quit"},
|
||||
// ↓ is intentionally not `toggleFullScreen`. The macOS full screen menu
|
||||
// item behaves weird. If we use `toggleFullScreen`, then the menu item will
|
||||
// use the default label, and not take the one provided.
|
||||
{@selector(toggleFullScreenMode:), "togglefullscreen"},
|
||||
{@selector(toggleTabBar:), "toggletabbar"},
|
||||
{@selector(selectNextTab:), "selectnexttab"},
|
||||
{@selector(selectPreviousTab:), "selectprevioustab"},
|
||||
{@selector(mergeAllWindows:), "mergeallwindows"},
|
||||
{@selector(moveTabToNewWindow:), "movetabtonewwindow"},
|
||||
{@selector(clearRecentDocuments:), "clearrecentdocuments"},
|
||||
};
|
||||
|
||||
// Called when adding a submenu to the menu and checks if the submenu, via its
|
||||
// |model|, has visible child items.
|
||||
bool MenuHasVisibleItems(const atom::AtomMenuModel* model) {
|
||||
int count = model->GetItemCount();
|
||||
for (int index = 0; index < count; index++) {
|
||||
if (model->IsVisibleAt(index))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Called when an empty submenu is created. This inserts a menu item labeled
|
||||
// "(empty)" into the submenu. Matches Windows behavior.
|
||||
NSMenu* MakeEmptySubmenu() {
|
||||
base::scoped_nsobject<NSMenu> submenu([[NSMenu alloc] initWithTitle:@""]);
|
||||
NSString* empty_menu_title =
|
||||
l10n_util::GetNSString(IDS_APP_MENU_EMPTY_SUBMENU);
|
||||
|
||||
[submenu addItemWithTitle:empty_menu_title action:NULL keyEquivalent:@""];
|
||||
[[submenu itemAtIndex:0] setEnabled:NO];
|
||||
return submenu.autorelease();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Menu item is located for ease of removing it from the parent owner
|
||||
static base::scoped_nsobject<NSMenuItem> recentDocumentsMenuItem_;
|
||||
|
||||
// Submenu retained to be swapped back to |recentDocumentsMenuItem_|
|
||||
static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||
|
||||
@implementation AtomMenuController
|
||||
|
||||
@synthesize model = model_;
|
||||
|
||||
- (id)initWithModel:(atom::AtomMenuModel*)model
|
||||
useDefaultAccelerator:(BOOL)use {
|
||||
if ((self = [super init])) {
|
||||
model_ = model;
|
||||
isMenuOpen_ = NO;
|
||||
useDefaultAccelerator_ = use;
|
||||
[self menu];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[menu_ setDelegate:nil];
|
||||
|
||||
// Close the menu if it is still open. This could happen if a tab gets closed
|
||||
// while its context menu is still open.
|
||||
[self cancel];
|
||||
|
||||
model_ = nil;
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)setCloseCallback:(const base::Callback<void()>&)callback {
|
||||
closeCallback = callback;
|
||||
}
|
||||
|
||||
- (void)populateWithModel:(atom::AtomMenuModel*)model {
|
||||
if (!menu_)
|
||||
return;
|
||||
|
||||
if (!recentDocumentsMenuItem_) {
|
||||
// Locate & retain the recent documents menu item
|
||||
recentDocumentsMenuItem_.reset(
|
||||
[[[[[NSApp mainMenu] itemWithTitle:@"Electron"] submenu]
|
||||
itemWithTitle:@"Open Recent"] retain]);
|
||||
}
|
||||
|
||||
model_ = model;
|
||||
[menu_ removeAllItems];
|
||||
|
||||
const int count = model->GetItemCount();
|
||||
for (int index = 0; index < count; index++) {
|
||||
if (model->GetTypeAt(index) == atom::AtomMenuModel::TYPE_SEPARATOR)
|
||||
[self addSeparatorToMenu:menu_ atIndex:index];
|
||||
else
|
||||
[self addItemToMenu:menu_ atIndex:index fromModel:model];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancel {
|
||||
if (isMenuOpen_) {
|
||||
[menu_ cancelTracking];
|
||||
isMenuOpen_ = NO;
|
||||
model_->MenuWillClose();
|
||||
if (!closeCallback.is_null()) {
|
||||
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closeCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a NSMenu from the given model. If the model has submenus, this can
|
||||
// be invoked recursively.
|
||||
- (NSMenu*)menuFromModel:(atom::AtomMenuModel*)model {
|
||||
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
|
||||
|
||||
const int count = model->GetItemCount();
|
||||
for (int index = 0; index < count; index++) {
|
||||
if (model->GetTypeAt(index) == atom::AtomMenuModel::TYPE_SEPARATOR)
|
||||
[self addSeparatorToMenu:menu atIndex:index];
|
||||
else
|
||||
[self addItemToMenu:menu atIndex:index fromModel:model];
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
// Adds a separator item at the given index. As the separator doesn't need
|
||||
// anything from the model, this method doesn't need the model index as the
|
||||
// other method below does.
|
||||
- (void)addSeparatorToMenu:(NSMenu*)menu atIndex:(int)index {
|
||||
NSMenuItem* separator = [NSMenuItem separatorItem];
|
||||
[menu insertItem:separator atIndex:index];
|
||||
}
|
||||
|
||||
// Empties the source menu items to the destination.
|
||||
- (void)moveMenuItems:(NSMenu*)source to:(NSMenu*)destination {
|
||||
const NSInteger count = [source numberOfItems];
|
||||
for (NSInteger index = 0; index < count; index++) {
|
||||
NSMenuItem* removedItem = [[[source itemAtIndex:0] retain] autorelease];
|
||||
[source removeItemAtIndex:0];
|
||||
[destination addItem:removedItem];
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces the item's submenu instance with the singleton recent documents
|
||||
// menu. Previously replaced menu items will be recovered.
|
||||
- (void)replaceSubmenuShowingRecentDocuments:(NSMenuItem*)item {
|
||||
NSMenu* recentDocumentsMenu =
|
||||
[[[recentDocumentsMenuItem_ submenu] retain] autorelease];
|
||||
|
||||
// Remove menu items in recent documents back to swap menu
|
||||
[self moveMenuItems:recentDocumentsMenu to:recentDocumentsMenuSwap_];
|
||||
// Swap back the submenu
|
||||
[recentDocumentsMenuItem_ setSubmenu:recentDocumentsMenuSwap_];
|
||||
|
||||
// Retain the item's submenu for a future recovery
|
||||
recentDocumentsMenuSwap_.reset([[item submenu] retain]);
|
||||
|
||||
// Repopulate with items from the submenu to be replaced
|
||||
[self moveMenuItems:recentDocumentsMenuSwap_ to:recentDocumentsMenu];
|
||||
// Update the submenu's title
|
||||
[recentDocumentsMenu setTitle:[recentDocumentsMenuSwap_ title]];
|
||||
// Replace submenu
|
||||
[item setSubmenu:recentDocumentsMenu];
|
||||
|
||||
// Remember the new menu item that carries the recent documents menu
|
||||
recentDocumentsMenuItem_.reset([item retain]);
|
||||
}
|
||||
|
||||
// Adds an item or a hierarchical menu to the item at the |index|,
|
||||
// associated with the entry in the model identified by |modelIndex|.
|
||||
- (void)addItemToMenu:(NSMenu*)menu
|
||||
atIndex:(NSInteger)index
|
||||
fromModel:(atom::AtomMenuModel*)model {
|
||||
base::string16 label16 = model->GetLabelAt(index);
|
||||
NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
|
||||
|
||||
base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc]
|
||||
initWithTitle:label
|
||||
action:@selector(itemSelected:)
|
||||
keyEquivalent:@""]);
|
||||
|
||||
// If the menu item has an icon, set it.
|
||||
gfx::Image icon;
|
||||
if (model->GetIconAt(index, &icon) && !icon.IsEmpty())
|
||||
[item setImage:icon.ToNSImage()];
|
||||
|
||||
base::string16 role = model->GetRoleAt(index);
|
||||
atom::AtomMenuModel::ItemType type = model->GetTypeAt(index);
|
||||
|
||||
if (role == base::ASCIIToUTF16("services")) {
|
||||
base::string16 title = base::ASCIIToUTF16("Services");
|
||||
NSString* label = l10n_util::FixUpWindowsStyleLabel(title);
|
||||
|
||||
[item setTarget:nil];
|
||||
[item setAction:nil];
|
||||
NSMenu* submenu = [[NSMenu alloc] initWithTitle:label];
|
||||
[item setSubmenu:submenu];
|
||||
[NSApp setServicesMenu:submenu];
|
||||
} else if (type == atom::AtomMenuModel::TYPE_SUBMENU &&
|
||||
model->IsVisibleAt(index)) {
|
||||
// We need to specifically check that the submenu top-level item has been
|
||||
// enabled as it's not validated by validateUserInterfaceItem
|
||||
if (!model->IsEnabledAt(index))
|
||||
[item setEnabled:NO];
|
||||
|
||||
// Recursively build a submenu from the sub-model at this index.
|
||||
[item setTarget:nil];
|
||||
[item setAction:nil];
|
||||
atom::AtomMenuModel* submenuModel =
|
||||
static_cast<atom::AtomMenuModel*>(model->GetSubmenuModelAt(index));
|
||||
NSMenu* submenu = MenuHasVisibleItems(submenuModel)
|
||||
? [self menuFromModel:submenuModel]
|
||||
: MakeEmptySubmenu();
|
||||
[submenu setTitle:[item title]];
|
||||
[item setSubmenu:submenu];
|
||||
|
||||
// Set submenu's role.
|
||||
if ((role == base::ASCIIToUTF16("window") ||
|
||||
role == base::ASCIIToUTF16("windowmenu")) &&
|
||||
[submenu numberOfItems])
|
||||
[NSApp setWindowsMenu:submenu];
|
||||
else if (role == base::ASCIIToUTF16("help"))
|
||||
[NSApp setHelpMenu:submenu];
|
||||
else if (role == base::ASCIIToUTF16("recentdocuments"))
|
||||
[self replaceSubmenuShowingRecentDocuments:item];
|
||||
} else {
|
||||
// The MenuModel works on indexes so we can't just set the command id as the
|
||||
// tag like we do in other menus. Also set the represented object to be
|
||||
// the model so hierarchical menus check the correct index in the correct
|
||||
// model. Setting the target to |self| allows this class to participate
|
||||
// in validation of the menu items.
|
||||
[item setTag:index];
|
||||
NSValue* modelObject = [NSValue valueWithPointer:model];
|
||||
[item setRepresentedObject:modelObject]; // Retains |modelObject|.
|
||||
ui::Accelerator accelerator;
|
||||
if (model->GetAcceleratorAtWithParams(index, useDefaultAccelerator_,
|
||||
&accelerator)) {
|
||||
NSString* key_equivalent;
|
||||
NSUInteger modifier_mask;
|
||||
GetKeyEquivalentAndModifierMaskFromAccelerator(
|
||||
accelerator, &key_equivalent, &modifier_mask);
|
||||
[item setKeyEquivalent:key_equivalent];
|
||||
[item setKeyEquivalentModifierMask:modifier_mask];
|
||||
}
|
||||
|
||||
if (@available(macOS 10.13, *)) {
|
||||
[(id)item
|
||||
setAllowsKeyEquivalentWhenHidden:(model->WorksWhenHiddenAt(index))];
|
||||
}
|
||||
|
||||
// Set menu item's role.
|
||||
[item setTarget:self];
|
||||
if (!role.empty()) {
|
||||
for (const Role& pair : kRolesMap) {
|
||||
if (role == base::ASCIIToUTF16(pair.role)) {
|
||||
[item setTarget:nil];
|
||||
[item setAction:pair.selector];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[menu insertItem:item atIndex:index];
|
||||
}
|
||||
|
||||
// Called before the menu is to be displayed to update the state (enabled,
|
||||
// radio, etc) of each item in the menu.
|
||||
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
|
||||
SEL action = [item action];
|
||||
if (action != @selector(itemSelected:))
|
||||
return NO;
|
||||
|
||||
NSInteger modelIndex = [item tag];
|
||||
atom::AtomMenuModel* model = static_cast<atom::AtomMenuModel*>(
|
||||
[[(id)item representedObject] pointerValue]);
|
||||
DCHECK(model);
|
||||
if (model) {
|
||||
BOOL checked = model->IsItemCheckedAt(modelIndex);
|
||||
DCHECK([(id)item isKindOfClass:[NSMenuItem class]]);
|
||||
|
||||
[(id)item setState:(checked ? NSOnState : NSOffState)];
|
||||
[(id)item setHidden:(!model->IsVisibleAt(modelIndex))];
|
||||
|
||||
return model->IsEnabledAt(modelIndex);
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Called when the user chooses a particular menu item. |sender| is the menu
|
||||
// item chosen.
|
||||
- (void)itemSelected:(id)sender {
|
||||
NSInteger modelIndex = [sender tag];
|
||||
atom::AtomMenuModel* model = static_cast<atom::AtomMenuModel*>(
|
||||
[[sender representedObject] pointerValue]);
|
||||
DCHECK(model);
|
||||
if (model) {
|
||||
NSEvent* event = [NSApp currentEvent];
|
||||
model->ActivatedAt(modelIndex,
|
||||
ui::EventFlagsFromModifiers([event modifierFlags]));
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMenu*)menu {
|
||||
if (menu_)
|
||||
return menu_.get();
|
||||
|
||||
menu_.reset([[NSMenu alloc] initWithTitle:@""]);
|
||||
[menu_ setDelegate:self];
|
||||
if (model_)
|
||||
[self populateWithModel:model_];
|
||||
return menu_.get();
|
||||
}
|
||||
|
||||
- (BOOL)isMenuOpen {
|
||||
return isMenuOpen_;
|
||||
}
|
||||
|
||||
- (void)menuWillOpen:(NSMenu*)menu {
|
||||
isMenuOpen_ = YES;
|
||||
model_->MenuWillShow();
|
||||
}
|
||||
|
||||
- (void)menuDidClose:(NSMenu*)menu {
|
||||
if (isMenuOpen_) {
|
||||
isMenuOpen_ = NO;
|
||||
model_->MenuWillClose();
|
||||
// Post async task so that itemSelected runs before the close callback
|
||||
// deletes the controller from the map which deallocates it
|
||||
if (!closeCallback.is_null()) {
|
||||
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closeCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
35
shell/browser/ui/cocoa/atom_native_widget_mac.h
Normal file
35
shell/browser/ui/cocoa/atom_native_widget_mac.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_NATIVE_WIDGET_MAC_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_NATIVE_WIDGET_MAC_H_
|
||||
|
||||
#include "ui/views/widget/native_widget_mac.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindowMac;
|
||||
|
||||
class AtomNativeWidgetMac : public views::NativeWidgetMac {
|
||||
public:
|
||||
AtomNativeWidgetMac(NativeWindowMac* shell,
|
||||
NSUInteger style_mask,
|
||||
views::internal::NativeWidgetDelegate* delegate);
|
||||
~AtomNativeWidgetMac() override;
|
||||
|
||||
protected:
|
||||
// NativeWidgetMac:
|
||||
NativeWidgetMacNSWindow* CreateNSWindow(
|
||||
const remote_cocoa::mojom::CreateWindowParams* params) override;
|
||||
|
||||
private:
|
||||
NativeWindowMac* shell_;
|
||||
NSUInteger style_mask_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AtomNativeWidgetMac);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_NATIVE_WIDGET_MAC_H_
|
27
shell/browser/ui/cocoa/atom_native_widget_mac.mm
Normal file
27
shell/browser/ui/cocoa/atom_native_widget_mac.mm
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_native_widget_mac.h"
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_ns_window.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
AtomNativeWidgetMac::AtomNativeWidgetMac(
|
||||
NativeWindowMac* shell,
|
||||
NSUInteger style_mask,
|
||||
views::internal::NativeWidgetDelegate* delegate)
|
||||
: views::NativeWidgetMac(delegate),
|
||||
shell_(shell),
|
||||
style_mask_(style_mask) {}
|
||||
|
||||
AtomNativeWidgetMac::~AtomNativeWidgetMac() {}
|
||||
|
||||
NativeWidgetMacNSWindow* AtomNativeWidgetMac::CreateNSWindow(
|
||||
const remote_cocoa::mojom::CreateWindowParams* params) {
|
||||
return [[[AtomNSWindow alloc] initWithShell:shell_
|
||||
styleMask:style_mask_] autorelease];
|
||||
}
|
||||
|
||||
} // namespace atom
|
47
shell/browser/ui/cocoa/atom_ns_window.h
Normal file
47
shell/browser/ui/cocoa/atom_ns_window.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_H_
|
||||
|
||||
#include "atom/browser/ui/cocoa/event_dispatching_window.h"
|
||||
#include "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
|
||||
#include "ui/views/widget/native_widget_mac.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindowMac;
|
||||
|
||||
// Prevents window from resizing during the scope.
|
||||
class ScopedDisableResize {
|
||||
public:
|
||||
ScopedDisableResize() { disable_resize_ = true; }
|
||||
~ScopedDisableResize() { disable_resize_ = false; }
|
||||
|
||||
static bool IsResizeDisabled() { return disable_resize_; }
|
||||
|
||||
private:
|
||||
static bool disable_resize_;
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
@interface AtomNSWindow : NativeWidgetMacNSWindow {
|
||||
@private
|
||||
atom::NativeWindowMac* shell_;
|
||||
}
|
||||
@property BOOL acceptsFirstMouse;
|
||||
@property BOOL enableLargerThanScreen;
|
||||
@property BOOL disableAutoHideCursor;
|
||||
@property BOOL disableKeyOrMainWindow;
|
||||
@property(nonatomic, retain) NSView* vibrantView;
|
||||
- (id)initWithShell:(atom::NativeWindowMac*)shell
|
||||
styleMask:(NSUInteger)styleMask;
|
||||
- (atom::NativeWindowMac*)shell;
|
||||
- (id)accessibilityFocusedUIElement;
|
||||
- (NSRect)originalContentRectForFrameRect:(NSRect)frameRect;
|
||||
- (void)toggleFullScreenMode:(id)sender;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_H_
|
191
shell/browser/ui/cocoa/atom_ns_window.mm
Normal file
191
shell/browser/ui/cocoa/atom_ns_window.mm
Normal file
|
@ -0,0 +1,191 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_ns_window.h"
|
||||
|
||||
#include "atom/browser/native_window_mac.h"
|
||||
#include "atom/browser/ui/cocoa/atom_preview_item.h"
|
||||
#include "atom/browser/ui/cocoa/atom_touch_bar.h"
|
||||
#include "atom/browser/ui/cocoa/root_view_mac.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "ui/base/cocoa/window_size_constants.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
bool ScopedDisableResize::disable_resize_ = false;
|
||||
|
||||
} // namespace atom
|
||||
|
||||
@implementation AtomNSWindow
|
||||
|
||||
@synthesize acceptsFirstMouse;
|
||||
@synthesize enableLargerThanScreen;
|
||||
@synthesize disableAutoHideCursor;
|
||||
@synthesize disableKeyOrMainWindow;
|
||||
@synthesize vibrantView;
|
||||
|
||||
- (id)initWithShell:(atom::NativeWindowMac*)shell
|
||||
styleMask:(NSUInteger)styleMask {
|
||||
if ((self = [super initWithContentRect:ui::kWindowSizeDeterminedLater
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:YES])) {
|
||||
shell_ = shell;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (atom::NativeWindowMac*)shell {
|
||||
return shell_;
|
||||
}
|
||||
|
||||
- (id)accessibilityFocusedUIElement {
|
||||
views::Widget* widget = shell_->widget();
|
||||
id superFocus = [super accessibilityFocusedUIElement];
|
||||
if (!widget || shell_->IsFocused())
|
||||
return superFocus;
|
||||
return nil;
|
||||
}
|
||||
- (NSRect)originalContentRectForFrameRect:(NSRect)frameRect {
|
||||
return [super contentRectForFrameRect:frameRect];
|
||||
}
|
||||
|
||||
- (NSTouchBar*)makeTouchBar API_AVAILABLE(macosx(10.12.2)) {
|
||||
if (shell_->touch_bar())
|
||||
return [shell_->touch_bar() makeTouchBar];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
// NSWindow overrides.
|
||||
|
||||
- (void)swipeWithEvent:(NSEvent*)event {
|
||||
if (event.deltaY == 1.0) {
|
||||
shell_->NotifyWindowSwipe("up");
|
||||
} else if (event.deltaX == -1.0) {
|
||||
shell_->NotifyWindowSwipe("right");
|
||||
} else if (event.deltaY == -1.0) {
|
||||
shell_->NotifyWindowSwipe("down");
|
||||
} else if (event.deltaX == 1.0) {
|
||||
shell_->NotifyWindowSwipe("left");
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRect)contentRectForFrameRect:(NSRect)frameRect {
|
||||
if (shell_->has_frame())
|
||||
return [super contentRectForFrameRect:frameRect];
|
||||
else
|
||||
return frameRect;
|
||||
}
|
||||
|
||||
- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen {
|
||||
// Resizing is disabled.
|
||||
if (atom::ScopedDisableResize::IsResizeDisabled())
|
||||
return [self frame];
|
||||
|
||||
// Enable the window to be larger than screen.
|
||||
if ([self enableLargerThanScreen])
|
||||
return frameRect;
|
||||
else
|
||||
return [super constrainFrameRect:frameRect toScreen:screen];
|
||||
}
|
||||
|
||||
- (void)setFrame:(NSRect)windowFrame display:(BOOL)displayViews {
|
||||
// constrainFrameRect is not called on hidden windows so disable adjusting
|
||||
// the frame directly when resize is disabled
|
||||
if (!atom::ScopedDisableResize::IsResizeDisabled())
|
||||
[super setFrame:windowFrame display:displayViews];
|
||||
}
|
||||
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute {
|
||||
if ([attribute isEqual:NSAccessibilityTitleAttribute])
|
||||
return base::SysUTF8ToNSString(shell_->GetTitle());
|
||||
if ([attribute isEqual:NSAccessibilityEnabledAttribute])
|
||||
return [NSNumber numberWithBool:YES];
|
||||
if (![attribute isEqualToString:@"AXChildren"])
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
|
||||
// Filter out objects that aren't the title bar buttons. This has the effect
|
||||
// of removing the window title, which VoiceOver already sees.
|
||||
// * when VoiceOver is disabled, this causes Cmd+C to be used for TTS but
|
||||
// still leaves the buttons available in the accessibility tree.
|
||||
// * when VoiceOver is enabled, the full accessibility tree is used.
|
||||
// Without removing the title and with VO disabled, the TTS would always read
|
||||
// the window title instead of using Cmd+C to get the selected text.
|
||||
NSPredicate* predicate = [NSPredicate
|
||||
predicateWithFormat:@"(self isKindOfClass: %@) OR (self.className == %@)",
|
||||
[NSButtonCell class], @"RenderWidgetHostViewCocoa"];
|
||||
|
||||
NSArray* children = [super accessibilityAttributeValue:attribute];
|
||||
return [children filteredArrayUsingPredicate:predicate];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeMainWindow {
|
||||
return !self.disableKeyOrMainWindow;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeKeyWindow {
|
||||
return !self.disableKeyOrMainWindow;
|
||||
}
|
||||
|
||||
- (NSView*)frameView {
|
||||
return [[self contentView] superview];
|
||||
}
|
||||
|
||||
// Quicklook methods
|
||||
|
||||
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel*)panel {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)beginPreviewPanelControl:(QLPreviewPanel*)panel {
|
||||
panel.delegate = [self delegate];
|
||||
panel.dataSource = static_cast<id<QLPreviewPanelDataSource>>([self delegate]);
|
||||
}
|
||||
|
||||
- (void)endPreviewPanelControl:(QLPreviewPanel*)panel {
|
||||
panel.delegate = nil;
|
||||
panel.dataSource = nil;
|
||||
}
|
||||
|
||||
// Custom window button methods
|
||||
|
||||
- (BOOL)windowShouldClose:(id)sender {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)performClose:(id)sender {
|
||||
if (shell_->title_bar_style() ==
|
||||
atom::NativeWindowMac::TitleBarStyle::CUSTOM_BUTTONS_ON_HOVER) {
|
||||
[[self delegate] windowShouldClose:self];
|
||||
} else if (shell_->IsSimpleFullScreen()) {
|
||||
if ([[self delegate] respondsToSelector:@selector(windowShouldClose:)]) {
|
||||
if (![[self delegate] windowShouldClose:self])
|
||||
return;
|
||||
} else if ([self respondsToSelector:@selector(windowShouldClose:)]) {
|
||||
if (![self windowShouldClose:self])
|
||||
return;
|
||||
}
|
||||
[self close];
|
||||
} else {
|
||||
[super performClose:sender];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)toggleFullScreenMode:(id)sender {
|
||||
if (shell_->simple_fullscreen())
|
||||
shell_->SetSimpleFullScreen(!shell_->IsSimpleFullScreen());
|
||||
else
|
||||
[super toggleFullScreen:sender];
|
||||
}
|
||||
|
||||
- (void)performMiniaturize:(id)sender {
|
||||
if (shell_->title_bar_style() ==
|
||||
atom::NativeWindowMac::TitleBarStyle::CUSTOM_BUTTONS_ON_HOVER)
|
||||
[self miniaturize:self];
|
||||
else
|
||||
[super performMiniaturize:sender];
|
||||
}
|
||||
|
||||
@end
|
27
shell/browser/ui/cocoa/atom_ns_window_delegate.h
Normal file
27
shell/browser/ui/cocoa/atom_ns_window_delegate.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_DELEGATE_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_DELEGATE_H_
|
||||
|
||||
#include <Quartz/Quartz.h>
|
||||
|
||||
#include "components/remote_cocoa/app_shim/views_nswindow_delegate.h"
|
||||
|
||||
namespace atom {
|
||||
class NativeWindowMac;
|
||||
}
|
||||
|
||||
@interface AtomNSWindowDelegate
|
||||
: ViewsNSWindowDelegate <NSTouchBarDelegate, QLPreviewPanelDataSource> {
|
||||
@private
|
||||
atom::NativeWindowMac* shell_;
|
||||
bool is_zooming_;
|
||||
int level_;
|
||||
bool is_resizable_;
|
||||
}
|
||||
- (id)initWithShell:(atom::NativeWindowMac*)shell;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_DELEGATE_H_
|
306
shell/browser/ui/cocoa/atom_ns_window_delegate.mm
Normal file
306
shell/browser/ui/cocoa/atom_ns_window_delegate.mm
Normal file
|
@ -0,0 +1,306 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_ns_window_delegate.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "atom/browser/browser.h"
|
||||
#include "atom/browser/native_window_mac.h"
|
||||
#include "atom/browser/ui/cocoa/atom_preview_item.h"
|
||||
#include "atom/browser/ui/cocoa/atom_touch_bar.h"
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
|
||||
#include "ui/views/cocoa/native_widget_mac_ns_window_host.h"
|
||||
#include "ui/views/widget/native_widget_mac.h"
|
||||
|
||||
using TitleBarStyle = atom::NativeWindowMac::TitleBarStyle;
|
||||
|
||||
@implementation AtomNSWindowDelegate
|
||||
|
||||
- (id)initWithShell:(atom::NativeWindowMac*)shell {
|
||||
// The views library assumes the window delegate must be an instance of
|
||||
// ViewsNSWindowDelegate, since we don't have a way to override the creation
|
||||
// of NSWindowDelegate, we have to dynamically replace the window delegate
|
||||
// on the fly.
|
||||
// TODO(zcbenz): Add interface in NativeWidgetMac to allow overriding creating
|
||||
// window delegate.
|
||||
auto* bridge_host = views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
|
||||
shell->GetNativeWindow());
|
||||
auto* bridged_view = bridge_host->bridge_impl();
|
||||
if ((self = [super initWithBridgedNativeWidget:bridged_view])) {
|
||||
shell_ = shell;
|
||||
is_zooming_ = false;
|
||||
level_ = [shell_->GetNativeWindow().GetNativeNSWindow() level];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - NSWindowDelegate
|
||||
|
||||
- (void)windowDidChangeOcclusionState:(NSNotification*)notification {
|
||||
// notification.object is the window that changed its state.
|
||||
// It's safe to use self.window instead if you don't assign one delegate to
|
||||
// many windows
|
||||
NSWindow* window = notification.object;
|
||||
|
||||
// check occlusion binary flag
|
||||
if (window.occlusionState & NSWindowOcclusionStateVisible) {
|
||||
// The app is visible
|
||||
shell_->NotifyWindowShow();
|
||||
} else {
|
||||
// The app is not visible
|
||||
shell_->NotifyWindowHide();
|
||||
}
|
||||
}
|
||||
|
||||
// Called when the user clicks the zoom button or selects it from the Window
|
||||
// menu to determine the "standard size" of the window.
|
||||
- (NSRect)windowWillUseStandardFrame:(NSWindow*)window
|
||||
defaultFrame:(NSRect)frame {
|
||||
if (!shell_->zoom_to_page_width())
|
||||
return frame;
|
||||
|
||||
// If the shift key is down, maximize.
|
||||
if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask)
|
||||
return frame;
|
||||
|
||||
// Get preferred width from observers. Usually the page width.
|
||||
int preferred_width = 0;
|
||||
shell_->NotifyWindowRequestPreferredWith(&preferred_width);
|
||||
|
||||
// Never shrink from the current size on zoom.
|
||||
NSRect window_frame = [window frame];
|
||||
CGFloat zoomed_width =
|
||||
std::max(static_cast<CGFloat>(preferred_width), NSWidth(window_frame));
|
||||
|
||||
// |frame| determines our maximum extents. We need to set the origin of the
|
||||
// frame -- and only move it left if necessary.
|
||||
if (window_frame.origin.x + zoomed_width > NSMaxX(frame))
|
||||
frame.origin.x = NSMaxX(frame) - zoomed_width;
|
||||
else
|
||||
frame.origin.x = window_frame.origin.x;
|
||||
|
||||
// Set the width. Don't touch y or height.
|
||||
frame.size.width = zoomed_width;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
- (void)windowDidBecomeMain:(NSNotification*)notification {
|
||||
shell_->NotifyWindowFocus();
|
||||
}
|
||||
|
||||
- (void)windowDidResignMain:(NSNotification*)notification {
|
||||
shell_->NotifyWindowBlur();
|
||||
}
|
||||
|
||||
- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize {
|
||||
NSSize newSize = frameSize;
|
||||
double aspectRatio = shell_->GetAspectRatio();
|
||||
|
||||
if (aspectRatio > 0.0) {
|
||||
gfx::Size windowSize = shell_->GetSize();
|
||||
gfx::Size contentSize = shell_->GetContentSize();
|
||||
gfx::Size extraSize = shell_->GetAspectRatioExtraSize();
|
||||
|
||||
double extraWidthPlusFrame =
|
||||
windowSize.width() - contentSize.width() + extraSize.width();
|
||||
double extraHeightPlusFrame =
|
||||
windowSize.height() - contentSize.height() + extraSize.height();
|
||||
|
||||
newSize.width =
|
||||
roundf((frameSize.height - extraHeightPlusFrame) * aspectRatio +
|
||||
extraWidthPlusFrame);
|
||||
newSize.height =
|
||||
roundf((newSize.width - extraWidthPlusFrame) / aspectRatio +
|
||||
extraHeightPlusFrame);
|
||||
}
|
||||
|
||||
{
|
||||
bool prevent_default = false;
|
||||
gfx::Rect new_bounds(gfx::Point(sender.frame.origin), gfx::Size(newSize));
|
||||
shell_->NotifyWindowWillResize(new_bounds, &prevent_default);
|
||||
if (prevent_default) {
|
||||
return sender.frame.size;
|
||||
}
|
||||
}
|
||||
|
||||
return newSize;
|
||||
}
|
||||
|
||||
- (void)windowDidResize:(NSNotification*)notification {
|
||||
[super windowDidResize:notification];
|
||||
shell_->NotifyWindowResize();
|
||||
}
|
||||
|
||||
- (void)windowDidMove:(NSNotification*)notification {
|
||||
[super windowDidMove:notification];
|
||||
// TODO(zcbenz): Remove the alias after figuring out a proper
|
||||
// way to dispatch move.
|
||||
shell_->NotifyWindowMove();
|
||||
shell_->NotifyWindowMoved();
|
||||
}
|
||||
|
||||
- (void)windowWillMiniaturize:(NSNotification*)notification {
|
||||
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
|
||||
// store the current status window level to be restored in
|
||||
// windowDidDeminiaturize
|
||||
level_ = [window level];
|
||||
[window setLevel:NSNormalWindowLevel];
|
||||
}
|
||||
|
||||
- (void)windowDidMiniaturize:(NSNotification*)notification {
|
||||
[super windowDidMiniaturize:notification];
|
||||
shell_->NotifyWindowMinimize();
|
||||
}
|
||||
|
||||
- (void)windowDidDeminiaturize:(NSNotification*)notification {
|
||||
[super windowDidDeminiaturize:notification];
|
||||
[shell_->GetNativeWindow().GetNativeNSWindow() setLevel:level_];
|
||||
shell_->NotifyWindowRestore();
|
||||
}
|
||||
|
||||
- (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame {
|
||||
is_zooming_ = true;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)windowDidEndLiveResize:(NSNotification*)notification {
|
||||
if (is_zooming_) {
|
||||
if (shell_->IsMaximized())
|
||||
shell_->NotifyWindowMaximize();
|
||||
else
|
||||
shell_->NotifyWindowUnmaximize();
|
||||
is_zooming_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowWillEnterFullScreen:(NSNotification*)notification {
|
||||
// Setting resizable to true before entering fullscreen
|
||||
is_resizable_ = shell_->IsResizable();
|
||||
shell_->SetResizable(true);
|
||||
// Hide the native toolbar before entering fullscreen, so there is no visual
|
||||
// artifacts.
|
||||
if (shell_->title_bar_style() == TitleBarStyle::HIDDEN_INSET) {
|
||||
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
|
||||
[window setToolbar:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowDidEnterFullScreen:(NSNotification*)notification {
|
||||
shell_->NotifyWindowEnterFullScreen();
|
||||
|
||||
// For frameless window we don't show set title for normal mode since the
|
||||
// titlebar is expected to be empty, but after entering fullscreen mode we
|
||||
// have to set one, because title bar is visible here.
|
||||
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
|
||||
if ((shell_->transparent() || !shell_->has_frame()) &&
|
||||
// FIXME(zcbenz): Showing titlebar for hiddenInset window is weird under
|
||||
// fullscreen mode.
|
||||
// Show title if fullscreen_window_title flag is set
|
||||
(shell_->title_bar_style() != TitleBarStyle::HIDDEN_INSET ||
|
||||
shell_->fullscreen_window_title())) {
|
||||
[window setTitleVisibility:NSWindowTitleVisible];
|
||||
}
|
||||
|
||||
// Restore the native toolbar immediately after entering fullscreen, if we
|
||||
// do this before leaving fullscreen, traffic light buttons will be jumping.
|
||||
if (shell_->title_bar_style() == TitleBarStyle::HIDDEN_INSET) {
|
||||
base::scoped_nsobject<NSToolbar> toolbar(
|
||||
[[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]);
|
||||
[toolbar setShowsBaselineSeparator:NO];
|
||||
[window setToolbar:toolbar];
|
||||
|
||||
// Set window style to hide the toolbar, otherwise the toolbar will show
|
||||
// in fullscreen mode.
|
||||
[window setTitlebarAppearsTransparent:NO];
|
||||
shell_->SetStyleMask(true, NSWindowStyleMaskFullSizeContentView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowWillExitFullScreen:(NSNotification*)notification {
|
||||
// Restore the titlebar visibility.
|
||||
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
|
||||
if ((shell_->transparent() || !shell_->has_frame()) &&
|
||||
(shell_->title_bar_style() != TitleBarStyle::HIDDEN_INSET ||
|
||||
shell_->fullscreen_window_title())) {
|
||||
[window setTitleVisibility:NSWindowTitleHidden];
|
||||
}
|
||||
|
||||
// Turn off the style for toolbar.
|
||||
if (shell_->title_bar_style() == TitleBarStyle::HIDDEN_INSET) {
|
||||
shell_->SetStyleMask(false, NSWindowStyleMaskFullSizeContentView);
|
||||
[window setTitlebarAppearsTransparent:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowDidExitFullScreen:(NSNotification*)notification {
|
||||
shell_->SetResizable(is_resizable_);
|
||||
shell_->NotifyWindowLeaveFullScreen();
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification*)notification {
|
||||
shell_->NotifyWindowClosed();
|
||||
|
||||
// Clears the delegate when window is going to be closed, since EL Capitan it
|
||||
// is possible that the methods of delegate would get called after the window
|
||||
// has been closed.
|
||||
auto* bridge_host = views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
|
||||
shell_->GetNativeWindow());
|
||||
auto* bridged_view = bridge_host->bridge_impl();
|
||||
bridged_view->OnWindowWillClose();
|
||||
}
|
||||
|
||||
- (BOOL)windowShouldClose:(id)window {
|
||||
shell_->NotifyWindowCloseButtonClicked();
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSRect)window:(NSWindow*)window
|
||||
willPositionSheet:(NSWindow*)sheet
|
||||
usingRect:(NSRect)rect {
|
||||
NSView* view = window.contentView;
|
||||
|
||||
rect.origin.x = shell_->GetSheetOffsetX();
|
||||
rect.origin.y = view.frame.size.height - shell_->GetSheetOffsetY();
|
||||
return rect;
|
||||
}
|
||||
|
||||
- (void)windowWillBeginSheet:(NSNotification*)notification {
|
||||
shell_->NotifyWindowSheetBegin();
|
||||
}
|
||||
|
||||
- (void)windowDidEndSheet:(NSNotification*)notification {
|
||||
shell_->NotifyWindowSheetEnd();
|
||||
}
|
||||
|
||||
- (IBAction)newWindowForTab:(id)sender {
|
||||
shell_->NotifyNewWindowForTab();
|
||||
atom::Browser::Get()->NewWindowForTab();
|
||||
}
|
||||
|
||||
#pragma mark - NSTouchBarDelegate
|
||||
|
||||
- (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar
|
||||
makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
if (touchBar && shell_->touch_bar())
|
||||
return [shell_->touch_bar() makeItemForIdentifier:identifier];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - QLPreviewPanelDataSource
|
||||
|
||||
- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel*)panel {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (id<QLPreviewItem>)previewPanel:(QLPreviewPanel*)panel
|
||||
previewItemAtIndex:(NSInteger)index {
|
||||
return shell_->preview_item();
|
||||
}
|
||||
|
||||
@end
|
17
shell/browser/ui/cocoa/atom_preview_item.h
Normal file
17
shell/browser/ui/cocoa/atom_preview_item.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_PREVIEW_ITEM_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_PREVIEW_ITEM_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Quartz/Quartz.h>
|
||||
|
||||
@interface AtomPreviewItem : NSObject <QLPreviewItem>
|
||||
@property(nonatomic, retain) NSURL* previewItemURL;
|
||||
@property(nonatomic, retain) NSString* previewItemTitle;
|
||||
- (id)initWithURL:(NSURL*)url title:(NSString*)title;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_PREVIEW_ITEM_H_
|
21
shell/browser/ui/cocoa/atom_preview_item.mm
Normal file
21
shell/browser/ui/cocoa/atom_preview_item.mm
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_preview_item.h"
|
||||
|
||||
@implementation AtomPreviewItem
|
||||
|
||||
@synthesize previewItemURL;
|
||||
@synthesize previewItemTitle;
|
||||
|
||||
- (id)initWithURL:(NSURL*)url title:(NSString*)title {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.previewItemURL = url;
|
||||
self.previewItemTitle = title;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
101
shell/browser/ui/cocoa/atom_touch_bar.h
Normal file
101
shell/browser/ui/cocoa/atom_touch_bar.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h"
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "native_mate/constructor.h"
|
||||
#include "native_mate/persistent_dictionary.h"
|
||||
|
||||
@interface AtomTouchBar : NSObject <NSScrubberDelegate,
|
||||
NSScrubberDataSource,
|
||||
NSScrubberFlowLayoutDelegate> {
|
||||
@protected
|
||||
std::vector<mate::PersistentDictionary> ordered_settings_;
|
||||
std::map<std::string, mate::PersistentDictionary> settings_;
|
||||
id<NSTouchBarDelegate> delegate_;
|
||||
atom::NativeWindow* window_;
|
||||
}
|
||||
|
||||
- (id)initWithDelegate:(id<NSTouchBarDelegate>)delegate
|
||||
window:(atom::NativeWindow*)window
|
||||
settings:(const std::vector<mate::PersistentDictionary>&)settings;
|
||||
|
||||
- (NSTouchBar*)makeTouchBar API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSMutableArray*)identifiersFromSettings:
|
||||
(const std::vector<mate::PersistentDictionary>&)settings;
|
||||
- (void)refreshTouchBarItem:(NSTouchBar*)touchBar
|
||||
id:(const std::string&)item_id
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)addNonDefaultTouchBarItems:
|
||||
(const std::vector<mate::PersistentDictionary>&)items;
|
||||
- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item
|
||||
forTouchBar:(NSTouchBar*)touchBar
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
|
||||
- (NSString*)idFromIdentifier:(NSString*)identifier
|
||||
withPrefix:(NSString*)prefix;
|
||||
- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id
|
||||
type:(const std::string&)typere;
|
||||
- (bool)hasItemWithID:(const std::string&)item_id;
|
||||
- (NSColor*)colorFromHexColorString:(const std::string&)colorString;
|
||||
|
||||
// Selector actions
|
||||
- (void)buttonAction:(id)sender;
|
||||
- (void)colorPickerAction:(id)sender API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)sliderAction:(id)sender API_AVAILABLE(macosx(10.12.2));
|
||||
|
||||
// Helpers to create touch bar items
|
||||
- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makeButtonForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makeLabelForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makeSliderForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makePopoverForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makeGroupForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
|
||||
// Helpers to update touch bar items
|
||||
- (void)updateButton:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)updateLabel:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)updateSlider:(NSSliderTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)updatePopover:(NSPopoverTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_
|
820
shell/browser/ui/cocoa/atom_touch_bar.mm
Normal file
820
shell/browser/ui/cocoa/atom_touch_bar.mm
Normal file
|
@ -0,0 +1,820 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "atom/browser/ui/cocoa/atom_touch_bar.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/common/color_util.h"
|
||||
#include "atom/common/native_mate_converters/image_converter.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "skia/ext/skia_utils_mac.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
|
||||
@implementation AtomTouchBar
|
||||
|
||||
static NSTouchBarItemIdentifier ButtonIdentifier =
|
||||
@"com.electron.touchbar.button.";
|
||||
static NSTouchBarItemIdentifier ColorPickerIdentifier =
|
||||
@"com.electron.touchbar.colorpicker.";
|
||||
static NSTouchBarItemIdentifier GroupIdentifier =
|
||||
@"com.electron.touchbar.group.";
|
||||
static NSTouchBarItemIdentifier LabelIdentifier =
|
||||
@"com.electron.touchbar.label.";
|
||||
static NSTouchBarItemIdentifier PopoverIdentifier =
|
||||
@"com.electron.touchbar.popover.";
|
||||
static NSTouchBarItemIdentifier SliderIdentifier =
|
||||
@"com.electron.touchbar.slider.";
|
||||
static NSTouchBarItemIdentifier SegmentedControlIdentifier =
|
||||
@"com.electron.touchbar.segmentedcontrol.";
|
||||
static NSTouchBarItemIdentifier ScrubberIdentifier =
|
||||
@"com.electron.touchbar.scrubber.";
|
||||
|
||||
static NSString* const TextScrubberItemIdentifier = @"scrubber.text.item";
|
||||
static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item";
|
||||
|
||||
- (id)initWithDelegate:(id<NSTouchBarDelegate>)delegate
|
||||
window:(atom::NativeWindow*)window
|
||||
settings:
|
||||
(const std::vector<mate::PersistentDictionary>&)settings {
|
||||
if ((self = [super init])) {
|
||||
delegate_ = delegate;
|
||||
window_ = window;
|
||||
ordered_settings_ = settings;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSTouchBar*)makeTouchBar {
|
||||
NSMutableArray* identifiers =
|
||||
[self identifiersFromSettings:ordered_settings_];
|
||||
return [self touchBarFromItemIdentifiers:identifiers];
|
||||
}
|
||||
|
||||
- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items {
|
||||
base::scoped_nsobject<NSTouchBar> bar(
|
||||
[[NSClassFromString(@"NSTouchBar") alloc] init]);
|
||||
[bar setDelegate:delegate_];
|
||||
[bar setDefaultItemIdentifiers:items];
|
||||
return bar.autorelease();
|
||||
}
|
||||
|
||||
- (NSMutableArray*)identifiersFromSettings:
|
||||
(const std::vector<mate::PersistentDictionary>&)dicts {
|
||||
NSMutableArray* identifiers = [NSMutableArray array];
|
||||
|
||||
if (@available(macOS 10.12.2, *)) {
|
||||
for (const auto& item : dicts) {
|
||||
std::string type;
|
||||
std::string item_id;
|
||||
if (item.Get("type", &type) && item.Get("id", &item_id)) {
|
||||
NSTouchBarItemIdentifier identifier = nil;
|
||||
if (type == "spacer") {
|
||||
std::string size;
|
||||
item.Get("size", &size);
|
||||
if (size == "large") {
|
||||
identifier = NSTouchBarItemIdentifierFixedSpaceLarge;
|
||||
} else if (size == "flexible") {
|
||||
identifier = NSTouchBarItemIdentifierFlexibleSpace;
|
||||
} else {
|
||||
identifier = NSTouchBarItemIdentifierFixedSpaceSmall;
|
||||
}
|
||||
} else {
|
||||
identifier = [self identifierFromID:item_id type:type];
|
||||
}
|
||||
|
||||
if (identifier) {
|
||||
settings_[item_id] = item;
|
||||
[identifiers addObject:identifier];
|
||||
}
|
||||
}
|
||||
}
|
||||
[identifiers addObject:NSTouchBarItemIdentifierOtherItemsProxy];
|
||||
}
|
||||
|
||||
return identifiers;
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
|
||||
NSString* item_id = nil;
|
||||
|
||||
if ([identifier hasPrefix:ButtonIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier];
|
||||
return [self makeButtonForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:LabelIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier];
|
||||
return [self makeLabelForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:ColorPickerIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier
|
||||
withPrefix:ColorPickerIdentifier];
|
||||
return [self makeColorPickerForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:SliderIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier];
|
||||
return [self makeSliderForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:PopoverIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:PopoverIdentifier];
|
||||
return [self makePopoverForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:GroupIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier];
|
||||
return [self makeGroupForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:SegmentedControlIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier
|
||||
withPrefix:SegmentedControlIdentifier];
|
||||
return [self makeSegmentedControlForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:ScrubberIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:ScrubberIdentifier];
|
||||
return [self makeScrubberForID:item_id withIdentifier:identifier];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)refreshTouchBarItem:(NSTouchBar*)touchBar
|
||||
id:(NSTouchBarItemIdentifier)identifier
|
||||
withType:(const std::string&)item_type
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
NSTouchBarItem* item = [touchBar itemForIdentifier:identifier];
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
if (item_type == "button") {
|
||||
[self updateButton:(NSCustomTouchBarItem*)item withSettings:settings];
|
||||
} else if (item_type == "label") {
|
||||
[self updateLabel:(NSCustomTouchBarItem*)item withSettings:settings];
|
||||
} else if (item_type == "colorpicker") {
|
||||
[self updateColorPicker:(NSColorPickerTouchBarItem*)item
|
||||
withSettings:settings];
|
||||
} else if (item_type == "slider") {
|
||||
[self updateSlider:(NSSliderTouchBarItem*)item withSettings:settings];
|
||||
} else if (item_type == "popover") {
|
||||
[self updatePopover:(NSPopoverTouchBarItem*)item withSettings:settings];
|
||||
} else if (item_type == "segmented_control") {
|
||||
[self updateSegmentedControl:(NSCustomTouchBarItem*)item
|
||||
withSettings:settings];
|
||||
} else if (item_type == "scrubber") {
|
||||
[self updateScrubber:(NSCustomTouchBarItem*)item withSettings:settings];
|
||||
} else if (item_type == "group") {
|
||||
[self updateGroup:(NSGroupTouchBarItem*)item withSettings:settings];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addNonDefaultTouchBarItems:
|
||||
(const std::vector<mate::PersistentDictionary>&)items {
|
||||
[self identifiersFromSettings:items];
|
||||
}
|
||||
|
||||
- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item
|
||||
forTouchBar:(NSTouchBar*)touchBar {
|
||||
if (![touchBar
|
||||
respondsToSelector:@selector(escapeKeyReplacementItemIdentifier)])
|
||||
return;
|
||||
std::string type;
|
||||
std::string item_id;
|
||||
NSTouchBarItemIdentifier identifier = nil;
|
||||
if (item.Get("type", &type) && item.Get("id", &item_id)) {
|
||||
identifier = [self identifierFromID:item_id type:type];
|
||||
}
|
||||
if (identifier) {
|
||||
[self addNonDefaultTouchBarItems:{item}];
|
||||
touchBar.escapeKeyReplacementItemIdentifier = identifier;
|
||||
} else {
|
||||
touchBar.escapeKeyReplacementItemIdentifier = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshTouchBarItem:(NSTouchBar*)touchBar
|
||||
id:(const std::string&)item_id {
|
||||
if (![self hasItemWithID:item_id])
|
||||
return;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[item_id];
|
||||
std::string item_type;
|
||||
settings.Get("type", &item_type);
|
||||
|
||||
auto identifier = [self identifierFromID:item_id type:item_type];
|
||||
if (!identifier)
|
||||
return;
|
||||
|
||||
std::vector<mate::Dictionary> parents;
|
||||
settings.Get("_parents", &parents);
|
||||
for (auto& parent : parents) {
|
||||
std::string parent_type;
|
||||
std::string parent_id;
|
||||
if (!parent.Get("type", &parent_type) || !parent.Get("id", &parent_id))
|
||||
continue;
|
||||
auto parentIdentifier = [self identifierFromID:parent_id type:parent_type];
|
||||
if (!parentIdentifier)
|
||||
continue;
|
||||
|
||||
if (parent_type == "popover") {
|
||||
NSPopoverTouchBarItem* popoverItem =
|
||||
[touchBar itemForIdentifier:parentIdentifier];
|
||||
[self refreshTouchBarItem:popoverItem.popoverTouchBar
|
||||
id:identifier
|
||||
withType:item_type
|
||||
withSettings:settings];
|
||||
} else if (parent_type == "group") {
|
||||
NSGroupTouchBarItem* groupItem =
|
||||
[touchBar itemForIdentifier:parentIdentifier];
|
||||
[self refreshTouchBarItem:groupItem.groupTouchBar
|
||||
id:identifier
|
||||
withType:item_type
|
||||
withSettings:settings];
|
||||
}
|
||||
}
|
||||
|
||||
[self refreshTouchBarItem:touchBar
|
||||
id:identifier
|
||||
withType:item_type
|
||||
withSettings:settings];
|
||||
}
|
||||
|
||||
- (void)buttonAction:(id)sender {
|
||||
NSString* item_id =
|
||||
[NSString stringWithFormat:@"%ld", ((NSButton*)sender).tag];
|
||||
window_->NotifyTouchBarItemInteraction([item_id UTF8String],
|
||||
base::DictionaryValue());
|
||||
}
|
||||
|
||||
- (void)colorPickerAction:(id)sender {
|
||||
NSString* identifier = ((NSColorPickerTouchBarItem*)sender).identifier;
|
||||
NSString* item_id = [self idFromIdentifier:identifier
|
||||
withPrefix:ColorPickerIdentifier];
|
||||
NSColor* color = ((NSColorPickerTouchBarItem*)sender).color;
|
||||
std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color));
|
||||
base::DictionaryValue details;
|
||||
details.SetString("color", hex_color);
|
||||
window_->NotifyTouchBarItemInteraction([item_id UTF8String], details);
|
||||
}
|
||||
|
||||
- (void)sliderAction:(id)sender {
|
||||
NSString* identifier = ((NSSliderTouchBarItem*)sender).identifier;
|
||||
NSString* item_id = [self idFromIdentifier:identifier
|
||||
withPrefix:SliderIdentifier];
|
||||
base::DictionaryValue details;
|
||||
details.SetInteger("value",
|
||||
[((NSSliderTouchBarItem*)sender).slider intValue]);
|
||||
window_->NotifyTouchBarItemInteraction([item_id UTF8String], details);
|
||||
}
|
||||
|
||||
- (NSString*)idFromIdentifier:(NSString*)identifier
|
||||
withPrefix:(NSString*)prefix {
|
||||
return [identifier substringFromIndex:[prefix length]];
|
||||
}
|
||||
|
||||
- (void)segmentedControlAction:(id)sender {
|
||||
NSString* item_id =
|
||||
[NSString stringWithFormat:@"%ld", ((NSSegmentedControl*)sender).tag];
|
||||
base::DictionaryValue details;
|
||||
details.SetInteger("selectedIndex",
|
||||
((NSSegmentedControl*)sender).selectedSegment);
|
||||
details.SetBoolean(
|
||||
"isSelected",
|
||||
[((NSSegmentedControl*)sender)
|
||||
isSelectedForSegment:((NSSegmentedControl*)sender).selectedSegment]);
|
||||
window_->NotifyTouchBarItemInteraction([item_id UTF8String], details);
|
||||
}
|
||||
|
||||
- (void)scrubber:(NSScrubber*)scrubber
|
||||
didSelectItemAtIndex:(NSInteger)selectedIndex
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
base::DictionaryValue details;
|
||||
details.SetInteger("selectedIndex", selectedIndex);
|
||||
details.SetString("type", "select");
|
||||
window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String],
|
||||
details);
|
||||
}
|
||||
|
||||
- (void)scrubber:(NSScrubber*)scrubber
|
||||
didHighlightItemAtIndex:(NSInteger)highlightedIndex
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
base::DictionaryValue details;
|
||||
details.SetInteger("highlightedIndex", highlightedIndex);
|
||||
details.SetString("type", "highlight");
|
||||
window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String],
|
||||
details);
|
||||
}
|
||||
|
||||
- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id
|
||||
type:(const std::string&)type {
|
||||
NSTouchBarItemIdentifier base_identifier = nil;
|
||||
if (type == "button")
|
||||
base_identifier = ButtonIdentifier;
|
||||
else if (type == "label")
|
||||
base_identifier = LabelIdentifier;
|
||||
else if (type == "colorpicker")
|
||||
base_identifier = ColorPickerIdentifier;
|
||||
else if (type == "slider")
|
||||
base_identifier = SliderIdentifier;
|
||||
else if (type == "popover")
|
||||
base_identifier = PopoverIdentifier;
|
||||
else if (type == "group")
|
||||
base_identifier = GroupIdentifier;
|
||||
else if (type == "segmented_control")
|
||||
base_identifier = SegmentedControlIdentifier;
|
||||
else if (type == "scrubber")
|
||||
base_identifier = ScrubberIdentifier;
|
||||
|
||||
if (base_identifier)
|
||||
return [NSString stringWithFormat:@"%@%s", base_identifier, item_id.data()];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (bool)hasItemWithID:(const std::string&)item_id {
|
||||
return settings_.find(item_id) != settings_.end();
|
||||
}
|
||||
|
||||
- (NSColor*)colorFromHexColorString:(const std::string&)colorString {
|
||||
SkColor color = atom::ParseHexColor(colorString);
|
||||
return skia::SkColorToDeviceNSColor(color);
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeButtonForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
|
||||
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
NSButton* button = [NSButton buttonWithTitle:@""
|
||||
target:self
|
||||
action:@selector(buttonAction:)];
|
||||
button.tag = [id floatValue];
|
||||
[item setView:button];
|
||||
[self updateButton:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateButton:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings {
|
||||
NSButton* button = (NSButton*)item.view;
|
||||
|
||||
std::string backgroundColor;
|
||||
if (settings.Get("backgroundColor", &backgroundColor)) {
|
||||
button.bezelColor = [self colorFromHexColorString:backgroundColor];
|
||||
}
|
||||
|
||||
std::string label;
|
||||
settings.Get("label", &label);
|
||||
button.title = base::SysUTF8ToNSString(label);
|
||||
|
||||
gfx::Image image;
|
||||
if (settings.Get("icon", &image)) {
|
||||
button.image = image.AsNSImage();
|
||||
|
||||
std::string iconPosition;
|
||||
settings.Get("iconPosition", &iconPosition);
|
||||
if (iconPosition == "left") {
|
||||
button.imagePosition = NSImageLeft;
|
||||
} else if (iconPosition == "right") {
|
||||
button.imagePosition = NSImageRight;
|
||||
} else {
|
||||
button.imagePosition = NSImageOverlaps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeLabelForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
|
||||
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
[item setView:[NSTextField labelWithString:@""]];
|
||||
[self updateLabel:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateLabel:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings {
|
||||
NSTextField* text_field = (NSTextField*)item.view;
|
||||
|
||||
std::string label;
|
||||
settings.Get("label", &label);
|
||||
text_field.stringValue = base::SysUTF8ToNSString(label);
|
||||
|
||||
std::string textColor;
|
||||
if (settings.Get("textColor", &textColor) && !textColor.empty()) {
|
||||
text_field.textColor = [self colorFromHexColorString:textColor];
|
||||
} else {
|
||||
text_field.textColor = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSColorPickerTouchBarItem> item([[NSClassFromString(
|
||||
@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
[item setTarget:self];
|
||||
[item setAction:@selector(colorPickerAction:)];
|
||||
[self updateColorPicker:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings {
|
||||
std::vector<std::string> colors;
|
||||
if (settings.Get("availableColors", &colors) && !colors.empty()) {
|
||||
NSColorList* color_list =
|
||||
[[[NSColorList alloc] initWithName:@""] autorelease];
|
||||
for (size_t i = 0; i < colors.size(); ++i) {
|
||||
[color_list insertColor:[self colorFromHexColorString:colors[i]]
|
||||
key:base::SysUTF8ToNSString(colors[i])
|
||||
atIndex:i];
|
||||
}
|
||||
item.colorList = color_list;
|
||||
}
|
||||
|
||||
std::string selectedColor;
|
||||
if (settings.Get("selectedColor", &selectedColor)) {
|
||||
item.color = [self colorFromHexColorString:selectedColor];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeSliderForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSSliderTouchBarItem> item([[NSClassFromString(
|
||||
@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
[item setTarget:self];
|
||||
[item setAction:@selector(sliderAction:)];
|
||||
[self updateSlider:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateSlider:(NSSliderTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings {
|
||||
std::string label;
|
||||
settings.Get("label", &label);
|
||||
item.label = base::SysUTF8ToNSString(label);
|
||||
|
||||
int maxValue = 100;
|
||||
int minValue = 0;
|
||||
int value = 50;
|
||||
settings.Get("minValue", &minValue);
|
||||
settings.Get("maxValue", &maxValue);
|
||||
settings.Get("value", &value);
|
||||
|
||||
item.slider.minValue = minValue;
|
||||
item.slider.maxValue = maxValue;
|
||||
item.slider.doubleValue = value;
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makePopoverForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSPopoverTouchBarItem> item([[NSClassFromString(
|
||||
@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
[self updatePopover:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updatePopover:(NSPopoverTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings {
|
||||
std::string label;
|
||||
settings.Get("label", &label);
|
||||
item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label);
|
||||
|
||||
gfx::Image image;
|
||||
if (settings.Get("icon", &image)) {
|
||||
item.collapsedRepresentationImage = image.AsNSImage();
|
||||
}
|
||||
|
||||
bool showCloseButton = true;
|
||||
settings.Get("showCloseButton", &showCloseButton);
|
||||
item.showsCloseButton = showCloseButton;
|
||||
|
||||
mate::PersistentDictionary child;
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
if (settings.Get("child", &child) && child.Get("ordereredItems", &items)) {
|
||||
item.popoverTouchBar =
|
||||
[self touchBarFromItemIdentifiers:[self identifiersFromSettings:items]];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeGroupForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
|
||||
mate::PersistentDictionary child;
|
||||
if (!settings.Get("child", &child))
|
||||
return nil;
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
if (!child.Get("ordereredItems", &items))
|
||||
return nil;
|
||||
|
||||
NSMutableArray* generatedItems = [NSMutableArray array];
|
||||
NSMutableArray* identifiers = [self identifiersFromSettings:items];
|
||||
for (NSUInteger i = 0; i < [identifiers count]; ++i) {
|
||||
if ([identifiers objectAtIndex:i] !=
|
||||
NSTouchBarItemIdentifierOtherItemsProxy) {
|
||||
NSTouchBarItem* generatedItem =
|
||||
[self makeItemForIdentifier:[identifiers objectAtIndex:i]];
|
||||
if (generatedItem) {
|
||||
[generatedItems addObject:generatedItem];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [NSClassFromString(@"NSGroupTouchBarItem")
|
||||
groupItemWithIdentifier:identifier
|
||||
items:generatedItems];
|
||||
}
|
||||
|
||||
- (void)updateGroup:(NSGroupTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
mate::PersistentDictionary child;
|
||||
if (!settings.Get("child", &child))
|
||||
return;
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
if (!child.Get("ordereredItems", &items))
|
||||
return;
|
||||
|
||||
item.groupTouchBar =
|
||||
[self touchBarFromItemIdentifiers:[self identifiersFromSettings:items]];
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeSegmentedControlForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
|
||||
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
|
||||
NSSegmentedControl* control = [NSSegmentedControl
|
||||
segmentedControlWithLabels:[NSMutableArray array]
|
||||
trackingMode:NSSegmentSwitchTrackingSelectOne
|
||||
target:self
|
||||
action:@selector(segmentedControlAction:)];
|
||||
control.tag = [id floatValue];
|
||||
[item setView:control];
|
||||
|
||||
[self updateSegmentedControl:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateSegmentedControl:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
NSSegmentedControl* control = item.view;
|
||||
|
||||
std::string segmentStyle;
|
||||
settings.Get("segmentStyle", &segmentStyle);
|
||||
if (segmentStyle == "rounded")
|
||||
control.segmentStyle = NSSegmentStyleRounded;
|
||||
else if (segmentStyle == "textured-rounded")
|
||||
control.segmentStyle = NSSegmentStyleTexturedRounded;
|
||||
else if (segmentStyle == "round-rect")
|
||||
control.segmentStyle = NSSegmentStyleRoundRect;
|
||||
else if (segmentStyle == "textured-square")
|
||||
control.segmentStyle = NSSegmentStyleTexturedSquare;
|
||||
else if (segmentStyle == "capsule")
|
||||
control.segmentStyle = NSSegmentStyleCapsule;
|
||||
else if (segmentStyle == "small-square")
|
||||
control.segmentStyle = NSSegmentStyleSmallSquare;
|
||||
else if (segmentStyle == "separated")
|
||||
control.segmentStyle = NSSegmentStyleSeparated;
|
||||
else
|
||||
control.segmentStyle = NSSegmentStyleAutomatic;
|
||||
|
||||
std::string segmentMode;
|
||||
settings.Get("mode", &segmentMode);
|
||||
if (segmentMode == "multiple")
|
||||
control.trackingMode = NSSegmentSwitchTrackingSelectAny;
|
||||
else if (segmentMode == "buttons")
|
||||
control.trackingMode = NSSegmentSwitchTrackingMomentary;
|
||||
else
|
||||
control.trackingMode = NSSegmentSwitchTrackingSelectOne;
|
||||
|
||||
std::vector<mate::Dictionary> segments;
|
||||
settings.Get("segments", &segments);
|
||||
|
||||
control.segmentCount = segments.size();
|
||||
for (size_t i = 0; i < segments.size(); ++i) {
|
||||
std::string label;
|
||||
gfx::Image image;
|
||||
bool enabled = true;
|
||||
segments[i].Get("enabled", &enabled);
|
||||
if (segments[i].Get("label", &label)) {
|
||||
[control setLabel:base::SysUTF8ToNSString(label) forSegment:i];
|
||||
} else {
|
||||
[control setLabel:@"" forSegment:i];
|
||||
}
|
||||
if (segments[i].Get("icon", &image)) {
|
||||
[control setImage:image.AsNSImage() forSegment:i];
|
||||
[control setImageScaling:NSImageScaleProportionallyUpOrDown forSegment:i];
|
||||
} else {
|
||||
[control setImage:nil forSegment:i];
|
||||
}
|
||||
[control setEnabled:enabled forSegment:i];
|
||||
}
|
||||
|
||||
int selectedIndex = 0;
|
||||
settings.Get("selectedIndex", &selectedIndex);
|
||||
if (selectedIndex >= 0 && selectedIndex < control.segmentCount)
|
||||
control.selectedSegment = selectedIndex;
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeScrubberForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
|
||||
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
|
||||
NSScrubber* scrubber = [[[NSClassFromString(@"NSScrubber") alloc]
|
||||
initWithFrame:NSZeroRect] autorelease];
|
||||
|
||||
[scrubber registerClass:NSClassFromString(@"NSScrubberTextItemView")
|
||||
forItemIdentifier:TextScrubberItemIdentifier];
|
||||
[scrubber registerClass:NSClassFromString(@"NSScrubberImageItemView")
|
||||
forItemIdentifier:ImageScrubberItemIdentifier];
|
||||
|
||||
scrubber.delegate = self;
|
||||
scrubber.dataSource = self;
|
||||
scrubber.identifier = id;
|
||||
|
||||
[item setView:scrubber];
|
||||
[self updateScrubber:item withSettings:settings];
|
||||
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateScrubber:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
NSScrubber* scrubber = item.view;
|
||||
|
||||
bool showsArrowButtons = false;
|
||||
settings.Get("showArrowButtons", &showsArrowButtons);
|
||||
scrubber.showsArrowButtons = showsArrowButtons;
|
||||
|
||||
std::string selectedStyle;
|
||||
std::string overlayStyle;
|
||||
settings.Get("selectedStyle", &selectedStyle);
|
||||
settings.Get("overlayStyle", &overlayStyle);
|
||||
|
||||
if (selectedStyle == "outline") {
|
||||
scrubber.selectionBackgroundStyle =
|
||||
[NSClassFromString(@"NSScrubberSelectionStyle") outlineOverlayStyle];
|
||||
} else if (selectedStyle == "background") {
|
||||
scrubber.selectionBackgroundStyle =
|
||||
[NSClassFromString(@"NSScrubberSelectionStyle") roundedBackgroundStyle];
|
||||
} else {
|
||||
scrubber.selectionBackgroundStyle = nil;
|
||||
}
|
||||
|
||||
if (overlayStyle == "outline") {
|
||||
scrubber.selectionOverlayStyle =
|
||||
[NSClassFromString(@"NSScrubberSelectionStyle") outlineOverlayStyle];
|
||||
} else if (overlayStyle == "background") {
|
||||
scrubber.selectionOverlayStyle =
|
||||
[NSClassFromString(@"NSScrubberSelectionStyle") roundedBackgroundStyle];
|
||||
} else {
|
||||
scrubber.selectionOverlayStyle = nil;
|
||||
}
|
||||
|
||||
std::string mode;
|
||||
settings.Get("mode", &mode);
|
||||
if (mode == "fixed") {
|
||||
scrubber.mode = NSScrubberModeFixed;
|
||||
} else {
|
||||
scrubber.mode = NSScrubberModeFree;
|
||||
}
|
||||
|
||||
bool continuous = true;
|
||||
settings.Get("continuous", &continuous);
|
||||
scrubber.continuous = continuous;
|
||||
|
||||
[scrubber reloadData];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)scrubber
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
std::string s_id([[scrubber identifier] UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return 0;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
settings.Get("items", &items);
|
||||
return items.size();
|
||||
}
|
||||
|
||||
- (NSScrubberItemView*)scrubber:(NSScrubber*)scrubber
|
||||
viewForItemAtIndex:(NSInteger)index
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
std::string s_id([[scrubber identifier] UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
if (!settings.Get("items", &items))
|
||||
return nil;
|
||||
|
||||
if (index >= static_cast<NSInteger>(items.size()))
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary item = items[index];
|
||||
|
||||
NSScrubberItemView* itemView;
|
||||
std::string title;
|
||||
|
||||
if (item.Get("label", &title)) {
|
||||
NSScrubberTextItemView* view =
|
||||
[scrubber makeItemWithIdentifier:TextScrubberItemIdentifier owner:self];
|
||||
view.title = base::SysUTF8ToNSString(title);
|
||||
itemView = view;
|
||||
} else {
|
||||
NSScrubberImageItemView* view =
|
||||
[scrubber makeItemWithIdentifier:ImageScrubberItemIdentifier
|
||||
owner:self];
|
||||
gfx::Image image;
|
||||
if (item.Get("icon", &image)) {
|
||||
view.image = image.AsNSImage();
|
||||
}
|
||||
itemView = view;
|
||||
}
|
||||
|
||||
return itemView;
|
||||
}
|
||||
|
||||
- (NSSize)scrubber:(NSScrubber*)scrubber
|
||||
layout:(NSScrubberFlowLayout*)layout
|
||||
sizeForItemAtIndex:(NSInteger)itemIndex API_AVAILABLE(macosx(10.12.2)) {
|
||||
NSInteger width = 50;
|
||||
NSInteger height = 30;
|
||||
NSInteger margin = 15;
|
||||
NSSize defaultSize = NSMakeSize(width, height);
|
||||
|
||||
std::string s_id([[scrubber identifier] UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return defaultSize;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
if (!settings.Get("items", &items))
|
||||
return defaultSize;
|
||||
|
||||
if (itemIndex >= static_cast<NSInteger>(items.size()))
|
||||
return defaultSize;
|
||||
|
||||
mate::PersistentDictionary item = items[itemIndex];
|
||||
std::string title;
|
||||
|
||||
if (item.Get("label", &title)) {
|
||||
NSSize size = NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX);
|
||||
NSRect textRect = [base::SysUTF8ToNSString(title)
|
||||
boundingRectWithSize:size
|
||||
options:NSStringDrawingUsesLineFragmentOrigin |
|
||||
NSStringDrawingUsesFontLeading
|
||||
attributes:@{
|
||||
NSFontAttributeName : [NSFont systemFontOfSize:0]
|
||||
}];
|
||||
|
||||
width = textRect.size.width + margin;
|
||||
} else {
|
||||
gfx::Image image;
|
||||
if (item.Get("icon", &image)) {
|
||||
width = image.AsNSImage().size.width;
|
||||
}
|
||||
}
|
||||
|
||||
return NSMakeSize(width, height);
|
||||
}
|
||||
|
||||
@end
|
21
shell/browser/ui/cocoa/delayed_native_view_host.cc
Normal file
21
shell/browser/ui/cocoa/delayed_native_view_host.cc
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/delayed_native_view_host.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
DelayedNativeViewHost::DelayedNativeViewHost(gfx::NativeView native_view)
|
||||
: native_view_(native_view) {}
|
||||
|
||||
DelayedNativeViewHost::~DelayedNativeViewHost() {}
|
||||
|
||||
void DelayedNativeViewHost::ViewHierarchyChanged(
|
||||
const views::ViewHierarchyChangedDetails& details) {
|
||||
NativeViewHost::ViewHierarchyChanged(details);
|
||||
if (details.is_add && GetWidget())
|
||||
Attach(native_view_);
|
||||
}
|
||||
|
||||
} // namespace atom
|
31
shell/browser/ui/cocoa/delayed_native_view_host.h
Normal file
31
shell/browser/ui/cocoa/delayed_native_view_host.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_
|
||||
|
||||
#include "ui/views/controls/native/native_view_host.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
// Automatically attach the native view after the NativeViewHost is attached to
|
||||
// a widget. (Attaching it directly would cause crash.)
|
||||
class DelayedNativeViewHost : public views::NativeViewHost {
|
||||
public:
|
||||
explicit DelayedNativeViewHost(gfx::NativeView native_view);
|
||||
~DelayedNativeViewHost() override;
|
||||
|
||||
// views::View:
|
||||
void ViewHierarchyChanged(
|
||||
const views::ViewHierarchyChangedDetails& details) override;
|
||||
|
||||
private:
|
||||
gfx::NativeView native_view_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DelayedNativeViewHost);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_
|
19
shell/browser/ui/cocoa/event_dispatching_window.h
Normal file
19
shell/browser/ui/cocoa/event_dispatching_window.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_EVENT_DISPATCHING_WINDOW_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_EVENT_DISPATCHING_WINDOW_H_
|
||||
|
||||
#import "ui/base/cocoa/underlay_opengl_hosting_window.h"
|
||||
|
||||
@interface EventDispatchingWindow : UnderlayOpenGLHostingWindow {
|
||||
@private
|
||||
BOOL redispatchingEvent_;
|
||||
}
|
||||
|
||||
- (void)redispatchKeyEvent:(NSEvent*)event;
|
||||
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_EVENT_DISPATCHING_WINDOW_H_
|
34
shell/browser/ui/cocoa/event_dispatching_window.mm
Normal file
34
shell/browser/ui/cocoa/event_dispatching_window.mm
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/event_dispatching_window.h"
|
||||
|
||||
@implementation EventDispatchingWindow
|
||||
|
||||
- (void)sendEvent:(NSEvent*)event {
|
||||
if (!redispatchingEvent_)
|
||||
[super sendEvent:event];
|
||||
}
|
||||
|
||||
- (BOOL)performKeyEquivalent:(NSEvent*)event {
|
||||
if (redispatchingEvent_)
|
||||
return NO;
|
||||
else
|
||||
return [super performKeyEquivalent:event];
|
||||
}
|
||||
|
||||
- (void)redispatchKeyEvent:(NSEvent*)event {
|
||||
NSEventType eventType = [event type];
|
||||
if (eventType != NSKeyDown && eventType != NSKeyUp &&
|
||||
eventType != NSFlagsChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Redispatch the event.
|
||||
redispatchingEvent_ = YES;
|
||||
[NSApp sendEvent:event];
|
||||
redispatchingEvent_ = NO;
|
||||
}
|
||||
|
||||
@end
|
33
shell/browser/ui/cocoa/root_view_mac.h
Normal file
33
shell/browser/ui/cocoa/root_view_mac.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ROOT_VIEW_MAC_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ROOT_VIEW_MAC_H_
|
||||
|
||||
#include "ui/views/view.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindow;
|
||||
|
||||
class RootViewMac : public views::View {
|
||||
public:
|
||||
explicit RootViewMac(NativeWindow* window);
|
||||
~RootViewMac() override;
|
||||
|
||||
// views::View:
|
||||
void Layout() override;
|
||||
gfx::Size GetMinimumSize() const override;
|
||||
gfx::Size GetMaximumSize() const override;
|
||||
|
||||
private:
|
||||
// Parent window, weak ref.
|
||||
NativeWindow* window_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RootViewMac);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ROOT_VIEW_MAC_H_
|
32
shell/browser/ui/cocoa/root_view_mac.mm
Normal file
32
shell/browser/ui/cocoa/root_view_mac.mm
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/root_view_mac.h"
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
RootViewMac::RootViewMac(NativeWindow* window) : window_(window) {
|
||||
set_owned_by_client();
|
||||
}
|
||||
|
||||
RootViewMac::~RootViewMac() {}
|
||||
|
||||
void RootViewMac::Layout() {
|
||||
if (!window_->content_view()) // Not ready yet.
|
||||
return;
|
||||
|
||||
window_->content_view()->SetBoundsRect(gfx::Rect(gfx::Point(), size()));
|
||||
}
|
||||
|
||||
gfx::Size RootViewMac::GetMinimumSize() const {
|
||||
return window_->GetMinimumSize();
|
||||
}
|
||||
|
||||
gfx::Size RootViewMac::GetMaximumSize() const {
|
||||
return window_->GetMaximumSize();
|
||||
}
|
||||
|
||||
} // namespace atom
|
267
shell/browser/ui/cocoa/touch_bar_forward_declarations.h
Normal file
267
shell/browser/ui/cocoa/touch_bar_forward_declarations.h
Normal file
|
@ -0,0 +1,267 @@
|
|||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_
|
||||
|
||||
// Once Chrome no longer supports OSX 10.12.0, this file can be deleted.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if !defined(MAC_OS_X_VERSION_10_12_1)
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
@class NSTouchBar, NSTouchBarItem;
|
||||
@class NSScrubber, NSScrubberItemView, NSScrubberArrangedView,
|
||||
NSScrubberTextItemView, NSScrubberImageItemView, NSScrubberSelectionStyle;
|
||||
@protocol NSTouchBarDelegate
|
||||
, NSScrubberDelegate, NSScrubberDataSource, NSScrubberFlowLayoutDelegate,
|
||||
NSScrubberFlowLayout;
|
||||
|
||||
typedef float NSTouchBarItemPriority;
|
||||
static const NSTouchBarItemPriority NSTouchBarItemPriorityHigh = 1000;
|
||||
static const NSTouchBarItemPriority NSTouchBarItemPriorityNormal = 0;
|
||||
static const NSTouchBarItemPriority NSTouchBarItemPriorityLow = -1000;
|
||||
|
||||
enum NSScrubberMode { NSScrubberModeFixed = 0, NSScrubberModeFree };
|
||||
|
||||
typedef NSString* NSTouchBarItemIdentifier;
|
||||
typedef NSString* NSTouchBarCustomizationIdentifier;
|
||||
|
||||
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceSmall =
|
||||
@"NSTouchBarItemIdentifierFixedSpaceSmall";
|
||||
|
||||
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceLarge =
|
||||
@"NSTouchBarItemIdentifierFixedSpaceLarge";
|
||||
|
||||
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFlexibleSpace =
|
||||
@"NSTouchBarItemIdentifierFlexibleSpace";
|
||||
|
||||
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy =
|
||||
@"NSTouchBarItemIdentifierOtherItemsProxy";
|
||||
|
||||
@interface NSTouchBar : NSObject <NSCoding>
|
||||
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithCoder:(NSCoder*)aDecoder
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property(copy, nullable)
|
||||
NSTouchBarCustomizationIdentifier customizationIdentifier;
|
||||
@property(copy) NSArray* customizationAllowedItemIdentifiers;
|
||||
@property(copy) NSArray* customizationRequiredItemIdentifiers;
|
||||
@property(copy) NSArray* defaultItemIdentifiers;
|
||||
@property(copy, readonly) NSArray* itemIdentifiers;
|
||||
@property(copy, nullable) NSTouchBarItemIdentifier principalItemIdentifier;
|
||||
@property(copy, nullable)
|
||||
NSTouchBarItemIdentifier escapeKeyReplacementItemIdentifier;
|
||||
@property(copy) NSSet* templateItems;
|
||||
@property(nullable, weak) id<NSTouchBarDelegate> delegate;
|
||||
|
||||
- (nullable __kindof NSTouchBarItem*)itemForIdentifier:
|
||||
(NSTouchBarItemIdentifier)identifier;
|
||||
|
||||
@property(readonly, getter=isVisible) BOOL visible;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSTouchBarItem : NSObject <NSCoding>
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSTouchBarItemIdentifier)identifier
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithCoder:(NSCoder*)coder
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@property(readonly, copy) NSTouchBarItemIdentifier identifier;
|
||||
@property NSTouchBarItemPriority visibilityPriority;
|
||||
@property(readonly, nullable) NSView* view;
|
||||
@property(readonly, nullable) NSViewController* viewController;
|
||||
@property(readwrite, copy) NSString* customizationLabel;
|
||||
@property(readonly, getter=isVisible) BOOL visible;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSGroupTouchBarItem : NSTouchBarItem
|
||||
|
||||
+ (NSGroupTouchBarItem*)groupItemWithIdentifier:
|
||||
(NSTouchBarItemIdentifier)identifier
|
||||
items:(NSArray*)items;
|
||||
|
||||
@property(strong) NSTouchBar* groupTouchBar;
|
||||
@property(readwrite, copy, null_resettable) NSString* customizationLabel;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSCustomTouchBarItem : NSTouchBarItem
|
||||
|
||||
@property(readwrite, strong) __kindof NSView* view;
|
||||
@property(readwrite, strong, nullable)
|
||||
__kindof NSViewController* viewController;
|
||||
@property(readwrite, copy, null_resettable) NSString* customizationLabel;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSColorPickerTouchBarItem : NSTouchBarItem
|
||||
|
||||
@property SEL action;
|
||||
@property(weak) id target;
|
||||
@property(copy) NSColor* color;
|
||||
@property(strong) NSColorList* colorList;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSPopoverTouchBarItem : NSTouchBarItem
|
||||
|
||||
@property BOOL showsCloseButton;
|
||||
@property(strong) NSImage* collapsedRepresentationImage;
|
||||
@property(strong) NSString* collapsedRepresentationLabel;
|
||||
@property(strong) NSTouchBar* popoverTouchBar;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSSliderTouchBarItem : NSTouchBarItem
|
||||
|
||||
@property SEL action;
|
||||
@property(weak) id target;
|
||||
@property(copy) NSString* label;
|
||||
@property(strong) NSSlider* slider;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubber : NSView
|
||||
|
||||
@property(weak) id<NSScrubberDelegate> delegate;
|
||||
@property(weak) id<NSScrubberDataSource> dataSource;
|
||||
@property NSScrubberMode mode;
|
||||
@property BOOL showsArrowButtons;
|
||||
@property(getter=isContinuous) BOOL continuous;
|
||||
@property(strong, nullable) NSScrubberSelectionStyle* selectionBackgroundStyle;
|
||||
@property(strong, nullable) NSScrubberSelectionStyle* selectionOverlayStyle;
|
||||
|
||||
- (void)registerClass:(Class)itemViewClass
|
||||
forItemIdentifier:(NSString*)itemIdentifier;
|
||||
|
||||
- (__kindof NSScrubberItemView*)makeItemWithIdentifier:(NSString*)itemIdentifier
|
||||
owner:(id)owner;
|
||||
- (void)reloadData;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubberFlowLayout : NSObject
|
||||
@end
|
||||
|
||||
@interface NSScrubberSelectionStyle : NSObject <NSCoding>
|
||||
|
||||
@property(class, strong, readonly)
|
||||
NSScrubberSelectionStyle* outlineOverlayStyle;
|
||||
@property(class, strong, readonly)
|
||||
NSScrubberSelectionStyle* roundedBackgroundStyle;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubberArrangedView : NSView
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubberItemView : NSScrubberArrangedView
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubberTextItemView : NSScrubberItemView
|
||||
|
||||
@property(copy) NSString* title;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubberImageItemView : NSScrubberItemView
|
||||
|
||||
@property(copy) NSImage* image;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSWindow (TouchBarSDK)
|
||||
|
||||
@property(strong, readwrite, nullable) NSTouchBar* touchBar;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSButton (TouchBarSDK)
|
||||
|
||||
@property(copy) NSColor* bezelColor;
|
||||
+ (instancetype)buttonWithTitle:(NSString*)title
|
||||
target:(id)target
|
||||
action:(SEL)action;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSTextField (TouchBarSDK)
|
||||
|
||||
+ (instancetype)labelWithString:(NSString*)stringValue;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSSegmentedControl (TouchBarSDK)
|
||||
|
||||
+ (instancetype)segmentedControlWithLabels:(NSArray*)labels
|
||||
trackingMode:(NSSegmentSwitchTracking)trackingMode
|
||||
target:(id)target
|
||||
action:(SEL)action;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSTouchBarDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar
|
||||
makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSScrubberDelegate <NSObject>
|
||||
|
||||
- (void)scrubber:(NSScrubber*)scrubber
|
||||
didHighlightItemAtIndex:(NSInteger)highlightedIndex;
|
||||
- (void)scrubber:(NSScrubber*)scrubber
|
||||
didSelectItemAtIndex:(NSInteger)selectedIndex;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSScrubberDataSource <NSObject>
|
||||
|
||||
- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)scrubber;
|
||||
- (__kindof NSScrubberItemView*)scrubber:(NSScrubber*)scrubber
|
||||
viewForItemAtIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSScrubberFlowLayoutDelegate <NSObject>
|
||||
|
||||
- (NSSize)scrubber:(NSScrubber*)scrubber
|
||||
layout:(NSScrubberFlowLayout*)layout
|
||||
sizeForItemAtIndex:(NSInteger)itemIndex;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12_1
|
||||
|
||||
// When compiling against the 10.12.1 SDK or later, just provide forward
|
||||
// declarations to suppress the partial availability warnings.
|
||||
|
||||
@class NSCustomTouchBarItem;
|
||||
@class NSGroupTouchBarItem;
|
||||
@class NSTouchBar;
|
||||
@protocol NSTouchBarDelegate;
|
||||
@class NSTouchBarItem;
|
||||
|
||||
@interface NSWindow (TouchBarSDK)
|
||||
@property(strong, readonly) NSTouchBar* touchBar API_AVAILABLE(macosx(10.12.2));
|
||||
@end
|
||||
|
||||
#endif // MAC_OS_X_VERSION_10_12_1
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_
|
30
shell/browser/ui/cocoa/views_delegate_mac.h
Normal file
30
shell/browser/ui/cocoa/views_delegate_mac.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_VIEWS_DELEGATE_MAC_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_VIEWS_DELEGATE_MAC_H_
|
||||
|
||||
#include "ui/views/views_delegate.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class ViewsDelegateMac : public views::ViewsDelegate {
|
||||
public:
|
||||
ViewsDelegateMac();
|
||||
~ViewsDelegateMac() override;
|
||||
|
||||
// ViewsDelegate:
|
||||
void OnBeforeWidgetInit(
|
||||
views::Widget::InitParams* params,
|
||||
views::internal::NativeWidgetDelegate* delegate) override;
|
||||
ui::ContextFactory* GetContextFactory() override;
|
||||
ui::ContextFactoryPrivate* GetContextFactoryPrivate() override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ViewsDelegateMac);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_VIEWS_DELEGATE_MAC_H_
|
43
shell/browser/ui/cocoa/views_delegate_mac.mm
Normal file
43
shell/browser/ui/cocoa/views_delegate_mac.mm
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/views_delegate_mac.h"
|
||||
|
||||
#include "content/public/browser/context_factory.h"
|
||||
#include "ui/views/widget/native_widget_mac.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
ViewsDelegateMac::ViewsDelegateMac() {}
|
||||
|
||||
ViewsDelegateMac::~ViewsDelegateMac() {}
|
||||
|
||||
void ViewsDelegateMac::OnBeforeWidgetInit(
|
||||
views::Widget::InitParams* params,
|
||||
views::internal::NativeWidgetDelegate* delegate) {
|
||||
// If we already have a native_widget, we don't have to try to come
|
||||
// up with one.
|
||||
if (params->native_widget)
|
||||
return;
|
||||
|
||||
if (!native_widget_factory().is_null()) {
|
||||
params->native_widget = native_widget_factory().Run(*params, delegate);
|
||||
if (params->native_widget)
|
||||
return;
|
||||
}
|
||||
|
||||
// Setting null here causes Widget to create the default NativeWidget
|
||||
// implementation.
|
||||
params->native_widget = nullptr;
|
||||
}
|
||||
|
||||
ui::ContextFactory* ViewsDelegateMac::GetContextFactory() {
|
||||
return content::GetContextFactory();
|
||||
}
|
||||
|
||||
ui::ContextFactoryPrivate* ViewsDelegateMac::GetContextFactoryPrivate() {
|
||||
return content::GetContextFactoryPrivate();
|
||||
}
|
||||
|
||||
} // namespace atom
|
Loading…
Add table
Add a link
Reference in a new issue