Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / location_bar / autocomplete_text_field_cell.mm
blob8fe3571ca45e3858588965287e0d02bc2d8fed17
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.
5 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h"
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 "extensions/common/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"
22 using extensions::FeatureSwitch;
24 namespace {
26 // Matches the clipping radius of |GradientButtonCell|.
27 const CGFloat kCornerRadius = 3.0;
29 // How far to inset the left-hand decorations from the field's bounds.
30 const CGFloat kLeftDecorationXOffset = 5.0;
32 NSString* const kButtonDecorationKey = @"ButtonDecoration";
34 const ui::NinePartImageIds kPopupBorderImageIds = {
35   IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_TOP_LEFT,
36   IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_TOP,
37   IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_TOP_RIGHT,
38   IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_LEFT,
39   IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_CENTER,
40   IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_RIGHT,
41   IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_BOTTOM_LEFT,
42   IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_BOTTOM,
43   IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_BOTTOM_RIGHT
46 const ui::NinePartImageIds kNormalBorderImageIds = {
47   IDR_OMNIBOX_BORDER_AND_SHADOW_TOP_LEFT,
48   IDR_OMNIBOX_BORDER_AND_SHADOW_TOP,
49   IDR_OMNIBOX_BORDER_AND_SHADOW_TOP_RIGHT,
50   IDR_OMNIBOX_BORDER_AND_SHADOW_LEFT,
51   IDR_OMNIBOX_BORDER_AND_SHADOW_CENTER,
52   IDR_OMNIBOX_BORDER_AND_SHADOW_RIGHT,
53   IDR_OMNIBOX_BORDER_AND_SHADOW_BOTTOM_LEFT,
54   IDR_OMNIBOX_BORDER_AND_SHADOW_BOTTOM,
55   IDR_OMNIBOX_BORDER_AND_SHADOW_BOTTOM_RIGHT
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;
65   if (FeatureSwitch::script_badges()->IsEnabled()) {
66     return kScriptBadgeRightDecorationXOffset;
67   } else {
68     return kRightDecorationXOffset;
69   }
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;
78   return FeatureSwitch::script_badges()->IsEnabled() ?
79       kScriptBadgeDecorationHorizontalPad : kDecorationHorizontalPad;
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;
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());
110   // The initial padding depends on whether the first visible decoration is
111   // a button or not.
112   bool is_first_visible_decoration = true;
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       }
123       NSRect padding_rect, available;
125       // Peel off the outside padding.
126       NSDivideRect(frame, &padding_rect, &available, padding, x_edge);
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));
133       if (used_width != LocationBarDecoration::kOmittedWidth) {
134         DCHECK_GT(used_width, 0.0);
135         NSRect decoration_frame;
137         // Peel off the desired width, leaving the remainder in
138         // |frame|.
139         NSDivideRect(available, &decoration_frame, &frame,
140                      used_width, x_edge);
142         decorations->push_back(all_decorations[i]);
143         decoration_frames->push_back(decoration_frame);
144         DCHECK_EQ(decorations->size(), decoration_frames->size());
146         // Adjust padding for between decorations.
147         padding = DecorationHorizontalPad();
148       }
149     }
150   }
152   DCHECK_EQ(decorations->size(), decoration_frames->size());
153   *remaining_frame = frame;
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();
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());
183   // Capture the number of visible left-hand decorations.
184   const size_t left_count = decorations->size();
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());
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());
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;
210 }  // namespace
212 @implementation AutocompleteTextFieldCell
214 @synthesize isPopupMode = isPopupMode_;
216 - (CGFloat)topTextFrameOffset {
217   return 3.0;
220 - (CGFloat)bottomTextFrameOffset {
221   return 3.0;
224 - (CGFloat)cornerRadius {
225   return kCornerRadius;
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];
233 - (BOOL)shouldDrawBezel {
234   return YES;
237 - (CGFloat)lineHeight {
238   return 17;
241 - (void)clearDecorations {
242   leftDecorations_.clear();
243   rightDecorations_.clear();
246 - (void)addLeftDecoration:(LocationBarDecoration*)decoration {
247   leftDecorations_.push_back(decoration);
250 - (void)addRightDecoration:(LocationBarDecoration*)decoration {
251   rightDecorations_.push_back(decoration);
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);
262   return NSWidth(textFrame);
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;
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);
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   }
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;
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);
304   // NOTE: This function must closely match the logic in
305   // |-drawInteriorWithFrame:inView:|.
307   return textFrame;
310 // Returns the sub-frame where clicks can happen within the cell.
311 - (NSRect)clickableFrameForFrame:(NSRect)cellFrame {
312   return [super textFrameForFrame:cellFrame];
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);
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;
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   }
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;
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   }
352   // I-beam cursor covers left-most to right-most.
353   return NSMakeRect(minX, NSMinY(textFrame), maxX - minX, NSHeight(textFrame));
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   }
371   // Interior contents.
372   [self drawInteriorWithFrame:frame inView:controlView];
374   // Border.
375   ui::DrawNinePartImage(frame,
376                         isPopupMode_ ? kPopupBorderImageIds
377                                      : kNormalBorderImageIds,
378                         NSCompositeSourceOver,
379                         1.0,
380                         true);
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   }
395 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
396   std::vector<LocationBarDecoration*> decorations;
397   std::vector<NSRect> decorationFrames;
398   NSRect workingFrame;
400   CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
401                             [self edgeWidth], &decorations, &decorationFrames,
402                             &workingFrame);
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   }
414   // NOTE: This function must closely match the logic in
415   // |-textFrameForFrame:|.
417   // Superclass draws text portion WRT original |cellFrame|.
418   [super drawInteriorWithFrame:cellFrame inView:controlView];
421 - (LocationBarDecoration*)decorationForEvent:(NSEvent*)theEvent
422                                       inRect:(NSRect)cellFrame
423                                       ofView:(AutocompleteTextField*)controlView
425   const BOOL flipped = [controlView isFlipped];
426   const NSPoint location =
427       [controlView convertPoint:[theEvent locationInWindow] fromView:nil];
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);
436   for (size_t i = 0; i < decorations.size(); ++i) {
437     if (NSMouseInRect(location, decorationFrames[i], flipped))
438       return decorations[i];
439   }
441   return NULL;
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;
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;
462   NSRect decorationRect =
463       [self frameForDecoration:decoration inFrame:cellFrame];
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);
479       NSImage* image = decoration->GetDragImage();
480       DCHECK(image);
482       NSRect dragImageRect = decoration->GetDragImageFrame(decorationRect);
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));
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];
507       return YES;
508     }
510     // On mouse-up fall through to mouse-pressed case.
511     DCHECK_EQ([event type], NSLeftMouseUp);
512   }
514   bool handled;
515   if (decoration->AsButtonDecoration()) {
516     ButtonDecoration* button = decoration->AsButtonDecoration();
518     button->SetButtonState(ButtonDecoration::kButtonStatePressed);
519     [controlView setNeedsDisplay:YES];
521     // Track the mouse until the user releases the button.
522     [self trackMouse:theEvent
523               inRect:cellFrame
524               ofView:controlView
525         untilMouseUp:YES];
527     // Set the proper state (hover or normal) once the mouse has been released,
528     // and call |OnMousePressed| if the button was released while the mouse was
529     // within the bounds of the button.
530     const NSPoint mouseLocation =
531         [[controlView window] mouseLocationOutsideOfEventStream];
532     const NSPoint point = [controlView convertPoint:mouseLocation fromView:nil];
533     if (NSMouseInRect(point, cellFrame, [controlView isFlipped])) {
534       button->SetButtonState(ButtonDecoration::kButtonStateHover);
535       [controlView setNeedsDisplay:YES];
536       handled = decoration->AsButtonDecoration()->OnMousePressed(
537           [self frameForDecoration:decoration inFrame:cellFrame]);
538     } else {
539       button->SetButtonState(ButtonDecoration::kButtonStateNormal);
540       [controlView setNeedsDisplay:YES];
541       handled = true;
542     }
543   } else {
544     handled = decoration->OnMousePressed(decorationRect);
545   }
547   return handled ? YES : NO;
550 // Helper method for the |mouseEntered:inView:| and |mouseExited:inView:|
551 // messages. Retrieves the |ButtonDecoration| for the specified event (received
552 // from a tracking area), and returns |NULL| if no decoration matches.
553 - (ButtonDecoration*)getButtonDecorationForEvent:(NSEvent*)theEvent {
554   ButtonDecoration* bd = static_cast<ButtonDecoration*>(
555       [[[[theEvent trackingArea] userInfo] valueForKey:kButtonDecorationKey]
556           pointerValue]);
558   CHECK(!bd ||
559       std::count(leftDecorations_.begin(), leftDecorations_.end(), bd) ||
560       std::count(rightDecorations_.begin(), rightDecorations_.end(), bd));
562   return bd;
565 // Helper method for |setUpTrackingAreasInView|. Creates an |NSDictionary| to
566 // be used as user information to identify which decoration is the source of an
567 // event (from a tracking area).
568 - (NSDictionary*)getDictionaryForButtonDecoration:
569     (ButtonDecoration*)decoration {
570   if (!decoration)
571     return nil;
573   DCHECK(
574     std::count(leftDecorations_.begin(), leftDecorations_.end(), decoration) ||
575     std::count(rightDecorations_.begin(), rightDecorations_.end(), decoration));
577   return [NSDictionary
578       dictionaryWithObject:[NSValue valueWithPointer:decoration]
579                     forKey:kButtonDecorationKey];
582 - (void)mouseEntered:(NSEvent*)theEvent
583               inView:(AutocompleteTextField*)controlView {
584   ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
585   if (decoration) {
586     decoration->SetButtonState(ButtonDecoration::kButtonStateHover);
587     [controlView setNeedsDisplay:YES];
588   }
591 - (void)mouseExited:(NSEvent*)theEvent
592              inView:(AutocompleteTextField*)controlView {
593   ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
594   if (decoration) {
595     decoration->SetButtonState(ButtonDecoration::kButtonStateNormal);
596     [controlView setNeedsDisplay:YES];
597   }
600 - (void)setUpTrackingAreasInRect:(NSRect)frame
601                           ofView:(AutocompleteTextField*)view {
602   std::vector<LocationBarDecoration*> decorations;
603   std::vector<NSRect> decorationFrames;
604   NSRect textFrame;
605   NSRect cellRect = [self clickableFrameForFrame:[view bounds]];
606   CalculatePositionsInFrame(cellRect, leftDecorations_, rightDecorations_,
607                             [self edgeWidth], &decorations, &decorationFrames,
608                             &textFrame);
610   // Remove previously-registered tracking areas, since we'll update them below.
611   for (CrTrackingArea* area in [view trackingAreas]) {
612     if ([[area userInfo] objectForKey:kButtonDecorationKey])
613       [view removeTrackingArea:area];
614   }
616   // Setup new tracking areas for the buttons.
617   for (size_t i = 0; i < decorations.size(); ++i) {
618     ButtonDecoration* button = decorations[i]->AsButtonDecoration();
619     if (button) {
620       // If the button isn't pressed (in which case we want to leave it as-is),
621       // update it's state since we might have missed some entered/exited events
622       // because of the removing/adding of the tracking areas.
623       if (button->GetButtonState() !=
624           ButtonDecoration::kButtonStatePressed) {
625         const NSPoint mouseLocationWindow =
626             [[view window] mouseLocationOutsideOfEventStream];
627         const NSPoint mouseLocation =
628             [view convertPoint:mouseLocationWindow fromView:nil];
629         const BOOL mouseInRect = NSMouseInRect(
630             mouseLocation, decorationFrames[i], [view isFlipped]);
631         button->SetButtonState(mouseInRect ?
632                                    ButtonDecoration::kButtonStateHover :
633                                    ButtonDecoration::kButtonStateNormal);
634         [view setNeedsDisplay:YES];
635       }
637       NSDictionary* info = [self getDictionaryForButtonDecoration:button];
638       base::scoped_nsobject<CrTrackingArea> area(
639           [[CrTrackingArea alloc] initWithRect:decorationFrames[i]
640                                        options:NSTrackingMouseEnteredAndExited |
641                                                NSTrackingActiveAlways
642                                          owner:view
643                                       userInfo:info]);
644       [view addTrackingArea:area];
645     }
646   }
649 // Given a newly created .webloc plist url file, also give it a resource
650 // fork and insert 'TEXT and 'url ' resources holding further copies of the
651 // url data. This is required for apps such as Terminal and Safari to accept it
652 // as a real webloc file when dragged in.
653 // It's expected that the resource fork requirement will go away at some
654 // point and this code can then be deleted.
655 OSErr WriteURLToNewWebLocFileResourceFork(NSURL* file, NSString* urlStr) {
656   ResFileRefNum refNum = kResFileNotOpened;
657   ResFileRefNum prevResRef = CurResFile();
658   FSRef fsRef;
659   OSErr err = noErr;
660   HFSUniStr255 resourceForkName;
661   FSGetResourceForkName(&resourceForkName);
663   if (![[NSFileManager defaultManager] fileExistsAtPath:[file path]])
664     return fnfErr;
666   if (!CFURLGetFSRef((CFURLRef)file, &fsRef))
667     return fnfErr;
669   err = FSCreateResourceFork(&fsRef,
670                              resourceForkName.length,
671                              resourceForkName.unicode,
672                              0);
673   if (err)
674     return err;
675   err = FSOpenResourceFile(&fsRef,
676                            resourceForkName.length,
677                            resourceForkName.unicode,
678                            fsRdWrPerm, &refNum);
679   if (err)
680     return err;
682   const char* utf8URL = [urlStr UTF8String];
683   int urlChars = strlen(utf8URL);
685   Handle urlHandle = NewHandle(urlChars);
686   memcpy(*urlHandle, utf8URL, urlChars);
688   Handle textHandle = NewHandle(urlChars);
689   memcpy(*textHandle, utf8URL, urlChars);
691   // Data for the 'drag' resource.
692   // This comes from derezzing webloc files made by the Finder.
693   // It's bigendian data, so it's represented here as chars to preserve
694   // byte order.
695   char dragData[] = {
696     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Header.
697     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
698     0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x01, 0x00, // 'TEXT', 0, 256
699     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
700     0x75, 0x72, 0x6C, 0x20, 0x00, 0x00, 0x01, 0x00, // 'url ', 0, 256
701     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
702   };
703   Handle dragHandle = NewHandleClear(sizeof(dragData));
704   memcpy(*dragHandle, &dragData[0], sizeof(dragData));
706   // Save the resources to the file.
707   ConstStr255Param noName = {0};
708   AddResource(dragHandle, 'drag', 128, noName);
709   AddResource(textHandle, 'TEXT', 256, noName);
710   AddResource(urlHandle, 'url ', 256, noName);
712   CloseResFile(refNum);
713   UseResFile(prevResRef);
714   return noErr;
717 // Returns the file path for file |name| if saved at NSURL |base|.
718 static NSString* PathWithBaseURLAndName(NSURL* base, NSString* name) {
719   NSString* filteredName =
720       [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
721   return [[NSURL URLWithString:filteredName relativeToURL:base] path];
724 // Returns if there is already a file |name| at dir NSURL |base|.
725 static BOOL FileAlreadyExists(NSURL* base, NSString* name) {
726   NSString* path = PathWithBaseURLAndName(base, name);
727   DCHECK([path hasSuffix:name]);
728   return [[NSFileManager defaultManager] fileExistsAtPath:path];
731 // Takes a destination URL, a suggested file name, & an extension (eg .webloc).
732 // Returns the complete file name with extension you should use.
733 // The name returned will not contain /, : or ?, will not be longer than
734 // kMaxNameLength + length of extension, and will not be a file name that
735 // already exists in that directory. If necessary it will try appending a space
736 // and a number to the name (but before the extension) trying numbers up to and
737 // including kMaxIndex.
738 // If the function gives up it returns nil.
739 static NSString* UnusedLegalNameForNewDropFile(NSURL* saveLocation,
740                                                NSString *fileName,
741                                                NSString *extension) {
742   int number = 1;
743   const int kMaxIndex = 20;
744   const unsigned kMaxNameLength = 64; // Arbitrary.
746   NSString* filteredName = [fileName stringByReplacingOccurrencesOfString:@"/"
747                                                                withString:@"-"];
748   filteredName = [filteredName stringByReplacingOccurrencesOfString:@":"
749                                                          withString:@"-"];
750   filteredName = [filteredName stringByReplacingOccurrencesOfString:@"?"
751                                                          withString:@"-"];
753   if ([filteredName length] > kMaxNameLength)
754     filteredName = [filteredName substringToIndex:kMaxNameLength];
756   NSString* candidateName = [filteredName stringByAppendingString:extension];
758   while (FileAlreadyExists(saveLocation, candidateName)) {
759     if (number > kMaxIndex)
760       return nil;
761     else
762       candidateName = [filteredName stringByAppendingFormat:@" %d%@",
763                        number++, extension];
764   }
766   return candidateName;
769 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination {
770   NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
771   NSFileManager* fileManager = [NSFileManager defaultManager];
773   if (![pboard containsURLData])
774     return NULL;
776   NSArray *urls = NULL;
777   NSArray* titles = NULL;
778   [pboard getURLs:&urls andTitles:&titles convertingFilenames:YES];
780   NSString* urlStr = [urls objectAtIndex:0];
781   NSString* nameStr = [titles objectAtIndex:0];
783   NSString* nameWithExtensionStr =
784       UnusedLegalNameForNewDropFile(dropDestination, nameStr, @".webloc");
785   if (!nameWithExtensionStr)
786     return NULL;
788   NSDictionary* urlDict = [NSDictionary dictionaryWithObject:urlStr
789                                                       forKey:@"URL"];
790   NSURL* outputURL =
791       [NSURL fileURLWithPath:PathWithBaseURLAndName(dropDestination,
792                                                     nameWithExtensionStr)];
793   [urlDict writeToURL:outputURL
794            atomically:NO];
796   if (![fileManager fileExistsAtPath:[outputURL path]])
797     return NULL;
799   NSDictionary* attr = [NSDictionary dictionaryWithObjectsAndKeys:
800       [NSNumber numberWithBool:YES], NSFileExtensionHidden,
801       [NSNumber numberWithUnsignedLong:'ilht'], NSFileHFSTypeCode,
802       [NSNumber numberWithUnsignedLong:'MACS'], NSFileHFSCreatorCode,
803       nil];
804   [fileManager setAttributes:attr
805                 ofItemAtPath:[outputURL path]
806                        error:nil];
807   // Add resource data.
808   OSErr resStatus = WriteURLToNewWebLocFileResourceFork(outputURL, urlStr);
809   OSSTATUS_DCHECK(resStatus == noErr, resStatus);
811   return [NSArray arrayWithObject:nameWithExtensionStr];
814 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
815   return NSDragOperationCopy;
818 - (void)updateToolTipsInRect:(NSRect)cellFrame
819                       ofView:(AutocompleteTextField*)controlView {
820   std::vector<LocationBarDecoration*> decorations;
821   std::vector<NSRect> decorationFrames;
822   NSRect textFrame;
823   CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
824                             [self edgeWidth], &decorations, &decorationFrames,
825                             &textFrame);
827   for (size_t i = 0; i < decorations.size(); ++i) {
828     NSString* tooltip = decorations[i]->GetToolTip();
829     if ([tooltip length] > 0)
830       [controlView addToolTip:tooltip forRect:decorationFrames[i]];
831   }
834 - (BOOL)hideFocusState {
835   return hideFocusState_;
838 - (void)setHideFocusState:(BOOL)hideFocusState
839                    ofView:(AutocompleteTextField*)controlView {
840   if (hideFocusState_ == hideFocusState)
841     return;
842   hideFocusState_ = hideFocusState;
843   [controlView setNeedsDisplay:YES];
844   NSTextView* fieldEditor =
845       base::mac::ObjCCastStrict<NSTextView>([controlView currentEditor]);
846   [fieldEditor updateInsertionPointStateAndRestartTimer:YES];
849 - (BOOL)showsFirstResponder {
850   return [super showsFirstResponder] && !hideFocusState_;
853 @end