6f07d893af591b3649b34e6c8edca77d3aa5138d
[WebKit-https.git] / Source / WebKit / UIProcess / ios / WebDataListSuggestionsDropdownIOS.mm
1 /*
2  * Copyright (C) 2018 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 #include "config.h"
27 #include "WebDataListSuggestionsDropdownIOS.h"
28
29 #if ENABLE(DATALIST_ELEMENT) && PLATFORM(IOS_FAMILY)
30
31 #import "WKContentView.h"
32 #import "WKContentViewInteraction.h"
33 #import "WKFormPeripheral.h"
34 #import "WKFormPopover.h"
35 #import "WebPageProxy.h"
36
37 static const CGFloat maxVisibleSuggestions = 5;
38 static const CGFloat suggestionsPopoverCellHeight = 44;
39 static const CGFloat suggestionsPopoverWidth = 320;
40 static NSString * const suggestionCellReuseIdentifier = @"WKDataListSuggestionCell";
41
42 @interface WKDataListSuggestionsControl : NSObject {
43     WebKit::WebDataListSuggestionsDropdownIOS* _dropdown;
44     Vector<String> _suggestions;
45 }
46
47 @property (nonatomic, weak) WKContentView *view;
48
49 - (instancetype)initWithInformation:(WebCore::DataListSuggestionInformation&&)information inView:(WKContentView *)view;
50 - (void)updateWithInformation:(WebCore::DataListSuggestionInformation&&)information;
51 - (void)showSuggestionsDropdown:(WebKit::WebDataListSuggestionsDropdownIOS *)dropdown activationType:(WebCore::DataListSuggestionActivationType)activationType;
52 - (void)didSelectOptionAtIndex:(NSInteger)index;
53 - (void)invalidate;
54
55 - (NSArray<WKDataListTextSuggestion *> *)textSuggestions;
56 - (NSInteger)suggestionsCount;
57 - (String)suggestionAtIndex:(NSInteger)index;
58 - (NSTextAlignment)textAlignment;
59 @end
60
61 @interface WKDataListSuggestionsPicker : WKDataListSuggestionsControl <UIPickerViewDataSource, UIPickerViewDelegate>
62 @end
63
64 @interface WKDataListSuggestionsPickerView : UIPickerView <WKFormControl>
65 @property (nonatomic, weak) WKDataListSuggestionsControl *control;
66 @end
67
68 @interface WKDataListSuggestionsPopover : WKDataListSuggestionsControl
69 @end
70
71 @interface WKDataListSuggestionsViewController : UITableViewController
72 @property (nonatomic, weak) WKDataListSuggestionsControl *control;
73
74 - (void)reloadData;
75 @end
76
77 @implementation WKDataListTextSuggestion
78 @end
79
80 #pragma mark - WebDataListSuggestionsDropdownIOS
81
82 namespace WebKit {
83
84 Ref<WebDataListSuggestionsDropdownIOS> WebDataListSuggestionsDropdownIOS::create(WebPageProxy& page, WKContentView *view)
85 {
86     return adoptRef(*new WebDataListSuggestionsDropdownIOS(page, view));
87 }
88
89 WebDataListSuggestionsDropdownIOS::WebDataListSuggestionsDropdownIOS(WebPageProxy& page, WKContentView *view)
90     : WebDataListSuggestionsDropdown(page)
91     , m_contentView(view)
92 {
93 }
94
95 void WebDataListSuggestionsDropdownIOS::show(WebCore::DataListSuggestionInformation&& information)
96 {
97     if (m_suggestionsControl) {
98         [m_suggestionsControl updateWithInformation:WTFMove(information)];
99         return;
100     }
101
102     WebCore::DataListSuggestionActivationType type = information.activationType;
103
104     if (currentUserInterfaceIdiomIsPad())
105         m_suggestionsControl = adoptNS([[WKDataListSuggestionsPopover alloc] initWithInformation:WTFMove(information) inView:m_contentView]);
106     else
107         m_suggestionsControl = adoptNS([[WKDataListSuggestionsPicker alloc] initWithInformation:WTFMove(information) inView:m_contentView]);
108
109     [m_suggestionsControl showSuggestionsDropdown:this activationType:type];
110 }
111
112 void WebDataListSuggestionsDropdownIOS::handleKeydownWithIdentifier(const String&)
113 {
114 }
115
116 void WebDataListSuggestionsDropdownIOS::close()
117 {
118     [m_suggestionsControl invalidate];
119     m_suggestionsControl = nil;
120     m_page->didCloseSuggestions();
121 }
122
123 void WebDataListSuggestionsDropdownIOS::didSelectOption(const String& selectedOption)
124 {
125     if (!m_page)
126         return;
127
128     m_page->didSelectOption(selectedOption);
129     close();
130 }
131
132 } // namespace WebKit
133
134 #pragma mark - WKDataListSuggestionsControl
135
136 @implementation WKDataListSuggestionsControl
137
138 - (instancetype)initWithInformation:(WebCore::DataListSuggestionInformation&&)information inView:(WKContentView *)view
139 {
140     if (!(self = [super init]))
141         return nil;
142
143     _view = view;
144     _suggestions = WTFMove(information.suggestions);
145
146     return self;
147 }
148
149 - (void)updateWithInformation:(WebCore::DataListSuggestionInformation&&)information
150 {
151     _suggestions = WTFMove(information.suggestions);
152 }
153
154 - (void)showSuggestionsDropdown:(WebKit::WebDataListSuggestionsDropdownIOS *)dropdown activationType:(WebCore::DataListSuggestionActivationType)activationType
155 {
156     _dropdown = dropdown;
157 }
158
159 - (void)didSelectOptionAtIndex:(NSInteger)index
160 {
161     _dropdown->didSelectOption(_suggestions[index]);
162 }
163
164 - (void)invalidate
165 {
166 }
167
168 - (NSArray<WKDataListTextSuggestion *> *)textSuggestions
169 {
170     NSMutableArray *suggestions = [NSMutableArray array];
171
172     for (auto suggestion : _suggestions) {
173         [suggestions addObject:[WKDataListTextSuggestion textSuggestionWithInputText:suggestion]];
174         if (suggestions.count == 3)
175             break;
176     }
177
178     return suggestions;
179 }
180
181 - (NSInteger)suggestionsCount
182 {
183     return _suggestions.size();
184 }
185
186 - (String)suggestionAtIndex:(NSInteger)index
187 {
188     return _suggestions[index];
189 }
190
191 - (NSTextAlignment)textAlignment
192 {
193     return _view.assistedNodeInformation.isRTL ? NSTextAlignmentRight : NSTextAlignmentLeft;
194 }
195
196 @end
197
198 #pragma mark - WKDataListSuggestionsPicker
199
200 @implementation WKDataListSuggestionsPicker  {
201     RetainPtr<WKDataListSuggestionsPickerView> _pickerView;
202 }
203
204 - (instancetype)initWithInformation:(WebCore::DataListSuggestionInformation&&)information inView:(WKContentView *)view
205 {
206     if (!(self = [super initWithInformation:WTFMove(information) inView:view]))
207         return nil;
208
209     _pickerView = adoptNS([[WKDataListSuggestionsPickerView alloc] initWithFrame:CGRectZero]);
210     [_pickerView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
211     [_pickerView setDataSource:self];
212     [_pickerView setDelegate:self];
213     [_pickerView setControl:self];
214     [_pickerView setSize:[UIKeyboard defaultSizeForInterfaceOrientation:UIApp.interfaceOrientation]];
215
216     return self;
217 }
218
219 - (void)updateWithInformation:(WebCore::DataListSuggestionInformation&&)information
220 {
221     [super updateWithInformation:WTFMove(information)];
222     if (information.activationType != WebCore::DataListSuggestionActivationType::IndicatorClicked) {
223         self.view.dataListTextSuggestionsInputView = nil;
224         self.view.dataListTextSuggestions = self.textSuggestions;
225         return;
226     }
227
228     self.view.dataListTextSuggestionsInputView = _pickerView.get();
229
230     [_pickerView reloadAllComponents];
231     [_pickerView selectRow:0 inComponent:0 animated:NO];
232 }
233
234 - (void)showSuggestionsDropdown:(WebKit::WebDataListSuggestionsDropdownIOS *)dropdown activationType:(WebCore::DataListSuggestionActivationType)activationType
235 {
236     [super showSuggestionsDropdown:dropdown activationType:activationType];
237     if (activationType == WebCore::DataListSuggestionActivationType::IndicatorClicked) {
238         self.view.dataListTextSuggestionsInputView = _pickerView.get();
239         [_pickerView selectRow:0 inComponent:0 animated:NO];
240     } else
241         self.view.dataListTextSuggestions = self.textSuggestions;
242 }
243
244 - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
245 {
246     return 1;
247 }
248
249 - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)columnIndex
250 {
251     return [self suggestionsCount];
252 }
253
254 - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
255 {
256     return [self suggestionAtIndex:row];
257 }
258
259 - (void)invalidate
260 {
261     if (self.view.dataListTextSuggestionsInputView == _pickerView.get())
262         self.view.dataListTextSuggestionsInputView = nil;
263
264     [_pickerView setDelegate:nil];
265     [_pickerView setDataSource:nil];
266     [_pickerView setControl:nil];
267 }
268
269 @end
270
271 @implementation WKDataListSuggestionsPickerView
272
273 - (UIView *)controlView
274 {
275     return self;
276 }
277
278 - (void)controlBeginEditing
279 {
280 }
281
282 - (void)controlEndEditing
283 {
284     [self.control didSelectOptionAtIndex:[self selectedRowInComponent:0]];
285 }
286
287 @end
288
289 #pragma mark - WKDataListSuggestionsPopover
290
291 @implementation WKDataListSuggestionsPopover  {
292     RetainPtr<WKFormRotatingAccessoryPopover> _popover;
293     RetainPtr<WKDataListSuggestionsViewController> _suggestionsViewController;
294 }
295
296 - (instancetype)initWithInformation:(WebCore::DataListSuggestionInformation&&)information inView:(WKContentView *)view
297 {
298     if (!(self = [super initWithInformation:WTFMove(information) inView:view]))
299         return nil;
300
301     _popover = adoptNS([[WKFormRotatingAccessoryPopover alloc] initWithView:view]);
302
303     return self;
304 }
305
306 - (void)updateWithInformation:(WebCore::DataListSuggestionInformation&&)information
307 {
308     [super updateWithInformation:WTFMove(information)];
309     [_suggestionsViewController reloadData];
310     self.view.dataListTextSuggestions = self.textSuggestions;
311 }
312
313 - (void)showSuggestionsDropdown:(WebKit::WebDataListSuggestionsDropdownIOS *)dropdown activationType:(WebCore::DataListSuggestionActivationType)activationType
314 {
315     [super showSuggestionsDropdown:dropdown activationType:activationType];
316
317     _suggestionsViewController = adoptNS([[WKDataListSuggestionsViewController alloc] initWithStyle:UITableViewStylePlain]);
318     [_suggestionsViewController setControl:self];
319     [_suggestionsViewController reloadData];
320     self.view.dataListTextSuggestions = self.textSuggestions;
321
322 ALLOW_DEPRECATED_DECLARATIONS_BEGIN
323     [_popover setPopoverController:[[[UIPopoverController alloc] initWithContentViewController:_suggestionsViewController.get()] autorelease]];
324 ALLOW_DEPRECATED_DECLARATIONS_END
325
326     [_popover presentPopoverAnimated:NO];
327 }
328
329 - (void)invalidate
330 {
331     [_suggestionsViewController setControl:nil];
332 }
333
334 - (void)didSelectOptionAtIndex:(NSInteger)index
335 {
336     [super didSelectOptionAtIndex:index];
337     [[_popover popoverController] dismissPopoverAnimated:YES];
338     self.view.dataListTextSuggestions = @[ [WKDataListTextSuggestion textSuggestionWithInputText:[self suggestionAtIndex:index]] ];
339 }
340
341 @end
342
343 @implementation WKDataListSuggestionsViewController
344
345 - (void)reloadData
346 {
347     [self.tableView reloadData];
348     [self setPreferredContentSize:CGSizeMake(suggestionsPopoverWidth, maxVisibleSuggestions * suggestionsPopoverCellHeight + suggestionsPopoverCellHeight / 2)];
349 }
350
351 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
352 {
353     return [self.control suggestionsCount];
354 }
355
356 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
357 {
358     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:suggestionCellReuseIdentifier];
359     if (!cell)
360         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:suggestionCellReuseIdentifier] autorelease];
361
362     cell.textLabel.text = [self.control suggestionAtIndex:indexPath.row];
363     cell.textLabel.lineBreakMode = NSLineBreakByTruncatingTail;
364     cell.textLabel.textAlignment = [self.control textAlignment];
365
366     return cell;
367 }
368
369 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
370 {
371     [self.control didSelectOptionAtIndex:indexPath.row];
372 }
373
374 @end
375
376 #endif // ENABLE(DATALIST_ELEMENT) && PLATFORM(IOS_FAMILY)