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