mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-21 09:15:35 +00:00
6925183c6d
Differential Revision: https://phabricator.services.mozilla.com/D52361 --HG-- extra : moz-landing-system : lando
882 lines
28 KiB
Plaintext
882 lines
28 KiB
Plaintext
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "nsTouchBar.h"
|
|
|
|
#include "mozilla/MacStringHelpers.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "nsArrayUtils.h"
|
|
#include "nsDirectoryServiceDefs.h"
|
|
#include "nsIArray.h"
|
|
|
|
@implementation nsTouchBar
|
|
|
|
static const NSTouchBarItemIdentifier BaseIdentifier = @"com.mozilla.firefox.touchbar";
|
|
|
|
// Non-JS scrubber implemention for the Share Scrubber,
|
|
// since it is defined by an Apple API.
|
|
static NSTouchBarItemIdentifier ShareScrubberIdentifier =
|
|
[TouchBarInput nativeIdentifierWithType:@"scrubber" withKey:@"share"];
|
|
|
|
// The search popover needs to show/hide depending on if the Urlbar is focused
|
|
// when it is created. We keep track of its identifier to accomodate this
|
|
// special handling.
|
|
static NSTouchBarItemIdentifier SearchPopoverIdentifier =
|
|
[TouchBarInput nativeIdentifierWithType:@"popover" withKey:@"search-popover"];
|
|
|
|
// Used to tie action strings to buttons.
|
|
static char sIdentifierAssociationKey;
|
|
|
|
static const NSArray<NSString*>* kAllowedInputTypes = @[
|
|
@"button",
|
|
@"mainButton",
|
|
@"scrubber",
|
|
@"popover",
|
|
@"scrollView",
|
|
@"label",
|
|
];
|
|
|
|
// The default space between inputs, used where layout is not automatic.
|
|
static const uint32_t kInputSpacing = 8;
|
|
// The width of buttons in Apple's Share ScrollView. We use this in our
|
|
// ScrollViews to give them a native appearance.
|
|
static const uint32_t kScrollViewButtonWidth = 144;
|
|
static const uint32_t kInputIconSize = 16;
|
|
|
|
// The system default width for Touch Bar inputs is 128px. This is double.
|
|
#define MAIN_BUTTON_WIDTH 256
|
|
|
|
#pragma mark - NSTouchBarDelegate
|
|
|
|
- (instancetype)init {
|
|
return [self initWithInputs:nil];
|
|
}
|
|
|
|
- (instancetype)initWithInputs:(NSMutableArray<TouchBarInput*>*)aInputs {
|
|
if ((self = [super init])) {
|
|
mTouchBarHelper = do_GetService(NS_TOUCHBARHELPER_CID);
|
|
if (!mTouchBarHelper) {
|
|
NS_ERROR("Unable to create Touch Bar Helper.");
|
|
return nil;
|
|
}
|
|
|
|
self.delegate = self;
|
|
self.mappedLayoutItems = [NSMutableDictionary dictionary];
|
|
self.customizationAllowedItemIdentifiers = @[];
|
|
|
|
if (!aInputs) {
|
|
// This customization identifier is how users' custom layouts are saved by macOS.
|
|
// If this changes, all users' layouts would be reset to the default layout.
|
|
self.customizationIdentifier = [BaseIdentifier stringByAppendingPathExtension:@"defaultbar"];
|
|
nsCOMPtr<nsIArray> allItems;
|
|
|
|
nsresult rv = mTouchBarHelper->GetAllItems(getter_AddRefs(allItems));
|
|
if (NS_FAILED(rv) || !allItems) {
|
|
return nil;
|
|
}
|
|
|
|
uint32_t itemCount = 0;
|
|
allItems->GetLength(&itemCount);
|
|
// This is copied to self.customizationAllowedItemIdentifiers.
|
|
// Required since [self.mappedItems allKeys] does not preserve order.
|
|
// One slot is added for the spacer item.
|
|
NSMutableArray* orderedIdentifiers = [NSMutableArray arrayWithCapacity:itemCount + 1];
|
|
for (uint32_t i = 0; i < itemCount; ++i) {
|
|
nsCOMPtr<nsITouchBarInput> input = do_QueryElementAt(allItems, i);
|
|
if (!input) {
|
|
continue;
|
|
}
|
|
|
|
TouchBarInput* convertedInput;
|
|
NSTouchBarItemIdentifier newInputIdentifier =
|
|
[TouchBarInput nativeIdentifierWithXPCOM:input];
|
|
if (!newInputIdentifier) {
|
|
continue;
|
|
}
|
|
|
|
// If there is already an input in mappedLayoutItems with this identifier,
|
|
// that means updateItem fired before this initialization. The input
|
|
// cached by updateItem is more current, so we should use that one.
|
|
if (self.mappedLayoutItems[newInputIdentifier]) {
|
|
convertedInput = self.mappedLayoutItems[newInputIdentifier];
|
|
} else {
|
|
convertedInput = [[TouchBarInput alloc] initWithXPCOM:input];
|
|
// Add new input to dictionary for lookup of properties in delegate.
|
|
self.mappedLayoutItems[[convertedInput nativeIdentifier]] = convertedInput;
|
|
}
|
|
|
|
orderedIdentifiers[i] = [convertedInput nativeIdentifier];
|
|
}
|
|
[orderedIdentifiers addObject:@"NSTouchBarItemIdentifierFlexibleSpace"];
|
|
self.customizationAllowedItemIdentifiers = [orderedIdentifiers copy];
|
|
|
|
NSArray* defaultItemIdentifiers = @[
|
|
[TouchBarInput nativeIdentifierWithType:@"button" withKey:@"back"],
|
|
[TouchBarInput nativeIdentifierWithType:@"button" withKey:@"forward"],
|
|
[TouchBarInput nativeIdentifierWithType:@"button" withKey:@"reload"],
|
|
[TouchBarInput nativeIdentifierWithType:@"mainButton" withKey:@"open-location"],
|
|
[TouchBarInput nativeIdentifierWithType:@"button" withKey:@"new-tab"],
|
|
ShareScrubberIdentifier, SearchPopoverIdentifier
|
|
];
|
|
self.defaultItemIdentifiers = [defaultItemIdentifiers copy];
|
|
} else {
|
|
NSMutableArray* defaultItemIdentifiers = [NSMutableArray arrayWithCapacity:[aInputs count]];
|
|
for (TouchBarInput* input in aInputs) {
|
|
self.mappedLayoutItems[[input nativeIdentifier]] = input;
|
|
[defaultItemIdentifiers addObject:[input nativeIdentifier]];
|
|
}
|
|
self.defaultItemIdentifiers = [defaultItemIdentifiers copy];
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
|
|
NSTouchBarItem* item = [self itemForIdentifier:identifier];
|
|
if (!item) {
|
|
continue;
|
|
}
|
|
if ([item isKindOfClass:[NSPopoverTouchBarItem class]]) {
|
|
[(NSPopoverTouchBarItem*)item setCollapsedRepresentationImage:nil];
|
|
[(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar] release];
|
|
} else if ([[item view] isKindOfClass:[NSScrollView class]]) {
|
|
[[(NSScrollView*)[item view] documentView] release];
|
|
[(NSScrollView*)[item view] release];
|
|
}
|
|
|
|
[item release];
|
|
}
|
|
|
|
[self.defaultItemIdentifiers release];
|
|
[self.customizationAllowedItemIdentifiers release];
|
|
[self.scrollViewButtons removeAllObjects];
|
|
[self.scrollViewButtons release];
|
|
[self.mappedLayoutItems removeAllObjects];
|
|
[self.mappedLayoutItems release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSTouchBarItem*)touchBar:(NSTouchBar*)aTouchBar
|
|
makeItemForIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
|
|
TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
|
|
|
|
if (!input) {
|
|
return nil;
|
|
}
|
|
|
|
// Checking to see if our new item is of an accepted type.
|
|
if (![kAllowedInputTypes
|
|
containsObject:[[[input type] componentsSeparatedByString:@"-"] lastObject]]) {
|
|
return nil;
|
|
}
|
|
|
|
if ([[input type] hasSuffix:@"scrubber"]) {
|
|
// We check the identifier rather than the type here as a special case.
|
|
if (![aIdentifier isEqualToString:ShareScrubberIdentifier]) {
|
|
// We're only supporting the Share scrubber for now.
|
|
return nil;
|
|
}
|
|
return [self makeShareScrubberForIdentifier:aIdentifier];
|
|
}
|
|
|
|
if ([[input type] hasSuffix:@"popover"]) {
|
|
NSPopoverTouchBarItem* newPopoverItem =
|
|
[[NSPopoverTouchBarItem alloc] initWithIdentifier:aIdentifier];
|
|
[newPopoverItem setCustomizationLabel:[input title]];
|
|
// We initialize popoverTouchBar here because we only allow setting this
|
|
// property on popover creation. Updating popoverTouchBar for every update
|
|
// of the popover item would be very expensive.
|
|
newPopoverItem.popoverTouchBar = [[nsTouchBar alloc] initWithInputs:[input children]];
|
|
[self updatePopover:newPopoverItem withIdentifier:[input nativeIdentifier]];
|
|
return newPopoverItem;
|
|
}
|
|
|
|
// Our new item, which will be initialized depending on aIdentifier.
|
|
NSCustomTouchBarItem* newItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:aIdentifier];
|
|
[newItem setCustomizationLabel:[input title]];
|
|
|
|
if ([[input type] hasSuffix:@"scrollView"]) {
|
|
[self updateScrollView:newItem withIdentifier:[input nativeIdentifier]];
|
|
return newItem;
|
|
} else if ([[input type] hasSuffix:@"label"]) {
|
|
NSTextField* label = [NSTextField labelWithString:@""];
|
|
[self updateLabel:label withIdentifier:[input nativeIdentifier]];
|
|
newItem.view = label;
|
|
return newItem;
|
|
}
|
|
|
|
// The cases of a button or main button require the same setup.
|
|
NSButton* button = [NSButton buttonWithTitle:@"" target:self action:@selector(touchBarAction:)];
|
|
newItem.view = button;
|
|
|
|
if ([[input type] hasSuffix:@"button"] && ![[input type] hasPrefix:@"scrollView"]) {
|
|
[self updateButton:button withIdentifier:[input nativeIdentifier]];
|
|
} else if ([[input type] hasSuffix:@"mainButton"]) {
|
|
[self updateMainButton:button withIdentifier:[input nativeIdentifier]];
|
|
}
|
|
return newItem;
|
|
}
|
|
|
|
- (bool)updateItem:(TouchBarInput*)aInput {
|
|
NSTouchBarItem* item = [self itemForIdentifier:[aInput nativeIdentifier]];
|
|
|
|
// Update our canonical copy of the input.
|
|
[self replaceMappedLayoutItem:aInput];
|
|
|
|
// If we can't immediately find item, there are three possibilities:
|
|
// * It is a button in a ScrollView, which can't be found with itemForIdentifier; or
|
|
// * It is contained within a popover; or
|
|
// * It simply does not exist.
|
|
// We check for each possibility here.
|
|
if (!item) {
|
|
if ([self maybeUpdateScrollViewChild:aInput]) {
|
|
return true;
|
|
}
|
|
if ([self maybeUpdatePopoverChild:aInput]) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ([[aInput type] hasSuffix:@"button"]) {
|
|
[(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
|
|
[self updateButton:(NSButton*)item.view withIdentifier:[aInput nativeIdentifier]];
|
|
} else if ([[aInput type] hasSuffix:@"mainButton"]) {
|
|
[(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
|
|
[self updateMainButton:(NSButton*)item.view withIdentifier:[aInput nativeIdentifier]];
|
|
} else if ([[aInput type] hasSuffix:@"scrollView"]) {
|
|
[(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
|
|
[self updateScrollView:(NSCustomTouchBarItem*)item withIdentifier:[aInput nativeIdentifier]];
|
|
} else if ([[aInput type] hasSuffix:@"popover"]) {
|
|
[(NSPopoverTouchBarItem*)item setCustomizationLabel:[aInput title]];
|
|
[self updatePopover:(NSPopoverTouchBarItem*)item withIdentifier:[aInput nativeIdentifier]];
|
|
} else if ([[aInput type] hasSuffix:@"label"]) {
|
|
[self updateLabel:(NSTextField*)item.view withIdentifier:[aInput nativeIdentifier]];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
- (bool)maybeUpdatePopoverChild:(TouchBarInput*)aInput {
|
|
for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
|
|
TouchBarInput* potentialPopover = self.mappedLayoutItems[identifier];
|
|
if (![[potentialPopover type] hasSuffix:@"popover"]) {
|
|
continue;
|
|
}
|
|
NSTouchBarItem* popover = [self itemForIdentifier:[potentialPopover nativeIdentifier]];
|
|
if (popover) {
|
|
if ([(nsTouchBar*)[(NSPopoverTouchBarItem*)popover popoverTouchBar] updateItem:aInput]) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
- (bool)maybeUpdateScrollViewChild:(TouchBarInput*)aInput {
|
|
NSButton* scrollViewButton = self.scrollViewButtons[[aInput nativeIdentifier]];
|
|
if (scrollViewButton) {
|
|
// ScrollView buttons are similar to mainButtons except for their width.
|
|
[self updateMainButton:scrollViewButton withIdentifier:[aInput nativeIdentifier]];
|
|
uint32_t buttonSize =
|
|
MAX(scrollViewButton.attributedTitle.size.width + kInputIconSize + kInputSpacing,
|
|
kScrollViewButtonWidth);
|
|
[[scrollViewButton widthAnchor] constraintGreaterThanOrEqualToConstant:buttonSize].active = YES;
|
|
}
|
|
// Updating the TouchBarInput* in the ScrollView's mChildren array.
|
|
for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
|
|
TouchBarInput* potentialScrollView = self.mappedLayoutItems[identifier];
|
|
if (![[potentialScrollView type] hasSuffix:@"scrollView"]) {
|
|
continue;
|
|
}
|
|
for (uint32_t i = 0; i < [[potentialScrollView children] count]; ++i) {
|
|
TouchBarInput* child = [potentialScrollView children][i];
|
|
if (![[child nativeIdentifier] isEqualToString:[aInput nativeIdentifier]]) {
|
|
continue;
|
|
}
|
|
[[potentialScrollView children] replaceObjectAtIndex:i withObject:aInput];
|
|
[child release];
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
- (void)replaceMappedLayoutItem:(TouchBarInput*)aItem {
|
|
[self.mappedLayoutItems[[aItem nativeIdentifier]] release];
|
|
self.mappedLayoutItems[[aItem nativeIdentifier]] = aItem;
|
|
}
|
|
|
|
- (void)updateButton:(NSButton*)aButton withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
|
|
if (!aButton || !aIdentifier) {
|
|
return;
|
|
}
|
|
|
|
TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
|
|
if (!input) {
|
|
return;
|
|
}
|
|
|
|
aButton.title = [input title];
|
|
if (![input isIconPositionSet]) {
|
|
[aButton setImagePosition:NSImageOnly];
|
|
[input setIconPositionSet:true];
|
|
}
|
|
|
|
if ([input imageURI]) {
|
|
RefPtr<nsTouchBarInputIcon> icon = [input icon];
|
|
if (!icon) {
|
|
icon = new nsTouchBarInputIcon([input document], aButton);
|
|
[input setIcon:icon];
|
|
}
|
|
icon->SetupIcon([input imageURI]);
|
|
}
|
|
[aButton setEnabled:![input isDisabled]];
|
|
|
|
if ([input color]) {
|
|
aButton.bezelColor = [input color];
|
|
}
|
|
|
|
objc_setAssociatedObject(aButton, &sIdentifierAssociationKey, aIdentifier,
|
|
OBJC_ASSOCIATION_RETAIN);
|
|
}
|
|
|
|
- (void)updateMainButton:(NSButton*)aMainButton
|
|
withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
|
|
if (!aMainButton || !aIdentifier) {
|
|
return;
|
|
}
|
|
|
|
TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
|
|
if (!input) {
|
|
return;
|
|
}
|
|
// If empty, string is still being localized. Display a blank input instead.
|
|
if ([[input title] isEqualToString:@""]) {
|
|
[aMainButton setImagePosition:NSNoImage];
|
|
} else {
|
|
[aMainButton setImagePosition:NSImageLeft];
|
|
}
|
|
aMainButton.imageHugsTitle = YES;
|
|
[input setIconPositionSet:true];
|
|
|
|
[self updateButton:aMainButton withIdentifier:aIdentifier];
|
|
|
|
[aMainButton.widthAnchor constraintGreaterThanOrEqualToConstant:MAIN_BUTTON_WIDTH].active = YES;
|
|
[aMainButton setContentHuggingPriority:1.0
|
|
forOrientation:NSLayoutConstraintOrientationHorizontal];
|
|
}
|
|
|
|
- (void)updatePopover:(NSPopoverTouchBarItem*)aPopoverItem
|
|
withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
|
|
if (!aPopoverItem || !aIdentifier) {
|
|
return;
|
|
}
|
|
|
|
TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
|
|
if (!input) {
|
|
return;
|
|
}
|
|
|
|
aPopoverItem.showsCloseButton = YES;
|
|
if ([input imageURI]) {
|
|
RefPtr<nsTouchBarInputIcon> icon = [input icon];
|
|
if (!icon) {
|
|
icon = new nsTouchBarInputIcon([input document], nil, nil, aPopoverItem);
|
|
[input setIcon:icon];
|
|
}
|
|
icon->SetupIcon([input imageURI]);
|
|
} else if ([input title]) {
|
|
aPopoverItem.collapsedRepresentationLabel = [input title];
|
|
}
|
|
|
|
// Special handling to show/hide the search popover if the Urlbar is focused.
|
|
if ([[input nativeIdentifier] isEqualToString:SearchPopoverIdentifier]) {
|
|
// We can reach this code during window shutdown. We only want to toggle
|
|
// showPopover if we are in a normal running state.
|
|
if (!mTouchBarHelper) {
|
|
return;
|
|
}
|
|
bool urlbarIsFocused = false;
|
|
mTouchBarHelper->GetIsUrlbarFocused(&urlbarIsFocused);
|
|
if (urlbarIsFocused) {
|
|
[aPopoverItem showPopover:self];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)updateScrollView:(NSCustomTouchBarItem*)aScrollViewItem
|
|
withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
|
|
if (!aScrollViewItem || !aIdentifier) {
|
|
return;
|
|
}
|
|
|
|
TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
|
|
if (!input || ![input children]) {
|
|
return;
|
|
}
|
|
|
|
NSMutableDictionary* constraintViews = [NSMutableDictionary dictionary];
|
|
NSView* documentView = [[NSView alloc] initWithFrame:NSZeroRect];
|
|
NSString* layoutFormat = @"H:|-8-";
|
|
NSSize size = NSMakeSize(kInputSpacing, 30);
|
|
// Layout strings allow only alphanumeric characters. We will use this
|
|
// NSCharacterSet to strip illegal characters.
|
|
NSCharacterSet* charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
|
|
|
|
for (TouchBarInput* childInput in [input children]) {
|
|
if (![[childInput type] hasSuffix:@"button"]) {
|
|
continue;
|
|
}
|
|
[self replaceMappedLayoutItem:childInput];
|
|
NSButton* button = [NSButton buttonWithTitle:[childInput title]
|
|
target:self
|
|
action:@selector(touchBarAction:)];
|
|
// ScrollView buttons are similar to mainButtons except for their width.
|
|
[self updateMainButton:button withIdentifier:[childInput nativeIdentifier]];
|
|
uint32_t buttonSize = MAX(button.attributedTitle.size.width + kInputIconSize + kInputSpacing,
|
|
kScrollViewButtonWidth);
|
|
[[button widthAnchor] constraintGreaterThanOrEqualToConstant:buttonSize].active = YES;
|
|
|
|
self.scrollViewButtons[[childInput nativeIdentifier]] = button;
|
|
|
|
button.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[documentView addSubview:button];
|
|
NSString* layoutKey = [[[childInput nativeIdentifier]
|
|
componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:@""];
|
|
|
|
// Iteratively create our layout string.
|
|
layoutFormat =
|
|
[layoutFormat stringByAppendingString:[NSString stringWithFormat:@"[%@]-8-", layoutKey]];
|
|
[constraintViews setObject:button forKey:layoutKey];
|
|
size.width += kInputSpacing + buttonSize;
|
|
}
|
|
layoutFormat = [layoutFormat stringByAppendingString:[NSString stringWithFormat:@"|"]];
|
|
NSArray* hConstraints =
|
|
[NSLayoutConstraint constraintsWithVisualFormat:layoutFormat
|
|
options:NSLayoutFormatAlignAllCenterY
|
|
metrics:nil
|
|
views:constraintViews];
|
|
NSScrollView* scrollView =
|
|
[[NSScrollView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
|
|
[documentView setFrame:NSMakeRect(0, 0, size.width, size.height)];
|
|
[NSLayoutConstraint activateConstraints:hConstraints];
|
|
scrollView.documentView = documentView;
|
|
|
|
aScrollViewItem.view = scrollView;
|
|
}
|
|
|
|
- (void)updateLabel:(NSTextField*)aLabel withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
|
|
if (!aLabel || !aIdentifier) {
|
|
return;
|
|
}
|
|
|
|
TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
|
|
if (!input || ![input title]) {
|
|
return;
|
|
}
|
|
[aLabel setStringValue:[input title]];
|
|
}
|
|
|
|
- (NSTouchBarItem*)makeShareScrubberForIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
|
|
TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
|
|
// System-default share menu
|
|
NSSharingServicePickerTouchBarItem* servicesItem =
|
|
[[NSSharingServicePickerTouchBarItem alloc] initWithIdentifier:aIdentifier];
|
|
|
|
// buttonImage needs to be set to nil while we wait for our icon to load.
|
|
// Otherwise, the default Apple share icon is automatically loaded.
|
|
servicesItem.buttonImage = nil;
|
|
if ([input imageURI]) {
|
|
RefPtr<nsTouchBarInputIcon> icon = [input icon];
|
|
if (!icon) {
|
|
icon = new nsTouchBarInputIcon([input document], nil, servicesItem);
|
|
[input setIcon:icon];
|
|
}
|
|
icon->SetupIcon([input imageURI]);
|
|
}
|
|
|
|
servicesItem.delegate = self;
|
|
return servicesItem;
|
|
}
|
|
|
|
- (void)showPopover:(TouchBarInput*)aPopover showing:(bool)aShowing {
|
|
if (!aPopover) {
|
|
return;
|
|
}
|
|
NSPopoverTouchBarItem* popoverItem =
|
|
(NSPopoverTouchBarItem*)[self itemForIdentifier:[aPopover nativeIdentifier]];
|
|
if (!popoverItem) {
|
|
return;
|
|
}
|
|
if (aShowing) {
|
|
[popoverItem showPopover:self];
|
|
} else {
|
|
[popoverItem dismissPopover:self];
|
|
}
|
|
}
|
|
|
|
- (void)touchBarAction:(id)aSender {
|
|
NSTouchBarItemIdentifier identifier =
|
|
objc_getAssociatedObject(aSender, &sIdentifierAssociationKey);
|
|
if (!identifier || [identifier isEqualToString:@""]) {
|
|
return;
|
|
}
|
|
|
|
TouchBarInput* input = self.mappedLayoutItems[identifier];
|
|
if (!input) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsITouchBarInputCallback> callback = [input callback];
|
|
if (!callback) {
|
|
NSLog(@"Touch Bar action attempted with no valid callback! Identifier: %@",
|
|
[input nativeIdentifier]);
|
|
return;
|
|
}
|
|
callback->OnCommand();
|
|
}
|
|
|
|
- (void)releaseJSObjects {
|
|
mTouchBarHelper = nil;
|
|
|
|
for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
|
|
TouchBarInput* input = self.mappedLayoutItems[identifier];
|
|
if (!input) {
|
|
continue;
|
|
}
|
|
|
|
// Childless popovers contain the default Touch Bar as its popoverTouchBar.
|
|
// We check for [input children] since the default Touch Bar contains a
|
|
// popover (search-popover), so this would infinitely loop if there was no check.
|
|
if ([[input type] hasSuffix:@"popover"] && [input children]) {
|
|
NSTouchBarItem* item = [self itemForIdentifier:identifier];
|
|
[(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar] releaseJSObjects];
|
|
}
|
|
|
|
[input setCallback:nil];
|
|
[input setDocument:nil];
|
|
[input setImageURI:nil];
|
|
|
|
[input releaseJSObjects];
|
|
}
|
|
}
|
|
|
|
#pragma mark - NSSharingServicePickerTouchBarItemDelegate
|
|
|
|
- (NSArray*)itemsForSharingServicePickerTouchBarItem:
|
|
(NSSharingServicePickerTouchBarItem*)aPickerTouchBarItem {
|
|
NSURL* urlToShare = nil;
|
|
NSString* titleToShare = @"";
|
|
nsAutoString url;
|
|
nsAutoString title;
|
|
if (mTouchBarHelper) {
|
|
nsresult rv = mTouchBarHelper->GetActiveUrl(url);
|
|
if (!NS_FAILED(rv)) {
|
|
urlToShare = [NSURL URLWithString:nsCocoaUtils::ToNSString(url)];
|
|
// NSURL URLWithString returns nil if the URL is invalid. At this point,
|
|
// it is too late to simply shut down the share menu, so we default to
|
|
// about:blank if the share button is clicked when the URL is invalid.
|
|
if (urlToShare == nil) {
|
|
urlToShare = [NSURL URLWithString:@"about:blank"];
|
|
}
|
|
}
|
|
|
|
rv = mTouchBarHelper->GetActiveTitle(title);
|
|
if (!NS_FAILED(rv)) {
|
|
titleToShare = nsCocoaUtils::ToNSString(title);
|
|
}
|
|
}
|
|
|
|
// If the user has gotten this far, they have clicked the share button so it
|
|
// is logged.
|
|
Telemetry::AccumulateCategorical(Telemetry::LABELS_TOUCHBAR_BUTTON_PRESSES::Share);
|
|
|
|
return @[ urlToShare, titleToShare ];
|
|
}
|
|
|
|
- (NSArray<NSSharingService*>*)sharingServicePicker:(NSSharingServicePicker*)aSharingServicePicker
|
|
sharingServicesForItems:(NSArray*)aItems
|
|
proposedSharingServices:(NSArray<NSSharingService*>*)aProposedServices {
|
|
// redundant services
|
|
NSArray* excludedServices = @[
|
|
@"com.apple.share.System.add-to-safari-reading-list",
|
|
];
|
|
|
|
NSArray* sharingServices = [aProposedServices
|
|
filteredArrayUsingPredicate:[NSPredicate
|
|
predicateWithFormat:@"NOT (name IN %@)", excludedServices]];
|
|
|
|
return sharingServices;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation TouchBarInput
|
|
- (NSString*)key {
|
|
return mKey;
|
|
}
|
|
- (NSString*)title {
|
|
return mTitle;
|
|
}
|
|
- (nsCOMPtr<nsIURI>)imageURI {
|
|
return mImageURI;
|
|
}
|
|
- (RefPtr<nsTouchBarInputIcon>)icon {
|
|
return mIcon;
|
|
}
|
|
- (NSString*)type {
|
|
return mType;
|
|
}
|
|
- (NSColor*)color {
|
|
return mColor;
|
|
}
|
|
- (BOOL)isDisabled {
|
|
return mDisabled;
|
|
}
|
|
- (NSTouchBarItemIdentifier)nativeIdentifier {
|
|
return [TouchBarInput nativeIdentifierWithType:mType withKey:mKey];
|
|
}
|
|
- (nsCOMPtr<nsITouchBarInputCallback>)callback {
|
|
return mCallback;
|
|
}
|
|
- (RefPtr<Document>)document {
|
|
return mDocument;
|
|
}
|
|
- (BOOL)isIconPositionSet {
|
|
return mIsIconPositionSet;
|
|
}
|
|
- (NSMutableArray<TouchBarInput*>*)children {
|
|
return mChildren;
|
|
}
|
|
- (void)setKey:(NSString*)aKey {
|
|
[aKey retain];
|
|
[mKey release];
|
|
mKey = aKey;
|
|
}
|
|
|
|
- (void)setTitle:(NSString*)aTitle {
|
|
[aTitle retain];
|
|
[mTitle release];
|
|
mTitle = aTitle;
|
|
}
|
|
|
|
- (void)setImageURI:(nsCOMPtr<nsIURI>)aImageURI {
|
|
mImageURI = aImageURI;
|
|
}
|
|
|
|
- (void)setIcon:(RefPtr<nsTouchBarInputIcon>)aIcon {
|
|
mIcon = aIcon;
|
|
}
|
|
|
|
- (void)setType:(NSString*)aType {
|
|
[aType retain];
|
|
[mType release];
|
|
mType = aType;
|
|
}
|
|
|
|
- (void)setColor:(NSColor*)aColor {
|
|
[aColor retain];
|
|
[mColor release];
|
|
mColor = aColor;
|
|
}
|
|
|
|
- (void)setDisabled:(BOOL)aDisabled {
|
|
mDisabled = aDisabled;
|
|
}
|
|
|
|
- (void)setCallback:(nsCOMPtr<nsITouchBarInputCallback>)aCallback {
|
|
mCallback = aCallback;
|
|
}
|
|
|
|
- (void)setDocument:(RefPtr<Document>)aDocument {
|
|
if (mIcon) {
|
|
mIcon->Destroy();
|
|
mIcon = nil;
|
|
}
|
|
mDocument = aDocument;
|
|
}
|
|
|
|
- (void)setIconPositionSet:(BOOL)aIsIconPositionSet {
|
|
mIsIconPositionSet = aIsIconPositionSet;
|
|
}
|
|
|
|
- (void)setChildren:(NSMutableArray<TouchBarInput*>*)aChildren {
|
|
[aChildren retain];
|
|
for (TouchBarInput* child in mChildren) {
|
|
[child releaseJSObjects];
|
|
}
|
|
[mChildren removeAllObjects];
|
|
[mChildren release];
|
|
mChildren = aChildren;
|
|
}
|
|
|
|
- (id)initWithKey:(NSString*)aKey
|
|
title:(NSString*)aTitle
|
|
imageURI:(nsCOMPtr<nsIURI>)aImageURI
|
|
type:(NSString*)aType
|
|
callback:(nsCOMPtr<nsITouchBarInputCallback>)aCallback
|
|
color:(uint32_t)aColor
|
|
disabled:(BOOL)aDisabled
|
|
document:(RefPtr<Document>)aDocument
|
|
children:(nsCOMPtr<nsIArray>)aChildren {
|
|
if (self = [super init]) {
|
|
[self setKey:aKey];
|
|
[self setTitle:aTitle];
|
|
[self setImageURI:aImageURI];
|
|
[self setType:aType];
|
|
[self setCallback:aCallback];
|
|
[self setDocument:aDocument];
|
|
[self setIconPositionSet:false];
|
|
[self setDisabled:aDisabled];
|
|
if (aColor) {
|
|
[self setColor:[NSColor colorWithDisplayP3Red:((aColor >> 16) & 0xFF) / 255.0
|
|
green:((aColor >> 8) & 0xFF) / 255.0
|
|
blue:((aColor)&0xFF) / 255.0
|
|
alpha:1.0]];
|
|
}
|
|
if (aChildren) {
|
|
uint32_t itemCount = 0;
|
|
aChildren->GetLength(&itemCount);
|
|
NSMutableArray* orderedChildren = [NSMutableArray arrayWithCapacity:itemCount];
|
|
for (uint32_t i = 0; i < itemCount; ++i) {
|
|
nsCOMPtr<nsITouchBarInput> child = do_QueryElementAt(aChildren, i);
|
|
if (!child) {
|
|
continue;
|
|
}
|
|
TouchBarInput* convertedChild = [[TouchBarInput alloc] initWithXPCOM:child];
|
|
if (convertedChild) {
|
|
orderedChildren[i] = convertedChild;
|
|
}
|
|
}
|
|
[self setChildren:orderedChildren];
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (TouchBarInput*)initWithXPCOM:(nsCOMPtr<nsITouchBarInput>)aInput {
|
|
nsAutoString keyStr;
|
|
nsresult rv = aInput->GetKey(keyStr);
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
|
|
nsAutoString titleStr;
|
|
rv = aInput->GetTitle(titleStr);
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> imageURI;
|
|
rv = aInput->GetImage(getter_AddRefs(imageURI));
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
|
|
nsAutoString typeStr;
|
|
rv = aInput->GetType(typeStr);
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
|
|
nsCOMPtr<nsITouchBarInputCallback> callback;
|
|
rv = aInput->GetCallback(getter_AddRefs(callback));
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
|
|
uint32_t colorInt;
|
|
rv = aInput->GetColor(&colorInt);
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
|
|
bool disabled = false;
|
|
rv = aInput->GetDisabled(&disabled);
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
|
|
RefPtr<Document> document;
|
|
rv = aInput->GetDocument(getter_AddRefs(document));
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
|
|
nsCOMPtr<nsIArray> children;
|
|
rv = aInput->GetChildren(getter_AddRefs(children));
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
|
|
return [self initWithKey:nsCocoaUtils::ToNSString(keyStr)
|
|
title:nsCocoaUtils::ToNSString(titleStr)
|
|
imageURI:imageURI
|
|
type:nsCocoaUtils::ToNSString(typeStr)
|
|
callback:callback
|
|
color:colorInt
|
|
disabled:(BOOL)disabled
|
|
document:document
|
|
children:children];
|
|
}
|
|
|
|
- (void)releaseJSObjects {
|
|
if (mIcon) {
|
|
mIcon->ReleaseJSObjects();
|
|
}
|
|
mCallback = nil;
|
|
mImageURI = nil;
|
|
mDocument = nil;
|
|
for (TouchBarInput* child in mChildren) {
|
|
[child releaseJSObjects];
|
|
}
|
|
}
|
|
|
|
- (void)dealloc {
|
|
if (mIcon) {
|
|
mIcon->Destroy();
|
|
mIcon = nil;
|
|
}
|
|
[mKey release];
|
|
[mTitle release];
|
|
[mType release];
|
|
[mColor release];
|
|
[mChildren removeAllObjects];
|
|
[mChildren release];
|
|
[super dealloc];
|
|
}
|
|
|
|
+ (NSTouchBarItemIdentifier)nativeIdentifierWithType:(NSString*)aType withKey:(NSString*)aKey {
|
|
NSTouchBarItemIdentifier identifier;
|
|
identifier = [BaseIdentifier stringByAppendingPathExtension:aType];
|
|
if (aKey) {
|
|
identifier = [identifier stringByAppendingPathExtension:aKey];
|
|
}
|
|
return identifier;
|
|
}
|
|
|
|
+ (NSTouchBarItemIdentifier)nativeIdentifierWithXPCOM:(nsCOMPtr<nsITouchBarInput>)aInput {
|
|
nsAutoString keyStr;
|
|
nsresult rv = aInput->GetKey(keyStr);
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
NSString* key = nsCocoaUtils::ToNSString(keyStr);
|
|
|
|
nsAutoString typeStr;
|
|
rv = aInput->GetType(typeStr);
|
|
if (NS_FAILED(rv)) {
|
|
return nil;
|
|
}
|
|
NSString* type = nsCocoaUtils::ToNSString(typeStr);
|
|
|
|
return [TouchBarInput nativeIdentifierWithType:type withKey:key];
|
|
}
|
|
|
|
@end
|