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;
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;
68 return kRightDecorationXOffset;
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(
100 const std::vector<LocationBarDecoration*>& all_decorations,
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
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;
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
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
139 NSDivideRect(available, &decoration_frame, &frame,
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();
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(
168 const std::vector<LocationBarDecoration*>& left_decorations,
169 const std::vector<LocationBarDecoration*>& right_decorations,
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
204 NSDivideRect(frame, &dummy, remaining_frame,
205 DecorationHorizontalPad(), NSMaxXEdge);
212 @implementation AutocompleteTextFieldCell
214 @synthesize isPopupMode = isPopupMode_;
216 - (CGFloat)topTextFrameOffset {
220 - (CGFloat)bottomTextFrameOffset {
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 {
237 - (CGFloat)lineHeight {
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;
258 CalculatePositionsInFrame(frame, leftDecorations_, rightDecorations_,
259 [self edgeWidth], &decorations, &decorationFrames,
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())
271 // Layout the decorations.
272 std::vector<LocationBarDecoration*> decorations;
273 std::vector<NSRect> decorationFrames;
275 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
276 [self edgeWidth], &decorations, &decorationFrames,
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];
287 // Decorations which are not visible should have been filtered out
288 // at the top, but return |NSZeroRect| rather than a 0-width rect
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,
304 // NOTE: This function must closely match the logic in
305 // |-drawInteriorWithFrame:inView:|.
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;
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())
330 // If at leftmost decoration, expand to edge of cell.
332 minX = NSMinX(cellFrame);
334 minX = NSMinX(decorationFrames[index]) - DecorationHorizontalPad();
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())
344 // If at rightmost decoration, expand to edge of cell.
345 if (index == decorations.size() - 1) {
346 maxX = NSMaxX(cellFrame);
348 maxX = NSMaxX(decorationFrames[index]) + DecorationHorizontalPad();
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 {
358 const CGFloat lineWidth = [controlView cr_lineWidth];
360 [[self backgroundColor] set];
361 NSRectFillUsingOperation(NSInsetRect(frame, 1, 1), NSCompositeSourceOver);
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];
371 // Interior contents.
372 [self drawInteriorWithFrame:frame inView:controlView];
375 ui::DrawNinePartImage(frame,
376 isPopupMode_ ? kPopupBorderImageIds
377 : kNormalBorderImageIds,
378 NSCompositeSourceOver,
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];
395 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
396 std::vector<LocationBarDecoration*> decorations;
397 std::vector<NSRect> decorationFrames;
400 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
401 [self edgeWidth], &decorations, &decorationFrames,
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);
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;
432 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
433 [self edgeWidth], &decorations, &decorationFrames,
436 for (size_t i = 0; i < decorations.size(); ++i) {
437 if (NSMouseInRect(location, decorationFrames[i], flipped))
438 return decorations[i];
444 - (NSMenu*)decorationMenuForEvent:(NSEvent*)theEvent
445 inRect:(NSRect)cellFrame
446 ofView:(AutocompleteTextField*)controlView {
447 LocationBarDecoration* decoration =
448 [self decorationForEvent:theEvent inRect:cellFrame ofView:controlView];
450 return decoration->GetMenu();
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())
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()) {
469 [NSDate dateWithTimeIntervalSinceNow:kLocationIconDragTimeout];
470 NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask |
473 inMode:NSEventTrackingRunLoopMode
475 if (!event || [event type] == NSLeftMouseDragged) {
476 NSPasteboard* pboard = decoration->GetDragPasteboard();
479 NSImage* image = decoration->GetDragImage();
482 NSRect dragImageRect = decoration->GetDragImageFrame(decorationRect);
484 // Center under mouse horizontally, with cursor below so the image
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
510 // On mouse-up fall through to mouse-pressed case.
511 DCHECK_EQ([event type], NSLeftMouseUp);
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
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]);
539 button->SetButtonState(ButtonDecoration::kButtonStateNormal);
540 [controlView setNeedsDisplay:YES];
544 handled = decoration->OnMousePressed(decorationRect);
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]
559 std::count(leftDecorations_.begin(), leftDecorations_.end(), bd) ||
560 std::count(rightDecorations_.begin(), rightDecorations_.end(), 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 {
574 std::count(leftDecorations_.begin(), leftDecorations_.end(), decoration) ||
575 std::count(rightDecorations_.begin(), rightDecorations_.end(), decoration));
578 dictionaryWithObject:[NSValue valueWithPointer:decoration]
579 forKey:kButtonDecorationKey];
582 - (void)mouseEntered:(NSEvent*)theEvent
583 inView:(AutocompleteTextField*)controlView {
584 ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
586 decoration->SetButtonState(ButtonDecoration::kButtonStateHover);
587 [controlView setNeedsDisplay:YES];
591 - (void)mouseExited:(NSEvent*)theEvent
592 inView:(AutocompleteTextField*)controlView {
593 ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
595 decoration->SetButtonState(ButtonDecoration::kButtonStateNormal);
596 [controlView setNeedsDisplay:YES];
600 - (void)setUpTrackingAreasInRect:(NSRect)frame
601 ofView:(AutocompleteTextField*)view {
602 std::vector<LocationBarDecoration*> decorations;
603 std::vector<NSRect> decorationFrames;
605 NSRect cellRect = [self clickableFrameForFrame:[view bounds]];
606 CalculatePositionsInFrame(cellRect, leftDecorations_, rightDecorations_,
607 [self edgeWidth], &decorations, &decorationFrames,
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];
616 // Setup new tracking areas for the buttons.
617 for (size_t i = 0; i < decorations.size(); ++i) {
618 ButtonDecoration* button = decorations[i]->AsButtonDecoration();
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];
637 NSDictionary* info = [self getDictionaryForButtonDecoration:button];
638 base::scoped_nsobject<CrTrackingArea> area(
639 [[CrTrackingArea alloc] initWithRect:decorationFrames[i]
640 options:NSTrackingMouseEnteredAndExited |
641 NSTrackingActiveAlways
644 [view addTrackingArea:area];
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();
660 HFSUniStr255 resourceForkName;
661 FSGetResourceForkName(&resourceForkName);
663 if (![[NSFileManager defaultManager] fileExistsAtPath:[file path]])
666 if (!CFURLGetFSRef((CFURLRef)file, &fsRef))
669 err = FSCreateResourceFork(&fsRef,
670 resourceForkName.length,
671 resourceForkName.unicode,
675 err = FSOpenResourceFile(&fsRef,
676 resourceForkName.length,
677 resourceForkName.unicode,
678 fsRdWrPerm, &refNum);
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
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
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);
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,
741 NSString *extension) {
743 const int kMaxIndex = 20;
744 const unsigned kMaxNameLength = 64; // Arbitrary.
746 NSString* filteredName = [fileName stringByReplacingOccurrencesOfString:@"/"
748 filteredName = [filteredName stringByReplacingOccurrencesOfString:@":"
750 filteredName = [filteredName stringByReplacingOccurrencesOfString:@"?"
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)
762 candidateName = [filteredName stringByAppendingFormat:@" %d%@",
763 number++, extension];
766 return candidateName;
769 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination {
770 NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
771 NSFileManager* fileManager = [NSFileManager defaultManager];
773 if (![pboard containsURLData])
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)
788 NSDictionary* urlDict = [NSDictionary dictionaryWithObject:urlStr
791 [NSURL fileURLWithPath:PathWithBaseURLAndName(dropDestination,
792 nameWithExtensionStr)];
793 [urlDict writeToURL:outputURL
796 if (![fileManager fileExistsAtPath:[outputURL path]])
799 NSDictionary* attr = [NSDictionary dictionaryWithObjectsAndKeys:
800 [NSNumber numberWithBool:YES], NSFileExtensionHidden,
801 [NSNumber numberWithUnsignedLong:'ilht'], NSFileHFSTypeCode,
802 [NSNumber numberWithUnsignedLong:'MACS'], NSFileHFSCreatorCode,
804 [fileManager setAttributes:attr
805 ofItemAtPath:[outputURL path]
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;
823 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
824 [self edgeWidth], &decorations, &decorationFrames,
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]];
834 - (BOOL)hideFocusState {
835 return hideFocusState_;
838 - (void)setHideFocusState:(BOOL)hideFocusState
839 ofView:(AutocompleteTextField*)controlView {
840 if (hideFocusState_ == hideFocusState)
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_;