WebColorPickerMac.mm   [plain text]


/* 
 * Copyright (c) 2013 The Chromium Authors. All rights reserved.
 * Copyright (C) 2013 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *    * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "config.h"
#import "WebColorPickerMac.h"

#if ENABLE(INPUT_TYPE_COLOR)

#if USE(APPKIT)

#import <WebCore/Color.h>
#import <WebCore/ColorMac.h>
#import <pal/spi/mac/NSColorWellSPI.h>
#import <pal/spi/mac/NSPopoverColorWellSPI.h>
#import <pal/spi/mac/NSPopoverSPI.h>
#import <wtf/WeakObjCPtr.h>

static const size_t maxColorSuggestions = 12;
static const CGFloat colorPickerMatrixNumColumns = 12.0;
static const CGFloat colorPickerMatrixBorderWidth = 1.0;

// FIXME: <rdar://problem/41173525> We should not have to track changes in NSPopoverColorWell's implementation.
static const CGFloat colorPickerMatrixSwatchWidth = 13.0;

@protocol WKPopoverColorWellDelegate <NSObject>
- (void)didClosePopover;
@end

@interface WKPopoverColorWell : NSPopoverColorWell {
    RetainPtr<NSColorList> _suggestedColors;
    WeakObjCPtr<id <WKPopoverColorWellDelegate>> _webDelegate;
}

@property (nonatomic, weak) id<WKPopoverColorWellDelegate> webDelegate;

- (void)setSuggestedColors:(NSColorList *)suggestedColors;
@end

@interface WKColorPopoverMac : NSObject<WKColorPickerUIMac, WKPopoverColorWellDelegate, NSWindowDelegate> {
@private
    BOOL _lastChangedByUser;
    WebKit::WebColorPickerMac *_picker;
    RetainPtr<WKPopoverColorWell> _popoverWell;
}
- (id)initWithFrame:(const WebCore::IntRect &)rect inView:(NSView *)view;
@end

namespace WebKit {

Ref<WebColorPickerMac> WebColorPickerMac::create(WebColorPicker::Client* client, const WebCore::Color& initialColor, const WebCore::IntRect& rect, Vector<WebCore::Color>&& suggestions, NSView *view)
{
    return adoptRef(*new WebColorPickerMac(client, initialColor, rect, WTFMove(suggestions), view));
}

WebColorPickerMac::~WebColorPickerMac()
{
    if (m_colorPickerUI) {
        [m_colorPickerUI invalidate];
        m_colorPickerUI = nil;
    }
}

WebColorPickerMac::WebColorPickerMac(WebColorPicker::Client* client, const WebCore::Color& initialColor, const WebCore::IntRect& rect, Vector<WebCore::Color>&& suggestions, NSView *view)
    : WebColorPicker(client)
    , m_suggestions(WTFMove(suggestions))
{
    m_colorPickerUI = adoptNS([[WKColorPopoverMac alloc] initWithFrame:rect inView:view]);
}

void WebColorPickerMac::endPicker()
{
    [m_colorPickerUI invalidate];
    m_colorPickerUI = nil;
    WebColorPicker::endPicker();
}

void WebColorPickerMac::setSelectedColor(const WebCore::Color& color)
{
    if (!m_client || !m_colorPickerUI)
        return;
    
    [m_colorPickerUI setColor:nsColor(color)];
}

void WebColorPickerMac::didChooseColor(const WebCore::Color& color)
{
    if (!m_client)
        return;
    
    m_client->didChooseColor(color);
}

void WebColorPickerMac::showColorPicker(const WebCore::Color& color)
{
    if (!m_client)
        return;

    [m_colorPickerUI setAndShowPicker:this withColor:nsColor(color) suggestions:WTFMove(m_suggestions)];
}

} // namespace WebKit

@implementation WKPopoverColorWell

+ (NSPopover *)_colorPopoverCreateIfNecessary:(BOOL)forceCreation
{
    static NSPopover *colorPopover = nil;
    if (forceCreation) {
        NSPopover *popover = [[NSPopover alloc] init];
        [popover _setRequiresCorrectContentAppearance:YES];
        popover.behavior = NSPopoverBehaviorTransient;

        NSColorPopoverController *controller = [[NSClassFromString(@"NSColorPopoverController") alloc] init];
        popover.contentViewController = controller;
        controller.popover = popover;
        [controller release];

        colorPopover = popover;
    }

    return colorPopover;
}

- (id <WKPopoverColorWellDelegate>)webDelegate
{
    return _webDelegate.getAutoreleased();
}

- (void)setWebDelegate:(id <WKPopoverColorWellDelegate>)webDelegate
{
    _webDelegate = webDelegate;
}

- (void)_showPopover
{
    NSPopover *popover = [[self class] _colorPopoverCreateIfNecessary:YES];
    popover.delegate = self;

    [self deactivate];

    // Deactivate previous NSPopoverColorWell
    NSColorWell *owner = [NSColorWell _exclusiveColorPanelOwner];
    if ([owner isKindOfClass:[NSPopoverColorWell class]])
        [owner deactivate];

    NSColorPopoverController *controller = (NSColorPopoverController *)[popover contentViewController];
    controller.delegate = self;

    if (_suggestedColors) {
        NSUInteger numColors = [[_suggestedColors allKeys] count];
        CGFloat swatchWidth = (colorPickerMatrixNumColumns * colorPickerMatrixSwatchWidth + (colorPickerMatrixNumColumns * colorPickerMatrixBorderWidth - numColors)) / numColors;
        CGFloat swatchHeight = colorPickerMatrixSwatchWidth;

        // topBarMatrixView cannot be accessed until view has been loaded
        if (!controller.isViewLoaded)
            [controller loadView];

        NSColorPickerMatrixView *topMatrix = controller.topBarMatrixView;
        [topMatrix setNumberOfColumns:numColors];
        [topMatrix setSwatchSize:NSMakeSize(swatchWidth, swatchHeight)];
        [topMatrix setColorList:_suggestedColors.get()];
    }

    [self activate:YES];
    [popover showRelativeToRect:self.bounds ofView:self preferredEdge:NSMinYEdge];
}

- (void)popoverDidClose:(NSNotification *)notification {
    [self.webDelegate didClosePopover];
}

- (NSView *)hitTest:(NSPoint)point
{
    return nil;
}

- (void)setSuggestedColors:(NSColorList *)suggestedColors
{
    _suggestedColors = suggestedColors;
}

@end

@implementation WKColorPopoverMac
- (id)initWithFrame:(const WebCore::IntRect &)rect inView:(NSView *)view
{
    if(!(self = [super init]))
        return self;

    _popoverWell = adoptNS([[WKPopoverColorWell alloc] initWithFrame:[view convertRect:NSRectFromCGRect(rect) toView:nil]]);
    if (!_popoverWell)
        return self;

    [_popoverWell setAlphaValue:0.0];
    [[view window].contentView addSubview:_popoverWell.get()];

    return self;
}

- (void)setAndShowPicker:(WebKit::WebColorPickerMac*)picker withColor:(NSColor *)color suggestions:(Vector<WebCore::Color>&&)suggestions
{
    _picker = picker;

    [_popoverWell setTarget:self];
    [_popoverWell setWebDelegate:self];
    [_popoverWell setAction:@selector(didChooseColor:)];
    [_popoverWell setColor:color];

    NSColorList *suggestedColors = nil;
    if (suggestions.size()) {
        suggestedColors = [[[NSColorList alloc] init] autorelease];
        for (size_t i = 0; i < std::min(suggestions.size(), maxColorSuggestions); i++)
            [suggestedColors insertColor:nsColor(suggestions.at(i)) key:@(i).stringValue atIndex:i];
    }

    [_popoverWell setSuggestedColors:suggestedColors];
    [_popoverWell _showPopover];

    [[NSColorPanel sharedColorPanel] setDelegate:self];
    
    _lastChangedByUser = YES;
}

- (void)invalidate
{
    [_popoverWell removeFromSuperviewWithoutNeedingDisplay];
    [_popoverWell setTarget:nil];
    [_popoverWell setAction:nil];
    [_popoverWell deactivate];
    
    _popoverWell = nil;
    _picker = nil;

    NSColorPanel *panel = [NSColorPanel sharedColorPanel];
    if (panel.delegate == self) {
        panel.delegate = nil;
        [panel close];
    }
}

- (void)windowWillClose:(NSNotification *)notification
{
    if (!_picker)
        return;

    if (notification.object == [NSColorPanel sharedColorPanel]) {
        _lastChangedByUser = YES;
        _picker->endPicker();
    }
}

- (void)didChooseColor:(id)sender
{
    if (sender != _popoverWell)
        return;

    // Handle the case where the <input type='color'> value is programmatically set.
    if (!_lastChangedByUser) {
        _lastChangedByUser = YES;
        return;
    }

    _picker->didChooseColor(WebCore::colorFromNSColor([_popoverWell color]));
}

- (void)setColor:(NSColor *)color
{
    _lastChangedByUser = NO;
    [_popoverWell setColor:color];
}

- (void)didClosePopover
{
    if (!_picker)
        return;

    if (![NSColorPanel sharedColorPanel].isVisible)
        _picker->endPicker();
}

@end

#endif // USE(APPKIT)

#endif // ENABLE(INPUT_TYPE_COLOR)