blob: 1cd62e3bb51813b714e34a3cfbeb73abaaa90ce3 [file] [log] [blame]
/*
* Copyright (C) 2014 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.
*/
#import "config.h"
#import "WKFormInputControl.h"
#if PLATFORM(IOS)
#import "UIKitSPI.h"
#import "WKContentView.h"
#import "WKContentViewInteraction.h"
#import "WKFormPopover.h"
#import "WebPageProxy.h"
#import <UIKit/UIBarButtonItem.h>
#import <UIKit/UIDatePicker.h>
#import <WebCore/LocalizedStrings.h>
#import <wtf/RetainPtr.h>
using namespace WebKit;
@interface WKDateTimePopoverViewController : UIViewController {
RetainPtr<NSObject<WKFormControl>> _innerControl;
}
- (id)initWithView:(WKContentView *)view datePickerMode:(UIDatePickerMode)datePickerMode;
- (NSObject<WKFormControl> *)innerControl;
@end
@interface WKDateTimePicker : NSObject<WKFormControl> {
RetainPtr<UIDatePicker> _datePicker;
NSString *_formatString;
BOOL _shouldRemoveTimeZoneInformation;
BOOL _isTimeInput;
WKContentView* _view;
}
- (id)initWithView:(WKContentView *)view datePickerMode:(UIDatePickerMode)mode;
- (UIDatePicker *)datePicker;
@end
@interface WKDateTimePopover : WKFormRotatingAccessoryPopover<WKFormControl> {
RetainPtr<WKDateTimePopoverViewController> _viewController;
WKContentView* _view;
}
- (id)initWithView:(WKContentView *)view datePickerMode:(UIDatePickerMode)mode;
- (WKDateTimePopoverViewController *) viewController;
@end
@implementation WKDateTimePicker
static NSString * const kDateFormatString = @"yyyy-MM-dd"; // "2011-01-27".
static NSString * const kMonthFormatString = @"yyyy-MM"; // "2011-01".
static NSString * const kTimeFormatString = @"HH:mm"; // "13:45".
static const NSTimeInterval kMillisecondsPerSecond = 1000;
- (UIDatePicker *)datePicker
{
return _datePicker.get();
}
- (id)initWithView:(WKContentView *)view datePickerMode:(UIDatePickerMode)mode
{
if (!(self = [super init]))
return nil;
_view = view;
_shouldRemoveTimeZoneInformation = NO;
_isTimeInput = NO;
switch (view.assistedNodeInformation.elementType) {
case InputType::Date:
_formatString = kDateFormatString;
break;
case InputType::Month:
_formatString = kMonthFormatString;
break;
case InputType::Time:
_formatString = kTimeFormatString;
_isTimeInput = YES;
break;
case InputType::DateTimeLocal:
_shouldRemoveTimeZoneInformation = YES;
break;
default:
break;
}
CGSize size = currentUserInterfaceIdiomIsPad() ? [UIPickerView defaultSizeForCurrentOrientation] : [UIKeyboard defaultSizeForInterfaceOrientation:[UIApp interfaceOrientation]];
_datePicker = adoptNS([[UIDatePicker alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)]);
_datePicker.get().datePickerMode = mode;
_datePicker.get().hidden = NO;
[_datePicker addTarget:self action:@selector(_dateChangeHandler:) forControlEvents:UIControlEventValueChanged];
return self;
}
- (void)dealloc
{
[_datePicker removeTarget:self action:NULL forControlEvents:UIControlEventValueChanged];
[super dealloc];
}
- (UIView *)controlView
{
return _datePicker.get();
}
- (NSInteger)_timeZoneOffsetFromGMT:(NSDate *)date
{
if (!_shouldRemoveTimeZoneInformation)
return 0;
return [_datePicker.get().timeZone secondsFromGMTForDate:date];
}
- (NSString *)_sanitizeInputValueForFormatter:(NSString *)value
{
// The "time" input type may have seconds and milliseconds information which we
// just ignore. For example: "01:56:20.391" is shortened to just "01:56".
if (_isTimeInput)
return [value substringToIndex:[kTimeFormatString length]];
return value;
}
- (void)_dateChangedSetAsNumber
{
NSDate *date = [_datePicker date];
[_view page]->setAssistedNodeValueAsNumber(([date timeIntervalSince1970] + [self _timeZoneOffsetFromGMT:date]) * kMillisecondsPerSecond);
}
- (RetainPtr<NSDateFormatter>)dateFormatterForPicker
{
RetainPtr<NSLocale> englishLocale = adoptNS([[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]);
RetainPtr<NSDateFormatter> dateFormatter = adoptNS([[NSDateFormatter alloc] init]);
[dateFormatter setTimeZone:_datePicker.get().timeZone];
[dateFormatter setDateFormat:_formatString];
[dateFormatter setLocale:englishLocale.get()];
return dateFormatter;
}
- (void)_dateChangedSetAsString
{
// Force English locale because that is what HTML5 value parsing expects.
RetainPtr<NSDateFormatter> dateFormatter = [self dateFormatterForPicker];
[_view page]->setAssistedNodeValue([dateFormatter stringFromDate:[_datePicker date]]);
}
- (void)_dateChanged
{
// Internally, DOMHTMLInputElement setValueAs* each take different values for
// different date types. It is sometimes easier to set the date in different ways:
// - use setValueAsString for "date", "month", and "time".
// - use setValueAsNumber for "datetime-local".
if (_formatString)
[self _dateChangedSetAsString];
else
[self _dateChangedSetAsNumber];
}
- (void)_dateChangeHandler:(id)sender
{
[self _dateChanged];
}
- (void)controlBeginEditing
{
// Set the time zone in case it changed.
_datePicker.get().timeZone = [NSTimeZone localTimeZone];
// Currently no value for the <input>. Start the picker with the current time.
// Also, update the actual <input> value.
NSString *value = _view.assistedNodeInformation.value;
if (_view.assistedNodeInformation.value.isEmpty()) {
[_datePicker setDate:[NSDate date]];
[self _dateChanged];
return;
}
// Convert the string value to a date object for the fields where we have a format string.
if (_formatString) {
value = [self _sanitizeInputValueForFormatter:value];
RetainPtr<NSDateFormatter> dateFormatter = [self dateFormatterForPicker];
NSDate *parsedDate = [dateFormatter dateFromString:value];
[_datePicker setDate:parsedDate ? parsedDate : [NSDate date]];
return;
}
// Convert the number value to a date object for the fields affected by timezones.
NSTimeInterval secondsSince1970 = _view.assistedNodeInformation.valueAsNumber / kMillisecondsPerSecond;
NSInteger timeZoneOffset = [self _timeZoneOffsetFromGMT:[NSDate dateWithTimeIntervalSince1970:secondsSince1970]];
NSTimeInterval adjustedSecondsSince1970 = secondsSince1970 - timeZoneOffset;
[_datePicker setDate:[NSDate dateWithTimeIntervalSince1970:adjustedSecondsSince1970]];
}
- (void)controlEndEditing
{
}
@end
// WKFormInputControl
@implementation WKFormInputControl {
RetainPtr<id<WKFormControl>> _control;
}
- (instancetype)initWithView:(WKContentView *)view
{
if (!(self = [super init]))
return nil;
UIDatePickerMode mode;
switch (view.assistedNodeInformation.elementType) {
case InputType::Date:
mode = UIDatePickerModeDate;
break;
case InputType::DateTimeLocal:
mode = UIDatePickerModeDateAndTime;
break;
case InputType::Time:
mode = UIDatePickerModeTime;
break;
case InputType::Month:
mode = (UIDatePickerMode)UIDatePickerModeYearAndMonth;
break;
default:
[self release];
return nil;
}
if (currentUserInterfaceIdiomIsPad())
_control = adoptNS([[WKDateTimePopover alloc] initWithView:view datePickerMode:mode]);
else
_control = adoptNS([[WKDateTimePicker alloc] initWithView:view datePickerMode:mode]);
return self;
}
- (void)beginEditing
{
[_control controlBeginEditing];
}
- (void)endEditing
{
[_control controlEndEditing];
}
- (UIView *)assistantView
{
return [_control controlView];
}
@end
@implementation WKDateTimePopoverViewController
- (id)initWithView:(WKContentView *)view datePickerMode:(UIDatePickerMode)datePickerMode
{
if (!(self = [super init]))
return nil;
_innerControl = adoptNS([[WKDateTimePicker alloc] initWithView:view datePickerMode:datePickerMode]);
return self;
}
- (NSObject<WKFormControl> *)innerControl
{
return _innerControl.get();
}
- (void)loadView
{
self.view = [_innerControl controlView];
}
@end
@implementation WKDateTimePopover
- (void)clear:(id)sender
{
[_view page]->setAssistedNodeValue(String());
}
- (id)initWithView:(WKContentView *)view datePickerMode:(UIDatePickerMode)mode
{
if (!(self = [super initWithView:view]))
return nil;
_view = view;
_viewController = adoptNS([[WKDateTimePopoverViewController alloc] initWithView:view datePickerMode:mode]);
UIDatePicker *datePicker = [(WKDateTimePicker *)_viewController.get().innerControl datePicker];
CGFloat popoverWidth = [datePicker _contentWidth];
CGFloat popoverHeight = _viewController.get().view.frame.size.height;
[_viewController setPreferredContentSize:CGSizeMake(popoverWidth, popoverHeight)];
[_viewController setEdgesForExtendedLayout:UIRectEdgeNone];
[_viewController setTitle:_view.assistedNodeInformation.title];
// Always have a navigation controller with a clear button, and a title if the input element has a title.
RetainPtr<UINavigationController> navigationController = adoptNS([[UINavigationController alloc] initWithRootViewController:_viewController.get()]);
UINavigationItem *navigationItem = navigationController.get().navigationBar.topItem;
NSString *clearString = WEB_UI_STRING_KEY("Clear", "Clear Button Date Popover", "Clear button in date input popover");
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
UIBarButtonItem *clearButton = [[[UIBarButtonItem alloc] initWithTitle:clearString style:UIBarButtonItemStyleBordered target:self action:@selector(clear:)] autorelease];
#pragma clang diagnostic pop
[navigationItem setRightBarButtonItem:clearButton];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
RetainPtr<UIPopoverController> controller = adoptNS([[UIPopoverController alloc] initWithContentViewController:navigationController.get()]);
#pragma clang diagnostic pop
[self setPopoverController:controller.get()];
return self;
}
- (WKDateTimePopoverViewController *)viewController
{
return _viewController.get();
}
- (void)controlBeginEditing
{
[self presentPopoverAnimated:NO];
[_viewController.get().innerControl controlBeginEditing];
}
- (void)controlEndEditing
{
}
- (UIView *)controlView
{
return nil;
}
@end
#endif // PLATFORM(IOS)