[macOS] Color wells should appear pressed when presenting a color picker
[WebKit-https.git] / Source / WebKit / UIProcess / mac / WebColorPickerMac.mm
1 /* 
2  * Copyright (c) 2013 The Chromium Authors. All rights reserved.
3  * Copyright (C) 2013 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *    * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *    * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *    * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #import "config.h"
33 #import "WebColorPickerMac.h"
34
35 #if ENABLE(INPUT_TYPE_COLOR)
36
37 #if USE(APPKIT)
38
39 #import <WebCore/Color.h>
40 #import <WebCore/ColorMac.h>
41
42 using namespace WebKit;
43
44 #if ENABLE(INPUT_TYPE_COLOR_POPOVER)
45
46 #import <pal/spi/mac/NSColorWellSPI.h>
47 #import <pal/spi/mac/NSPopoverColorWellSPI.h>
48 #import <pal/spi/mac/NSPopoverSPI.h>
49
50 static const size_t maxColorSuggestions = 12;
51 static const CGFloat colorPickerMatrixNumColumns = 12.0;
52 static const CGFloat colorPickerMatrixSwatchWidth = 12.0;
53 static const CGFloat colorPickerMatrixBorderWidth = 1.0;
54
55 @protocol WKPopoverColorWellDelegate <NSObject>
56 - (void)didClosePopover;
57 @end
58
59 @interface WKPopoverColorWell : NSPopoverColorWell {
60     RetainPtr<NSColorList> _suggestedColors;
61 }
62
63 @property (nonatomic, weak) id<WKPopoverColorWellDelegate> webDelegate;
64
65 - (void)setSuggestedColors:(NSColorList *)suggestedColors;
66 @end
67
68 @interface WKColorPopoverMac : NSObject<WKColorPickerUIMac, WKPopoverColorWellDelegate, NSWindowDelegate> {
69 @private
70     BOOL _lastChangedByUser;
71     WebColorPickerMac *_picker;
72     RetainPtr<WKPopoverColorWell> _popoverWell;
73 }
74 - (id)initWithFrame:(const WebCore::IntRect &)rect inView:(NSView *)view;
75 @end
76
77 #else
78
79 @interface WKColorPanelMac : NSObject<WKColorPickerUIMac, NSWindowDelegate> {
80 @private
81     BOOL _lastChangedByUser;
82     WebColorPickerMac *_picker;
83 }
84 - (id)init;
85 @end
86
87 #endif // ENABLE(INPUT_TYPE_COLOR_POPOVER)
88
89 namespace WebKit {
90
91 Ref<WebColorPickerMac> WebColorPickerMac::create(WebColorPicker::Client* client, const WebCore::Color& initialColor, const WebCore::IntRect& rect, Vector<WebCore::Color>&& suggestions, NSView *view)
92 {
93     return adoptRef(*new WebColorPickerMac(client, initialColor, rect, WTFMove(suggestions), view));
94 }
95
96 WebColorPickerMac::~WebColorPickerMac()
97 {
98     if (m_colorPickerUI) {
99         [m_colorPickerUI invalidate];
100         m_colorPickerUI = nil;
101     }
102 }
103
104 WebColorPickerMac::WebColorPickerMac(WebColorPicker::Client* client, const WebCore::Color& initialColor, const WebCore::IntRect& rect, Vector<WebCore::Color>&& suggestions, NSView *view)
105     : WebColorPicker(client)
106     , m_suggestions(WTFMove(suggestions))
107 {
108 #if ENABLE(INPUT_TYPE_COLOR_POPOVER)
109     m_colorPickerUI = adoptNS([[WKColorPopoverMac alloc] initWithFrame:rect inView:view]);
110 #else
111     m_colorPickerUI = adoptNS([[WKColorPanelMac alloc] init]);
112 #endif
113 }
114
115 void WebColorPickerMac::endPicker()
116 {
117     [m_colorPickerUI invalidate];
118     m_colorPickerUI = nil;
119     WebColorPicker::endPicker();
120 }
121
122 void WebColorPickerMac::setSelectedColor(const WebCore::Color& color)
123 {
124     if (!m_client || !m_colorPickerUI)
125         return;
126     
127     [m_colorPickerUI setColor:nsColor(color)];
128 }
129
130 void WebColorPickerMac::didChooseColor(const WebCore::Color& color)
131 {
132     if (!m_client)
133         return;
134     
135     m_client->didChooseColor(color);
136 }
137
138 void WebColorPickerMac::showColorPicker(const WebCore::Color& color)
139 {
140     if (!m_client)
141         return;
142
143 #if !ENABLE(INPUT_TYPE_COLOR_POPOVER)
144     if (!m_colorPickerUI)
145         m_colorPickerUI = adoptNS([[WKColorPanelMac alloc] init]);
146 #endif
147
148     [m_colorPickerUI setAndShowPicker:this withColor:nsColor(color) suggestions:WTFMove(m_suggestions)];
149 }
150
151 } // namespace WebKit
152
153 #if ENABLE(INPUT_TYPE_COLOR_POPOVER)
154
155 @implementation WKPopoverColorWell
156
157 + (NSPopover *)_colorPopoverCreateIfNecessary:(BOOL)forceCreation
158 {
159     static NSPopover *colorPopover = nil;
160     if (forceCreation) {
161         NSPopover *popover = [[NSPopover alloc] init];
162         [popover _setRequiresCorrectContentAppearance:YES];
163         popover.behavior = NSPopoverBehaviorTransient;
164
165         NSColorPopoverController *controller = [[NSClassFromString(@"NSColorPopoverController") alloc] init];
166         popover.contentViewController = controller;
167         controller.popover = popover;
168         [controller release];
169
170         colorPopover = popover;
171     }
172
173     return colorPopover;
174 }
175
176 - (void)_showPopover
177 {
178     NSPopover *popover = [[self class] _colorPopoverCreateIfNecessary:YES];
179     popover.delegate = self;
180
181     [self deactivate];
182
183     // Deactivate previous NSPopoverColorWell
184     NSColorWell *owner = [NSColorWell _exclusiveColorPanelOwner];
185     if ([owner isKindOfClass:[NSPopoverColorWell class]])
186         [owner deactivate];
187
188     NSColorPopoverController *controller = (NSColorPopoverController *)[popover contentViewController];
189     controller.delegate = self;
190
191     if (_suggestedColors) {
192         NSUInteger numColors = [[_suggestedColors allKeys] count];
193         CGFloat swatchWidth = (colorPickerMatrixNumColumns * colorPickerMatrixSwatchWidth + (colorPickerMatrixNumColumns * colorPickerMatrixBorderWidth - numColors)) / numColors;
194         CGFloat swatchHeight = colorPickerMatrixSwatchWidth;
195
196         // topBarMatrixView cannot be accessed until view has been loaded
197         if (!controller.isViewLoaded)
198             [controller loadView];
199
200         NSColorPickerMatrixView *topMatrix = controller.topBarMatrixView;
201         [topMatrix setNumberOfColumns:numColors];
202         [topMatrix setSwatchSize:NSMakeSize(swatchWidth, swatchHeight)];
203         [topMatrix setColorList:_suggestedColors.get()];
204     }
205
206     [self activate:YES];
207     [popover showRelativeToRect:self.bounds ofView:self preferredEdge:NSMinYEdge];
208 }
209
210 - (void)popoverDidClose:(NSNotification *)notification {
211     [self.webDelegate didClosePopover];
212 }
213
214 - (NSView *)hitTest:(NSPoint)point
215 {
216     return nil;
217 }
218
219 - (void)setSuggestedColors:(NSColorList *)suggestedColors
220 {
221     _suggestedColors = suggestedColors;
222 }
223
224 @end
225
226 @implementation WKColorPopoverMac
227 - (id)initWithFrame:(const WebCore::IntRect &)rect inView:(NSView *)view
228 {
229     if(!(self = [super init]))
230         return self;
231
232     _popoverWell = adoptNS([[WKPopoverColorWell alloc] initWithFrame:[view convertRect:NSRectFromCGRect(rect) toView:nil]]);
233     if (!_popoverWell)
234         return self;
235
236     [_popoverWell setAlphaValue:0.0];
237     [[view window].contentView addSubview:_popoverWell.get()];
238
239     return self;
240 }
241
242 - (void)setAndShowPicker:(WebKit::WebColorPickerMac*)picker withColor:(NSColor *)color suggestions:(Vector<WebCore::Color>&&)suggestions
243 {
244     _picker = picker;
245
246     [_popoverWell setTarget:self];
247     [_popoverWell setWebDelegate:self];
248     [_popoverWell setAction:@selector(didChooseColor:)];
249     [_popoverWell setColor:color];
250
251     NSColorList *suggestedColors = nil;
252     if (suggestions.size()) {
253         suggestedColors = [[[NSColorList alloc] init] autorelease];
254         for (size_t i = 0; i < std::min(suggestions.size(), maxColorSuggestions); i++)
255             [suggestedColors insertColor:nsColor(suggestions.at(i)) key:@(i).stringValue atIndex:i];
256     }
257
258     [_popoverWell setSuggestedColors:suggestedColors];
259     [_popoverWell _showPopover];
260
261     [[NSColorPanel sharedColorPanel] setDelegate:self];
262     
263     _lastChangedByUser = YES;
264 }
265
266 - (void)invalidate
267 {
268     [_popoverWell removeFromSuperviewWithoutNeedingDisplay];
269     [_popoverWell setTarget:nil];
270     [_popoverWell setAction:nil];
271     [_popoverWell deactivate];
272     
273     _popoverWell = nil;
274     _picker = nil;
275
276     NSColorPanel *panel = [NSColorPanel sharedColorPanel];
277     if (panel.delegate == self) {
278         panel.delegate = nil;
279         [panel close];
280     }
281 }
282
283 - (void)windowWillClose:(NSNotification *)notification
284 {
285     if (!_picker)
286         return;
287
288     if (notification.object == [NSColorPanel sharedColorPanel]) {
289         _lastChangedByUser = YES;
290         _picker->endPicker();
291     }
292 }
293
294 - (void)didChooseColor:(id)sender
295 {
296     if (sender != _popoverWell)
297         return;
298
299     // Handle the case where the <input type='color'> value is programmatically set.
300     if (!_lastChangedByUser) {
301         _lastChangedByUser = YES;
302         return;
303     }
304
305     _picker->didChooseColor(WebCore::colorFromNSColor([_popoverWell color]));
306 }
307
308 - (void)setColor:(NSColor *)color
309 {
310     _lastChangedByUser = NO;
311     [_popoverWell setColor:color];
312 }
313
314 - (void)didClosePopover
315 {
316     if (!_picker)
317         return;
318
319     if (![NSColorPanel sharedColorPanel].isVisible)
320         _picker->endPicker();
321 }
322
323 @end
324
325 #else
326
327 @implementation WKColorPanelMac
328
329 - (id)init
330 {
331     self = [super init];
332     return self;
333 }
334
335 - (void)setAndShowPicker:(WebColorPickerMac*)picker withColor:(NSColor *)color suggestions:(Vector<WebCore::Color>&&)suggestions
336 {
337     _picker = picker;
338
339     NSColorPanel *panel = [NSColorPanel sharedColorPanel];
340
341     [panel setShowsAlpha:NO];
342     [panel setDelegate:self];
343     [panel setTarget:self];
344
345     [panel setColor:color];
346
347     _lastChangedByUser = YES;
348     [panel setAction:@selector(didChooseColor:)];
349     [panel makeKeyAndOrderFront:nil];
350 }
351
352 - (void)invalidate
353 {
354     NSColorPanel *panel = [NSColorPanel sharedColorPanel];
355     if ([panel delegate] == self) {
356         [panel setDelegate:nil];
357         [panel setTarget:nil];
358         [panel setAction:nil];
359     }
360     _picker = nil;
361 }
362
363 - (void)windowWillClose:(NSNotification *)notification
364 {
365     _lastChangedByUser = YES;
366     _picker->endPicker();
367 }
368
369 - (void)didChooseColor:(id)sender
370 {
371     if (sender != [NSColorPanel sharedColorPanel])
372         return;
373
374     // Handle the case where the <input type='color'> value is programmatically set.
375     if (!_lastChangedByUser) {
376         _lastChangedByUser = YES;
377         return;
378     }
379
380     _picker->didChooseColor(WebCore::colorFromNSColor([sender color]));
381 }
382
383 - (void)setColor:(NSColor *)color
384 {
385     _lastChangedByUser = NO;
386     [[NSColorPanel sharedColorPanel] setColor:color];
387 }
388
389 @end
390
391 #endif // ENABLE(INPUT_TYPE_COLOR_POPOVER)
392
393 #endif // USE(APPKIT)
394
395 #endif // ENABLE(INPUT_TYPE_COLOR)