OLD | NEW |
---|---|
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 Loading... | |
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 |
OLD | NEW |