Home | History | Annotate | Download | only in location_bar
      1 // Copyright (c) 2012 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 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/mac/foundation_util.h"
      9 #include "base/mac/mac_logging.h"
     10 #include "chrome/browser/search/search.h"
     11 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h"
     12 #import "chrome/browser/ui/cocoa/location_bar/button_decoration.h"
     13 #import "chrome/browser/ui/cocoa/location_bar/location_bar_decoration.h"
     14 #import "extensions/common/feature_switch.h"
     15 #include "grit/theme_resources.h"
     16 #import "third_party/mozilla/NSPasteboard+Utils.h"
     17 #import "ui/base/cocoa/appkit_utils.h"
     18 #import "ui/base/cocoa/nsview_additions.h"
     19 #import "ui/base/cocoa/tracking_area.h"
     20 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
     21 
     22 using extensions::FeatureSwitch;
     23 
     24 namespace {
     25 
     26 // Matches the clipping radius of |GradientButtonCell|.
     27 const CGFloat kCornerRadius = 3.0;
     28 
     29 // How far to inset the left- and right-hand decorations from the field's
     30 // bounds.
     31 const CGFloat kLeftDecorationXOffset = 5.0;
     32 const CGFloat kRightDecorationXOffset = 5.0;
     33 
     34 // The amount of padding on either side reserved for drawing
     35 // decorations.  [Views has |kItemPadding| == 3.]
     36 const CGFloat kDecorationHorizontalPad = 3.0;
     37 
     38 NSString* const kButtonDecorationKey = @"ButtonDecoration";
     39 
     40 const ui::NinePartImageIds kPopupBorderImageIds =
     41     IMAGE_GRID(IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW);
     42 
     43 const ui::NinePartImageIds kNormalBorderImageIds = IMAGE_GRID(IDR_TEXTFIELD);
     44 
     45 // How long to wait for mouse-up on the location icon before assuming
     46 // that the user wants to drag.
     47 const NSTimeInterval kLocationIconDragTimeout = 0.25;
     48 
     49 // Calculate the positions for a set of decorations.  |frame| is the
     50 // overall frame to do layout in, |remaining_frame| will get the
     51 // left-over space.  |all_decorations| is the set of decorations to
     52 // lay out, |decorations| will be set to the decorations which are
     53 // visible and which fit, in the same order as |all_decorations|,
     54 // while |decoration_frames| will be the corresponding frames.
     55 // |x_edge| describes the edge to layout the decorations against
     56 // (|NSMinXEdge| or |NSMaxXEdge|).  |regular_padding| is the padding
     57 // from the edge of |cell_frame| to use when the first visible decoration
     58 // is a regular decoration. |action_padding| is the padding to use when the
     59 // first decoration is a button decoration, ie. the action box button.
     60 // (|kDecorationHorizontalPad| is used between decorations).
     61 void CalculatePositionsHelper(
     62     NSRect frame,
     63     const std::vector<LocationBarDecoration*>& all_decorations,
     64     NSRectEdge x_edge,
     65     CGFloat regular_padding,
     66     CGFloat action_padding,
     67     std::vector<LocationBarDecoration*>* decorations,
     68     std::vector<NSRect>* decoration_frames,
     69     NSRect* remaining_frame) {
     70   DCHECK(x_edge == NSMinXEdge || x_edge == NSMaxXEdge);
     71   DCHECK_EQ(decorations->size(), decoration_frames->size());
     72 
     73   // The initial padding depends on whether the first visible decoration is
     74   // a button or not.
     75   bool is_first_visible_decoration = true;
     76 
     77   for (size_t i = 0; i < all_decorations.size(); ++i) {
     78     if (all_decorations[i]->IsVisible()) {
     79       CGFloat padding = kDecorationHorizontalPad;
     80       if (is_first_visible_decoration) {
     81         padding = all_decorations[i]->AsButtonDecoration() ?
     82             action_padding : regular_padding;
     83         is_first_visible_decoration = false;
     84       }
     85 
     86       NSRect padding_rect, available;
     87 
     88       // Peel off the outside padding.
     89       NSDivideRect(frame, &padding_rect, &available, padding, x_edge);
     90 
     91       // Find out how large the decoration will be in the remaining
     92       // space.
     93       const CGFloat used_width =
     94           all_decorations[i]->GetWidthForSpace(NSWidth(available));
     95 
     96       if (used_width != LocationBarDecoration::kOmittedWidth) {
     97         DCHECK_GT(used_width, 0.0);
     98         NSRect decoration_frame;
     99 
    100         // Peel off the desired width, leaving the remainder in
    101         // |frame|.
    102         NSDivideRect(available, &decoration_frame, &frame,
    103                      used_width, x_edge);
    104 
    105         decorations->push_back(all_decorations[i]);
    106         decoration_frames->push_back(decoration_frame);
    107         DCHECK_EQ(decorations->size(), decoration_frames->size());
    108 
    109         // Adjust padding for between decorations.
    110         padding = kDecorationHorizontalPad;
    111       }
    112     }
    113   }
    114 
    115   DCHECK_EQ(decorations->size(), decoration_frames->size());
    116   *remaining_frame = frame;
    117 }
    118 
    119 // Helper function for calculating placement of decorations w/in the cell.
    120 // |frame| is the cell's boundary rectangle, |remaining_frame| will get any
    121 // space left after decorations are laid out (for text).  |left_decorations| is
    122 // a set of decorations for the left-hand side of the cell, |right_decorations|
    123 // for the right-hand side.  |edge_width| is the width of one vertical edge of
    124 // the omnibox, this depends on whether the display is low DPI or high DPI.
    125 // |decorations| will contain the resulting visible decorations, and
    126 // |decoration_frames| will contain their frames in the same coordinates as
    127 // |frame|.  Decorations will be ordered left to right. As a convenience returns
    128 // the index of the first right-hand decoration.
    129 size_t CalculatePositionsInFrame(
    130     NSRect frame,
    131     const std::vector<LocationBarDecoration*>& left_decorations,
    132     const std::vector<LocationBarDecoration*>& right_decorations,
    133     CGFloat edge_width,
    134     std::vector<LocationBarDecoration*>* decorations,
    135     std::vector<NSRect>* decoration_frames,
    136     NSRect* remaining_frame) {
    137   decorations->clear();
    138   decoration_frames->clear();
    139 
    140   // Layout |left_decorations| against the LHS.
    141   CalculatePositionsHelper(frame, left_decorations, NSMinXEdge,
    142                            kLeftDecorationXOffset, edge_width,
    143                            decorations, decoration_frames, &frame);
    144   DCHECK_EQ(decorations->size(), decoration_frames->size());
    145 
    146   // Capture the number of visible left-hand decorations.
    147   const size_t left_count = decorations->size();
    148 
    149   // Layout |right_decorations| against the RHS.
    150   CalculatePositionsHelper(frame, right_decorations, NSMaxXEdge,
    151                            kRightDecorationXOffset, edge_width, decorations,
    152                            decoration_frames, &frame);
    153   DCHECK_EQ(decorations->size(), decoration_frames->size());
    154 
    155   // Reverse the right-hand decorations so that overall everything is
    156   // sorted left to right.
    157   std::reverse(decorations->begin() + left_count, decorations->end());
    158   std::reverse(decoration_frames->begin() + left_count,
    159                decoration_frames->end());
    160 
    161   *remaining_frame = frame;
    162   return left_count;
    163 }
    164 
    165 }  // namespace
    166 
    167 @interface AutocompleteTextFieldCell ()
    168 // Post an OnSetFocus notification to the observer of |controlView|.
    169 - (void)focusNotificationFor:(NSEvent*)event
    170                       ofView:(AutocompleteTextField*)controlView;
    171 @end
    172 
    173 @implementation AutocompleteTextFieldCell
    174 
    175 @synthesize isPopupMode = isPopupMode_;
    176 
    177 - (CGFloat)topTextFrameOffset {
    178   return 3.0;
    179 }
    180 
    181 - (CGFloat)bottomTextFrameOffset {
    182   return 3.0;
    183 }
    184 
    185 - (CGFloat)cornerRadius {
    186   return kCornerRadius;
    187 }
    188 
    189 - (CGFloat)edgeWidth {
    190   // The omnibox vertical edge width is 1 pixel both in low DPI and high DPI.
    191   return [[self controlView] cr_lineWidth];
    192 }
    193 
    194 - (BOOL)shouldDrawBezel {
    195   return YES;
    196 }
    197 
    198 - (CGFloat)lineHeight {
    199   return 17;
    200 }
    201 
    202 - (void)clearDecorations {
    203   leftDecorations_.clear();
    204   rightDecorations_.clear();
    205 }
    206 
    207 - (void)addLeftDecoration:(LocationBarDecoration*)decoration {
    208   leftDecorations_.push_back(decoration);
    209 }
    210 
    211 - (void)addRightDecoration:(LocationBarDecoration*)decoration {
    212   rightDecorations_.push_back(decoration);
    213 }
    214 
    215 - (CGFloat)availableWidthInFrame:(const NSRect)frame {
    216   std::vector<LocationBarDecoration*> decorations;
    217   std::vector<NSRect> decorationFrames;
    218   NSRect textFrame;
    219   CalculatePositionsInFrame(frame, leftDecorations_, rightDecorations_,
    220                             [self edgeWidth], &decorations, &decorationFrames,
    221                             &textFrame);
    222 
    223   return NSWidth(textFrame);
    224 }
    225 
    226 - (NSRect)frameForDecoration:(const LocationBarDecoration*)aDecoration
    227                      inFrame:(NSRect)cellFrame {
    228   // Short-circuit if the decoration is known to be not visible.
    229   if (aDecoration && !aDecoration->IsVisible())
    230     return NSZeroRect;
    231 
    232   // Layout the decorations.
    233   std::vector<LocationBarDecoration*> decorations;
    234   std::vector<NSRect> decorationFrames;
    235   NSRect textFrame;
    236   CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
    237                             [self edgeWidth], &decorations, &decorationFrames,
    238                             &textFrame);
    239 
    240   // Find our decoration and return the corresponding frame.
    241   std::vector<LocationBarDecoration*>::const_iterator iter =
    242       std::find(decorations.begin(), decorations.end(), aDecoration);
    243   if (iter != decorations.end()) {
    244     const size_t index = iter - decorations.begin();
    245     return decorationFrames[index];
    246   }
    247 
    248   // Decorations which are not visible should have been filtered out
    249   // at the top, but return |NSZeroRect| rather than a 0-width rect
    250   // for consistency.
    251   NOTREACHED();
    252   return NSZeroRect;
    253 }
    254 
    255 // Overriden to account for the decorations.
    256 - (NSRect)textFrameForFrame:(NSRect)cellFrame {
    257   // Get the frame adjusted for decorations.
    258   std::vector<LocationBarDecoration*> decorations;
    259   std::vector<NSRect> decorationFrames;
    260   NSRect textFrame = [super textFrameForFrame:cellFrame];
    261   CalculatePositionsInFrame(textFrame, leftDecorations_, rightDecorations_,
    262                             [self edgeWidth], &decorations, &decorationFrames,
    263                             &textFrame);
    264 
    265   // NOTE: This function must closely match the logic in
    266   // |-drawInteriorWithFrame:inView:|.
    267 
    268   return textFrame;
    269 }
    270 
    271 // Returns the sub-frame where clicks can happen within the cell.
    272 - (NSRect)clickableFrameForFrame:(NSRect)cellFrame {
    273   return [super textFrameForFrame:cellFrame];
    274 }
    275 
    276 - (NSRect)textCursorFrameForFrame:(NSRect)cellFrame {
    277   std::vector<LocationBarDecoration*> decorations;
    278   std::vector<NSRect> decorationFrames;
    279   NSRect textFrame;
    280   size_t left_count =
    281       CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
    282                                 [self edgeWidth], &decorations,
    283                                 &decorationFrames, &textFrame);
    284 
    285   // Determine the left-most extent for the i-beam cursor.
    286   CGFloat minX = NSMinX(textFrame);
    287   for (size_t index = left_count; index--; ) {
    288     if (decorations[index]->AcceptsMousePress())
    289       break;
    290 
    291     // If at leftmost decoration, expand to edge of cell.
    292     if (!index) {
    293       minX = NSMinX(cellFrame);
    294     } else {
    295       minX = NSMinX(decorationFrames[index]) - kDecorationHorizontalPad;
    296     }
    297   }
    298 
    299   // Determine the right-most extent for the i-beam cursor.
    300   CGFloat maxX = NSMaxX(textFrame);
    301   for (size_t index = left_count; index < decorations.size(); ++index) {
    302     if (decorations[index]->AcceptsMousePress())
    303       break;
    304 
    305     // If at rightmost decoration, expand to edge of cell.
    306     if (index == decorations.size() - 1) {
    307       maxX = NSMaxX(cellFrame);
    308     } else {
    309       maxX = NSMaxX(decorationFrames[index]) + kDecorationHorizontalPad;
    310     }
    311   }
    312 
    313   // I-beam cursor covers left-most to right-most.
    314   return NSMakeRect(minX, NSMinY(textFrame), maxX - minX, NSHeight(textFrame));
    315 }
    316 
    317 - (void)drawWithFrame:(NSRect)frame inView:(NSView*)controlView {
    318   // Background color.
    319   const CGFloat lineWidth = [controlView cr_lineWidth];
    320   if (isPopupMode_) {
    321     [[self backgroundColor] set];
    322     NSRectFillUsingOperation(NSInsetRect(frame, 1, 1), NSCompositeSourceOver);
    323   } else {
    324     CGFloat insetSize = lineWidth == 0.5 ? 1.5 : 2.0;
    325     NSRect fillRect = NSInsetRect(frame, insetSize, insetSize);
    326     [[self backgroundColor] set];
    327     [[NSBezierPath bezierPathWithRoundedRect:fillRect
    328                                      xRadius:kCornerRadius
    329                                      yRadius:kCornerRadius] fill];
    330   }
    331 
    332   // Border.
    333   ui::DrawNinePartImage(frame,
    334                         isPopupMode_ ? kPopupBorderImageIds
    335                                      : kNormalBorderImageIds,
    336                         NSCompositeSourceOver,
    337                         1.0,
    338                         true);
    339 
    340   // Interior contents. Drawn after the border as some of the interior controls
    341   // draw over the border.
    342   [self drawInteriorWithFrame:frame inView:controlView];
    343 
    344   // Focus ring.
    345   if ([self showsFirstResponder]) {
    346     NSRect focusRingRect = NSInsetRect(frame, lineWidth, lineWidth);
    347     [[[NSColor keyboardFocusIndicatorColor]
    348         colorWithAlphaComponent:0.5 / lineWidth] set];
    349     NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:focusRingRect
    350                                                          xRadius:kCornerRadius
    351                                                          yRadius:kCornerRadius];
    352     [path setLineWidth:lineWidth * 2.0];
    353     [path stroke];
    354   }
    355 }
    356 
    357 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
    358   std::vector<LocationBarDecoration*> decorations;
    359   std::vector<NSRect> decorationFrames;
    360   NSRect workingFrame;
    361 
    362   CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
    363                             [self edgeWidth], &decorations, &decorationFrames,
    364                             &workingFrame);
    365 
    366   // Draw the decorations.
    367   for (size_t i = 0; i < decorations.size(); ++i) {
    368     if (decorations[i]) {
    369       NSRect background_frame = NSInsetRect(
    370           decorationFrames[i], -(kDecorationHorizontalPad + 1) / 2, 2);
    371       decorations[i]->DrawWithBackgroundInFrame(
    372           background_frame, decorationFrames[i], controlView);
    373     }
    374   }
    375 
    376   // NOTE: This function must closely match the logic in
    377   // |-textFrameForFrame:|.
    378 
    379   // Superclass draws text portion WRT original |cellFrame|.
    380   // Even though -isOpaque is NO due to rounded corners, we know that the text
    381   // will be drawn on top of an opaque area, therefore it is safe to enable
    382   // font smoothing.
    383   {
    384     gfx::ScopedNSGraphicsContextSaveGState scopedGState;
    385     NSGraphicsContext* context = [NSGraphicsContext currentContext];
    386     CGContextRef cgContext = static_cast<CGContextRef>([context graphicsPort]);
    387     CGContextSetShouldSmoothFonts(cgContext, true);
    388     [super drawInteriorWithFrame:cellFrame inView:controlView];
    389   }
    390 }
    391 
    392 - (LocationBarDecoration*)decorationForEvent:(NSEvent*)theEvent
    393                                       inRect:(NSRect)cellFrame
    394                                       ofView:(AutocompleteTextField*)controlView
    395 {
    396   const BOOL flipped = [controlView isFlipped];
    397   const NSPoint location =
    398       [controlView convertPoint:[theEvent locationInWindow] fromView:nil];
    399 
    400   std::vector<LocationBarDecoration*> decorations;
    401   std::vector<NSRect> decorationFrames;
    402   NSRect textFrame;
    403   CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
    404                             [self edgeWidth], &decorations, &decorationFrames,
    405                             &textFrame);
    406 
    407   for (size_t i = 0; i < decorations.size(); ++i) {
    408     if (NSMouseInRect(location, decorationFrames[i], flipped))
    409       return decorations[i];
    410   }
    411 
    412   return NULL;
    413 }
    414 
    415 - (NSMenu*)decorationMenuForEvent:(NSEvent*)theEvent
    416                            inRect:(NSRect)cellFrame
    417                            ofView:(AutocompleteTextField*)controlView {
    418   LocationBarDecoration* decoration =
    419       [self decorationForEvent:theEvent inRect:cellFrame ofView:controlView];
    420   if (decoration)
    421     return decoration->GetMenu();
    422   return nil;
    423 }
    424 
    425 - (BOOL)mouseDown:(NSEvent*)theEvent
    426            inRect:(NSRect)cellFrame
    427            ofView:(AutocompleteTextField*)controlView {
    428   // TODO(groby): Factor this into three pieces - find target for event, handle
    429   // delayed focus (for any and all events), execute mouseDown for target.
    430 
    431   // Check if this mouseDown was the reason the control became firstResponder.
    432   // If not, discard focus event.
    433   base::scoped_nsobject<NSEvent> focusEvent(focusEvent_.release());
    434   if (![theEvent isEqual:focusEvent])
    435     focusEvent.reset();
    436 
    437   LocationBarDecoration* decoration =
    438       [self decorationForEvent:theEvent inRect:cellFrame ofView:controlView];
    439   if (!decoration || !decoration->AcceptsMousePress())
    440     return NO;
    441 
    442   NSRect decorationRect =
    443       [self frameForDecoration:decoration inFrame:cellFrame];
    444 
    445   // If the decoration is draggable, then initiate a drag if the user
    446   // drags or holds the mouse down for awhile.
    447   if (decoration->IsDraggable()) {
    448     NSDate* timeout =
    449         [NSDate dateWithTimeIntervalSinceNow:kLocationIconDragTimeout];
    450     NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask |
    451                                                    NSLeftMouseUpMask)
    452                                         untilDate:timeout
    453                                            inMode:NSEventTrackingRunLoopMode
    454                                           dequeue:YES];
    455     if (!event || [event type] == NSLeftMouseDragged) {
    456       NSPasteboard* pboard = decoration->GetDragPasteboard();
    457       DCHECK(pboard);
    458 
    459       NSImage* image = decoration->GetDragImage();
    460       DCHECK(image);
    461 
    462       NSRect dragImageRect = decoration->GetDragImageFrame(decorationRect);
    463 
    464       // Center under mouse horizontally, with cursor below so the image
    465       // can be seen.
    466       const NSPoint mousePoint =
    467           [controlView convertPoint:[theEvent locationInWindow] fromView:nil];
    468       dragImageRect.origin =
    469           NSMakePoint(mousePoint.x - NSWidth(dragImageRect) / 2.0,
    470                       mousePoint.y - NSHeight(dragImageRect));
    471 
    472       // -[NSView dragImage:at:*] wants the images lower-left point,
    473       // regardless of -isFlipped.  Converting the rect to window base
    474       // coordinates doesn't require any special-casing.  Note that
    475       // -[NSView dragFile:fromRect:*] takes a rect rather than a
    476       // point, likely for this exact reason.
    477       const NSPoint dragPoint =
    478           [controlView convertRect:dragImageRect toView:nil].origin;
    479       [[controlView window] dragImage:image
    480                                    at:dragPoint
    481                                offset:NSZeroSize
    482                                 event:theEvent
    483                            pasteboard:pboard
    484                                source:self
    485                             slideBack:YES];
    486 
    487       return YES;
    488     }
    489 
    490     // On mouse-up fall through to mouse-pressed case.
    491     DCHECK_EQ([event type], NSLeftMouseUp);
    492   }
    493 
    494   bool handled;
    495   if (decoration->AsButtonDecoration()) {
    496     ButtonDecoration* button = decoration->AsButtonDecoration();
    497 
    498     button->SetButtonState(ButtonDecoration::kButtonStatePressed);
    499     [controlView setNeedsDisplay:YES];
    500 
    501     // Track the mouse until the user releases the button.
    502     [self trackMouse:theEvent
    503               inRect:decorationRect
    504               ofView:controlView
    505         untilMouseUp:YES];
    506 
    507     const NSPoint mouseLocation = [[NSApp currentEvent] locationInWindow];
    508     const NSPoint point = [controlView convertPoint:mouseLocation fromView:nil];
    509 
    510     // Post delayed focus notification, if necessary.
    511     if (focusEvent.get() && !button->PreventFocus(point))
    512       [self focusNotificationFor:focusEvent ofView:controlView];
    513     focusEvent.reset();
    514 
    515     // Set the proper state (hover or normal) once the mouse has been released,
    516     // and call |OnMousePressed| if the button was released while the mouse was
    517     // within the bounds of the button.
    518     if (NSMouseInRect(point, decorationRect, [controlView isFlipped])) {
    519       button->SetButtonState(ButtonDecoration::kButtonStateHover);
    520       [controlView setNeedsDisplay:YES];
    521       handled = decoration->OnMousePressed(
    522           [self frameForDecoration:decoration inFrame:cellFrame],
    523           NSMakePoint(point.x - decorationRect.origin.x,
    524                       point.y - decorationRect.origin.y));
    525     } else {
    526       button->SetButtonState(ButtonDecoration::kButtonStateNormal);
    527       [controlView setNeedsDisplay:YES];
    528       handled = true;
    529     }
    530   } else {
    531     const NSPoint mouseLocation = [theEvent locationInWindow];
    532     const NSPoint point = [controlView convertPoint:mouseLocation fromView:nil];
    533     handled = decoration->OnMousePressed(
    534         decorationRect,
    535         NSMakePoint(point.x - decorationRect.origin.x,
    536                     point.y - decorationRect.origin.y));
    537   }
    538 
    539   return handled ? YES : NO;
    540 }
    541 
    542 // Helper method for the |mouseEntered:inView:| and |mouseExited:inView:|
    543 // messages. Retrieves the |ButtonDecoration| for the specified event (received
    544 // from a tracking area), and returns |NULL| if no decoration matches.
    545 - (ButtonDecoration*)getButtonDecorationForEvent:(NSEvent*)theEvent {
    546   ButtonDecoration* bd = static_cast<ButtonDecoration*>(
    547       [[[[theEvent trackingArea] userInfo] valueForKey:kButtonDecorationKey]
    548           pointerValue]);
    549 
    550   CHECK(!bd ||
    551       std::count(leftDecorations_.begin(), leftDecorations_.end(), bd) ||
    552       std::count(rightDecorations_.begin(), rightDecorations_.end(), bd));
    553 
    554   return bd;
    555 }
    556 
    557 // Helper method for |setUpTrackingAreasInView|. Creates an |NSDictionary| to
    558 // be used as user information to identify which decoration is the source of an
    559 // event (from a tracking area).
    560 - (NSDictionary*)getDictionaryForButtonDecoration:
    561     (ButtonDecoration*)decoration {
    562   if (!decoration)
    563     return nil;
    564 
    565   DCHECK(
    566     std::count(leftDecorations_.begin(), leftDecorations_.end(), decoration) ||
    567     std::count(rightDecorations_.begin(), rightDecorations_.end(), decoration));
    568 
    569   return [NSDictionary
    570       dictionaryWithObject:[NSValue valueWithPointer:decoration]
    571                     forKey:kButtonDecorationKey];
    572 }
    573 
    574 - (void)mouseEntered:(NSEvent*)theEvent
    575               inView:(AutocompleteTextField*)controlView {
    576   ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
    577   if (decoration) {
    578     decoration->SetButtonState(ButtonDecoration::kButtonStateHover);
    579     [controlView setNeedsDisplay:YES];
    580   }
    581 }
    582 
    583 - (void)mouseExited:(NSEvent*)theEvent
    584              inView:(AutocompleteTextField*)controlView {
    585   ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
    586   if (decoration) {
    587     decoration->SetButtonState(ButtonDecoration::kButtonStateNormal);
    588     [controlView setNeedsDisplay:YES];
    589   }
    590 }
    591 
    592 - (void)setUpTrackingAreasInRect:(NSRect)frame
    593                           ofView:(AutocompleteTextField*)view {
    594   std::vector<LocationBarDecoration*> decorations;
    595   std::vector<NSRect> decorationFrames;
    596   NSRect textFrame;
    597   NSRect cellRect = [self clickableFrameForFrame:[view bounds]];
    598   CalculatePositionsInFrame(cellRect, leftDecorations_, rightDecorations_,
    599                             [self edgeWidth], &decorations, &decorationFrames,
    600                             &textFrame);
    601 
    602   // Remove previously-registered tracking areas, since we'll update them below.
    603   for (CrTrackingArea* area in [view trackingAreas]) {
    604     if ([[area userInfo] objectForKey:kButtonDecorationKey])
    605       [view removeTrackingArea:area];
    606   }
    607 
    608   // Setup new tracking areas for the buttons.
    609   for (size_t i = 0; i < decorations.size(); ++i) {
    610     ButtonDecoration* button = decorations[i]->AsButtonDecoration();
    611     if (button) {
    612       // If the button isn't pressed (in which case we want to leave it as-is),
    613       // update it's state since we might have missed some entered/exited events
    614       // because of the removing/adding of the tracking areas.
    615       if (button->GetButtonState() !=
    616           ButtonDecoration::kButtonStatePressed) {
    617         const NSPoint mouseLocationWindow =
    618             [[view window] mouseLocationOutsideOfEventStream];
    619         const NSPoint mouseLocation =
    620             [view convertPoint:mouseLocationWindow fromView:nil];
    621         const BOOL mouseInRect = NSMouseInRect(
    622             mouseLocation, decorationFrames[i], [view isFlipped]);
    623         button->SetButtonState(mouseInRect ?
    624                                    ButtonDecoration::kButtonStateHover :
    625                                    ButtonDecoration::kButtonStateNormal);
    626         [view setNeedsDisplay:YES];
    627       }
    628 
    629       NSDictionary* info = [self getDictionaryForButtonDecoration:button];
    630       base::scoped_nsobject<CrTrackingArea> area(
    631           [[CrTrackingArea alloc] initWithRect:decorationFrames[i]
    632                                        options:NSTrackingMouseEnteredAndExited |
    633                                                NSTrackingActiveAlways
    634                                          owner:view
    635                                       userInfo:info]);
    636       [view addTrackingArea:area];
    637     }
    638   }
    639 }
    640 
    641 // Given a newly created .webloc plist url file, also give it a resource
    642 // fork and insert 'TEXT and 'url ' resources holding further copies of the
    643 // url data. This is required for apps such as Terminal and Safari to accept it
    644 // as a real webloc file when dragged in.
    645 // It's expected that the resource fork requirement will go away at some
    646 // point and this code can then be deleted.
    647 OSErr WriteURLToNewWebLocFileResourceFork(NSURL* file, NSString* urlStr) {
    648   ResFileRefNum refNum = kResFileNotOpened;
    649   ResFileRefNum prevResRef = CurResFile();
    650   FSRef fsRef;
    651   OSErr err = noErr;
    652   HFSUniStr255 resourceForkName;
    653   FSGetResourceForkName(&resourceForkName);
    654 
    655   if (![[NSFileManager defaultManager] fileExistsAtPath:[file path]])
    656     return fnfErr;
    657 
    658   if (!CFURLGetFSRef((CFURLRef)file, &fsRef))
    659     return fnfErr;
    660 
    661   err = FSCreateResourceFork(&fsRef,
    662                              resourceForkName.length,
    663                              resourceForkName.unicode,
    664                              0);
    665   if (err)
    666     return err;
    667   err = FSOpenResourceFile(&fsRef,
    668                            resourceForkName.length,
    669                            resourceForkName.unicode,
    670                            fsRdWrPerm, &refNum);
    671   if (err)
    672     return err;
    673 
    674   const char* utf8URL = [urlStr UTF8String];
    675   int urlChars = strlen(utf8URL);
    676 
    677   Handle urlHandle = NewHandle(urlChars);
    678   memcpy(*urlHandle, utf8URL, urlChars);
    679 
    680   Handle textHandle = NewHandle(urlChars);
    681   memcpy(*textHandle, utf8URL, urlChars);
    682 
    683   // Data for the 'drag' resource.
    684   // This comes from derezzing webloc files made by the Finder.
    685   // It's bigendian data, so it's represented here as chars to preserve
    686   // byte order.
    687   char dragData[] = {
    688     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Header.
    689     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
    690     0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x01, 0x00, // 'TEXT', 0, 256
    691     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    692     0x75, 0x72, 0x6C, 0x20, 0x00, 0x00, 0x01, 0x00, // 'url ', 0, 256
    693     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    694   };
    695   Handle dragHandle = NewHandleClear(sizeof(dragData));
    696   memcpy(*dragHandle, &dragData[0], sizeof(dragData));
    697 
    698   // Save the resources to the file.
    699   ConstStr255Param noName = {0};
    700   AddResource(dragHandle, 'drag', 128, noName);
    701   AddResource(textHandle, 'TEXT', 256, noName);
    702   AddResource(urlHandle, 'url ', 256, noName);
    703 
    704   CloseResFile(refNum);
    705   UseResFile(prevResRef);
    706   return noErr;
    707 }
    708 
    709 // Returns the file path for file |name| if saved at NSURL |base|.
    710 static NSString* PathWithBaseURLAndName(NSURL* base, NSString* name) {
    711   NSString* filteredName =
    712       [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    713   return [[NSURL URLWithString:filteredName relativeToURL:base] path];
    714 }
    715 
    716 // Returns if there is already a file |name| at dir NSURL |base|.
    717 static BOOL FileAlreadyExists(NSURL* base, NSString* name) {
    718   NSString* path = PathWithBaseURLAndName(base, name);
    719   DCHECK([path hasSuffix:name]);
    720   return [[NSFileManager defaultManager] fileExistsAtPath:path];
    721 }
    722 
    723 // Takes a destination URL, a suggested file name, & an extension (eg .webloc).
    724 // Returns the complete file name with extension you should use.
    725 // The name returned will not contain /, : or ?, will not be longer than
    726 // kMaxNameLength + length of extension, and will not be a file name that
    727 // already exists in that directory. If necessary it will try appending a space
    728 // and a number to the name (but before the extension) trying numbers up to and
    729 // including kMaxIndex.
    730 // If the function gives up it returns nil.
    731 static NSString* UnusedLegalNameForNewDropFile(NSURL* saveLocation,
    732                                                NSString *fileName,
    733                                                NSString *extension) {
    734   int number = 1;
    735   const int kMaxIndex = 20;
    736   const unsigned kMaxNameLength = 64; // Arbitrary.
    737 
    738   NSString* filteredName = [fileName stringByReplacingOccurrencesOfString:@"/"
    739                                                                withString:@"-"];
    740   filteredName = [filteredName stringByReplacingOccurrencesOfString:@":"
    741                                                          withString:@"-"];
    742   filteredName = [filteredName stringByReplacingOccurrencesOfString:@"?"
    743                                                          withString:@"-"];
    744 
    745   if ([filteredName length] > kMaxNameLength)
    746     filteredName = [filteredName substringToIndex:kMaxNameLength];
    747 
    748   NSString* candidateName = [filteredName stringByAppendingString:extension];
    749 
    750   while (FileAlreadyExists(saveLocation, candidateName)) {
    751     if (number > kMaxIndex)
    752       return nil;
    753     else
    754       candidateName = [filteredName stringByAppendingFormat:@" %d%@",
    755                        number++, extension];
    756   }
    757 
    758   return candidateName;
    759 }
    760 
    761 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination {
    762   NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
    763   NSFileManager* fileManager = [NSFileManager defaultManager];
    764 
    765   if (![pboard containsURLData])
    766     return NULL;
    767 
    768   NSArray *urls = NULL;
    769   NSArray* titles = NULL;
    770   [pboard getURLs:&urls andTitles:&titles convertingFilenames:YES];
    771 
    772   NSString* urlStr = [urls objectAtIndex:0];
    773   NSString* nameStr = [titles objectAtIndex:0];
    774 
    775   NSString* nameWithExtensionStr =
    776       UnusedLegalNameForNewDropFile(dropDestination, nameStr, @".webloc");
    777   if (!nameWithExtensionStr)
    778     return NULL;
    779 
    780   NSDictionary* urlDict = [NSDictionary dictionaryWithObject:urlStr
    781                                                       forKey:@"URL"];
    782   NSURL* outputURL =
    783       [NSURL fileURLWithPath:PathWithBaseURLAndName(dropDestination,
    784                                                     nameWithExtensionStr)];
    785   [urlDict writeToURL:outputURL
    786            atomically:NO];
    787 
    788   if (![fileManager fileExistsAtPath:[outputURL path]])
    789     return NULL;
    790 
    791   NSDictionary* attr = [NSDictionary dictionaryWithObjectsAndKeys:
    792       [NSNumber numberWithBool:YES], NSFileExtensionHidden,
    793       [NSNumber numberWithUnsignedLong:'ilht'], NSFileHFSTypeCode,
    794       [NSNumber numberWithUnsignedLong:'MACS'], NSFileHFSCreatorCode,
    795       nil];
    796   [fileManager setAttributes:attr
    797                 ofItemAtPath:[outputURL path]
    798                        error:nil];
    799   // Add resource data.
    800   OSErr resStatus = WriteURLToNewWebLocFileResourceFork(outputURL, urlStr);
    801   OSSTATUS_DCHECK(resStatus == noErr, resStatus);
    802 
    803   return [NSArray arrayWithObject:nameWithExtensionStr];
    804 }
    805 
    806 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
    807   return NSDragOperationCopy;
    808 }
    809 
    810 - (void)updateToolTipsInRect:(NSRect)cellFrame
    811                       ofView:(AutocompleteTextField*)controlView {
    812   std::vector<LocationBarDecoration*> decorations;
    813   std::vector<NSRect> decorationFrames;
    814   NSRect textFrame;
    815   CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
    816                             [self edgeWidth], &decorations, &decorationFrames,
    817                             &textFrame);
    818 
    819   for (size_t i = 0; i < decorations.size(); ++i) {
    820     NSString* tooltip = decorations[i]->GetToolTip();
    821     if ([tooltip length] > 0)
    822       [controlView addToolTip:tooltip forRect:decorationFrames[i]];
    823   }
    824 }
    825 
    826 - (BOOL)hideFocusState {
    827   return hideFocusState_;
    828 }
    829 
    830 - (void)setHideFocusState:(BOOL)hideFocusState
    831                    ofView:(AutocompleteTextField*)controlView {
    832   if (hideFocusState_ == hideFocusState)
    833     return;
    834   hideFocusState_ = hideFocusState;
    835   [controlView setNeedsDisplay:YES];
    836   NSTextView* fieldEditor =
    837       base::mac::ObjCCastStrict<NSTextView>([controlView currentEditor]);
    838   [fieldEditor updateInsertionPointStateAndRestartTimer:YES];
    839 }
    840 
    841 - (BOOL)showsFirstResponder {
    842   return [super showsFirstResponder] && !hideFocusState_;
    843 }
    844 
    845 - (void)focusNotificationFor:(NSEvent*)event
    846                       ofView:(AutocompleteTextField*)controlView {
    847   if ([controlView observer]) {
    848     const bool controlDown = ([event modifierFlags] & NSControlKeyMask) != 0;
    849     [controlView observer]->OnSetFocus(controlDown);
    850   }
    851 }
    852 
    853 - (void)handleFocusEvent:(NSEvent*)event
    854                   ofView:(AutocompleteTextField*)controlView {
    855   // Only intercept left button click. All other events cause immediate focus.
    856   if ([event type] == NSLeftMouseDown) {
    857     LocationBarDecoration* decoration =
    858         [self decorationForEvent:event
    859                           inRect:[controlView bounds]
    860                           ofView:controlView];
    861     // Only ButtonDecorations need a delayed focus handling.
    862     if (decoration && decoration->AsButtonDecoration()) {
    863       focusEvent_.reset([event retain]);
    864       return;
    865     }
    866   }
    867 
    868   // Handle event immediately.
    869   [self focusNotificationFor:event ofView:controlView];
    870 }
    871 
    872 @end
    873