OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 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/cocoa/autocomplete_text_field_cell.h" | 5 #import "chrome/browser/cocoa/autocomplete_text_field_cell.h" |
| 6 |
| 7 #import "base/logging.h" |
6 #import "third_party/GTM/AppKit/GTMTheme.h" | 8 #import "third_party/GTM/AppKit/GTMTheme.h" |
7 | 9 |
8 const NSInteger kBaselineOffset = 2; | 10 namespace { |
| 11 |
| 12 const NSInteger kBaselineAdjust = 2; |
| 13 |
| 14 // How far to offset the keyword token into the field. |
| 15 const NSInteger kKeywordXOffset = 3; |
| 16 |
| 17 // How much width (beyond text) to add to the keyword token on each |
| 18 // side. |
| 19 const NSInteger kKeywordTokenInset = 3; |
| 20 |
| 21 // Gap to leave between hint and right-hand-side of cell. |
| 22 const NSInteger kHintXOffset = 4; |
| 23 |
| 24 // How far to shift bounding box of hint down from top of field. |
| 25 // Assumes -setFlipped:YES. |
| 26 const NSInteger kHintYOffset = 4; |
| 27 |
| 28 // How far to inset the keywork token from sides. |
| 29 const NSInteger kKeywordYInset = 4; |
| 30 |
| 31 // TODO(shess): The keyword hint image wants to sit on the baseline. |
| 32 // This moves it down so that there is approximately as much image |
| 33 // above the lowercase ascender as below the baseline. A better |
| 34 // technique would be nice to have, though. |
| 35 const NSInteger kKeywordHintImageBaseline = -6; |
| 36 |
| 37 // Offset from the bottom of the field for drawing decoration text. |
| 38 // TODO(shess): Somehow determine the baseline for the text field and |
| 39 // use that. |
| 40 const NSInteger kBaselineOffset = 4; |
| 41 |
| 42 } // namespace |
9 | 43 |
10 @implementation AutocompleteTextFieldCell | 44 @implementation AutocompleteTextFieldCell |
11 | 45 |
| 46 @synthesize fieldEditorNeedsReset = fieldEditorNeedsReset_; |
| 47 |
| 48 // @synthesize doesn't seem to compile for this transition. |
| 49 - (NSAttributedString*)keywordString { |
| 50 return keywordString_.get(); |
| 51 } |
| 52 - (NSAttributedString*)hintString { |
| 53 return hintString_.get(); |
| 54 } |
| 55 |
| 56 - (void)setKeywordString:(NSString*)aString { |
| 57 DCHECK(aString != nil); |
| 58 if (hintString_ || ![[keywordString_ string] isEqualToString:aString]) { |
| 59 NSDictionary* attributes = |
| 60 [NSDictionary dictionaryWithObjectsAndKeys: |
| 61 [self font], NSFontAttributeName, |
| 62 nil]; |
| 63 |
| 64 keywordString_.reset( |
| 65 [[NSAttributedString alloc] initWithString:aString |
| 66 attributes:attributes]); |
| 67 hintString_.reset(); |
| 68 |
| 69 fieldEditorNeedsReset_ = YES; |
| 70 } |
| 71 } |
| 72 |
| 73 - (void)setHintString:(NSAttributedString*)aString { |
| 74 keywordString_.reset(); |
| 75 hintString_.reset([aString copy]); |
| 76 |
| 77 fieldEditorNeedsReset_ = YES; |
| 78 } |
| 79 |
| 80 // Convenience for the attributes used in the right-justified info |
| 81 // cells. |
| 82 - (NSDictionary*)hintAttributes { |
| 83 NSMutableParagraphStyle* style = |
| 84 [[[NSMutableParagraphStyle alloc] init] autorelease]; |
| 85 [style setAlignment:NSRightTextAlignment]; |
| 86 |
| 87 return [NSDictionary dictionaryWithObjectsAndKeys: |
| 88 [self font], NSFontAttributeName, |
| 89 [NSColor lightGrayColor], NSForegroundColorAttributeName, |
| 90 style, NSParagraphStyleAttributeName, |
| 91 nil]; |
| 92 } |
| 93 |
| 94 - (void)setKeywordHintPrefix:(NSString*)prefixString |
| 95 image:(NSImage*)anImage |
| 96 suffix:(NSString*)suffixString { |
| 97 DCHECK(prefixString != nil); |
| 98 DCHECK(anImage != nil); |
| 99 DCHECK(suffixString != nil); |
| 100 |
| 101 // TODO(shess): Also check the length? |
| 102 if (keywordString_ || |
| 103 ![[hintString_ string] hasPrefix:prefixString] || |
| 104 ![[hintString_ string] hasSuffix:suffixString]) { |
| 105 |
| 106 // Build an attributed string with the concatenation of the prefix |
| 107 // and suffix. |
| 108 NSString* s = [prefixString stringByAppendingString:suffixString]; |
| 109 NSMutableAttributedString* as = |
| 110 [[[NSMutableAttributedString alloc] |
| 111 initWithString:s attributes:[self hintAttributes]] autorelease]; |
| 112 |
| 113 // Build an attachment containing the hint image. |
| 114 NSTextAttachmentCell* attachmentCell = |
| 115 [[[NSTextAttachmentCell alloc] initImageCell:anImage] autorelease]; |
| 116 NSTextAttachment* attachment = |
| 117 [[[NSTextAttachment alloc] init] autorelease]; |
| 118 [attachment setAttachmentCell:attachmentCell]; |
| 119 |
| 120 // The attachment's baseline needs to be adjusted so the image |
| 121 // doesn't sit on the same baseline as the text and make |
| 122 // everything too tall. |
| 123 NSMutableAttributedString* is = |
| 124 [[[NSAttributedString attributedStringWithAttachment:attachment] |
| 125 mutableCopy] autorelease]; |
| 126 [is addAttribute:NSBaselineOffsetAttributeName |
| 127 value:[NSNumber numberWithFloat:kKeywordHintImageBaseline] |
| 128 range:NSMakeRange(0, [is length])]; |
| 129 |
| 130 // Stuff the image attachment between the prefix and suffix. |
| 131 [as insertAttributedString:is atIndex:[prefixString length]]; |
| 132 |
| 133 [self setHintString:as]; |
| 134 } |
| 135 } |
| 136 |
| 137 - (void)setSearchHintString:(NSString*)aString { |
| 138 DCHECK(aString != nil); |
| 139 |
| 140 if (keywordString_ || ![[hintString_ string] isEqualToString:aString]) { |
| 141 NSAttributedString* as = |
| 142 [[[NSAttributedString alloc] initWithString:aString |
| 143 attributes:[self hintAttributes]] |
| 144 autorelease]; |
| 145 |
| 146 [self setHintString:as]; |
| 147 } |
| 148 } |
| 149 |
| 150 - (void)clearKeywordAndHint { |
| 151 if (keywordString_ || hintString_) { |
| 152 keywordString_.reset(); |
| 153 hintString_.reset(); |
| 154 |
| 155 fieldEditorNeedsReset_ = YES; |
| 156 } |
| 157 } |
| 158 |
12 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { | 159 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
| 160 DCHECK([controlView isFlipped]); |
13 [[NSColor colorWithCalibratedWhite:1.0 alpha:0.25] set]; | 161 [[NSColor colorWithCalibratedWhite:1.0 alpha:0.25] set]; |
14 NSFrameRectWithWidthUsingOperation(cellFrame, 1, NSCompositeSourceOver); | 162 NSFrameRectWithWidthUsingOperation(cellFrame, 1, NSCompositeSourceOver); |
15 | 163 |
16 NSRect frame = NSInsetRect(cellFrame, 0, 1); | 164 NSRect frame = NSInsetRect(cellFrame, 0, 1); |
17 [[self backgroundColor] setFill]; | 165 [[self backgroundColor] setFill]; |
18 NSRect innerFrame = NSInsetRect(frame, 1, 1); | 166 NSRect innerFrame = NSInsetRect(frame, 1, 1); |
19 NSRectFill(innerFrame); | 167 NSRectFill(innerFrame); |
20 | 168 |
21 NSRect shadowFrame, restFrame; | 169 NSRect shadowFrame, restFrame; |
22 NSDivideRect(innerFrame, &shadowFrame, &restFrame, 1, NSMinYEdge); | 170 NSDivideRect(innerFrame, &shadowFrame, &restFrame, 1, NSMinYEdge); |
23 | 171 |
24 BOOL isMainWindow = [[controlView window] isMainWindow]; | 172 BOOL isMainWindow = [[controlView window] isMainWindow]; |
25 GTMTheme *theme = [controlView gtm_theme]; | 173 GTMTheme *theme = [controlView gtm_theme]; |
26 NSColor* stroke = [theme strokeColorForStyle:GTMThemeStyleToolBarButton | 174 NSColor* stroke = [theme strokeColorForStyle:GTMThemeStyleToolBarButton |
27 state:isMainWindow]; | 175 state:isMainWindow]; |
28 [stroke set]; | 176 [stroke set]; |
29 NSFrameRectWithWidthUsingOperation(frame, 1.0, NSCompositeSourceOver); | 177 NSFrameRectWithWidthUsingOperation(frame, 1.0, NSCompositeSourceOver); |
30 | 178 |
31 // Draw the shadow. | 179 // Draw the shadow. |
32 [[NSColor colorWithCalibratedWhite:0.0 alpha:0.05] setFill]; | 180 [[NSColor colorWithCalibratedWhite:0.0 alpha:0.05] setFill]; |
33 NSRectFillUsingOperation(shadowFrame, NSCompositeSourceOver); | 181 NSRectFillUsingOperation(shadowFrame, NSCompositeSourceOver); |
34 | 182 |
35 if ([self showsFirstResponder]) { | 183 if ([self showsFirstResponder]) { |
36 [[[NSColor keyboardFocusIndicatorColor] colorWithAlphaComponent:0.5] set]; | 184 [[[NSColor keyboardFocusIndicatorColor] colorWithAlphaComponent:0.5] set]; |
37 NSFrameRectWithWidthUsingOperation(NSInsetRect(frame, 0, 0), 2, | 185 NSFrameRectWithWidthUsingOperation(NSInsetRect(frame, 0, 0), 2, |
38 NSCompositeSourceOver); | 186 NSCompositeSourceOver); |
39 } | 187 } |
40 | 188 |
41 [self drawInteriorWithFrame:cellFrame | 189 [self drawInteriorWithFrame:cellFrame inView:controlView]; |
42 inView:controlView]; | |
43 | |
44 } | 190 } |
45 | 191 |
46 - (void)drawInteriorWithFrame:(NSRect)cellFrame | 192 - (NSRect)textFrameForFrame:(NSRect)cellFrame { |
47 inView:(NSView*)controlView { | 193 NSRect textFrame(cellFrame); |
48 [super drawInteriorWithFrame:NSInsetRect(cellFrame, 0, kBaselineOffset) | 194 |
| 195 if (hintString_) { |
| 196 DCHECK(!keywordString_); |
| 197 const CGFloat hintWidth = kHintXOffset + ceil([hintString_ size].width); |
| 198 |
| 199 // TODO(shess): This could be better. Show the hint until the |
| 200 // non-hint text bumps against it? |
| 201 if (hintWidth < NSWidth(cellFrame)) { |
| 202 textFrame.size.width -= hintWidth; |
| 203 } |
| 204 } else if (keywordString_) { |
| 205 DCHECK(!hintString_); |
| 206 const CGFloat keywordWidth = kKeywordXOffset + |
| 207 ceil([keywordString_ size].width) + 2 * kKeywordTokenInset; |
| 208 |
| 209 // TODO(shess): This could be better. There's support for a |
| 210 // "short" version of the keyword string, work that in in a |
| 211 // follow-on pass. |
| 212 if (keywordWidth < NSWidth(cellFrame)) { |
| 213 textFrame.origin.x += keywordWidth; |
| 214 textFrame.size.width = NSMaxX(cellFrame) - NSMinX(textFrame); |
| 215 } |
| 216 } |
| 217 |
| 218 return NSInsetRect(textFrame, 0, kBaselineAdjust); |
| 219 } |
| 220 |
| 221 - (void)drawHintWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
| 222 DCHECK(hintString_); |
| 223 |
| 224 NSRect textFrame = [self textFrameForFrame:cellFrame]; |
| 225 NSRect infoFrame(NSMakeRect(NSMaxX(textFrame), |
| 226 cellFrame.origin.y + kHintYOffset, |
| 227 ceil([hintString_ size].width), |
| 228 cellFrame.size.height - kHintYOffset)); |
| 229 [hintString_.get() drawInRect:infoFrame]; |
| 230 } |
| 231 |
| 232 - (void)drawKeywordWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
| 233 DCHECK(keywordString_); |
| 234 |
| 235 NSRect textFrame = [self textFrameForFrame:cellFrame]; |
| 236 const CGFloat x = NSMinX(cellFrame) + kKeywordXOffset; |
| 237 NSRect infoFrame(NSMakeRect(x, |
| 238 cellFrame.origin.y + kKeywordYInset, |
| 239 NSMinX(textFrame) - x, |
| 240 cellFrame.size.height - 2 * kKeywordYInset)); |
| 241 |
| 242 // Draw a token rectangle with rounded corners. |
| 243 NSRect frame(NSInsetRect(infoFrame, 0.5, 0.5)); |
| 244 NSBezierPath* path = |
| 245 [NSBezierPath bezierPathWithRoundedRect:frame xRadius:4.0 yRadius:4.0]; |
| 246 |
| 247 [[NSColor controlColor] set]; |
| 248 [path fill]; |
| 249 |
| 250 GTMTheme *theme = [controlView gtm_theme]; |
| 251 NSColor* stroke = [theme strokeColorForStyle:GTMThemeStyleToolBarButton |
| 252 state:YES]; |
| 253 [stroke setStroke]; |
| 254 [path setLineWidth:1.0]; |
| 255 [path stroke]; |
| 256 |
| 257 // Draw text w/in the rectangle. |
| 258 infoFrame.origin.x += 4.0; |
| 259 infoFrame.origin.y += 1.0; |
| 260 [keywordString_.get() drawInRect:infoFrame]; |
| 261 } |
| 262 |
| 263 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
| 264 if (hintString_) { |
| 265 [self drawHintWithFrame:cellFrame inView:controlView]; |
| 266 } else if (keywordString_) { |
| 267 [self drawKeywordWithFrame:cellFrame inView:controlView]; |
| 268 } |
| 269 |
| 270 [super drawInteriorWithFrame:[self textFrameForFrame:cellFrame] |
49 inView:controlView]; | 271 inView:controlView]; |
50 } | 272 } |
51 | 273 |
52 // Override these methods so that the field editor shows up in the right place | 274 // Override these methods so that the field editor shows up in the right place |
53 - (void)editWithFrame:(NSRect)cellFrame | 275 - (void)editWithFrame:(NSRect)cellFrame |
54 inView:(NSView*)controlView | 276 inView:(NSView*)controlView |
55 editor:(NSText*)textObj | 277 editor:(NSText*)textObj |
56 delegate:(id)anObject | 278 delegate:(id)anObject |
57 event:(NSEvent*)theEvent { | 279 event:(NSEvent*)theEvent { |
58 [super editWithFrame:NSInsetRect(cellFrame, 0, kBaselineOffset) | 280 [super editWithFrame:[self textFrameForFrame:cellFrame] |
59 inView:controlView | 281 inView:controlView |
60 editor:textObj | 282 editor:textObj |
61 delegate:anObject | 283 delegate:anObject |
62 event:theEvent]; | 284 event:theEvent]; |
63 } | 285 } |
64 | 286 |
65 | |
66 // Override these methods so that the field editor shows up in the right place | 287 // Override these methods so that the field editor shows up in the right place |
67 - (void)selectWithFrame:(NSRect)cellFrame | 288 - (void)selectWithFrame:(NSRect)cellFrame |
68 inView:(NSView*)controlView | 289 inView:(NSView*)controlView |
69 editor:(NSText*)textObj | 290 editor:(NSText*)textObj |
70 delegate:(id)anObject | 291 delegate:(id)anObject |
71 start:(NSInteger)selStart | 292 start:(NSInteger)selStart |
72 length:(NSInteger)selLength { | 293 length:(NSInteger)selLength { |
73 [super selectWithFrame:NSInsetRect(cellFrame, 0, kBaselineOffset) | 294 [super selectWithFrame:[self textFrameForFrame:cellFrame] |
74 inView:controlView editor:textObj | 295 inView:controlView editor:textObj |
75 delegate:anObject | 296 delegate:anObject |
76 start:selStart | 297 start:selStart |
77 length:selLength]; | 298 length:selLength]; |
78 } | 299 } |
79 | 300 |
80 @end | 301 @end |
OLD | NEW |