[iOS] Remove unused keyboard code in WKSelectPopover class.
[WebKit-https.git] / Source / WebKit2 / UIProcess / ios / forms / WKFormSelectPopover.mm
1 /*
2  * Copyright (C) 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WKFormSelectControl.h"
28
29 #if PLATFORM(IOS)
30
31 #import "UIKitSPI.h"
32 #import "WKContentView.h"
33 #import "WKContentViewInteraction.h"
34 #import "WKFormPopover.h"
35 #import "WebPageProxy.h"
36 #import <UIKit/UIPickerView.h>
37 #import <WebCore/LocalizedStrings.h>
38 #import <wtf/RetainPtr.h>
39
40 using namespace WebKit;
41
42 static NSString* WKPopoverTableViewCellReuseIdentifier  = @"WKPopoverTableViewCellReuseIdentifier";
43
44 @interface UITableViewCell (Internal)
45 - (CGRect)textRectForContentRect:(CGRect)contentRect;
46 - (CGRect)contentRectForBounds:(CGRect)bounds;
47 @end
48
49 static NSString *stringWithWritingDirection(NSString *string, UITextWritingDirection writingDirection, bool override)
50 {
51     if (![string length] || writingDirection == UITextWritingDirectionNatural)
52         return string;
53     
54     if (!override) {
55         UCharDirection firstCharacterDirection = u_charDirection([string characterAtIndex:0]);
56         if ((firstCharacterDirection == U_LEFT_TO_RIGHT && writingDirection == UITextWritingDirectionLeftToRight)
57             || (firstCharacterDirection == U_RIGHT_TO_LEFT && writingDirection == UITextWritingDirectionRightToLeft))
58             return string;
59     }
60     
61     const unichar leftToRightEmbedding = 0x202A;
62     const unichar rightToLeftEmbedding = 0x202B;
63     const unichar popDirectionalFormatting = 0x202C;
64     const unichar leftToRightOverride = 0x202D;
65     const unichar rightToLeftOverride = 0x202E;
66     
67     unichar directionalFormattingCharacter;
68     if (writingDirection == UITextWritingDirectionLeftToRight)
69         directionalFormattingCharacter = (override ? leftToRightOverride : leftToRightEmbedding);
70     else
71         directionalFormattingCharacter = (override ? rightToLeftOverride : rightToLeftEmbedding);
72     
73     return [NSString stringWithFormat:@"%C%@%C", directionalFormattingCharacter, string, popDirectionalFormatting];
74 }
75
76 @class WKSelectPopover;
77
78 @interface WKSelectTableViewController : UITableViewController <UIKeyInput>
79 {
80     NSUInteger _singleSelectionIndex;
81     NSUInteger _singleSelectionSection;
82     NSInteger _numberOfSections;
83     BOOL _allowsMultipleSelection;
84     
85     CGFloat _fontSize;
86     CGFloat _maximumTextWidth;
87     NSTextAlignment _textAlignment;
88     
89     WKSelectPopover *_popover;
90     WKContentView *_contentView;
91 }
92
93 @property(nonatomic,assign) WKSelectPopover *popover;
94 @end
95
96 @implementation WKSelectTableViewController
97
98 - (id)initWithView:(WKContentView *)view hasGroups:(BOOL)hasGroups
99 {
100     if (!(self = [super initWithStyle:UITableViewStylePlain]))
101         return nil;
102     
103     _contentView = view;
104     Vector<OptionItem>& selectOptions = [_contentView assistedNodeSelectOptions];
105     _allowsMultipleSelection = _contentView.assistedNodeInformation.isMultiSelect;
106     
107     // Even if the select is empty, there is at least one tableview section.
108     _numberOfSections = 1;
109     _singleSelectionIndex = NSNotFound;
110     NSInteger currentIndex = 0;
111     for (size_t i = 0; i < selectOptions.size(); ++i) {
112         const OptionItem& item = selectOptions[i];
113         if (item.isGroup) {
114             _numberOfSections++;
115             currentIndex = 0;
116             continue;
117         }
118         if (!_allowsMultipleSelection && item.isSelected) {
119             _singleSelectionIndex = currentIndex;
120             _singleSelectionSection = item.parentGroupID;
121         }
122         currentIndex++;
123     }
124     
125     UITextWritingDirection writingDirection = UITextWritingDirectionLeftToRight;
126     BOOL override = NO;
127     // FIXME: retrieve from WebProcess writing direction and override.
128     _textAlignment = (writingDirection == UITextWritingDirectionLeftToRight) ? NSTextAlignmentLeft : NSTextAlignmentRight;
129     
130     [self setTitle:stringWithWritingDirection(_contentView.assistedNodeInformation.title, writingDirection, override)];
131     
132     return self;
133 }
134
135 - (void)viewWillAppear:(BOOL)animated
136 {
137     [super viewWillAppear:animated];
138
139     if (_singleSelectionIndex == NSNotFound)
140         return;
141
142     if (_singleSelectionSection >= (NSUInteger)[self.tableView numberOfSections])
143         return;
144
145     if (_singleSelectionIndex >= (NSUInteger)[self.tableView numberOfRowsInSection:_singleSelectionSection])
146         return;
147
148     NSIndexPath *indexPath = [NSIndexPath indexPathForRow:_singleSelectionIndex inSection:_singleSelectionSection];
149     [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
150 }
151
152 #pragma mark UITableView delegate methods
153
154 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
155 {
156     return _numberOfSections;
157 }
158
159 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
160 {
161     if ([_contentView assistedNodeSelectOptions].isEmpty())
162         return 1;
163     
164     int rowCount = 0;
165     for (size_t i = 0; i < [_contentView assistedNodeSelectOptions].size(); ++i) {
166         const OptionItem& item = [_contentView assistedNodeSelectOptions][i];
167         if (item.isGroup)
168             continue;
169         if (item.parentGroupID == section)
170             rowCount++;
171         if (item.parentGroupID > section)
172             break;
173     }
174     return rowCount;
175 }
176
177 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
178 {
179     // The first section never has a header. It is for selects without groups.
180     if (section == 0)
181         return nil;
182     
183     int groupCount = 0;
184     for (size_t i = 0; i < [_contentView assistedNodeSelectOptions].size(); ++i) {
185         const OptionItem& item = [_contentView assistedNodeSelectOptions][i];
186         if (!item.isGroup)
187             continue;
188         groupCount++;
189         if (item.isGroup && groupCount == section)
190             return item.text;
191     }
192     return nil;
193 }
194
195 - (void)populateCell:(UITableViewCell *)cell withItem:(const OptionItem&)item
196 {
197     [cell.textLabel setText:item.text];
198     [cell.textLabel setEnabled:!item.disabled];
199     [cell setSelectionStyle:item.disabled ? UITableViewCellSelectionStyleNone : UITableViewCellSelectionStyleBlue];
200     [cell setAccessoryType:item.isSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone];
201 }
202
203 - (NSInteger)findItemIndexAt:(NSIndexPath *)indexPath
204 {
205     ASSERT(indexPath.row >= 0);
206     ASSERT(indexPath.section <= _numberOfSections);
207     
208     int optionIndex = 0;
209     int rowIndex = 0;
210     for (size_t i = 0; i < [_contentView assistedNodeSelectOptions].size(); ++i) {
211         const OptionItem& item = [_contentView assistedNodeSelectOptions][i];
212         if (item.isGroup) {
213             rowIndex = 0;
214             continue;
215         }
216         if (item.parentGroupID == indexPath.section && rowIndex == indexPath.row)
217             return optionIndex;
218         optionIndex++;
219         rowIndex++;
220     }
221     return NSNotFound;
222 }
223
224 - (OptionItem *)findItemAt:(NSIndexPath *)indexPath
225 {
226     ASSERT(indexPath.row >= 0);
227     ASSERT(indexPath.section <= _numberOfSections);
228
229     int index = 0;
230     for (size_t i = 0; i < [_contentView assistedNodeSelectOptions].size(); ++i) {
231         OptionItem& item = [_contentView assistedNodeSelectOptions][i];
232         if (item.isGroup || item.parentGroupID != indexPath.section)
233             continue;
234         if (index == indexPath.row)
235             return &item;
236         index++;
237     }
238     return nil;
239 }
240
241 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
242 {
243     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:WKPopoverTableViewCellReuseIdentifier];
244     if (!cell)
245         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:WKPopoverTableViewCellReuseIdentifier] autorelease];
246     
247     cell.textLabel.textAlignment = _textAlignment;
248     
249     if (_contentView.assistedNodeInformation.selectOptions.isEmpty()) {
250         cell.textLabel.enabled = NO;
251         cell.textLabel.text = WEB_UI_STRING_KEY("No Options", "No Options Select Popover", "Empty select list");
252         cell.accessoryType = UITableViewCellAccessoryNone;
253         cell.selectionStyle = UITableViewCellSelectionStyleNone;
254         return cell;
255     }
256     
257     CGRect textRect = [cell textRectForContentRect:[cell contentRectForBounds:[cell bounds]]];
258     ASSERT(textRect.size.width > 0.0);
259     
260     // Assume all cells have the same available text width.
261     CGFloat initialFontSize = _UIApplicationUsesLegacyUI() ? UITableViewCellDefaultFontSize : cell.textLabel.font.pointSize;
262     UIFont *font = _UIApplicationUsesLegacyUI() ? [UIFont boldSystemFontOfSize:initialFontSize] : cell.textLabel.font;
263     ASSERT(initialFontSize);
264     if (textRect.size.width != _maximumTextWidth || _fontSize == 0) {
265         _maximumTextWidth = textRect.size.width;
266         _fontSize = adjustedFontSize(_maximumTextWidth, font, initialFontSize, _contentView.assistedNodeInformation.selectOptions);
267     }
268     
269     const OptionItem* item = [self findItemAt:indexPath];
270     ASSERT(item);
271     
272     [self populateCell:cell withItem:*item];
273     [cell.textLabel setFont:[font fontWithSize:_fontSize]];
274     [cell.textLabel setLineBreakMode:NSLineBreakByWordWrapping];
275     [cell.textLabel setNumberOfLines:2];
276     return cell;
277 }
278
279 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
280 {
281     if (_contentView.assistedNodeInformation.selectOptions.isEmpty())
282         return;
283     
284     NSInteger itemIndex = [self findItemIndexAt:indexPath];
285     ASSERT(itemIndex != NSNotFound);
286     
287     if (_allowsMultipleSelection) {
288         [tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:NO];
289         
290         UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
291         if (!cell.textLabel.enabled)
292             return;
293         
294         BOOL newStateIsSelected = (cell.accessoryType == UITableViewCellAccessoryNone);
295         
296         cell.accessoryType = newStateIsSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
297         
298         ASSERT(itemIndex != NSNotFound);
299         
300         // To trigger onchange events programmatically we need to go through this
301         // SPI which mimics a user action on the <select>. Normally programmatic
302         // changes do not trigger "change" events on such selects.
303     
304         [_contentView page]->setAssistedNodeSelectedIndex(itemIndex, true);
305         OptionItem& item = [_contentView assistedNodeSelectOptions][itemIndex];
306         item.isSelected = newStateIsSelected;
307     } else {
308         [tableView deselectRowAtIndexPath:indexPath animated:NO];
309         
310         // It is possible for there to be no selection, for example with <select size="2">.
311         NSIndexPath *oldIndexPath = nil;
312         if (_singleSelectionIndex != NSNotFound) {
313             oldIndexPath = [NSIndexPath indexPathForRow:_singleSelectionIndex inSection:_singleSelectionSection];
314             if ([indexPath isEqual:oldIndexPath]) {
315                 [_popover _userActionDismissedPopover:nil];
316                 return;
317             }
318         }
319         
320         UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
321         
322         if (!newCell.textLabel.enabled)
323             return;
324         
325         if (oldIndexPath) {
326             UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:oldIndexPath];
327             if (oldCell && oldCell.accessoryType == UITableViewCellAccessoryCheckmark)
328                 oldCell.accessoryType = UITableViewCellAccessoryNone;
329         }
330         
331         if (newCell && newCell.accessoryType == UITableViewCellAccessoryNone) {
332             newCell.accessoryType = UITableViewCellAccessoryCheckmark;
333             
334             _singleSelectionIndex = indexPath.row;
335             _singleSelectionSection = indexPath.section;
336  
337             [_contentView page]->setAssistedNodeSelectedIndex(itemIndex);
338             OptionItem& newItem = [_contentView assistedNodeSelectOptions][itemIndex];
339             newItem.isSelected = true;
340         }
341         
342         // Need to update the model even if there isn't a cell.
343         if (oldIndexPath) {
344             if (OptionItem* oldItem = [self findItemAt:oldIndexPath])
345                 oldItem->isSelected = false;
346         }
347         
348         [_popover _userActionDismissedPopover:nil];
349     }
350 }
351
352 #pragma mark UIKeyInput delegate methods
353
354 - (BOOL)hasText
355 {
356     return NO;
357 }
358
359 - (void)insertText:(NSString *)text
360 {
361 }
362
363 - (void)deleteBackward
364 {
365 }
366
367 @end
368
369 @implementation WKSelectPopover {
370     WKContentView *_view;
371     RetainPtr<WKSelectTableViewController> _tableViewController;
372 }
373
374 - (instancetype)initWithView:(WKContentView *)view hasGroups:(BOOL)hasGroups
375 {
376     if (!(self = [super initWithView:view]))
377         return nil;
378     
379     _view = view;
380     CGRect frame;
381     frame.origin = CGPointZero;
382     frame.size = [UIKeyboard defaultSizeForInterfaceOrientation:[UIApp interfaceOrientation]];
383
384     _tableViewController = adoptNS([[WKSelectTableViewController alloc] initWithView:view hasGroups:hasGroups]);
385     [_tableViewController setPopover:self];
386     UIViewController *popoverViewController = _tableViewController.get();
387     UINavigationController *navController = nil;
388     NSString *title = view.assistedNodeInformation.title;
389     BOOL needsNavigationController = (_view && _UIApplicationUsesLegacyUI()) || [title length];
390     if (needsNavigationController) {
391         navController = [[UINavigationController alloc] initWithRootViewController:_tableViewController.get()];
392         popoverViewController = navController;
393         
394         if (_view.assistedNodeInformation.isMultiSelect && _UIApplicationUsesLegacyUI())
395             _tableViewController.get().navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(_userActionDismissedPopover:)] autorelease];
396     }
397     
398     CGSize popoverSize = [_tableViewController.get().tableView sizeThatFits:CGSizeMake(320, CGFLOAT_MAX)];
399     if (needsNavigationController)
400         [(UINavigationController *)popoverViewController topViewController].preferredContentSize = popoverSize;
401     else
402         popoverViewController.preferredContentSize = popoverSize;
403     
404     self.popoverController = [[[UIPopoverController alloc] initWithContentViewController:popoverViewController] autorelease];
405     [navController release];
406     
407     [[UIKeyboardImpl sharedInstance] setDelegate:_tableViewController.get()];
408     
409     return self;
410 }
411
412 - (void)dealloc
413 {
414     [_tableViewController setPopover:nil];
415     _tableViewController.get().tableView.dataSource = nil;
416     _tableViewController.get().tableView.delegate = nil;
417     
418     [super dealloc];
419 }
420
421 - (UIView *)controlView
422 {
423     return nil;
424 }
425
426 - (void)controlBeginEditing
427 {
428     [self presentPopoverAnimated:NO];
429 }
430
431 - (void)controlEndEditing
432 {
433 }
434
435 - (void)_userActionDismissedPopover:(id)sender
436 {
437     [self accessoryDone];
438 }
439
440 @end
441
442 #endif  // PLATFORM(IOS)