Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / location_bar / autocomplete_text_field_cell.mm
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