Home | History | Annotate | Download | only in location_bar
      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