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" |
11 #include "base/mac/scoped_nsobject.h" | 11 #include "base/mac/scoped_nsobject.h" |
12 #include "base/strings/string_number_conversions.h" | 12 #include "base/strings/string_number_conversions.h" |
13 #include "base/strings/string_util.h" | 13 #include "base/strings/string_util.h" |
14 #include "base/strings/sys_string_conversions.h" | 14 #include "base/strings/sys_string_conversions.h" |
15 #include "base/strings/utf_string_conversions.h" | 15 #include "base/strings/utf_string_conversions.h" |
16 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h" | 16 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h" |
17 #include "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h" | 17 #include "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h" |
18 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h" | 18 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h" |
19 #include "chrome/grit/generated_resources.h" | 19 #include "chrome/grit/generated_resources.h" |
20 #include "components/omnibox/suggestion_answer.h" | 20 #include "components/omnibox/suggestion_answer.h" |
21 #include "ui/base/l10n/l10n_util.h" | 21 #include "ui/base/l10n/l10n_util.h" |
22 #include "ui/gfx/font.h" | 22 #include "ui/gfx/font.h" |
23 | 23 |
24 namespace { | 24 namespace { |
25 | 25 |
26 // How much to adjust the cell sizing up from the default determined | |
27 // by the font. | |
28 const CGFloat kCellHeightAdjust = 6.0; | |
29 | |
26 // How far to offset image column from the left. | 30 // How far to offset image column from the left. |
27 const CGFloat kImageXOffset = 5.0; | 31 const CGFloat kImageXOffset = 5.0; |
28 | 32 |
29 // How far to offset the text column from the left. | 33 // How far to offset the text column from the left. |
30 const CGFloat kTextStartOffset = 28.0; | 34 const CGFloat kTextStartOffset = 28.0; |
31 | 35 |
32 // Rounding radius of selection and hover background on popup items. | 36 // Rounding radius of selection and hover background on popup items. |
33 const CGFloat kCellRoundingRadius = 2.0; | 37 const CGFloat kCellRoundingRadius = 2.0; |
34 | 38 |
35 // Flips the given |rect| in context of the given |frame|. | 39 // Flips the given |rect| in context of the given |frame|. |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
148 value:DimTextColor() | 152 value:DimTextColor() |
149 range:range]; | 153 range:range]; |
150 } | 154 } |
151 } | 155 } |
152 | 156 |
153 return as; | 157 return as; |
154 } | 158 } |
155 | 159 |
156 } // namespace | 160 } // namespace |
157 | 161 |
158 @implementation OmniboxPopupCell | 162 @interface OmniboxPopupCell () |
163 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString | |
164 withFrame:(NSRect)cellFrame | |
165 atOffset:(CGFloat)offset | |
166 withMaxWidth:(int)maxWidth | |
167 inView:(NSView*)controlView; | |
168 @end | |
159 | 169 |
160 - (id)init { | 170 @implementation OmniboxPopupCellData |
161 self = [super init]; | |
162 if (self) { | |
163 [self setImagePosition:NSImageLeft]; | |
164 [self setBordered:NO]; | |
165 [self setButtonType:NSRadioButton]; | |
166 | 171 |
167 // Without this highlighting messes up white areas of images. | 172 - (id)initWithMatch:(const AutocompleteMatch&)match image:(NSImage*)image { |
168 [self setHighlightsBy:NSNoCellMask]; | 173 if ((self = [super init])) { |
174 image_.reset([image retain]); | |
169 | 175 |
170 const base::string16& raw_separator = | 176 const base::string16& raw_separator = |
171 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); | 177 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); |
172 separator_.reset( | 178 separator_.reset( |
173 [CreateAttributedString(raw_separator, DimTextColor()) retain]); | 179 [CreateAttributedString(raw_separator, DimTextColor()) retain]); |
180 | |
181 isContentsRTL_ = | |
182 (base::i18n::RIGHT_TO_LEFT == | |
183 base::i18n::GetFirstStrongCharacterDirection(match.contents)); | |
184 matchType_ = match.type; | |
185 | |
186 // Prefix may not have any characters with strong directionality, and may | |
187 // take | |
188 // the UI directionality. But prefix needs to appear in continuation of the | |
189 // contents so we force the directionality. | |
Scott Hess - ex-Googler
2015/05/21 20:40:26
Reformat comment.
dschuyler
2015/05/26 18:40:20
Done.
| |
190 NSTextAlignment textAlignment = | |
191 isContentsRTL_ ? NSRightTextAlignment : NSLeftTextAlignment; | |
192 prefix_.reset( | |
193 [CreateAttributedString(base::UTF8ToUTF16(match.GetAdditionalInfo( | |
194 kACMatchPropertyContentsPrefix)), | |
195 ContentTextColor(), textAlignment) retain]); | |
196 | |
197 contents_.reset([CreateClassifiedAttributedString( | |
198 match.contents, ContentTextColor(), match.contents_class) retain]); | |
199 | |
200 if (match.answer) { | |
201 base::string16 answerString; | |
202 DCHECK(!match.answer->second_line().text_fields().empty()); | |
203 for (const SuggestionAnswer::TextField& textField : | |
204 match.answer->second_line().text_fields()) | |
205 answerString.append(textField.text()); | |
206 const base::string16 space(base::ASCIIToUTF16(" ")); | |
207 const SuggestionAnswer::TextField* textField = | |
208 match.answer->second_line().additional_text(); | |
209 if (textField) | |
210 answerString.append(space).append(textField->text()); | |
211 textField = match.answer->second_line().status_text(); | |
212 if (textField) | |
213 answerString.append(space).append(textField->text()); | |
214 description_.reset([CreateClassifiedAttributedString( | |
215 answerString, DimTextColor(), match.description_class) retain]); | |
216 } else if (match.description.empty()) { | |
217 description_.reset(); | |
218 } else { | |
219 description_.reset([CreateClassifiedAttributedString( | |
220 match.description, DimTextColor(), match.description_class) retain]); | |
221 } | |
174 } | 222 } |
175 return self; | 223 return self; |
176 } | 224 } |
177 | 225 |
178 - (void)setMatch:(const AutocompleteMatch&)match { | 226 - (void)setContents:(NSAttributedString*)contents { |
179 match_ = match; | 227 contents_.reset([contents retain]); |
180 NSAttributedString *contents = CreateClassifiedAttributedString( | 228 } |
181 match_.contents, ContentTextColor(), match_.contents_class); | |
182 [self setAttributedTitle:contents]; | |
183 | 229 |
184 if (match_.answer) { | 230 - (void)setImage:(NSImage*)image { |
185 base::string16 answerString; | 231 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 } | 232 } |
207 | 233 |
208 - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { | 234 - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { |
209 maxMatchContentsWidth_ = maxMatchContentsWidth; | 235 maxMatchContentsWidth_ = maxMatchContentsWidth; |
210 } | 236 } |
211 | 237 |
212 - (void)setContentsOffset:(CGFloat)contentsOffset { | 238 - (void)setContentsOffset:(CGFloat)contentsOffset { |
213 contentsOffset_ = contentsOffset; | 239 contentsOffset_ = contentsOffset; |
214 } | 240 } |
215 | 241 |
216 // The default NSButtonCell drawing leaves the image flush left and | 242 - (CGFloat)getMatchContentsWidth { |
217 // the title next to the image. This spaces things out to line up | 243 return [contents_ size].width; |
218 // with the star button and autocomplete field. | 244 } |
219 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { | 245 |
220 if ([self state] == NSOnState || [self isHighlighted]) { | 246 - (CGFloat)rowHeight { |
221 if ([self state] == NSOnState) | 247 return [image_ size].height + kCellHeightAdjust; |
Scott Hess - ex-Googler
2015/05/21 20:40:26
I realize that the current version doesn't have th
dschuyler
2015/05/26 18:40:20
Done.
| |
222 [SelectedBackgroundColor() set]; | 248 } |
223 else | 249 |
224 [HoveredBackgroundColor() set]; | 250 - (void)drawMatchWithFrame:(NSRect)cellFrame |
225 NSBezierPath* path = | 251 inCell:(OmniboxPopupCell*)cell |
226 [NSBezierPath bezierPathWithRoundedRect:cellFrame | 252 inView:(NSView*)controlView { |
227 xRadius:kCellRoundingRadius | 253 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); |
228 yRadius:kCellRoundingRadius]; | 254 CGFloat contentsWidth = [self getMatchContentsWidth]; |
229 [path fill]; | 255 CGFloat separatorWidth = [separator_ size].width; |
230 } | 256 CGFloat descriptionWidth = [description_ size].width; |
257 int contentsMaxWidth, descriptionMaxWidth; | |
258 OmniboxPopupModel::ComputeMatchMaxWidths( | |
259 ceilf(contentsWidth), ceilf(separatorWidth), ceilf(descriptionWidth), | |
260 ceilf(remainingWidth), !AutocompleteMatch::IsSearchType(matchType_), | |
261 &contentsMaxWidth, &descriptionMaxWidth); | |
231 | 262 |
232 // Put the image centered vertically but in a fixed column. | 263 // Put the image centered vertically but in a fixed column. |
233 NSImage* image = [self image]; | 264 if (image_) { |
234 if (image) { | |
235 NSRect imageRect = cellFrame; | 265 NSRect imageRect = cellFrame; |
236 imageRect.size = [image size]; | 266 imageRect.size = [image_ size]; |
237 imageRect.origin.y += | 267 imageRect.origin.y += |
238 std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); | 268 std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); |
239 imageRect.origin.x += kImageXOffset; | 269 imageRect.origin.x += kImageXOffset; |
240 [image drawInRect:FlipIfRTL(imageRect, cellFrame) | 270 [image_ drawInRect:FlipIfRTL(imageRect, cellFrame) |
241 fromRect:NSZeroRect // Entire image | 271 fromRect:NSZeroRect |
242 operation:NSCompositeSourceOver | 272 operation:NSCompositeSourceOver |
243 fraction:1.0 | 273 fraction:1.0 |
244 respectFlipped:YES | 274 respectFlipped:YES |
245 hints:nil]; | 275 hints:nil]; |
246 } | 276 } |
247 | 277 |
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; | 278 CGFloat offset = kTextStartOffset; |
269 if (match_.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { | 279 if (matchType_ == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { |
270 // Infinite suggestions are rendered with a prefix (usually ellipsis), which | 280 // Infinite suggestions are rendered with a prefix (usually ellipsis), which |
271 // appear vertically stacked. | 281 // appear vertically stacked. |
272 offset += [self drawMatchPrefixWithFrame:cellFrame | 282 offset += [self drawMatchPrefixWithFrame:cellFrame |
283 inCell:cell | |
273 inView:controlView | 284 inView:controlView |
274 withContentsMaxWidth:&contentsMaxWidth]; | 285 withContentsMaxWidth:&contentsMaxWidth]; |
275 } | 286 } |
276 offset += [self drawMatchPart:contents | 287 offset += [cell drawMatchPart:contents_ |
277 withFrame:cellFrame | 288 withFrame:cellFrame |
278 atOffset:offset | 289 atOffset:offset |
279 withMaxWidth:contentsMaxWidth | 290 withMaxWidth:contentsMaxWidth |
280 inView:controlView]; | 291 inView:controlView]; |
281 | 292 |
282 if (descriptionMaxWidth != 0) { | 293 if (descriptionMaxWidth != 0) { |
283 offset += [self drawMatchPart:separator_ | 294 offset += [cell drawMatchPart:separator_ |
284 withFrame:cellFrame | 295 withFrame:cellFrame |
285 atOffset:offset | 296 atOffset:offset |
286 withMaxWidth:separatorWidth | 297 withMaxWidth:separatorWidth |
287 inView:controlView]; | 298 inView:controlView]; |
288 offset += [self drawMatchPart:description_ | 299 offset += [cell drawMatchPart:description_ |
289 withFrame:cellFrame | 300 withFrame:cellFrame |
290 atOffset:offset | 301 atOffset:offset |
291 withMaxWidth:descriptionMaxWidth | 302 withMaxWidth:descriptionMaxWidth |
292 inView:controlView]; | 303 inView:controlView]; |
293 } | 304 } |
294 } | 305 } |
295 | 306 |
296 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame | 307 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame |
308 inCell:(OmniboxPopupCell*)cell | |
297 inView:(NSView*)controlView | 309 inView:(NSView*)controlView |
298 withContentsMaxWidth:(int*)contentsMaxWidth { | 310 withContentsMaxWidth:(int*)contentsMaxWidth { |
299 CGFloat offset = 0.0f; | 311 CGFloat offset = 0.0f; |
300 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); | 312 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); |
301 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; | 313 CGFloat prefixWidth = [prefix_ size].width; |
313 | 314 |
314 CGFloat prefixOffset = 0.0f; | 315 CGFloat prefixOffset = 0.0f; |
315 if (isRTL != isContentsRTL) { | 316 if (base::i18n::IsRTL() != isContentsRTL_) { |
316 // The contents is rendered between the contents offset extending towards | 317 // The contents is rendered between the contents offset extending towards |
317 // the start edge, while prefix is rendered in opposite direction. Ideally | 318 // the start edge, while prefix is rendered in opposite direction. Ideally |
318 // the prefix should be rendered at |contentsOffset_|. If that is not | 319 // the prefix should be rendered at |contentsOffset_|. If that is not |
319 // sufficient to render the widest suggestion, we increase it to | 320 // sufficient to render the widest suggestion, we increase it to |
320 // |maxMatchContentsWidth_|. If |remainingWidth| is not sufficient to | 321 // |maxMatchContentsWidth_|. If |remainingWidth| is not sufficient to |
321 // accommodate that, we reduce the offset so that the prefix gets rendered. | 322 // accommodate that, we reduce the offset so that the prefix gets rendered. |
322 prefixOffset = std::min( | 323 prefixOffset = std::min( |
323 remainingWidth - prefixWidth, std::max(contentsOffset_, | 324 remainingWidth - prefixWidth, std::max(contentsOffset_, |
324 maxMatchContentsWidth_)); | 325 maxMatchContentsWidth_)); |
325 offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth); | 326 offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth); |
326 } else { // The direction of contents is same as UI direction. | 327 } else { // The direction of contents is same as UI direction. |
327 // Ideally the offset should be |contentsOffset_|. If the max total width | 328 // Ideally the offset should be |contentsOffset_|. If the max total width |
328 // (|prefixWidth| + |maxMatchContentsWidth_|) from offset will exceed the | 329 // (|prefixWidth| + |maxMatchContentsWidth_|) from offset will exceed the |
329 // |remainingWidth|, then we shift the offset to the left , so that all | 330 // |remainingWidth|, then we shift the offset to the left , so that all |
330 // postfix suggestions are visible. | 331 // postfix suggestions are visible. |
331 // We have to render the prefix, so offset has to be at least |prefixWidth|. | 332 // We have to render the prefix, so offset has to be at least |prefixWidth|. |
332 offset = std::max(prefixWidth, | 333 offset = std::max( |
334 prefixWidth, | |
333 std::min(remainingWidth - maxMatchContentsWidth_, contentsOffset_)); | 335 std::min(remainingWidth - maxMatchContentsWidth_, contentsOffset_)); |
334 prefixOffset = offset - prefixWidth; | 336 prefixOffset = offset - prefixWidth; |
335 } | 337 } |
336 *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth), | 338 *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth), |
337 *contentsMaxWidth); | 339 *contentsMaxWidth); |
338 [self drawMatchPart:prefix_ | 340 [cell drawMatchPart:prefix_ |
339 withFrame:cellFrame | 341 withFrame:cellFrame |
340 atOffset:prefixOffset + kTextStartOffset | 342 atOffset:prefixOffset + kTextStartOffset |
341 withMaxWidth:prefixWidth | 343 withMaxWidth:prefixWidth |
342 inView:controlView]; | 344 inView:controlView]; |
343 return offset; | 345 return offset; |
344 } | 346 } |
345 | 347 |
346 - (CGFloat)drawMatchPart:(NSAttributedString*)as | 348 @end |
349 | |
350 @implementation OmniboxPopupCell | |
351 | |
352 - (id)copyWithZone:(NSZone*)zone { | |
353 OmniboxPopupCell* copy = [super copyWithZone:zone]; | |
354 // The representedObject is set to nil in the copy. | |
Scott Hess - ex-Googler
2015/05/21 20:40:27
What did they do, put _all_ of the special edge ca
groby-ooo-7-16
2015/05/22 05:03:07
Hey, at least this one is documented. (Under repre
dschuyler
2015/05/26 18:40:19
Acknowledged.
dschuyler
2015/05/26 18:40:19
I'm looking into the objectValue vs representedObj
| |
355 [copy setRepresentedObject:[self representedObject]]; | |
356 return copy; | |
357 } | |
358 | |
359 // The default NSButtonCell drawing leaves the image flush left and | |
groby-ooo-7-16
2015/05/21 03:01:48
Comment doesn't apply any more - it's just an NSCe
dschuyler
2015/05/26 18:40:19
Done.
| |
360 // the title next to the image. This spaces things out to line up | |
361 // with the star button and autocomplete field. | |
362 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { | |
363 if ([self state] == NSOnState || [self isHighlighted]) { | |
364 if ([self state] == NSOnState) | |
365 [SelectedBackgroundColor() set]; | |
366 else | |
367 [HoveredBackgroundColor() set]; | |
368 NSBezierPath* path = | |
369 [NSBezierPath bezierPathWithRoundedRect:cellFrame | |
370 xRadius:kCellRoundingRadius | |
371 yRadius:kCellRoundingRadius]; | |
372 [path fill]; | |
373 } | |
374 | |
375 base::scoped_nsobject<OmniboxPopupCellData> cellData( | |
376 [self representedObject]); | |
groby-ooo-7-16
2015/05/21 03:01:48
No need to scope - representedObject (and objectVa
Scott Hess - ex-Googler
2015/05/21 20:40:26
Scoping is wrong, because -representedObject is no
dschuyler
2015/05/26 18:40:20
Done.
dschuyler
2015/05/26 18:40:20
Acknowledged.
| |
377 [cellData drawMatchWithFrame:cellFrame inCell:self inView:controlView]; | |
378 } | |
379 | |
380 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString | |
347 withFrame:(NSRect)cellFrame | 381 withFrame:(NSRect)cellFrame |
348 atOffset:(CGFloat)offset | 382 atOffset:(CGFloat)offset |
349 withMaxWidth:(int)maxWidth | 383 withMaxWidth:(int)maxWidth |
350 inView:(NSView*)controlView { | 384 inView:(NSView*)controlView { |
351 if (offset > NSWidth(cellFrame)) | 385 if (offset > NSWidth(cellFrame)) |
352 return 0.0f; | 386 return 0.0f; |
353 NSRect renderRect = ShiftRect(cellFrame, offset); | 387 NSRect renderRect = ShiftRect(cellFrame, offset); |
354 renderRect.size.width = | 388 renderRect.size.width = |
355 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); | 389 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); |
356 if (renderRect.size.width != 0) { | 390 NSRect textRect = |
357 [self drawTitle:as | 391 [attributedString boundingRectWithSize:renderRect.size options:nil]; |
358 withFrame:FlipIfRTL(renderRect, cellFrame) | 392 renderRect.origin.y += |
359 inView:controlView]; | 393 std::floor((NSHeight(cellFrame) - NSHeight(textRect)) / 2.0); |
360 } | 394 if (NSWidth(renderRect) != 0) |
Scott Hess - ex-Googler
2015/05/21 20:40:26
I'd say > 0.0. Just because I'm that paranoid.
groby-ooo-7-16
2015/05/22 05:03:07
Let's be properly paranoid and say !NSIsEmptyRect(
dschuyler
2015/05/26 18:40:20
Done.
| |
395 [attributedString drawInRect:FlipIfRTL(renderRect, cellFrame)]; | |
361 return NSWidth(renderRect); | 396 return NSWidth(renderRect); |
362 } | 397 } |
363 | 398 |
364 - (CGFloat)getMatchContentsWidth { | |
365 NSAttributedString* contents = [self attributedTitle]; | |
366 return contents ? [contents size].width : 0; | |
367 } | |
368 | |
369 | |
370 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { | 399 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { |
371 const base::string16& inputText = base::UTF8ToUTF16( | 400 const base::string16& inputText = base::UTF8ToUTF16( |
372 match.GetAdditionalInfo(kACMatchPropertyInputText)); | 401 match.GetAdditionalInfo(kACMatchPropertyInputText)); |
373 int contentsStartIndex = 0; | 402 int contentsStartIndex = 0; |
374 base::StringToInt( | 403 base::StringToInt( |
375 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), | 404 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), |
376 &contentsStartIndex); | 405 &contentsStartIndex); |
377 // Ignore invalid state. | 406 // Ignore invalid state. |
378 if (!StartsWith(match.fill_into_edit, inputText, true) | 407 if (!StartsWith(match.fill_into_edit, inputText, true) |
379 || !EndsWith(match.fill_into_edit, match.contents, true) | 408 || !EndsWith(match.fill_into_edit, match.contents, true) |
380 || ((size_t)contentsStartIndex >= inputText.length())) { | 409 || ((size_t)contentsStartIndex >= inputText.length())) { |
381 return 0; | 410 return 0; |
382 } | 411 } |
383 bool isRTL = base::i18n::IsRTL(); | 412 bool isRTL = base::i18n::IsRTL(); |
384 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == | 413 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == |
385 base::i18n::GetFirstStrongCharacterDirection(match.contents)); | 414 base::i18n::GetFirstStrongCharacterDirection(match.contents)); |
386 | 415 |
387 // Color does not matter. | 416 // Color does not matter. |
388 NSAttributedString* as = CreateAttributedString(inputText, DimTextColor()); | 417 NSAttributedString* attributedString = |
389 base::scoped_nsobject<NSTextStorage> textStorage([[NSTextStorage alloc] | 418 CreateAttributedString(inputText, DimTextColor()); |
390 initWithAttributedString:as]); | 419 base::scoped_nsobject<NSTextStorage> textStorage( |
420 [[NSTextStorage alloc] initWithAttributedString:attributedString]); | |
391 base::scoped_nsobject<NSLayoutManager> layoutManager( | 421 base::scoped_nsobject<NSLayoutManager> layoutManager( |
392 [[NSLayoutManager alloc] init]); | 422 [[NSLayoutManager alloc] init]); |
393 base::scoped_nsobject<NSTextContainer> textContainer( | 423 base::scoped_nsobject<NSTextContainer> textContainer( |
394 [[NSTextContainer alloc] init]); | 424 [[NSTextContainer alloc] init]); |
395 [layoutManager addTextContainer:textContainer]; | 425 [layoutManager addTextContainer:textContainer]; |
396 [textStorage addLayoutManager:layoutManager]; | 426 [textStorage addLayoutManager:layoutManager]; |
397 | 427 |
398 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); | 428 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); |
399 NSUInteger glyphIndex = | 429 NSUInteger glyphIndex = |
400 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; | 430 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; |
401 | 431 |
402 // This offset is computed from the left edge of the glyph always from the | 432 // 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. | 433 // left edge of the string, irrespective of the directionality of UI or text. |
404 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; | 434 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
405 | 435 |
406 CGFloat inputWidth = [as size].width; | 436 CGFloat inputWidth = [attributedString size].width; |
407 | 437 |
408 // The offset obtained above may need to be corrected because the left-most | 438 // 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 | 439 // 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. | 440 // subtract it from the offset of the glyph we obtained above. |
411 CGFloat minOffset = glyphOffset; | 441 CGFloat minOffset = glyphOffset; |
412 | 442 |
413 // If content is RTL, we are interested in the right-edge of the glyph. | 443 // If content is RTL, we are interested in the right-edge of the glyph. |
414 // Unfortunately the bounding rect computation methods from NSLayoutManager or | 444 // Unfortunately the bounding rect computation methods from NSLayoutManager or |
415 // NSFont don't work correctly with bidirectional text. So we compute the | 445 // 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 | 446 // glyph width by finding the closest glyph offset to the right of the glyph |
417 // we are looking for. | 447 // we are looking for. |
418 CGFloat glyphWidth = inputWidth; | 448 CGFloat glyphWidth = inputWidth; |
419 | 449 |
420 for (NSUInteger i = 0; i < [as length]; i++) { | 450 for (NSUInteger i = 0; i < [attributedString length]; i++) { |
421 if (i == charIndex) continue; | 451 if (i == charIndex) continue; |
422 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; | 452 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; |
423 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; | 453 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
424 minOffset = std::min(minOffset, offset); | 454 minOffset = std::min(minOffset, offset); |
425 if (offset > glyphOffset) | 455 if (offset > glyphOffset) |
426 glyphWidth = std::min(glyphWidth, offset - glyphOffset); | 456 glyphWidth = std::min(glyphWidth, offset - glyphOffset); |
427 } | 457 } |
428 glyphOffset -= minOffset; | 458 glyphOffset -= minOffset; |
429 if (glyphWidth == 0) | 459 if (glyphWidth == 0) |
430 glyphWidth = inputWidth - glyphOffset; | 460 glyphWidth = inputWidth - glyphOffset; |
431 if (isContentsRTL) | 461 if (isContentsRTL) |
432 glyphOffset += glyphWidth; | 462 glyphOffset += glyphWidth; |
433 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; | 463 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; |
434 } | 464 } |
435 | 465 |
436 @end | 466 @end |
OLD | NEW |