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