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 = |
groby-ooo-7-16
2015/05/20 01:02:30
Looks like nobody cares about raw_separator any mo
dschuyler
2015/05/21 00:38:39
I don't follow what ya mean...(?)
groby-ooo-7-16
2015/05/21 03:01:47
Never mind, my brain mis-parsed the next line.
Ei
dschuyler
2015/05/26 18:40:19
Done.
| |
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. | |
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()); | |
groby-ooo-7-16
2015/05/20 01:02:30
We don't care about type just yet, I assume?
dschuyler
2015/05/21 00:38:39
Not for this CL, please.
groby-ooo-7-16
2015/05/21 03:01:47
Acknowledged.
| |
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_ ? [contents_ size].width : 0; |
groby-ooo-7-16
2015/05/20 01:02:30
return [contents_ size].width.
See https://develop
dschuyler
2015/05/21 00:38:39
Good to know, thanks!
Done.
| |
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; |
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_ ? [description_ size].width : 0; |
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 - (void)setCellData:(OmniboxPopupCellData*)cellData { | |
353 cellData_.reset([cellData retain]); | |
354 } | |
355 | |
356 - (id)copyWithZone:(NSZone*)zone { | |
357 // Note: Before changing this code, please check with groby, shess, or | |
358 // dschuyler about why it's written with swaps and local data. | |
359 base::scoped_nsobject<OmniboxPopupCellData> cellData; | |
360 cellData.swap(cellData_); | |
361 OmniboxPopupCell* copy = [super copyWithZone:zone]; | |
362 copy->cellData_ = cellData; | |
363 cellData_.swap(cellData); | |
364 return copy; | |
365 } | |
366 | |
367 // The default NSButtonCell drawing leaves the image flush left and | |
368 // the title next to the image. This spaces things out to line up | |
369 // with the star button and autocomplete field. | |
370 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { | |
371 if ([self state] == NSOnState || [self isHighlighted]) { | |
372 if ([self state] == NSOnState) | |
373 [SelectedBackgroundColor() set]; | |
374 else | |
375 [HoveredBackgroundColor() set]; | |
376 NSBezierPath* path = | |
377 [NSBezierPath bezierPathWithRoundedRect:cellFrame | |
378 xRadius:kCellRoundingRadius | |
379 yRadius:kCellRoundingRadius]; | |
380 [path fill]; | |
381 } | |
382 | |
383 [cellData_ drawMatchWithFrame:cellFrame inCell:self inView:controlView]; | |
384 } | |
385 | |
386 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString | |
347 withFrame:(NSRect)cellFrame | 387 withFrame:(NSRect)cellFrame |
348 atOffset:(CGFloat)offset | 388 atOffset:(CGFloat)offset |
349 withMaxWidth:(int)maxWidth | 389 withMaxWidth:(int)maxWidth |
350 inView:(NSView*)controlView { | 390 inView:(NSView*)controlView { |
351 if (offset > NSWidth(cellFrame)) | 391 if (offset > NSWidth(cellFrame)) |
352 return 0.0f; | 392 return 0.0f; |
353 NSRect renderRect = ShiftRect(cellFrame, offset); | 393 NSRect renderRect = ShiftRect(cellFrame, offset); |
354 renderRect.size.width = | 394 renderRect.size.width = |
355 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); | 395 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); |
356 if (renderRect.size.width != 0) { | 396 NSRect textRect = |
357 [self drawTitle:as | 397 [attributedString boundingRectWithSize:renderRect.size options:nil]; |
358 withFrame:FlipIfRTL(renderRect, cellFrame) | 398 renderRect.origin.y += |
359 inView:controlView]; | 399 std::floor((NSHeight(cellFrame) - NSHeight(textRect)) / 2.0); |
360 } | 400 if (renderRect.size.width != 0) |
groby-ooo-7-16
2015/05/20 01:02:30
NSWidth(renderRect), please
dschuyler
2015/05/21 00:38:39
Done.
| |
401 [attributedString drawInRect:FlipIfRTL(renderRect, cellFrame)]; | |
361 return NSWidth(renderRect); | 402 return NSWidth(renderRect); |
362 } | 403 } |
363 | 404 |
364 - (CGFloat)getMatchContentsWidth { | |
365 NSAttributedString* contents = [self attributedTitle]; | |
366 return contents ? [contents size].width : 0; | |
367 } | |
368 | |
369 | |
370 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { | 405 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { |
371 const base::string16& inputText = base::UTF8ToUTF16( | 406 const base::string16& inputText = base::UTF8ToUTF16( |
372 match.GetAdditionalInfo(kACMatchPropertyInputText)); | 407 match.GetAdditionalInfo(kACMatchPropertyInputText)); |
373 int contentsStartIndex = 0; | 408 int contentsStartIndex = 0; |
374 base::StringToInt( | 409 base::StringToInt( |
375 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), | 410 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), |
376 &contentsStartIndex); | 411 &contentsStartIndex); |
377 // Ignore invalid state. | 412 // Ignore invalid state. |
378 if (!StartsWith(match.fill_into_edit, inputText, true) | 413 if (!StartsWith(match.fill_into_edit, inputText, true) |
379 || !EndsWith(match.fill_into_edit, match.contents, true) | 414 || !EndsWith(match.fill_into_edit, match.contents, true) |
380 || ((size_t)contentsStartIndex >= inputText.length())) { | 415 || ((size_t)contentsStartIndex >= inputText.length())) { |
381 return 0; | 416 return 0; |
382 } | 417 } |
383 bool isRTL = base::i18n::IsRTL(); | 418 bool isRTL = base::i18n::IsRTL(); |
384 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == | 419 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == |
385 base::i18n::GetFirstStrongCharacterDirection(match.contents)); | 420 base::i18n::GetFirstStrongCharacterDirection(match.contents)); |
386 | 421 |
387 // Color does not matter. | 422 // Color does not matter. |
388 NSAttributedString* as = CreateAttributedString(inputText, DimTextColor()); | 423 NSAttributedString* attributedString = |
389 base::scoped_nsobject<NSTextStorage> textStorage([[NSTextStorage alloc] | 424 CreateAttributedString(inputText, DimTextColor()); |
390 initWithAttributedString:as]); | 425 base::scoped_nsobject<NSTextStorage> textStorage( |
426 [[NSTextStorage alloc] initWithAttributedString:attributedString]); | |
391 base::scoped_nsobject<NSLayoutManager> layoutManager( | 427 base::scoped_nsobject<NSLayoutManager> layoutManager( |
392 [[NSLayoutManager alloc] init]); | 428 [[NSLayoutManager alloc] init]); |
393 base::scoped_nsobject<NSTextContainer> textContainer( | 429 base::scoped_nsobject<NSTextContainer> textContainer( |
394 [[NSTextContainer alloc] init]); | 430 [[NSTextContainer alloc] init]); |
395 [layoutManager addTextContainer:textContainer]; | 431 [layoutManager addTextContainer:textContainer]; |
396 [textStorage addLayoutManager:layoutManager]; | 432 [textStorage addLayoutManager:layoutManager]; |
397 | 433 |
398 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); | 434 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); |
399 NSUInteger glyphIndex = | 435 NSUInteger glyphIndex = |
400 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; | 436 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; |
401 | 437 |
402 // This offset is computed from the left edge of the glyph always from the | 438 // 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. | 439 // left edge of the string, irrespective of the directionality of UI or text. |
404 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; | 440 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
405 | 441 |
406 CGFloat inputWidth = [as size].width; | 442 CGFloat inputWidth = [attributedString size].width; |
407 | 443 |
408 // The offset obtained above may need to be corrected because the left-most | 444 // 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 | 445 // 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. | 446 // subtract it from the offset of the glyph we obtained above. |
411 CGFloat minOffset = glyphOffset; | 447 CGFloat minOffset = glyphOffset; |
412 | 448 |
413 // If content is RTL, we are interested in the right-edge of the glyph. | 449 // If content is RTL, we are interested in the right-edge of the glyph. |
414 // Unfortunately the bounding rect computation methods from NSLayoutManager or | 450 // Unfortunately the bounding rect computation methods from NSLayoutManager or |
415 // NSFont don't work correctly with bidirectional text. So we compute the | 451 // 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 | 452 // glyph width by finding the closest glyph offset to the right of the glyph |
417 // we are looking for. | 453 // we are looking for. |
418 CGFloat glyphWidth = inputWidth; | 454 CGFloat glyphWidth = inputWidth; |
419 | 455 |
420 for (NSUInteger i = 0; i < [as length]; i++) { | 456 for (NSUInteger i = 0; i < [attributedString length]; i++) { |
421 if (i == charIndex) continue; | 457 if (i == charIndex) continue; |
422 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; | 458 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; |
423 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; | 459 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
424 minOffset = std::min(minOffset, offset); | 460 minOffset = std::min(minOffset, offset); |
425 if (offset > glyphOffset) | 461 if (offset > glyphOffset) |
426 glyphWidth = std::min(glyphWidth, offset - glyphOffset); | 462 glyphWidth = std::min(glyphWidth, offset - glyphOffset); |
427 } | 463 } |
428 glyphOffset -= minOffset; | 464 glyphOffset -= minOffset; |
429 if (glyphWidth == 0) | 465 if (glyphWidth == 0) |
430 glyphWidth = inputWidth - glyphOffset; | 466 glyphWidth = inputWidth - glyphOffset; |
431 if (isContentsRTL) | 467 if (isContentsRTL) |
432 glyphOffset += glyphWidth; | 468 glyphOffset += glyphWidth; |
433 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; | 469 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; |
434 } | 470 } |
435 | 471 |
436 @end | 472 @end |
OLD | NEW |