1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <cmath> 6 7 #import "chrome/browser/ui/cocoa/location_bar/keyword_hint_decoration.h" 8 9 #include "base/logging.h" 10 #include "base/string_util.h" 11 #include "base/sys_string_conversions.h" 12 #import "chrome/browser/ui/cocoa/image_utils.h" 13 #include "grit/theme_resources.h" 14 #include "grit/generated_resources.h" 15 #include "skia/ext/skia_utils_mac.h" 16 #include "ui/base/l10n/l10n_util.h" 17 #include "ui/base/resource/resource_bundle.h" 18 19 namespace { 20 21 // How far to inset the hint text area from sides. 22 const CGFloat kHintTextYInset = 4.0; 23 24 // How far to inset the hint image from sides. Lines baseline of text 25 // in image with baseline of prefix and suffix. 26 const CGFloat kHintImageYInset = 4.0; 27 28 // Extra padding right and left of the image. 29 const CGFloat kHintImagePadding = 1.0; 30 31 // Maxmimum of the available space to allow the hint to take over. 32 // Should leave enough so that the user has space to edit things. 33 const CGFloat kHintAvailableRatio = 2.0 / 3.0; 34 35 // Helper to convert |s| to an |NSString|, trimming whitespace at 36 // ends. 37 NSString* TrimAndConvert(const string16& s) { 38 string16 output; 39 TrimWhitespace(s, TRIM_ALL, &output); 40 return base::SysUTF16ToNSString(output); 41 } 42 43 } // namespace 44 45 KeywordHintDecoration::KeywordHintDecoration(NSFont* font) { 46 NSColor* text_color = [NSColor lightGrayColor]; 47 NSDictionary* attributes = 48 [NSDictionary dictionaryWithObjectsAndKeys: 49 font, NSFontAttributeName, 50 text_color, NSForegroundColorAttributeName, 51 nil]; 52 attributes_.reset([attributes retain]); 53 } 54 55 KeywordHintDecoration::~KeywordHintDecoration() { 56 } 57 58 NSImage* KeywordHintDecoration::GetHintImage() { 59 if (!hint_image_) { 60 SkBitmap* skiaBitmap = ResourceBundle::GetSharedInstance(). 61 GetBitmapNamed(IDR_LOCATION_BAR_KEYWORD_HINT_TAB); 62 if (skiaBitmap) 63 hint_image_.reset([gfx::SkBitmapToNSImage(*skiaBitmap) retain]); 64 } 65 return hint_image_; 66 } 67 68 void KeywordHintDecoration::SetKeyword(const string16& short_name, 69 bool is_extension_keyword) { 70 // KEYWORD_HINT is a message like "Press [tab] to search <site>". 71 // [tab] is a parameter to be replaced by an image. "<site>" is 72 // derived from |short_name|. 73 std::vector<size_t> content_param_offsets; 74 int message_id = is_extension_keyword ? 75 IDS_OMNIBOX_EXTENSION_KEYWORD_HINT : IDS_OMNIBOX_KEYWORD_HINT; 76 const string16 keyword_hint( 77 l10n_util::GetStringFUTF16(message_id, 78 string16(), short_name, 79 &content_param_offsets)); 80 81 // Should always be 2 offsets, see the comment in 82 // location_bar_view.cc after IDS_OMNIBOX_KEYWORD_HINT fetch. 83 DCHECK_EQ(content_param_offsets.size(), 2U); 84 85 // Where to put the [tab] image. 86 const size_t split = content_param_offsets.front(); 87 88 // Trim the spaces from the edges (there is space in the image) and 89 // convert to |NSString|. 90 hint_prefix_.reset([TrimAndConvert(keyword_hint.substr(0, split)) retain]); 91 hint_suffix_.reset([TrimAndConvert(keyword_hint.substr(split)) retain]); 92 } 93 94 CGFloat KeywordHintDecoration::GetWidthForSpace(CGFloat width) { 95 NSImage* image = GetHintImage(); 96 const CGFloat image_width = image ? [image size].width : 0.0; 97 98 // AFAICT, on Windows the choices are "everything" if it fits, then 99 // "image only" if it fits. 100 101 // Entirely too small to fit, omit. 102 if (width < image_width) 103 return kOmittedWidth; 104 105 // Show the full hint if it won't take up too much space. The image 106 // needs to be placed at a pixel boundary, round the text widths so 107 // that any partially-drawn pixels don't look too close (or too 108 // far). 109 CGFloat full_width = 110 std::floor([hint_prefix_ sizeWithAttributes:attributes_].width + 0.5) + 111 kHintImagePadding + image_width + kHintImagePadding + 112 std::floor([hint_suffix_ sizeWithAttributes:attributes_].width + 0.5); 113 if (full_width <= width * kHintAvailableRatio) 114 return full_width; 115 116 return image_width; 117 } 118 119 void KeywordHintDecoration::DrawInFrame(NSRect frame, NSView* control_view) { 120 NSImage* image = GetHintImage(); 121 const CGFloat image_width = image ? [image size].width : 0.0; 122 123 const bool draw_full = NSWidth(frame) > image_width; 124 125 if (draw_full) { 126 NSRect prefix_rect = NSInsetRect(frame, 0.0, kHintTextYInset); 127 const CGFloat prefix_width = 128 [hint_prefix_ sizeWithAttributes:attributes_].width; 129 DCHECK_GE(NSWidth(prefix_rect), prefix_width); 130 [hint_prefix_ drawInRect:prefix_rect withAttributes:attributes_]; 131 132 // The image should be drawn at a pixel boundary, round the prefix 133 // so that partial pixels aren't oddly close (or distant). 134 frame.origin.x += std::floor(prefix_width + 0.5) + kHintImagePadding; 135 frame.size.width -= std::floor(prefix_width + 0.5) + kHintImagePadding; 136 } 137 138 NSRect image_rect = NSInsetRect(frame, 0.0, kHintImageYInset); 139 image_rect.size = [image size]; 140 [image drawInRect:image_rect 141 fromRect:NSZeroRect // Entire image 142 operation:NSCompositeSourceOver 143 fraction:1.0 144 neverFlipped:YES]; 145 frame.origin.x += NSWidth(image_rect); 146 frame.size.width -= NSWidth(image_rect); 147 148 if (draw_full) { 149 NSRect suffix_rect = NSInsetRect(frame, 0.0, kHintTextYInset); 150 const CGFloat suffix_width = 151 [hint_suffix_ sizeWithAttributes:attributes_].width; 152 153 // Right-justify the text within the remaining space, so it 154 // doesn't get too close to the image relative to a following 155 // decoration. 156 suffix_rect.origin.x = NSMaxX(suffix_rect) - suffix_width; 157 DCHECK_GE(NSWidth(suffix_rect), suffix_width); 158 [hint_suffix_ drawInRect:suffix_rect withAttributes:attributes_]; 159 } 160 } 161