/* * 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 "WKFormSelectControl.h" #if PLATFORM(IOS) #import "WKContentView.h" #import "WKContentViewInteraction.h" #import "WKFormPopover.h" #import "WebPageProxy.h" #import #import #import #import #import #import #import #import #import #import #import #import using namespace WebKit; static NSString* WKPopoverTableViewCellReuseIdentifier = @"WKPopoverTableViewCellReuseIdentifier"; @interface UITableViewCell (Internal) - (CGRect)textRectForContentRect:(CGRect)contentRect; - (CGRect)contentRectForBounds:(CGRect)bounds; @end static NSString *stringWithWritingDirection(NSString *string, UITextWritingDirection writingDirection, bool override) { if (![string length] || writingDirection == UITextWritingDirectionNatural) return string; if (!override) { UCharDirection firstCharacterDirection = u_charDirection([string characterAtIndex:0]); if ((firstCharacterDirection == U_LEFT_TO_RIGHT && writingDirection == UITextWritingDirectionLeftToRight) || (firstCharacterDirection == U_RIGHT_TO_LEFT && writingDirection == UITextWritingDirectionRightToLeft)) return string; } const unichar leftToRightEmbedding = 0x202A; const unichar rightToLeftEmbedding = 0x202B; const unichar popDirectionalFormatting = 0x202C; const unichar leftToRightOverride = 0x202D; const unichar rightToLeftOverride = 0x202E; unichar directionalFormattingCharacter; if (writingDirection == UITextWritingDirectionLeftToRight) directionalFormattingCharacter = (override ? leftToRightOverride : leftToRightEmbedding); else directionalFormattingCharacter = (override ? rightToLeftOverride : rightToLeftEmbedding); return [NSString stringWithFormat:@"%C%@%C", directionalFormattingCharacter, string, popDirectionalFormatting]; } @class WKSelectPopover; @interface WKSelectTableViewController : UITableViewController { NSUInteger _singleSelectionIndex; NSUInteger _singleSelectionSection; NSInteger _numberOfSections; BOOL _allowsMultipleSelection; CGFloat _fontSize; CGFloat _maximumTextWidth; NSTextAlignment _textAlignment; WKSelectPopover *_popover; WKContentView *_contentView; } @property(nonatomic,assign) WKSelectPopover *popover; @end @implementation WKSelectTableViewController - (id)initWithView:(WKContentView *)view hasGroups:(BOOL)hasGroups { if (!(self = [super initWithStyle:UITableViewStylePlain])) return nil; _contentView = view; Vector& selectOptions = [_contentView assistedNodeSelectOptions]; _allowsMultipleSelection = _contentView.assistedNodeInformation.isMultiSelect; // Even if the select is empty, there is at least one tableview section. _numberOfSections = 1; _singleSelectionIndex = NSNotFound; NSInteger currentIndex = 0; for (size_t i = 0; i < selectOptions.size(); ++i) { const WKOptionItem& item = selectOptions[i]; if (item.isGroup) { _numberOfSections++; currentIndex = 0; continue; } if (!_allowsMultipleSelection && item.isSelected) { _singleSelectionIndex = currentIndex; _singleSelectionSection = item.parentGroupID; } currentIndex++; } UITextWritingDirection writingDirection = UITextWritingDirectionLeftToRight; BOOL override = NO; // FIXME: retrieve from WebProcess writing direction and override. _textAlignment = (writingDirection == UITextWritingDirectionLeftToRight) ? NSTextAlignmentLeft : NSTextAlignmentRight; [self setTitle:stringWithWritingDirection(_contentView.assistedNodeInformation.title, writingDirection, override)]; return self; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (_singleSelectionIndex != NSNotFound) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:_singleSelectionIndex inSection:_singleSelectionSection]; [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO]; } } #pragma mark UITableView delegate methods - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return _numberOfSections; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if ([_contentView assistedNodeSelectOptions].isEmpty()) return 1; int rowCount = 0; for (size_t i = 0; i < [_contentView assistedNodeSelectOptions].size(); ++i) { const WKOptionItem& item = [_contentView assistedNodeSelectOptions][i]; if (item.isGroup) continue; if (item.parentGroupID == section) rowCount++; if (item.parentGroupID > section) break; } return rowCount; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { // The first section never has a header. It is for selects without groups. if (section == 0) return nil; int groupCount = 0; for (size_t i = 0; i < [_contentView assistedNodeSelectOptions].size(); ++i) { const WKOptionItem& item = [_contentView assistedNodeSelectOptions][i]; if (!item.isGroup) continue; groupCount++; if (item.isGroup && groupCount == section) return item.text; } return nil; } - (void)populateCell:(UITableViewCell *)cell withItem:(const WKOptionItem *)item { [cell.textLabel setText:item->text]; [cell.textLabel setEnabled:!item->disabled]; [cell setSelectionStyle:item->disabled ? UITableViewCellSelectionStyleNone : UITableViewCellSelectionStyleBlue]; [cell setAccessoryType:item->isSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone]; } - (NSInteger)findItemIndexAt:(NSIndexPath *)indexPath { ASSERT(indexPath.row >= 0); ASSERT(indexPath.section <= _numberOfSections); int optionIndex = 0; int rowIndex = 0; for (size_t i = 0; i < [_contentView assistedNodeSelectOptions].size(); ++i) { const WKOptionItem& item = [_contentView assistedNodeSelectOptions][i]; if (item.isGroup) { rowIndex = 0; continue; } if (item.parentGroupID == indexPath.section && rowIndex == indexPath.row) return optionIndex; optionIndex++; rowIndex++; } return NSNotFound; } - (WKOptionItem *)findItemAt:(NSIndexPath *)indexPath { ASSERT(indexPath.row >= 0); ASSERT(indexPath.section <= _numberOfSections); int index = 0; for (size_t i = 0; i < [_contentView assistedNodeSelectOptions].size(); ++i) { WKOptionItem& item = [_contentView assistedNodeSelectOptions][i]; if (item.isGroup || item.parentGroupID != indexPath.section) continue; if (index == indexPath.row) return &item; index++; } return nil; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:WKPopoverTableViewCellReuseIdentifier]; if (!cell) cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:WKPopoverTableViewCellReuseIdentifier] autorelease]; cell.textLabel.textAlignment = _textAlignment; if (_contentView.assistedNodeInformation.selectOptions.isEmpty()) { cell.textLabel.enabled = NO; cell.textLabel.text = WEB_UI_STRING_KEY("No Options", "No Options Select Popover", "Empty select list"); cell.accessoryType = UITableViewCellAccessoryNone; cell.selectionStyle = UITableViewCellSelectionStyleNone; return cell; } CGRect textRect = [cell textRectForContentRect:[cell contentRectForBounds:[cell bounds]]]; ASSERT(textRect.size.width > 0.0); // Assume all cells have the same available text width. CGFloat initialFontSize = _UIApplicationUsesLegacyUI() ? UITableViewCellDefaultFontSize : cell.textLabel.font.pointSize; UIFont *font = _UIApplicationUsesLegacyUI() ? [UIFont boldSystemFontOfSize:initialFontSize] : cell.textLabel.font; ASSERT(initialFontSize); if (textRect.size.width != _maximumTextWidth || _fontSize == 0) { _maximumTextWidth = textRect.size.width; _fontSize = adjustedFontSize(_maximumTextWidth, font, initialFontSize, _contentView.assistedNodeInformation.selectOptions); } const WKOptionItem* item = [self findItemAt:indexPath]; ASSERT(item); [self populateCell:cell withItem:item]; [cell.textLabel setFont:[font fontWithSize:_fontSize]]; [cell.textLabel setLineBreakMode:NSLineBreakByWordWrapping]; [cell.textLabel setNumberOfLines:2]; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (_contentView.assistedNodeInformation.selectOptions.isEmpty()) return; NSInteger itemIndex = [self findItemIndexAt:indexPath]; ASSERT(itemIndex != NSNotFound); if (_allowsMultipleSelection) { [tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:NO]; UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; if (!cell.textLabel.enabled) return; BOOL newStateIsSelected = (cell.accessoryType == UITableViewCellAccessoryNone); cell.accessoryType = newStateIsSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone; ASSERT(itemIndex != NSNotFound); // To trigger onchange events programmatically we need to go through this // SPI which mimics a user action on the . NSIndexPath *oldIndexPath = nil; if (_singleSelectionIndex != NSNotFound) { oldIndexPath = [NSIndexPath indexPathForRow:_singleSelectionIndex inSection:_singleSelectionSection]; if ([indexPath isEqual:oldIndexPath]) { [_popover _userActionDismissedPopover:nil]; return; } } UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath]; if (!newCell.textLabel.enabled) return; if (oldIndexPath) { UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:oldIndexPath]; if (oldCell && oldCell.accessoryType == UITableViewCellAccessoryCheckmark) oldCell.accessoryType = UITableViewCellAccessoryNone; } if (newCell && newCell.accessoryType == UITableViewCellAccessoryNone) { newCell.accessoryType = UITableViewCellAccessoryCheckmark; _singleSelectionIndex = indexPath.row; _singleSelectionSection = indexPath.section; [_contentView page]->setAssistedNodeSelectedIndex(itemIndex); WKOptionItem& newItem = [_contentView assistedNodeSelectOptions][itemIndex]; newItem.isSelected = true; } // Need to update the model even if there isn't a cell. if (oldIndexPath) { WKOptionItem* oldItem = [self findItemAt:oldIndexPath]; if (oldItem) oldItem->isSelected = false; } [_popover _userActionDismissedPopover:nil]; } } #pragma mark UIKeyInput delegate methods - (BOOL)hasText { return NO; } - (void)insertText:(NSString *)text { } - (void)deleteBackward { } @end @implementation WKSelectPopover { WKContentView *_view; RetainPtr _tableViewController; RetainPtr _keyboard; } - (instancetype)initWithView:(WKContentView *)view hasGroups:(BOOL)hasGroups { if (!(self = [super initWithView:view])) return nil; _view = view; CGRect frame; frame.origin = CGPointZero; frame.size = [UIKeyboard defaultSizeForInterfaceOrientation:[UIApp interfaceOrientation]]; _keyboard = adoptNS([[UIKeyboard alloc] initWithFrame:frame]); _tableViewController = adoptNS([[WKSelectTableViewController alloc] initWithView:view hasGroups:hasGroups]); [_tableViewController setPopover:self]; UIViewController *popoverViewController = _tableViewController.get(); UINavigationController *navController = nil; NSString *title = view.assistedNodeInformation.title; BOOL needsNavigationController = (_view && _UIApplicationUsesLegacyUI()) || [title length]; if (needsNavigationController) { navController = [[UINavigationController alloc] initWithRootViewController:_tableViewController.get()]; popoverViewController = navController; if (_view.assistedNodeInformation.isMultiSelect && _UIApplicationUsesLegacyUI()) _tableViewController.get().navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(_userActionDismissedPopover:)] autorelease]; } CGSize popoverSize = [_tableViewController.get().tableView sizeThatFits:CGSizeMake(320, CGFLOAT_MAX)]; if (needsNavigationController) [(UINavigationController *)popoverViewController topViewController].preferredContentSize = popoverSize; else popoverViewController.preferredContentSize = popoverSize; self.popoverController = [[[UIPopoverController alloc] initWithContentViewController:popoverViewController] autorelease]; [navController release]; [[UIKeyboardImpl sharedInstance] setDelegate:_tableViewController.get()]; [_keyboard setSize:[UIKeyboard defaultSizeForInterfaceOrientation:[UIApp interfaceOrientation]]]; [_keyboard prepareForGeometryChange]; [_keyboard geometryChangeDone:NO]; [_keyboard activate]; return self; } - (void)dealloc { [_tableViewController setPopover:nil]; _tableViewController.get().tableView.dataSource = nil; _tableViewController.get().tableView.delegate = nil; [super dealloc]; } - (UIView *)controlView { return nil; } - (void)controlBeginEditing { [self presentPopoverAnimated:NO]; } - (void)controlEndEditing { } - (void)_userActionDismissedPopover:(id)sender { [self accessoryDone]; } @end #endif // PLATFORM(IOS)