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

Side by Side Diff: chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm

Issue 1099403005: [AiS] changing mac omnibox suggestions form NSMatrix to NSTableView (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Review changes Created 5 years, 7 months 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
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h" 5 #import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <cmath> 8 #include <cmath>
9 9
10 #include "base/i18n/rtl.h" 10 #include "base/i18n/rtl.h"
(...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after
148 value:DimTextColor() 148 value:DimTextColor()
149 range:range]; 149 range:range];
150 } 150 }
151 } 151 }
152 152
153 return as; 153 return as;
154 } 154 }
155 155
156 } // namespace 156 } // namespace
157 157
158 @implementation OmniboxPopupCell 158 @implementation OmniboxPopupCellData
159 159
160 - (id)init { 160 @synthesize rect = rect_;
161
162 - (id)initWithMatch:(const AutocompleteMatch&)match {
161 self = [super init]; 163 self = [super init];
162 if (self) { 164 if (!self)
163 [self setImagePosition:NSImageLeft]; 165 return self;
Scott Hess - ex-Googler 2015/05/15 00:09:38 Ack! Chromium style conflicts with my years of Ob
groby-ooo-7-16 2015/05/15 16:53:56 This is really rubbing all ObjC senses the wrong w
dschuyler 2015/05/15 21:52:04 So I'm kinda guessing what may be so offensive. A
Scott Hess - ex-Googler 2015/05/15 22:02:07 Ha, no, there's this whole genre of lore that has
groby-ooo-7-16 2015/05/15 22:41:33 Yup, what Scott said. What you do in init is a rel
dschuyler 2015/05/18 23:38:44 Acknowledged.
dschuyler 2015/05/18 23:38:44 Acknowledged.
164 [self setBordered:NO];
165 [self setButtonType:NSRadioButton];
166 166
167 // Without this highlighting messes up white areas of images. 167 const base::string16& raw_separator =
168 [self setHighlightsBy:NSNoCellMask]; 168 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR);
169 separator_.reset(
170 [CreateAttributedString(raw_separator, DimTextColor()) retain]);
Scott Hess - ex-Googler 2015/05/15 00:09:38 Note that scoped_nsobject<> implements operator= t
dschuyler 2015/05/15 21:52:04 Acknowledged.
169 171
170 const base::string16& raw_separator = 172 isContentsRTL_ =
171 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); 173 (base::i18n::RIGHT_TO_LEFT ==
172 separator_.reset( 174 base::i18n::GetFirstStrongCharacterDirection(match.contents));
173 [CreateAttributedString(raw_separator, DimTextColor()) retain]); 175 matchType_ = match.type;
176
177 // Prefix may not have any characters with strong directionality, and may take
178 // the UI directionality. But prefix needs to appear in continuation of the
179 // contents so we force the directionality.
180 NSTextAlignment textAlignment =
181 isContentsRTL_ ? NSRightTextAlignment : NSLeftTextAlignment;
182 prefix_.reset(
183 [CreateAttributedString(base::UTF8ToUTF16(match.GetAdditionalInfo(
184 kACMatchPropertyContentsPrefix)),
185 ContentTextColor(), textAlignment) retain]);
186
187 contents_.reset([CreateClassifiedAttributedString(
188 match.contents, ContentTextColor(), match.contents_class) retain]);
189
190 if (match.answer) {
191 base::string16 answerString;
192 DCHECK(!match.answer->second_line().text_fields().empty());
193 for (const SuggestionAnswer::TextField& textField :
194 match.answer->second_line().text_fields())
195 answerString += textField.text();
196 const base::char16 space(' ');
197 const SuggestionAnswer::TextField* textField =
198 match.answer->second_line().additional_text();
199 if (textField)
200 answerString += space + textField->text();
Scott Hess - ex-Googler 2015/05/15 00:09:38 I find my brain really questioning this. I'd gues
dschuyler 2015/05/15 21:52:04 Done.
201 textField = match.answer->second_line().status_text();
202 if (textField)
203 answerString += space + textField->text();
204 description_.reset([CreateClassifiedAttributedString(
205 answerString, DimTextColor(), match.description_class) retain]);
206 } else if (match.description.empty()) {
207 description_.reset();
208 } else {
209 description_.reset([CreateClassifiedAttributedString(
210 match.description, DimTextColor(), match.description_class) retain]);
174 } 211 }
175 return self; 212 return self;
176 } 213 }
177 214
178 - (void)setMatch:(const AutocompleteMatch&)match { 215 - (void)setContents:(NSAttributedString*)contents {
179 match_ = match; 216 contents_.reset([contents retain]);
180 NSAttributedString *contents = CreateClassifiedAttributedString( 217 }
181 match_.contents, ContentTextColor(), match_.contents_class);
182 [self setAttributedTitle:contents];
183 218
184 if (match_.answer) { 219 - (void)setImage:(NSImage*)image {
185 base::string16 answerString; 220 image_.reset([image retain]);
186 DCHECK(!match_.answer->second_line().text_fields().empty());
187 for (const SuggestionAnswer::TextField& textField :
188 match_.answer->second_line().text_fields())
189 answerString += textField.text();
190 const base::char16 space(' ');
191 const SuggestionAnswer::TextField* textField =
192 match_.answer->second_line().additional_text();
193 if (textField)
194 answerString += space + textField->text();
195 textField = match_.answer->second_line().status_text();
196 if (textField)
197 answerString += space + textField->text();
198 description_.reset([CreateClassifiedAttributedString(
199 answerString, DimTextColor(), match_.description_class) retain]);
200 } else if (match_.description.empty()) {
201 description_.reset();
202 } else {
203 description_.reset([CreateClassifiedAttributedString(
204 match_.description, DimTextColor(), match_.description_class) retain]);
205 }
206 } 221 }
207 222
208 - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { 223 - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth {
209 maxMatchContentsWidth_ = maxMatchContentsWidth; 224 maxMatchContentsWidth_ = maxMatchContentsWidth;
210 } 225 }
211 226
212 - (void)setContentsOffset:(CGFloat)contentsOffset { 227 - (void)setContentsOffset:(CGFloat)contentsOffset {
213 contentsOffset_ = contentsOffset; 228 contentsOffset_ = contentsOffset;
214 } 229 }
215 230
216 // The default NSButtonCell drawing leaves the image flush left and 231 - (CGFloat)getMatchContentsWidth {
217 // the title next to the image. This spaces things out to line up 232 return contents_ ? [contents_ size].width : 0;
218 // with the star button and autocomplete field. 233 }
219 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { 234
220 if ([self state] == NSOnState || [self isHighlighted]) { 235 - (id)copyWithZone:(NSZone*)zone {
221 if ([self state] == NSOnState) 236 // Note: Before changing this code, please check with groby, shess, or
222 [SelectedBackgroundColor() set]; 237 // dschuyler about why it's written with swaps and local data.
Scott Hess - ex-Googler 2015/05/15 00:09:38 Consult with Rachel, first, but ... in this case,
groby-ooo-7-16 2015/05/15 16:53:56 Rachel is fully supportive of that argument :) T
Scott Hess - ex-Googler 2015/05/15 17:28:59 operator= already implies retain, so either copy->
dschuyler 2015/05/15 21:52:04 Done.
223 else 238 base::scoped_nsobject<NSAttributedString> contents;
224 [HoveredBackgroundColor() set]; 239 base::scoped_nsobject<NSAttributedString> separator;
225 NSBezierPath* path = 240 base::scoped_nsobject<NSAttributedString> description;
226 [NSBezierPath bezierPathWithRoundedRect:cellFrame 241 base::scoped_nsobject<NSAttributedString> prefix;
227 xRadius:kCellRoundingRadius 242 base::scoped_nsobject<NSImage> image;
228 yRadius:kCellRoundingRadius]; 243 contents_.swap(contents);
229 [path fill]; 244 separator_.swap(separator);
230 } 245 description_.swap(description);
246 prefix_.swap(prefix);
247 image_.swap(image);
248
249 OmniboxPopupCellData* copy = [[OmniboxPopupCellData alloc] init];
250 copy->contents_.reset(contents);
251 copy->separator_.reset(separator);
252 copy->description_.reset(description);
253 copy->prefix_.reset(prefix);
254 copy->image_.reset(image);
255 copy->maxMatchContentsWidth_ = maxMatchContentsWidth_;
256 copy->contentsOffset_ = contentsOffset_;
257 copy->isContentsRTL_ = isContentsRTL_;
258 copy->matchType_ = matchType_;
259 copy->rect_ = rect_;
260
261 contents_.swap(contents);
262 separator_.swap(separator);
263 description_.swap(description);
264 prefix_.swap(prefix);
265 image_.swap(image);
266 return copy;
267 }
268
269 - (void)drawMatchWithFrame:(NSRect)cellFrame
270 inCell:(OmniboxPopupCell*)cell
271 inView:(NSView*)controlView {
272 CGFloat remainingWidth = GetContentAreaWidth(cellFrame);
273 CGFloat contentsWidth = [self getMatchContentsWidth];
274 CGFloat separatorWidth = [separator_ size].width;
275 CGFloat descriptionWidth = description_ ? [description_ size].width : 0;
276 int contentsMaxWidth, descriptionMaxWidth;
277 OmniboxPopupModel::ComputeMatchMaxWidths(
278 ceilf(contentsWidth), ceilf(separatorWidth), ceilf(descriptionWidth),
279 ceilf(remainingWidth), !AutocompleteMatch::IsSearchType(matchType_),
280 &contentsMaxWidth, &descriptionMaxWidth);
231 281
232 // Put the image centered vertically but in a fixed column. 282 // Put the image centered vertically but in a fixed column.
233 NSImage* image = [self image]; 283 if (image_) {
234 if (image) {
235 NSRect imageRect = cellFrame; 284 NSRect imageRect = cellFrame;
236 imageRect.size = [image size]; 285 imageRect.size = [image_ size];
237 imageRect.origin.y += 286 imageRect.origin.y +=
238 std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); 287 std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0);
239 imageRect.origin.x += kImageXOffset; 288 imageRect.origin.x += kImageXOffset;
240 [image drawInRect:FlipIfRTL(imageRect, cellFrame) 289 [image_ drawInRect:FlipIfRTL(imageRect, cellFrame)
241 fromRect:NSZeroRect // Entire image 290 fromRect:NSZeroRect
242 operation:NSCompositeSourceOver 291 operation:NSCompositeSourceOver
243 fraction:1.0 292 fraction:1.0
244 respectFlipped:YES 293 respectFlipped:YES
245 hints:nil]; 294 hints:nil];
246 } 295 }
247 296
248 [self drawMatchWithFrame:cellFrame inView:controlView];
249 }
250
251 - (void)drawMatchWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
252 NSAttributedString* contents = [self attributedTitle];
253
254 CGFloat remainingWidth = GetContentAreaWidth(cellFrame);
255 CGFloat contentsWidth = [self getMatchContentsWidth];
256 CGFloat separatorWidth = [separator_ size].width;
257 CGFloat descriptionWidth = description_.get() ? [description_ size].width : 0;
258 int contentsMaxWidth, descriptionMaxWidth;
259 OmniboxPopupModel::ComputeMatchMaxWidths(
260 ceilf(contentsWidth),
261 ceilf(separatorWidth),
262 ceilf(descriptionWidth),
263 ceilf(remainingWidth),
264 !AutocompleteMatch::IsSearchType(match_.type),
265 &contentsMaxWidth,
266 &descriptionMaxWidth);
267
268 CGFloat offset = kTextStartOffset; 297 CGFloat offset = kTextStartOffset;
269 if (match_.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { 298 if (matchType_ == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) {
270 // Infinite suggestions are rendered with a prefix (usually ellipsis), which 299 // Infinite suggestions are rendered with a prefix (usually ellipsis), which
271 // appear vertically stacked. 300 // appear vertically stacked.
272 offset += [self drawMatchPrefixWithFrame:cellFrame 301 offset += [self drawMatchPrefixWithFrame:cellFrame
302 inCell:cell
273 inView:controlView 303 inView:controlView
274 withContentsMaxWidth:&contentsMaxWidth]; 304 withContentsMaxWidth:&contentsMaxWidth];
275 } 305 }
276 offset += [self drawMatchPart:contents 306 offset += [cell drawMatchPart:contents_
277 withFrame:cellFrame 307 withFrame:cellFrame
278 atOffset:offset 308 atOffset:offset
279 withMaxWidth:contentsMaxWidth 309 withMaxWidth:contentsMaxWidth
280 inView:controlView]; 310 inView:controlView];
281 311
282 if (descriptionMaxWidth != 0) { 312 if (descriptionMaxWidth != 0) {
283 offset += [self drawMatchPart:separator_ 313 offset += [cell drawMatchPart:separator_
284 withFrame:cellFrame 314 withFrame:cellFrame
285 atOffset:offset 315 atOffset:offset
286 withMaxWidth:separatorWidth 316 withMaxWidth:separatorWidth
287 inView:controlView]; 317 inView:controlView];
288 offset += [self drawMatchPart:description_ 318 offset += [cell drawMatchPart:description_
289 withFrame:cellFrame 319 withFrame:cellFrame
290 atOffset:offset 320 atOffset:offset
291 withMaxWidth:descriptionMaxWidth 321 withMaxWidth:descriptionMaxWidth
292 inView:controlView]; 322 inView:controlView];
293 } 323 }
294 } 324 }
295 325
296 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame 326 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame
327 inCell:(OmniboxPopupCell*)cell
297 inView:(NSView*)controlView 328 inView:(NSView*)controlView
298 withContentsMaxWidth:(int*)contentsMaxWidth { 329 withContentsMaxWidth:(int*)contentsMaxWidth {
299 CGFloat offset = 0.0f; 330 CGFloat offset = 0.0f;
300 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); 331 CGFloat remainingWidth = GetContentAreaWidth(cellFrame);
301 bool isRTL = base::i18n::IsRTL(); 332 bool isRTL = base::i18n::IsRTL();
302 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT ==
303 base::i18n::GetFirstStrongCharacterDirection(match_.contents));
304 // Prefix may not have any characters with strong directionality, and may take
305 // the UI directionality. But prefix needs to appear in continuation of the
306 // contents so we force the directionality.
307 NSTextAlignment textAlignment = isContentsRTL ?
308 NSRightTextAlignment : NSLeftTextAlignment;
309 prefix_.reset([CreateAttributedString(base::UTF8ToUTF16(
310 match_.GetAdditionalInfo(kACMatchPropertyContentsPrefix)),
311 ContentTextColor(), textAlignment) retain]);
312 CGFloat prefixWidth = [prefix_ size].width; 333 CGFloat prefixWidth = [prefix_ size].width;
313 334
314 CGFloat prefixOffset = 0.0f; 335 CGFloat prefixOffset = 0.0f;
315 if (isRTL != isContentsRTL) { 336 if (isRTL != isContentsRTL_) {
316 // The contents is rendered between the contents offset extending towards 337 // The contents is rendered between the contents offset extending towards
317 // the start edge, while prefix is rendered in opposite direction. Ideally 338 // the start edge, while prefix is rendered in opposite direction. Ideally
318 // the prefix should be rendered at |contentsOffset_|. If that is not 339 // the prefix should be rendered at |contentsOffset_|. If that is not
319 // sufficient to render the widest suggestion, we increase it to 340 // sufficient to render the widest suggestion, we increase it to
320 // |maxMatchContentsWidth_|. If |remainingWidth| is not sufficient to 341 // |maxMatchContentsWidth_|. If |remainingWidth| is not sufficient to
321 // accommodate that, we reduce the offset so that the prefix gets rendered. 342 // accommodate that, we reduce the offset so that the prefix gets rendered.
322 prefixOffset = std::min( 343 prefixOffset = std::min(
323 remainingWidth - prefixWidth, std::max(contentsOffset_, 344 remainingWidth - prefixWidth, std::max(contentsOffset_,
324 maxMatchContentsWidth_)); 345 maxMatchContentsWidth_));
325 offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth); 346 offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth);
326 } else { // The direction of contents is same as UI direction. 347 } else { // The direction of contents is same as UI direction.
327 // Ideally the offset should be |contentsOffset_|. If the max total width 348 // Ideally the offset should be |contentsOffset_|. If the max total width
328 // (|prefixWidth| + |maxMatchContentsWidth_|) from offset will exceed the 349 // (|prefixWidth| + |maxMatchContentsWidth_|) from offset will exceed the
329 // |remainingWidth|, then we shift the offset to the left , so that all 350 // |remainingWidth|, then we shift the offset to the left , so that all
330 // postfix suggestions are visible. 351 // postfix suggestions are visible.
331 // We have to render the prefix, so offset has to be at least |prefixWidth|. 352 // We have to render the prefix, so offset has to be at least |prefixWidth|.
332 offset = std::max(prefixWidth, 353 offset = std::max(
354 prefixWidth,
333 std::min(remainingWidth - maxMatchContentsWidth_, contentsOffset_)); 355 std::min(remainingWidth - maxMatchContentsWidth_, contentsOffset_));
334 prefixOffset = offset - prefixWidth; 356 prefixOffset = offset - prefixWidth;
335 } 357 }
336 *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth), 358 *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth),
Scott Hess - ex-Googler 2015/05/15 17:28:59 Oooh, maybe give this code a once-over for inappro
dschuyler 2015/05/15 21:52:04 May we do a separate CL for this?
Scott Hess - ex-Googler 2015/05/15 22:02:07 If you prefer.
dschuyler 2015/05/18 23:38:44 I made bug 489478 to track this.
337 *contentsMaxWidth); 359 *contentsMaxWidth);
338 [self drawMatchPart:prefix_ 360 [cell drawMatchPart:prefix_
339 withFrame:cellFrame 361 withFrame:cellFrame
340 atOffset:prefixOffset + kTextStartOffset 362 atOffset:prefixOffset + kTextStartOffset
341 withMaxWidth:prefixWidth 363 withMaxWidth:prefixWidth
342 inView:controlView]; 364 inView:controlView];
343 return offset; 365 return offset;
344 } 366 }
345 367
346 - (CGFloat)drawMatchPart:(NSAttributedString*)as 368 @end
369
370 @implementation OmniboxPopupCell
371
372 - (void)setCellData:(OmniboxPopupCellData*)cellData {
373 cellData_.reset([cellData retain]);
374 }
375
376 - (id)copyWithZone:(NSZone*)zone {
377 // Note: Before changing this code, please check with groby, shess, or
378 // dschuyler about why it's written with swaps and local data.
Scott Hess - ex-Googler 2015/05/15 00:09:38 Now _this_ code has to keep the crazy.
dschuyler 2015/05/15 21:52:04 Acknowledged.
379 base::scoped_nsobject<OmniboxPopupCellData> cellData(nil);
Scott Hess - ex-Googler 2015/05/15 00:09:38 Though I think the (nil) can be disposed of. It's
dschuyler 2015/05/15 21:52:04 Done.
380 cellData_.swap(cellData);
Scott Hess - ex-Googler 2015/05/15 00:09:38 And I would say cellData.swap(cellData_), to make
dschuyler 2015/05/15 21:52:04 I wondered about that. One one hand there's seein
381 OmniboxPopupCell* copy = [super copyWithZone:zone];
382 copy->cellData_ = cellData;
383 cellData_.swap(cellData);
384 return copy;
385 }
386
387 // The default NSButtonCell drawing leaves the image flush left and
388 // the title next to the image. This spaces things out to line up
389 // with the star button and autocomplete field.
390 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
391 if ([self state] == NSOnState || [self isHighlighted]) {
392 if ([self state] == NSOnState)
393 [SelectedBackgroundColor() set];
394 else
395 [HoveredBackgroundColor() set];
396 NSBezierPath* path =
397 [NSBezierPath bezierPathWithRoundedRect:cellFrame
398 xRadius:kCellRoundingRadius
399 yRadius:kCellRoundingRadius];
400 [path fill];
401 }
402
403 [cellData_ drawMatchWithFrame:cellFrame inCell:self inView:controlView];
404 }
405
406 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString
347 withFrame:(NSRect)cellFrame 407 withFrame:(NSRect)cellFrame
348 atOffset:(CGFloat)offset 408 atOffset:(CGFloat)offset
349 withMaxWidth:(int)maxWidth 409 withMaxWidth:(int)maxWidth
350 inView:(NSView*)controlView { 410 inView:(NSView*)controlView {
351 if (offset > NSWidth(cellFrame)) 411 if (offset > NSWidth(cellFrame))
352 return 0.0f; 412 return 0.0f;
353 NSRect renderRect = ShiftRect(cellFrame, offset); 413 NSRect renderRect = ShiftRect(cellFrame, offset);
354 renderRect.size.width = 414 renderRect.size.width =
355 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); 415 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth));
356 if (renderRect.size.width != 0) { 416 NSRect textRect =
357 [self drawTitle:as 417 [attributedString boundingRectWithSize:renderRect.size options:nil];
358 withFrame:FlipIfRTL(renderRect, cellFrame) 418 renderRect.origin.y +=
359 inView:controlView]; 419 std::floor((NSHeight(cellFrame) - NSHeight(textRect)) / 2.0);
360 } 420 if (renderRect.size.width != 0)
421 [attributedString drawInRect:FlipIfRTL(renderRect, cellFrame)];
361 return NSWidth(renderRect); 422 return NSWidth(renderRect);
362 } 423 }
363 424
364 - (CGFloat)getMatchContentsWidth {
365 NSAttributedString* contents = [self attributedTitle];
366 return contents ? [contents size].width : 0;
367 }
368
369
370 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { 425 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match {
371 const base::string16& inputText = base::UTF8ToUTF16( 426 const base::string16& inputText = base::UTF8ToUTF16(
372 match.GetAdditionalInfo(kACMatchPropertyInputText)); 427 match.GetAdditionalInfo(kACMatchPropertyInputText));
373 int contentsStartIndex = 0; 428 int contentsStartIndex = 0;
374 base::StringToInt( 429 base::StringToInt(
375 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), 430 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex),
376 &contentsStartIndex); 431 &contentsStartIndex);
377 // Ignore invalid state. 432 // Ignore invalid state.
378 if (!StartsWith(match.fill_into_edit, inputText, true) 433 if (!StartsWith(match.fill_into_edit, inputText, true)
379 || !EndsWith(match.fill_into_edit, match.contents, true) 434 || !EndsWith(match.fill_into_edit, match.contents, true)
380 || ((size_t)contentsStartIndex >= inputText.length())) { 435 || ((size_t)contentsStartIndex >= inputText.length())) {
381 return 0; 436 return 0;
382 } 437 }
383 bool isRTL = base::i18n::IsRTL(); 438 bool isRTL = base::i18n::IsRTL();
384 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == 439 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT ==
385 base::i18n::GetFirstStrongCharacterDirection(match.contents)); 440 base::i18n::GetFirstStrongCharacterDirection(match.contents));
386 441
387 // Color does not matter. 442 // Color does not matter.
388 NSAttributedString* as = CreateAttributedString(inputText, DimTextColor()); 443 NSAttributedString* attributedString =
389 base::scoped_nsobject<NSTextStorage> textStorage([[NSTextStorage alloc] 444 CreateAttributedString(inputText, DimTextColor());
390 initWithAttributedString:as]); 445 base::scoped_nsobject<NSTextStorage> textStorage(
446 [[NSTextStorage alloc] initWithAttributedString:attributedString]);
391 base::scoped_nsobject<NSLayoutManager> layoutManager( 447 base::scoped_nsobject<NSLayoutManager> layoutManager(
392 [[NSLayoutManager alloc] init]); 448 [[NSLayoutManager alloc] init]);
393 base::scoped_nsobject<NSTextContainer> textContainer( 449 base::scoped_nsobject<NSTextContainer> textContainer(
394 [[NSTextContainer alloc] init]); 450 [[NSTextContainer alloc] init]);
395 [layoutManager addTextContainer:textContainer]; 451 [layoutManager addTextContainer:textContainer];
396 [textStorage addLayoutManager:layoutManager]; 452 [textStorage addLayoutManager:layoutManager];
397 453
398 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); 454 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex);
399 NSUInteger glyphIndex = 455 NSUInteger glyphIndex =
400 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; 456 [layoutManager glyphIndexForCharacterAtIndex:charIndex];
401 457
402 // This offset is computed from the left edge of the glyph always from the 458 // This offset is computed from the left edge of the glyph always from the
403 // left edge of the string, irrespective of the directionality of UI or text. 459 // left edge of the string, irrespective of the directionality of UI or text.
404 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; 460 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x;
405 461
406 CGFloat inputWidth = [as size].width; 462 CGFloat inputWidth = [attributedString size].width;
407 463
408 // The offset obtained above may need to be corrected because the left-most 464 // The offset obtained above may need to be corrected because the left-most
409 // glyph may not have 0 offset. So we find the offset of left-most glyph, and 465 // glyph may not have 0 offset. So we find the offset of left-most glyph, and
410 // subtract it from the offset of the glyph we obtained above. 466 // subtract it from the offset of the glyph we obtained above.
411 CGFloat minOffset = glyphOffset; 467 CGFloat minOffset = glyphOffset;
412 468
413 // If content is RTL, we are interested in the right-edge of the glyph. 469 // If content is RTL, we are interested in the right-edge of the glyph.
414 // Unfortunately the bounding rect computation methods from NSLayoutManager or 470 // Unfortunately the bounding rect computation methods from NSLayoutManager or
415 // NSFont don't work correctly with bidirectional text. So we compute the 471 // NSFont don't work correctly with bidirectional text. So we compute the
416 // glyph width by finding the closest glyph offset to the right of the glyph 472 // glyph width by finding the closest glyph offset to the right of the glyph
417 // we are looking for. 473 // we are looking for.
418 CGFloat glyphWidth = inputWidth; 474 CGFloat glyphWidth = inputWidth;
419 475
420 for (NSUInteger i = 0; i < [as length]; i++) { 476 for (NSUInteger i = 0; i < [attributedString length]; i++) {
421 if (i == charIndex) continue; 477 if (i == charIndex) continue;
422 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; 478 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i];
423 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; 479 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x;
424 minOffset = std::min(minOffset, offset); 480 minOffset = std::min(minOffset, offset);
425 if (offset > glyphOffset) 481 if (offset > glyphOffset)
426 glyphWidth = std::min(glyphWidth, offset - glyphOffset); 482 glyphWidth = std::min(glyphWidth, offset - glyphOffset);
427 } 483 }
428 glyphOffset -= minOffset; 484 glyphOffset -= minOffset;
429 if (glyphWidth == 0) 485 if (glyphWidth == 0)
430 glyphWidth = inputWidth - glyphOffset; 486 glyphWidth = inputWidth - glyphOffset;
431 if (isContentsRTL) 487 if (isContentsRTL)
432 glyphOffset += glyphWidth; 488 glyphOffset += glyphWidth;
433 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; 489 return isRTL ? (inputWidth - glyphOffset) : glyphOffset;
434 } 490 }
435 491
436 @end 492 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698