Index: chrome/browser/cocoa/autocomplete_text_field_cell.mm |
=================================================================== |
--- chrome/browser/cocoa/autocomplete_text_field_cell.mm (revision 23951) |
+++ chrome/browser/cocoa/autocomplete_text_field_cell.mm (working copy) |
@@ -3,13 +3,161 @@ |
// found in the LICENSE file. |
#import "chrome/browser/cocoa/autocomplete_text_field_cell.h" |
+ |
+#import "base/logging.h" |
#import "third_party/GTM/AppKit/GTMTheme.h" |
-const NSInteger kBaselineOffset = 2; |
+namespace { |
+const NSInteger kBaselineAdjust = 2; |
+ |
+// How far to offset the keyword token into the field. |
+const NSInteger kKeywordXOffset = 3; |
+ |
+// How much width (beyond text) to add to the keyword token on each |
+// side. |
+const NSInteger kKeywordTokenInset = 3; |
+ |
+// Gap to leave between hint and right-hand-side of cell. |
+const NSInteger kHintXOffset = 4; |
+ |
+// How far to shift bounding box of hint down from top of field. |
+// Assumes -setFlipped:YES. |
+const NSInteger kHintYOffset = 4; |
+ |
+// How far to inset the keywork token from sides. |
+const NSInteger kKeywordYInset = 4; |
+ |
+// TODO(shess): The keyword hint image wants to sit on the baseline. |
+// This moves it down so that there is approximately as much image |
+// above the lowercase ascender as below the baseline. A better |
+// technique would be nice to have, though. |
+const NSInteger kKeywordHintImageBaseline = -6; |
+ |
+// Offset from the bottom of the field for drawing decoration text. |
+// TODO(shess): Somehow determine the baseline for the text field and |
+// use that. |
+const NSInteger kBaselineOffset = 4; |
+ |
+} // namespace |
+ |
@implementation AutocompleteTextFieldCell |
+@synthesize fieldEditorNeedsReset = fieldEditorNeedsReset_; |
+ |
+// @synthesize doesn't seem to compile for this transition. |
+- (NSAttributedString*)keywordString { |
+ return keywordString_.get(); |
+} |
+- (NSAttributedString*)hintString { |
+ return hintString_.get(); |
+} |
+ |
+- (void)setKeywordString:(NSString*)aString { |
+ DCHECK(aString != nil); |
+ if (hintString_ || ![[keywordString_ string] isEqualToString:aString]) { |
+ NSDictionary* attributes = |
+ [NSDictionary dictionaryWithObjectsAndKeys: |
+ [self font], NSFontAttributeName, |
+ nil]; |
+ |
+ keywordString_.reset( |
+ [[NSAttributedString alloc] initWithString:aString |
+ attributes:attributes]); |
+ hintString_.reset(); |
+ |
+ fieldEditorNeedsReset_ = YES; |
+ } |
+} |
+ |
+- (void)setHintString:(NSAttributedString*)aString { |
+ keywordString_.reset(); |
+ hintString_.reset([aString copy]); |
+ |
+ fieldEditorNeedsReset_ = YES; |
+} |
+ |
+// Convenience for the attributes used in the right-justified info |
+// cells. |
+- (NSDictionary*)hintAttributes { |
+ NSMutableParagraphStyle* style = |
+ [[[NSMutableParagraphStyle alloc] init] autorelease]; |
+ [style setAlignment:NSRightTextAlignment]; |
+ |
+ return [NSDictionary dictionaryWithObjectsAndKeys: |
+ [self font], NSFontAttributeName, |
+ [NSColor lightGrayColor], NSForegroundColorAttributeName, |
+ style, NSParagraphStyleAttributeName, |
+ nil]; |
+} |
+ |
+- (void)setKeywordHintPrefix:(NSString*)prefixString |
+ image:(NSImage*)anImage |
+ suffix:(NSString*)suffixString { |
+ DCHECK(prefixString != nil); |
+ DCHECK(anImage != nil); |
+ DCHECK(suffixString != nil); |
+ |
+ // TODO(shess): Also check the length? |
+ if (keywordString_ || |
+ ![[hintString_ string] hasPrefix:prefixString] || |
+ ![[hintString_ string] hasSuffix:suffixString]) { |
+ |
+ // Build an attributed string with the concatenation of the prefix |
+ // and suffix. |
+ NSString* s = [prefixString stringByAppendingString:suffixString]; |
+ NSMutableAttributedString* as = |
+ [[[NSMutableAttributedString alloc] |
+ initWithString:s attributes:[self hintAttributes]] autorelease]; |
+ |
+ // Build an attachment containing the hint image. |
+ NSTextAttachmentCell* attachmentCell = |
+ [[[NSTextAttachmentCell alloc] initImageCell:anImage] autorelease]; |
+ NSTextAttachment* attachment = |
+ [[[NSTextAttachment alloc] init] autorelease]; |
+ [attachment setAttachmentCell:attachmentCell]; |
+ |
+ // The attachment's baseline needs to be adjusted so the image |
+ // doesn't sit on the same baseline as the text and make |
+ // everything too tall. |
+ NSMutableAttributedString* is = |
+ [[[NSAttributedString attributedStringWithAttachment:attachment] |
+ mutableCopy] autorelease]; |
+ [is addAttribute:NSBaselineOffsetAttributeName |
+ value:[NSNumber numberWithFloat:kKeywordHintImageBaseline] |
+ range:NSMakeRange(0, [is length])]; |
+ |
+ // Stuff the image attachment between the prefix and suffix. |
+ [as insertAttributedString:is atIndex:[prefixString length]]; |
+ |
+ [self setHintString:as]; |
+ } |
+} |
+ |
+- (void)setSearchHintString:(NSString*)aString { |
+ DCHECK(aString != nil); |
+ |
+ if (keywordString_ || ![[hintString_ string] isEqualToString:aString]) { |
+ NSAttributedString* as = |
+ [[[NSAttributedString alloc] initWithString:aString |
+ attributes:[self hintAttributes]] |
+ autorelease]; |
+ |
+ [self setHintString:as]; |
+ } |
+} |
+ |
+- (void)clearKeywordAndHint { |
+ if (keywordString_ || hintString_) { |
+ keywordString_.reset(); |
+ hintString_.reset(); |
+ |
+ fieldEditorNeedsReset_ = YES; |
+ } |
+} |
+ |
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
+ DCHECK([controlView isFlipped]); |
[[NSColor colorWithCalibratedWhite:1.0 alpha:0.25] set]; |
NSFrameRectWithWidthUsingOperation(cellFrame, 1, NSCompositeSourceOver); |
@@ -38,14 +186,88 @@ |
NSCompositeSourceOver); |
} |
- [self drawInteriorWithFrame:cellFrame |
- inView:controlView]; |
+ [self drawInteriorWithFrame:cellFrame inView:controlView]; |
+} |
+- (NSRect)textFrameForFrame:(NSRect)cellFrame { |
+ NSRect textFrame(cellFrame); |
+ |
+ if (hintString_) { |
+ DCHECK(!keywordString_); |
+ const CGFloat hintWidth = kHintXOffset + ceil([hintString_ size].width); |
+ |
+ // TODO(shess): This could be better. Show the hint until the |
+ // non-hint text bumps against it? |
+ if (hintWidth < NSWidth(cellFrame)) { |
+ textFrame.size.width -= hintWidth; |
+ } |
+ } else if (keywordString_) { |
+ DCHECK(!hintString_); |
+ const CGFloat keywordWidth = kKeywordXOffset + |
+ ceil([keywordString_ size].width) + 2 * kKeywordTokenInset; |
+ |
+ // TODO(shess): This could be better. There's support for a |
+ // "short" version of the keyword string, work that in in a |
+ // follow-on pass. |
+ if (keywordWidth < NSWidth(cellFrame)) { |
+ textFrame.origin.x += keywordWidth; |
+ textFrame.size.width = NSMaxX(cellFrame) - NSMinX(textFrame); |
+ } |
+ } |
+ |
+ return NSInsetRect(textFrame, 0, kBaselineAdjust); |
} |
-- (void)drawInteriorWithFrame:(NSRect)cellFrame |
- inView:(NSView*)controlView { |
- [super drawInteriorWithFrame:NSInsetRect(cellFrame, 0, kBaselineOffset) |
+- (void)drawHintWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
+ DCHECK(hintString_); |
+ |
+ NSRect textFrame = [self textFrameForFrame:cellFrame]; |
+ NSRect infoFrame(NSMakeRect(NSMaxX(textFrame), |
+ cellFrame.origin.y + kHintYOffset, |
+ ceil([hintString_ size].width), |
+ cellFrame.size.height - kHintYOffset)); |
+ [hintString_.get() drawInRect:infoFrame]; |
+} |
+ |
+- (void)drawKeywordWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
+ DCHECK(keywordString_); |
+ |
+ NSRect textFrame = [self textFrameForFrame:cellFrame]; |
+ const CGFloat x = NSMinX(cellFrame) + kKeywordXOffset; |
+ NSRect infoFrame(NSMakeRect(x, |
+ cellFrame.origin.y + kKeywordYInset, |
+ NSMinX(textFrame) - x, |
+ cellFrame.size.height - 2 * kKeywordYInset)); |
+ |
+ // Draw a token rectangle with rounded corners. |
+ NSRect frame(NSInsetRect(infoFrame, 0.5, 0.5)); |
+ NSBezierPath* path = |
+ [NSBezierPath bezierPathWithRoundedRect:frame xRadius:4.0 yRadius:4.0]; |
+ |
+ [[NSColor controlColor] set]; |
+ [path fill]; |
+ |
+ GTMTheme *theme = [controlView gtm_theme]; |
+ NSColor* stroke = [theme strokeColorForStyle:GTMThemeStyleToolBarButton |
+ state:YES]; |
+ [stroke setStroke]; |
+ [path setLineWidth:1.0]; |
+ [path stroke]; |
+ |
+ // Draw text w/in the rectangle. |
+ infoFrame.origin.x += 4.0; |
+ infoFrame.origin.y += 1.0; |
+ [keywordString_.get() drawInRect:infoFrame]; |
+} |
+ |
+- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
+ if (hintString_) { |
+ [self drawHintWithFrame:cellFrame inView:controlView]; |
+ } else if (keywordString_) { |
+ [self drawKeywordWithFrame:cellFrame inView:controlView]; |
+ } |
+ |
+ [super drawInteriorWithFrame:[self textFrameForFrame:cellFrame] |
inView:controlView]; |
} |
@@ -55,14 +277,13 @@ |
editor:(NSText*)textObj |
delegate:(id)anObject |
event:(NSEvent*)theEvent { |
- [super editWithFrame:NSInsetRect(cellFrame, 0, kBaselineOffset) |
+ [super editWithFrame:[self textFrameForFrame:cellFrame] |
inView:controlView |
editor:textObj |
delegate:anObject |
event:theEvent]; |
} |
- |
// Override these methods so that the field editor shows up in the right place |
- (void)selectWithFrame:(NSRect)cellFrame |
inView:(NSView*)controlView |
@@ -70,7 +291,7 @@ |
delegate:(id)anObject |
start:(NSInteger)selStart |
length:(NSInteger)selLength { |
- [super selectWithFrame:NSInsetRect(cellFrame, 0, kBaselineOffset) |
+ [super selectWithFrame:[self textFrameForFrame:cellFrame] |
inView:controlView editor:textObj |
delegate:anObject |
start:selStart |