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