blob: 2a4233f3f79003801f62c925d254527a68c46a38 [file] [log] [blame]
/*
* Copyright (C) 2020 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:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
*/
#include "config.h"
#include "WebDateTimePickerMac.h"
#if ENABLE(DATE_AND_TIME_INPUT_TYPES) && USE(APPKIT)
#import "AppKitSPI.h"
#import "WebPageProxy.h"
constexpr CGFloat kCalendarWidth = 139;
constexpr CGFloat kCalendarHeight = 148;
constexpr CGFloat kCalendarCornerRadius = 10;
constexpr CGFloat kWindowBorderSize = 0.5;
constexpr NSString * kDateFormatString = @"yyyy-MM-dd";
constexpr NSString * kDateTimeFormatString = @"yyyy-MM-dd'T'HH:mm";
constexpr NSString * kDateTimeWithSecondsFormatString = @"yyyy-MM-dd'T'HH:mm:ss";
constexpr NSString * kDateTimeWithMillisecondsFormatString = @"yyyy-MM-dd'T'HH:mm:ss.SSS";
constexpr NSString * kDefaultLocaleIdentifier = @"en_US_POSIX";
constexpr NSString * kDefaultTimeZoneIdentifier = @"UTC";
@interface WKDateTimePicker : NSObject
- (id)initWithParams:(WebCore::DateTimeChooserParameters&&)params inView:(NSView *)view;
- (void)showPicker:(WebKit::WebDateTimePickerMac&)picker;
- (void)updatePicker:(WebCore::DateTimeChooserParameters&&)params;
- (void)invalidate;
@end
@interface WKDateTimePickerWindow : NSWindow
@end
@interface WKDateTimePickerBackdropView : NSView
@end
namespace WebKit {
Ref<WebDateTimePickerMac> WebDateTimePickerMac::create(WebPageProxy& page, NSView *view)
{
return adoptRef(*new WebDateTimePickerMac(page, view));
}
WebDateTimePickerMac::~WebDateTimePickerMac()
{
[m_picker invalidate];
}
WebDateTimePickerMac::WebDateTimePickerMac(WebPageProxy& page, NSView *view)
: WebDateTimePicker(page)
, m_view(view)
{
}
void WebDateTimePickerMac::endPicker()
{
[m_picker invalidate];
m_picker = nil;
WebDateTimePicker::endPicker();
}
void WebDateTimePickerMac::showDateTimePicker(WebCore::DateTimeChooserParameters&& params)
{
if (m_picker) {
[m_picker updatePicker:WTFMove(params)];
return;
}
m_picker = adoptNS([[WKDateTimePicker alloc] initWithParams:WTFMove(params) inView:m_view.get().get()]);
[m_picker showPicker:*this];
}
void WebDateTimePickerMac::didChooseDate(StringView date)
{
if (!m_page)
return;
m_page->didChooseDate(date);
}
} // namespace WebKit
@implementation WKDateTimePickerWindow {
RetainPtr<WKDateTimePickerBackdropView> _backdropView;
}
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingStoreType defer:(BOOL)defer
{
self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingStoreType defer:defer];
if (!self)
return nil;
self.hasShadow = YES;
self.releasedWhenClosed = NO;
self.titleVisibility = NSWindowTitleHidden;
self.titlebarAppearsTransparent = YES;
self.movable = NO;
self.backgroundColor = [NSColor clearColor];
self.opaque = NO;
_backdropView = adoptNS([[WKDateTimePickerBackdropView alloc] initWithFrame:contentRect]);
[_backdropView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[self setContentView:_backdropView.get()];
return self;
}
- (BOOL)canBecomeKeyWindow
{
return NO;
}
- (BOOL)hasKeyAppearance
{
return YES;
}
- (NSWindowShadowOptions)shadowOptions
{
return NSWindowShadowSecondaryWindow;
}
@end
@implementation WKDateTimePickerBackdropView
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
[[NSColor controlBackgroundColor] setFill];
NSRect rect = NSInsetRect(self.frame, kWindowBorderSize, 0);
NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect));
NSPoint bottomLeft = NSMakePoint(NSMinX(rect), NSMinY(rect));
NSBezierPath *path = [NSBezierPath bezierPath];
[path moveToPoint:topLeft];
[path lineToPoint:NSMakePoint(topRight.x - kCalendarCornerRadius, topRight.y)];
[path curveToPoint:NSMakePoint(topRight.x, topRight.y - kCalendarCornerRadius) controlPoint1:topRight controlPoint2:topRight];
[path lineToPoint:NSMakePoint(bottomRight.x, bottomRight.y + kCalendarCornerRadius)];
[path curveToPoint:NSMakePoint(bottomRight.x - kCalendarCornerRadius, bottomRight.y) controlPoint1:bottomRight controlPoint2:bottomRight];
[path lineToPoint:NSMakePoint(bottomLeft.x + kCalendarCornerRadius, bottomLeft.y)];
[path curveToPoint:NSMakePoint(bottomLeft.x, bottomLeft.y + kCalendarCornerRadius) controlPoint1:bottomLeft controlPoint2:bottomLeft];
[path lineToPoint:topLeft];
[path fill];
[NSGraphicsContext restoreGraphicsState];
}
@end
@implementation WKDateTimePicker {
WeakPtr<WebKit::WebDateTimePickerMac> _picker;
WebCore::DateTimeChooserParameters _params;
WeakObjCPtr<NSView> _presentingView;
RetainPtr<WKDateTimePickerWindow> _enclosingWindow;
RetainPtr<NSDatePicker> _datePicker;
RetainPtr<NSDateFormatter> _dateFormatter;
}
- (id)initWithParams:(WebCore::DateTimeChooserParameters&&)params inView:(NSView *)view
{
if (!(self = [super init]))
return self;
_presentingView = view;
NSRect windowRect = [[_presentingView window] convertRectToScreen:[_presentingView convertRect:params.anchorRectInRootView toView:nil]];
windowRect.origin.y = NSMinY(windowRect) - kCalendarHeight;
windowRect.size.width = kCalendarWidth;
windowRect.size.height = kCalendarHeight;
// Use a UTC timezone as all incoming double values are UTC timestamps. This also ensures that
// the date value of the NSDatePicker matches the date value returned by JavaScript. The timezone
// has no effect on the value returned to the WebProcess, as a timezone-agnostic format string is
// used to return the date.
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:kDefaultTimeZoneIdentifier];
_enclosingWindow = adoptNS([[WKDateTimePickerWindow alloc] initWithContentRect:NSZeroRect styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO]);
[_enclosingWindow setFrame:windowRect display:YES];
_datePicker = adoptNS([[NSDatePicker alloc] initWithFrame:[_enclosingWindow contentView].bounds]);
[_datePicker setBezeled:NO];
[_datePicker setDrawsBackground:NO];
[_datePicker setDatePickerStyle:NSDatePickerStyleClockAndCalendar];
[_datePicker setDatePickerElements:NSDatePickerElementFlagYearMonthDay];
[_datePicker setTimeZone:timeZone];
[_datePicker setTarget:self];
[_datePicker setAction:@selector(didChooseDate:)];
auto englishLocale = adoptNS([[NSLocale alloc] initWithLocaleIdentifier:kDefaultLocaleIdentifier]);
_dateFormatter = adoptNS([[NSDateFormatter alloc] init]);
[_dateFormatter setLocale:englishLocale.get()];
[_dateFormatter setTimeZone:timeZone];
[self updatePicker:WTFMove(params)];
return self;
}
- (void)showPicker:(WebKit::WebDateTimePickerMac&)picker
{
_picker = picker;
[[_enclosingWindow contentView] addSubview:_datePicker.get()];
[[_presentingView window] addChildWindow:_enclosingWindow.get() ordered:NSWindowAbove];
}
- (void)updatePicker:(WebCore::DateTimeChooserParameters&&)params
{
_params = WTFMove(params);
NSString *currentDateValueString = _params.currentValue;
[_dateFormatter setDateFormat:[self dateFormatStringForType:_params.type]];
if (![currentDateValueString length])
[_datePicker setDateValue:[self initialDateForEmptyValue]];
else
[_datePicker setDateValue:[_dateFormatter dateFromString:currentDateValueString]];
[_datePicker setMinDate:[NSDate dateWithTimeIntervalSince1970:_params.minimum / 1000.0]];
[_datePicker setMaxDate:[NSDate dateWithTimeIntervalSince1970:_params.maximum / 1000.0]];
[_enclosingWindow setAppearance:[NSAppearance appearanceNamed:_params.useDarkAppearance ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua]];
}
- (void)invalidate
{
[_datePicker removeFromSuperviewWithoutNeedingDisplay];
[_datePicker setTarget:nil];
[_datePicker setAction:nil];
_datePicker = nil;
_dateFormatter = nil;
[[_presentingView window] removeChildWindow:_enclosingWindow.get()];
[_enclosingWindow close];
_enclosingWindow = nil;
}
- (void)didChooseDate:(id)sender
{
if (sender != _datePicker)
return;
String dateString = [_dateFormatter stringFromDate:[_datePicker dateValue]];
_picker->didChooseDate(StringView(dateString));
}
- (NSString *)dateFormatStringForType:(NSString *)type
{
if ([type isEqualToString:@"datetime-local"]) {
if (_params.hasMillisecondField)
return kDateTimeWithMillisecondsFormatString;
if (_params.hasSecondField)
return kDateTimeWithSecondsFormatString;
return kDateTimeFormatString;
}
return kDateFormatString;
}
- (NSDate *)initialDateForEmptyValue
{
NSDate *now = [NSDate date];
NSTimeZone *defaultTimeZone = [NSTimeZone defaultTimeZone];
NSInteger offset = [defaultTimeZone secondsFromGMTForDate:now];
return [now dateByAddingTimeInterval:offset];
}
@end
#endif // ENABLE(DATE_AND_TIME_INPUT_TYPES) && USE(APPKIT)