Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(69)

Side by Side Diff: ios/chrome/browser/ui/omnibox/omnibox_popup_material_view_controller.mm

Issue 2589803002: Upstream Chrome on iOS source code [6/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/browser/ui/omnibox/omnibox_popup_material_view_controller.h"
6
7 #include <memory>
8
9 #include "base/ios/ios_util.h"
10 #include "base/mac/scoped_cftyperef.h"
11 #include "base/mac/scoped_nsobject.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "components/omnibox/browser/autocomplete_input.h"
15 #include "components/omnibox/browser/autocomplete_match.h"
16 #include "components/omnibox/browser/autocomplete_result.h"
17 #include "components/omnibox/browser/suggestion_answer.h"
18 #include "ios/chrome/browser/ui/animation_util.h"
19 #import "ios/chrome/browser/ui/omnibox/omnibox_popup_material_row.h"
20 #import "ios/chrome/browser/ui/omnibox/omnibox_popup_view_ios.h"
21 #include "ios/chrome/browser/ui/omnibox/omnibox_util.h"
22 #import "ios/chrome/browser/ui/omnibox/truncating_attributed_label.h"
23 #include "ios/chrome/browser/ui/rtl_geometry.h"
24 #include "ios/chrome/browser/ui/ui_util.h"
25 #import "ios/chrome/browser/ui/uikit_ui_util.h"
26 #include "ios/chrome/grit/ios_theme_resources.h"
27 #import "ios/third_party/material_components_ios/src/components/Typography/src/M aterialTypography.h"
28 #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoF ontLoader.h"
29 #include "ios/web/public/image_fetcher/image_data_fetcher.h"
30 #include "net/base/escape.h"
31
32 namespace {
33 const int kRowCount = 6;
34 const CGFloat kRowHeight = 48.0;
35 const CGFloat kAnswerRowHeight = 64.0;
36 const CGFloat kTopAndBottomPadding = 8.0;
37 // The color of the main text of a suggest cell.
38 UIColor* SuggestionTextColor() {
39 return [UIColor colorWithWhite:(51 / 255.0) alpha:1.0];
40 }
41 // The color of the detail text of a suggest cell.
42 UIColor* SuggestionDetailTextColor() {
43 return [UIColor colorWithRed:(85 / 255.0)
44 green:(149 / 255.0)
45 blue:(254 / 255.0)
46 alpha:1.0];
47 }
48 // The color of the text in the portion of a search suggestion that matches the
49 // omnibox input text.
50 UIColor* DimColor() {
51 return [UIColor colorWithWhite:(161 / 255.0) alpha:1.0];
52 }
53 UIColor* SuggestionTextColorIncognito() {
54 return [UIColor whiteColor];
55 }
56 UIColor* DimColorIncognito() {
57 return [UIColor whiteColor];
58 }
59 UIColor* BackgroundColorTablet() {
60 return [UIColor whiteColor];
61 }
62 UIColor* BackgroundColorPhone() {
63 return [UIColor colorWithRed:(245 / 255.0)
64 green:(245 / 255.0)
65 blue:(246 / 255.0)
66 alpha:1.0];
67 }
68 UIColor* BackgroundColorIncognito() {
69 return [UIColor colorWithRed:(50 / 255.0)
70 green:(50 / 255.0)
71 blue:(50 / 255.0)
72 alpha:1.0];
73 }
74 } // namespace
75
76 @interface OmniboxPopupMaterialViewController () {
77 // CTFontRef's are needed for drawing attributed strings but are expensive
78 // to create. Since we only need four, we create them here and hold on to
79 // them.
80 base::ScopedCFTypeRef<CTFontRef> _smallFont;
81 base::ScopedCFTypeRef<CTFontRef> _bigFont;
82 base::ScopedCFTypeRef<CTFontRef> _smallBoldFont;
83 base::ScopedCFTypeRef<CTFontRef> _bigBoldFont;
84
85 // Alignment of omnibox text. Popup text should match this alignment.
86 NSTextAlignment _alignment;
87
88 OmniboxPopupViewIOS* _popupView; // weak, owns us
89
90 // Fetcher for Answers in Suggest images.
91 std::unique_ptr<web::ImageDataFetcher> imageFetcher_;
92
93 // The data source.
94 AutocompleteResult _currentResult;
95
96 // Array containing the OmniboxPopupMaterialRow objects displayed in the view.
97 base::scoped_nsobject<NSArray> _rows;
98
99 // The height of the keyboard. Used to determine the content inset for the
100 // scroll view.
101 CGFloat keyboardHeight_;
102 }
103
104 @end
105
106 @implementation OmniboxPopupMaterialViewController
107
108 @synthesize incognito = _incognito;
109
110 #pragma mark -
111 #pragma mark Initialization
112
113 - (instancetype)initWithPopupView:(OmniboxPopupViewIOS*)view
114 withFetcher:
115 (std::unique_ptr<web::ImageDataFetcher>)imageFetcher {
116 if ((self = [super init])) {
117 _popupView = view;
118 imageFetcher_ = std::move(imageFetcher);
119
120 if (IsIPadIdiom()) {
121 // The iPad keyboard can cover some of the rows of the scroll view. The
122 // scroll view's content inset may need to be updated when the keyboard is
123 // displayed.
124 NSNotificationCenter* defaultCenter =
125 [NSNotificationCenter defaultCenter];
126 [defaultCenter addObserver:self
127 selector:@selector(keyboardDidShow:)
128 name:UIKeyboardDidShowNotification
129 object:nil];
130 }
131 }
132 return self;
133 }
134
135 - (void)dealloc {
136 self.tableView.delegate = nil;
137 [[NSNotificationCenter defaultCenter] removeObserver:self];
138 [super dealloc];
139 }
140
141 - (UIScrollView*)scrollView {
142 return (UIScrollView*)self.tableView;
143 }
144
145 - (void)viewDidLoad {
146 [super viewDidLoad];
147
148 // Initialize the same size as the parent view, autoresize will correct this.
149 [self.view setFrame:CGRectZero];
150 if (_incognito) {
151 self.view.backgroundColor = BackgroundColorIncognito();
152 } else {
153 self.view.backgroundColor =
154 IsIPadIdiom() ? BackgroundColorTablet() : BackgroundColorPhone();
155 }
156 [self.view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth |
157 UIViewAutoresizingFlexibleHeight)];
158
159 // Cache fonts needed for omnibox attributed string.
160 UIFont* smallFont = [MDCTypography body1Font];
161 UIFont* bigFont = [MDCTypography subheadFont];
162 UIFont* smallBoldFont = [[MDFRobotoFontLoader sharedInstance]
163 mediumFontOfSize:smallFont.pointSize];
164 UIFont* bigBoldFont =
165 [[MDFRobotoFontLoader sharedInstance] mediumFontOfSize:bigFont.pointSize];
166 _smallFont.reset(CTFontCreateWithName((CFStringRef)smallFont.fontName,
167 smallFont.pointSize, NULL));
168 _bigFont.reset(CTFontCreateWithName((CFStringRef)bigFont.fontName,
169 bigFont.pointSize, NULL));
170 _smallBoldFont.reset(CTFontCreateWithName((CFStringRef)smallBoldFont.fontName,
171 smallBoldFont.pointSize, NULL));
172 _bigBoldFont.reset(CTFontCreateWithName((CFStringRef)bigBoldFont.fontName,
173 bigBoldFont.pointSize, NULL));
174 NSMutableArray* rowsBuilder = [[[NSMutableArray alloc] init] autorelease];
175 for (int i = 0; i < kRowCount; i++) {
176 OmniboxPopupMaterialRow* row = [[[OmniboxPopupMaterialRow alloc]
177 initWithIncognito:_incognito] autorelease];
178 row.accessibilityIdentifier =
179 [NSString stringWithFormat:@"omnibox suggestion %i", i];
180 row.autoresizingMask = UIViewAutoresizingFlexibleWidth;
181 [rowsBuilder addObject:row];
182 [row.appendButton addTarget:self
183 action:@selector(appendButtonTapped:)
184 forControlEvents:UIControlEventTouchUpInside];
185 [row.appendButton setTag:i];
186 row.rowHeight = kRowHeight;
187 }
188 _rows.reset([rowsBuilder copy]);
189
190 // Table configuration.
191 self.tableView.allowsMultipleSelectionDuringEditing = NO;
192 self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
193 self.tableView.separatorInset = UIEdgeInsetsZero;
194 if ([self.tableView respondsToSelector:@selector(setLayoutMargins:)]) {
195 [self.tableView setLayoutMargins:UIEdgeInsetsZero];
196 }
197 self.automaticallyAdjustsScrollViewInsets = NO;
198 [self.tableView setContentInset:UIEdgeInsetsMake(kTopAndBottomPadding, 0,
199 kTopAndBottomPadding, 0)];
200 }
201
202 - (void)didReceiveMemoryWarning {
203 [super didReceiveMemoryWarning];
204 if (![self isViewLoaded]) {
205 _smallFont.reset();
206 _bigFont.reset();
207 _smallBoldFont.reset();
208 _bigBoldFont.reset();
209 _rows.reset();
210 }
211 }
212
213 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
214 [self layoutRows];
215 }
216
217 #pragma mark -
218 #pragma mark Updating data and UI
219
220 - (void)updateRow:(OmniboxPopupMaterialRow*)row
221 withMatch:(const AutocompleteMatch&)match {
222 const CGFloat kTextCellLeadingPadding =
223 IsIPadIdiom() ? (!IsCompactTablet() ? 192 : 100) : 16;
224 const CGFloat kTextCellTopPadding = 6;
225 const CGFloat kDetailCellTopPadding = 26;
226 const CGFloat kTextLabelHeight = 24;
227 const CGFloat kTextDetailLabelHeight = 22;
228 const CGFloat kAppendButtonWidth = 40;
229 const CGFloat kAnswerLabelHeight = 50;
230 const CGFloat kAnswerImageWidth = 30;
231 const CGFloat kAnswerImageLeftPadding = -1;
232 const CGFloat kAnswerImageRightPadding = 4;
233 const CGFloat kAnswerImageTopPadding = -3;
234 const CGFloat kAnswerRowPadding = 4;
235 const BOOL alignmentRight = _alignment == NSTextAlignmentRight;
236
237 BOOL LTRTextInRTLLayout = _alignment == NSTextAlignmentLeft && UseRTLLayout();
238
239 // Update the frame to reflect whether we have an answer or not.
240 const BOOL answerPresent = match.answer.get() != nil;
241 row.rowHeight = answerPresent ? kAnswerRowHeight : kRowHeight;
242
243 [row.detailTruncatingLabel setTextAlignment:_alignment];
244 [row.textTruncatingLabel setTextAlignment:_alignment];
245
246 // Fetch the answer image if specified. Currently, no answer types specify an
247 // image on the first line so for now we only look at the second line.
248 const BOOL answerImagePresent =
249 answerPresent && match.answer->second_line().image_url().is_valid();
250 if (answerImagePresent) {
251 web::ImageFetchedCallback callback =
252 ^(const GURL& original_url, int response_code, NSData* data) {
253 if (data) {
254 UIImage* image =
255 [UIImage imageWithData:data scale:[UIScreen mainScreen].scale];
256 if (image) {
257 row.answerImageView.image = image;
258 }
259 }
260 };
261 imageFetcher_->StartDownload(match.answer->second_line().image_url(),
262 callback);
263
264 // Answers in suggest do not support RTL, left align only.
265 CGFloat imageLeftPadding =
266 kTextCellLeadingPadding + kAnswerImageLeftPadding;
267 if (alignmentRight) {
268 imageLeftPadding =
269 row.frame.size.width - (kAnswerImageWidth + kAppendButtonWidth);
270 }
271 CGFloat imageTopPadding =
272 kDetailCellTopPadding + kAnswerRowPadding + kAnswerImageTopPadding;
273 row.answerImageView.frame =
274 CGRectMake(imageLeftPadding, imageTopPadding, kAnswerImageWidth,
275 kAnswerImageWidth);
276 row.answerImageView.hidden = NO;
277 } else {
278 row.answerImageView.hidden = YES;
279 }
280
281 // DetailTextLabel and textLabel are fading labels placed in each row. The
282 // textLabel is layed out above the detailTextLabel, and vertically centered
283 // if the detailTextLabel is empty.
284 OmniboxPopupTruncatingLabel* detailTextLabel = row.detailTruncatingLabel;
285 // The width must be positive for CGContextRef to be valid.
286 CGFloat labelWidth =
287 MAX(40, floorf(row.frame.size.width) - kTextCellLeadingPadding);
288 CGFloat labelHeight =
289 answerPresent ? kAnswerLabelHeight : kTextDetailLabelHeight;
290 CGFloat answerImagePadding = kAnswerImageWidth + kAnswerImageRightPadding;
291 CGFloat leadingPadding =
292 (answerImagePresent && !alignmentRight ? answerImagePadding : 0) +
293 kTextCellLeadingPadding;
294 CGFloat topPadding =
295 (answerPresent ? kAnswerRowPadding : 0) + kDetailCellTopPadding;
296
297 LayoutRect detailTextLabelLayout =
298 LayoutRectMake(leadingPadding, CGRectGetWidth(self.view.bounds),
299 topPadding, labelWidth, labelHeight);
300 detailTextLabel.frame = LayoutRectGetRect(detailTextLabelLayout);
301
302 // Details should be the URL (|match.contents|). For searches |match.contents|
303 // is the default search engine name, which for mobile we suppress.
304 NSString* detailText = ![self isSearchMatch:match.type]
305 ? base::SysUTF16ToNSString(match.contents)
306 : nil;
307 if (answerPresent) {
308 detailTextLabel.attributedText =
309 [self attributedStringWithAnswerLine:match.answer->second_line()];
310 } else {
311 const ACMatchClassifications* classifications =
312 ![self isSearchMatch:match.type] ? &match.contents_class : nil;
313 detailTextLabel.attributedText =
314 [self attributedStringWithString:detailText
315 classifications:classifications
316 smallFont:YES
317 color:SuggestionDetailTextColor()
318 dimColor:DimColor()];
319 }
320 [detailTextLabel setNeedsDisplay];
321
322 OmniboxPopupTruncatingLabel* textLabel = row.textTruncatingLabel;
323 LayoutRect textLabelLayout =
324 LayoutRectMake(kTextCellLeadingPadding, CGRectGetWidth(self.view.bounds),
325 0, labelWidth, kTextLabelHeight);
326 textLabel.frame = LayoutRectGetRect(textLabelLayout);
327
328 // Match should be search term (match.contents) for searches, otherwise
329 // page title (|match.description|).
330 base::string16 textString =
331 [self isSearchMatch:match.type] ? match.contents : match.description;
332 NSString* text = base::SysUTF16ToNSString(textString);
333 const ACMatchClassifications* textClassifications =
334 [self isSearchMatch:match.type] ? &match.contents_class
335 : &match.description_class;
336
337 // If for some reason the title is empty, copy the detailText.
338 if ([text length] == 0 && [detailText length] != 0) {
339 text = detailText;
340 }
341 // Center the textLabel if detailLabel is empty.
342 if (!answerPresent && [detailText length] == 0) {
343 textLabel.center = CGPointMake(textLabel.center.x, floor(kRowHeight / 2));
344 textLabel.frame = AlignRectToPixel(textLabel.frame);
345 } else {
346 CGRect frame = textLabel.frame;
347 frame.origin.y = kTextCellTopPadding;
348 textLabel.frame = frame;
349 }
350 textLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
351 UIColor* suggestionTextColor =
352 _incognito ? SuggestionTextColorIncognito() : SuggestionTextColor();
353 UIColor* dimColor = _incognito ? DimColorIncognito() : DimColor();
354 if (answerPresent) {
355 textLabel.attributedText =
356 [self attributedStringWithAnswerLine:match.answer->first_line()];
357 } else {
358 textLabel.attributedText =
359 [self attributedStringWithString:text
360 classifications:textClassifications
361 smallFont:NO
362 color:suggestionTextColor
363 dimColor:dimColor];
364 }
365 [textLabel setNeedsDisplay];
366
367 // The left image (e.g. magnifying glass, star, clock) is only shown on iPad.
368 if (IsIPadIdiom()) {
369 int imageId = GetIconForAutocompleteMatchType(
370 match.type, _popupView->IsStarredMatch(match), _incognito);
371 [row updateLeftImage:imageId];
372 }
373
374 // Show append button for search history/search suggestions/voice search as
375 // the right control element (aka an accessory element of a table view cell).
376 BOOL autocompleteSearchMatch =
377 match.type == AutocompleteMatchType::SEARCH_HISTORY ||
378 match.type == AutocompleteMatchType::SEARCH_SUGGEST;
379 row.appendButton.hidden = !autocompleteSearchMatch;
380 [row.appendButton cancelTrackingWithEvent:nil];
381
382 // Show the Physical Web logo as the right accessory image for Physical Web
383 // suggestions.
384 BOOL physicalWebMatch =
385 match.type == AutocompleteMatchType::PHYSICAL_WEB ||
386 match.type == AutocompleteMatchType::PHYSICAL_WEB_OVERFLOW;
387 row.physicalWebImageView.hidden = !physicalWebMatch;
388
389 // If a right accessory element is present or the text alignment is right
390 // aligned, adjust the width to align with the accessory element.
391 if (autocompleteSearchMatch || physicalWebMatch || alignmentRight) {
392 LayoutRect layout =
393 LayoutRectForRectInBoundingRect(textLabel.frame, self.view.frame);
394 layout.size.width -= kAppendButtonWidth;
395 textLabel.frame = LayoutRectGetRect(layout);
396 layout =
397 LayoutRectForRectInBoundingRect(detailTextLabel.frame, self.view.frame);
398 layout.size.width -=
399 kAppendButtonWidth + (answerImagePresent ? answerImagePadding : 0);
400 detailTextLabel.frame = LayoutRectGetRect(layout);
401 }
402
403 // Since it's a common use case to type in a left-to-right URL while the
404 // device is set to a native RTL language, make sure the left alignment looks
405 // good by anchoring the leading edge to the left.
406 if (LTRTextInRTLLayout) {
407 // This is really a left padding, not a leading padding.
408 const CGFloat kLTRTextInRTLLayoutLeftPadding =
409 IsIPadIdiom() ? (!IsCompactTablet() ? 176 : 94) : 94;
410 CGRect frame = textLabel.frame;
411 frame.size.width -= kLTRTextInRTLLayoutLeftPadding - frame.origin.x;
412 frame.origin.x = kLTRTextInRTLLayoutLeftPadding;
413 textLabel.frame = frame;
414
415 frame = detailTextLabel.frame;
416 frame.size.width -= kLTRTextInRTLLayoutLeftPadding - frame.origin.x;
417 frame.origin.x = kLTRTextInRTLLayoutLeftPadding;
418 detailTextLabel.frame = frame;
419 }
420 }
421
422 - (NSMutableAttributedString*)attributedStringWithAnswerLine:
423 (const SuggestionAnswer::ImageLine&)line {
424 NSMutableAttributedString* result =
425 [[[NSMutableAttributedString alloc] initWithString:@""] autorelease];
426
427 for (size_t i = 0; i < line.text_fields().size(); i++) {
428 const SuggestionAnswer::TextField& field = line.text_fields()[i];
429 [result
430 appendAttributedString:[self attributedStringWithString:field.text()
431 type:field.type()]];
432 }
433
434 base::scoped_nsobject<NSAttributedString> spacer(
435 [[NSAttributedString alloc] initWithString:@" "]);
436 if (line.additional_text() != nil) {
437 const SuggestionAnswer::TextField* field = line.additional_text();
438 [result appendAttributedString:spacer];
439 [result
440 appendAttributedString:[self attributedStringWithString:field->text()
441 type:field->type()]];
442 }
443
444 if (line.status_text() != nil) {
445 const SuggestionAnswer::TextField* field = line.status_text();
446 [result appendAttributedString:spacer];
447 [result
448 appendAttributedString:[self attributedStringWithString:field->text()
449 type:field->type()]];
450 }
451
452 return result;
453 }
454
455 - (NSAttributedString*)attributedStringWithString:(const base::string16&)string
456 type:(int)type {
457 NSDictionary* attributes = nil;
458
459 const id font = (id)kCTFontAttributeName;
460 NSString* foregroundColor = (NSString*)kCTForegroundColorAttributeName;
461 const id baselineOffset = (id)NSBaselineOffsetAttributeName;
462
463 // Answer types, sizes and colors specified at http://goto.google.com/ais_api.
464 switch (type) {
465 case SuggestionAnswer::TOP_ALIGNED:
466 attributes = @{
467 font : [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:12],
468 baselineOffset : @10.0f,
469 foregroundColor : (id)[UIColor grayColor].CGColor,
470 };
471 break;
472 case SuggestionAnswer::DESCRIPTION_POSITIVE:
473 attributes = @{
474 font : [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:16],
475 foregroundColor : (id)[UIColor colorWithRed:11 / 255.0
476 green:128 / 255.0
477 blue:67 / 255.0
478 alpha:1.0]
479 .CGColor,
480 };
481 break;
482 case SuggestionAnswer::DESCRIPTION_NEGATIVE:
483 attributes = @{
484 font : [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:16],
485 foregroundColor : (id)[UIColor colorWithRed:197 / 255.0
486 green:57 / 255.0
487 blue:41 / 255.0
488 alpha:1.0]
489 .CGColor,
490 };
491 break;
492 case SuggestionAnswer::PERSONALIZED_SUGGESTION:
493 attributes = @{
494 font : [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:16],
495 };
496 break;
497 case SuggestionAnswer::ANSWER_TEXT_MEDIUM:
498 attributes = @{
499 font : [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:20],
500 };
501 break;
502 case SuggestionAnswer::ANSWER_TEXT_LARGE:
503 attributes = @{
504 font : [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:24],
505 };
506 break;
507 case SuggestionAnswer::SUGGESTION_SECONDARY_TEXT_SMALL:
508 attributes = @{
509 font : [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:12],
510 foregroundColor : (id)[UIColor grayColor].CGColor,
511 };
512 break;
513 case SuggestionAnswer::SUGGESTION_SECONDARY_TEXT_MEDIUM:
514 attributes = @{
515 font : [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:14],
516 foregroundColor : (id)[UIColor grayColor].CGColor,
517 };
518 break;
519 case SuggestionAnswer::SUGGESTION:
520 // Fall through.
521 default:
522 attributes = @{
523 font : [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:16],
524 };
525 }
526
527 NSString* unescapedString =
528 base::SysUTF16ToNSString(net::UnescapeForHTML(string));
529 // TODO(jdonnelly): Remove this tag stripping once the JSON parsing class
530 // handles HTML tags.
531 unescapedString = [unescapedString stringByReplacingOccurrencesOfString:@"<b>"
532 withString:@""];
533 unescapedString =
534 [unescapedString stringByReplacingOccurrencesOfString:@"</b>"
535 withString:@""];
536
537 return [[[NSAttributedString alloc] initWithString:unescapedString
538 attributes:attributes] autorelease];
539 }
540
541 - (void)updateMatches:(const AutocompleteResult&)result
542 withAnimation:(BOOL)animation {
543 AutocompleteResult oldResults;
544 AutocompleteInput emptyInput;
545 oldResults.Swap(&_currentResult);
546 _currentResult.CopyOldMatches(emptyInput, result, nil);
547
548 [self layoutRows];
549
550 size_t size = _currentResult.size();
551 if (animation && size > 0) {
552 [self fadeInRows];
553 }
554 }
555
556 - (void)layoutRows {
557 size_t size = _currentResult.size();
558
559 [self.tableView reloadData];
560 [self.tableView beginUpdates];
561 for (size_t i = 0; i < kRowCount; i++) {
562 OmniboxPopupMaterialRow* row = _rows[i];
563
564 if (i < size) {
565 const AutocompleteMatch& match =
566 ((const AutocompleteResult&)_currentResult).match_at((NSUInteger)i);
567 [self updateRow:row withMatch:match];
568 row.hidden = NO;
569 } else {
570 row.hidden = YES;
571 }
572 }
573 [self.tableView endUpdates];
574
575 if (IsIPadIdiom())
576 [self updateContentInsetForKeyboard];
577 }
578
579 - (void)keyboardDidShow:(NSNotification*)notification {
580 NSDictionary* keyboardInfo = [notification userInfo];
581 NSValue* keyboardFrameValue =
582 [keyboardInfo valueForKey:UIKeyboardFrameEndUserInfoKey];
583 keyboardHeight_ = CurrentKeyboardHeight(keyboardFrameValue);
584 if (self.tableView.contentSize.height > 0)
585 [self updateContentInsetForKeyboard];
586 }
587
588 - (void)updateContentInsetForKeyboard {
589 UIView* toView =
590 [UIApplication sharedApplication].keyWindow.rootViewController.view;
591 CGRect absoluteRect =
592 [self.tableView convertRect:self.tableView.bounds toView:toView];
593 CGFloat screenHeight = CurrentScreenHeight();
594 CGFloat bottomInset = screenHeight - self.tableView.contentSize.height -
595 keyboardHeight_ - absoluteRect.origin.y -
596 kTopAndBottomPadding * 2;
597 bottomInset = MAX(kTopAndBottomPadding, -bottomInset);
598 self.tableView.contentInset =
599 UIEdgeInsetsMake(kTopAndBottomPadding, 0, bottomInset, 0);
600 self.tableView.scrollIndicatorInsets = self.tableView.contentInset;
601 }
602
603 - (void)fadeInRows {
604 [CATransaction begin];
605 [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction
606 functionWithControlPoints:
607 0:
608 0:
609 0.2:1]];
610 for (size_t i = 0; i < kRowCount; i++) {
611 OmniboxPopupMaterialRow* row = _rows[i];
612 CGFloat beginTime = (i + 1) * .05;
613 CABasicAnimation* transformAnimation =
614 [CABasicAnimation animationWithKeyPath:@"transform"];
615 [transformAnimation
616 setFromValue:[NSValue
617 valueWithCATransform3D:CATransform3DMakeTranslation(
618 0, -20, 0)]];
619 [transformAnimation
620 setToValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]];
621 [transformAnimation setDuration:0.5];
622 [transformAnimation setBeginTime:beginTime];
623
624 CAAnimation* fadeAnimation = OpacityAnimationMake(0, 1);
625 [fadeAnimation setDuration:0.5];
626 [fadeAnimation setBeginTime:beginTime];
627
628 [[row layer]
629 addAnimation:AnimationGroupMake(@[ transformAnimation, fadeAnimation ])
630 forKey:@"animateIn"];
631 }
632 [CATransaction commit];
633 }
634
635 #pragma mark -
636 #pragma mark Action for append UIButton
637
638 - (void)appendButtonTapped:(id)sender {
639 NSUInteger row = [sender tag];
640 const AutocompleteMatch& match =
641 ((const AutocompleteResult&)_currentResult).match_at(row);
642 // Make a defensive copy of |match.contents|, as CopyToOmnibox() will trigger
643 // a new round of autocomplete and modify |_currentResult|.
644 base::string16 contents(match.contents);
645 _popupView->CopyToOmnibox(contents);
646 }
647
648 #pragma mark -
649 #pragma mark UIScrollViewDelegate
650
651 - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
652 // Setting the top inset of the scrollView to |kTopAndBottomPadding| causes a
653 // one time scrollViewDidScroll to |-kTopAndBottomPadding|. It's easier to
654 // just ignore this one scroll tick.
655 if (scrollView.contentOffset.y == 0 - kTopAndBottomPadding)
656 return;
657
658 _popupView->DidScroll();
659 for (OmniboxPopupMaterialRow* row in _rows.get()) {
660 row.highlighted = NO;
661 }
662 }
663
664 // Set text alignment for popup cells.
665 - (void)setTextAlignment:(NSTextAlignment)alignment {
666 _alignment = alignment;
667 }
668
669 - (BOOL)isSearchMatch:(const AutocompleteMatch::Type&)type {
670 return (type == AutocompleteMatchType::NAVSUGGEST ||
671 type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
672 type == AutocompleteMatchType::SEARCH_HISTORY ||
673 type == AutocompleteMatchType::SEARCH_SUGGEST ||
674 type == AutocompleteMatchType::SEARCH_OTHER_ENGINE);
675 }
676
677 - (NSMutableAttributedString*)
678 attributedStringWithString:(NSString*)text
679 classifications:(const ACMatchClassifications*)classifications
680 smallFont:(BOOL)smallFont
681 color:(UIColor*)defaultColor
682 dimColor:(UIColor*)dimColor {
683 if (text == nil)
684 return nil;
685
686 CTFontRef fontRef = smallFont ? _smallFont : _bigFont;
687
688 NSMutableAttributedString* as =
689 [[[NSMutableAttributedString alloc] initWithString:text] autorelease];
690
691 // Set the base attributes to the default font and color.
692 NSDictionary* dict = [NSDictionary
693 dictionaryWithObjectsAndKeys:(id)fontRef, (NSString*)kCTFontAttributeName,
694 defaultColor.CGColor,
695 (NSString*)kCTForegroundColorAttributeName,
696 nil];
697 [as addAttributes:dict range:NSMakeRange(0, [text length])];
698
699 if (classifications != NULL) {
700 CTFontRef boldFontRef = smallFont ? _smallBoldFont : _bigBoldFont;
701
702 for (ACMatchClassifications::const_iterator i = classifications->begin();
703 i != classifications->end(); ++i) {
704 const BOOL isLast = (i + 1) == classifications->end();
705 const size_t nextOffset = (isLast ? [text length] : (i + 1)->offset);
706 const NSInteger location = static_cast<NSInteger>(i->offset);
707 const NSInteger length = static_cast<NSInteger>(nextOffset - i->offset);
708 // Guard against bad, off-the-end classification ranges due to
709 // crbug.com/121703 and crbug.com/131370.
710 if (i->offset + length > [text length] || length <= 0)
711 break;
712 const NSRange range = NSMakeRange(location, length);
713 if (0 != (i->style & ACMatchClassification::MATCH)) {
714 [as addAttribute:(id)kCTFontAttributeName
715 value:(id)boldFontRef
716 range:range];
717 }
718
719 if (0 != (i->style & ACMatchClassification::DIM)) {
720 [as addAttribute:(id)kCTForegroundColorAttributeName
721 value:(id)dimColor.CGColor
722 range:range];
723 }
724 }
725 }
726 return as;
727 }
728
729 #pragma mark -
730 #pragma mark Table view delegate
731
732 - (void)tableView:(UITableView*)tableView
733 didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
734 DCHECK_EQ(0U, (NSUInteger)indexPath.section);
735 DCHECK_LT((NSUInteger)indexPath.row, _currentResult.size());
736 NSUInteger row = indexPath.row;
737 _popupView->OpenURLForRow(row);
738 }
739
740 #pragma mark -
741 #pragma mark Table view data source
742
743 - (CGFloat)tableView:(UITableView*)tableView
744 heightForRowAtIndexPath:(NSIndexPath*)indexPath {
745 DCHECK_EQ(0U, (NSUInteger)indexPath.section);
746 DCHECK_LT((NSUInteger)indexPath.row, _currentResult.size());
747 return ((OmniboxPopupMaterialRow*)(_rows[indexPath.row])).rowHeight;
748 }
749
750 - (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
751 return 1;
752 }
753
754 - (NSInteger)tableView:(UITableView*)tableView
755 numberOfRowsInSection:(NSInteger)section {
756 DCHECK_EQ(0, section);
757 return _currentResult.size();
758 }
759
760 // Customize the appearance of table view cells.
761 - (UITableViewCell*)tableView:(UITableView*)tableView
762 cellForRowAtIndexPath:(NSIndexPath*)indexPath {
763 DCHECK_EQ(0U, (NSUInteger)indexPath.section);
764 DCHECK_LT((NSUInteger)indexPath.row, _currentResult.size());
765 return _rows[indexPath.row];
766 }
767
768 - (BOOL)tableView:(UITableView*)tableView
769 canEditRowAtIndexPath:(NSIndexPath*)indexPath {
770 DCHECK_EQ(0U, (NSUInteger)indexPath.section);
771 // iOS doesn't check -numberOfRowsInSection before checking
772 // -canEditRowAtIndexPath in a reload call. If |indexPath.row| is too large,
773 // simple return |NO|.
774 if ((NSUInteger)indexPath.row >= _currentResult.size())
775 return NO;
776
777 const AutocompleteMatch& match =
778 ((const AutocompleteResult&)_currentResult).match_at(indexPath.row);
779 return match.SupportsDeletion();
780 }
781
782 - (void)tableView:(UITableView*)tableView
783 commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
784 forRowAtIndexPath:(NSIndexPath*)indexPath {
785 DCHECK_EQ(0U, (NSUInteger)indexPath.section);
786 DCHECK_LT((NSUInteger)indexPath.row, _currentResult.size());
787 if (editingStyle == UITableViewCellEditingStyleDelete) {
788 // The delete button never disappears if you don't call this after a tap.
789 // It doesn't seem to be required anywhere else.
790 [_rows[indexPath.row] prepareForReuse];
791 const AutocompleteMatch& match =
792 ((const AutocompleteResult&)_currentResult).match_at(indexPath.row);
793 _popupView->DeleteMatch(match);
794 }
795 }
796
797 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698