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- and right-hand decorations from the field's
31 const CGFloat kLeftDecorationXOffset = 5.0;
32 const CGFloat kRightDecorationXOffset = 5.0;
34 // The amount of padding on either side reserved for drawing
35 // decorations. [Views has |kItemPadding| == 3.]
36 const CGFloat kDecorationHorizontalPad = 3.0;
38 NSString* const kButtonDecorationKey = @"ButtonDecoration";
40 const ui::NinePartImageIds kPopupBorderImageIds =
41 IMAGE_GRID(IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW);
43 const ui::NinePartImageIds kNormalBorderImageIds =
44 IMAGE_GRID(IDR_OMNIBOX_BORDER_AND_SHADOW);
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(
64 const std::vector<LocationBarDecoration*>& all_decorations,
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
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;
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
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
103 NSDivideRect(available, &decoration_frame, &frame,
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;
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(
132 const std::vector<LocationBarDecoration*>& left_decorations,
133 const std::vector<LocationBarDecoration*>& right_decorations,
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, kLeftDecorationXOffset,
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;
168 @implementation AutocompleteTextFieldCell
170 @synthesize isPopupMode = isPopupMode_;
172 - (CGFloat)topTextFrameOffset {
176 - (CGFloat)bottomTextFrameOffset {
180 - (CGFloat)cornerRadius {
181 return kCornerRadius;
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];
189 - (BOOL)shouldDrawBezel {
193 - (CGFloat)lineHeight {
197 - (void)clearDecorations {
198 leftDecorations_.clear();
199 rightDecorations_.clear();
202 - (void)addLeftDecoration:(LocationBarDecoration*)decoration {
203 leftDecorations_.push_back(decoration);
206 - (void)addRightDecoration:(LocationBarDecoration*)decoration {
207 rightDecorations_.push_back(decoration);
210 - (CGFloat)availableWidthInFrame:(const NSRect)frame {
211 std::vector<LocationBarDecoration*> decorations;
212 std::vector<NSRect> decorationFrames;
214 CalculatePositionsInFrame(frame, leftDecorations_, rightDecorations_,
215 [self edgeWidth], &decorations, &decorationFrames,
218 return NSWidth(textFrame);
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())
227 // Layout the decorations.
228 std::vector<LocationBarDecoration*> decorations;
229 std::vector<NSRect> decorationFrames;
231 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
232 [self edgeWidth], &decorations, &decorationFrames,
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];
243 // Decorations which are not visible should have been filtered out
244 // at the top, but return |NSZeroRect| rather than a 0-width rect
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,
260 // NOTE: This function must closely match the logic in
261 // |-drawInteriorWithFrame:inView:|.
266 // Returns the sub-frame where clicks can happen within the cell.
267 - (NSRect)clickableFrameForFrame:(NSRect)cellFrame {
268 return [super textFrameForFrame:cellFrame];
271 - (NSRect)textCursorFrameForFrame:(NSRect)cellFrame {
272 std::vector<LocationBarDecoration*> decorations;
273 std::vector<NSRect> decorationFrames;
276 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
277 [self edgeWidth], &decorations,
278 &decorationFrames, &textFrame);
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())
286 // If at leftmost decoration, expand to edge of cell.
288 minX = NSMinX(cellFrame);
290 minX = NSMinX(decorationFrames[index]) - kDecorationHorizontalPad;
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())
300 // If at rightmost decoration, expand to edge of cell.
301 if (index == decorations.size() - 1) {
302 maxX = NSMaxX(cellFrame);
304 maxX = NSMaxX(decorationFrames[index]) + kDecorationHorizontalPad;
308 // I-beam cursor covers left-most to right-most.
309 return NSMakeRect(minX, NSMinY(textFrame), maxX - minX, NSHeight(textFrame));
312 - (void)drawWithFrame:(NSRect)frame inView:(NSView*)controlView {
314 const CGFloat lineWidth = [controlView cr_lineWidth];
316 [[self backgroundColor] set];
317 NSRectFillUsingOperation(NSInsetRect(frame, 1, 1), NSCompositeSourceOver);
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];
327 // Interior contents.
328 [self drawInteriorWithFrame:frame inView:controlView];
331 ui::DrawNinePartImage(frame,
332 isPopupMode_ ? kPopupBorderImageIds
333 : kNormalBorderImageIds,
334 NSCompositeSourceOver,
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];
351 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
352 std::vector<LocationBarDecoration*> decorations;
353 std::vector<NSRect> decorationFrames;
356 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
357 [self edgeWidth], &decorations, &decorationFrames,
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);
370 // NOTE: This function must closely match the logic in
371 // |-textFrameForFrame:|.
373 // Superclass draws text portion WRT original |cellFrame|.
374 [super drawInteriorWithFrame:cellFrame inView:controlView];
377 - (LocationBarDecoration*)decorationForEvent:(NSEvent*)theEvent
378 inRect:(NSRect)cellFrame
379 ofView:(AutocompleteTextField*)controlView
381 const BOOL flipped = [controlView isFlipped];
382 const NSPoint location =
383 [controlView convertPoint:[theEvent locationInWindow] fromView:nil];
385 std::vector<LocationBarDecoration*> decorations;
386 std::vector<NSRect> decorationFrames;
388 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
389 [self edgeWidth], &decorations, &decorationFrames,
392 for (size_t i = 0; i < decorations.size(); ++i) {
393 if (NSMouseInRect(location, decorationFrames[i], flipped))
394 return decorations[i];
400 - (NSMenu*)decorationMenuForEvent:(NSEvent*)theEvent
401 inRect:(NSRect)cellFrame
402 ofView:(AutocompleteTextField*)controlView {
403 LocationBarDecoration* decoration =
404 [self decorationForEvent:theEvent inRect:cellFrame ofView:controlView];
406 return decoration->GetMenu();
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())
418 NSRect decorationRect =
419 [self frameForDecoration:decoration inFrame:cellFrame];
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()) {
425 [NSDate dateWithTimeIntervalSinceNow:kLocationIconDragTimeout];
426 NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask |
429 inMode:NSEventTrackingRunLoopMode
431 if (!event || [event type] == NSLeftMouseDragged) {
432 NSPasteboard* pboard = decoration->GetDragPasteboard();
435 NSImage* image = decoration->GetDragImage();
438 NSRect dragImageRect = decoration->GetDragImageFrame(decorationRect);
440 // Center under mouse horizontally, with cursor below so the image
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));
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
466 // On mouse-up fall through to mouse-pressed case.
467 DCHECK_EQ([event type], NSLeftMouseUp);
471 if (decoration->AsButtonDecoration()) {
472 ButtonDecoration* button = decoration->AsButtonDecoration();
474 button->SetButtonState(ButtonDecoration::kButtonStatePressed);
475 [controlView setNeedsDisplay:YES];
477 // Track the mouse until the user releases the button.
478 [self trackMouse:theEvent
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]);
495 button->SetButtonState(ButtonDecoration::kButtonStateNormal);
496 [controlView setNeedsDisplay:YES];
500 handled = decoration->OnMousePressed(decorationRect);
503 return handled ? YES : NO;
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]
515 std::count(leftDecorations_.begin(), leftDecorations_.end(), bd) ||
516 std::count(rightDecorations_.begin(), rightDecorations_.end(), bd));
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 {
530 std::count(leftDecorations_.begin(), leftDecorations_.end(), decoration) ||
531 std::count(rightDecorations_.begin(), rightDecorations_.end(), decoration));
534 dictionaryWithObject:[NSValue valueWithPointer:decoration]
535 forKey:kButtonDecorationKey];
538 - (void)mouseEntered:(NSEvent*)theEvent
539 inView:(AutocompleteTextField*)controlView {
540 ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
542 decoration->SetButtonState(ButtonDecoration::kButtonStateHover);
543 [controlView setNeedsDisplay:YES];
547 - (void)mouseExited:(NSEvent*)theEvent
548 inView:(AutocompleteTextField*)controlView {
549 ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
551 decoration->SetButtonState(ButtonDecoration::kButtonStateNormal);
552 [controlView setNeedsDisplay:YES];
556 - (void)setUpTrackingAreasInRect:(NSRect)frame
557 ofView:(AutocompleteTextField*)view {
558 std::vector<LocationBarDecoration*> decorations;
559 std::vector<NSRect> decorationFrames;
561 NSRect cellRect = [self clickableFrameForFrame:[view bounds]];
562 CalculatePositionsInFrame(cellRect, leftDecorations_, rightDecorations_,
563 [self edgeWidth], &decorations, &decorationFrames,
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];
572 // Setup new tracking areas for the buttons.
573 for (size_t i = 0; i < decorations.size(); ++i) {
574 ButtonDecoration* button = decorations[i]->AsButtonDecoration();
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];
593 NSDictionary* info = [self getDictionaryForButtonDecoration:button];
594 base::scoped_nsobject<CrTrackingArea> area(
595 [[CrTrackingArea alloc] initWithRect:decorationFrames[i]
596 options:NSTrackingMouseEnteredAndExited |
597 NSTrackingActiveAlways
600 [view addTrackingArea:area];
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();
616 HFSUniStr255 resourceForkName;
617 FSGetResourceForkName(&resourceForkName);
619 if (![[NSFileManager defaultManager] fileExistsAtPath:[file path]])
622 if (!CFURLGetFSRef((CFURLRef)file, &fsRef))
625 err = FSCreateResourceFork(&fsRef,
626 resourceForkName.length,
627 resourceForkName.unicode,
631 err = FSOpenResourceFile(&fsRef,
632 resourceForkName.length,
633 resourceForkName.unicode,
634 fsRdWrPerm, &refNum);
638 const char* utf8URL = [urlStr UTF8String];
639 int urlChars = strlen(utf8URL);
641 Handle urlHandle = NewHandle(urlChars);
642 memcpy(*urlHandle, utf8URL, urlChars);
644 Handle textHandle = NewHandle(urlChars);
645 memcpy(*textHandle, utf8URL, urlChars);
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
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
659 Handle dragHandle = NewHandleClear(sizeof(dragData));
660 memcpy(*dragHandle, &dragData[0], sizeof(dragData));
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);
668 CloseResFile(refNum);
669 UseResFile(prevResRef);
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];
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];
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,
697 NSString *extension) {
699 const int kMaxIndex = 20;
700 const unsigned kMaxNameLength = 64; // Arbitrary.
702 NSString* filteredName = [fileName stringByReplacingOccurrencesOfString:@"/"
704 filteredName = [filteredName stringByReplacingOccurrencesOfString:@":"
706 filteredName = [filteredName stringByReplacingOccurrencesOfString:@"?"
709 if ([filteredName length] > kMaxNameLength)
710 filteredName = [filteredName substringToIndex:kMaxNameLength];
712 NSString* candidateName = [filteredName stringByAppendingString:extension];
714 while (FileAlreadyExists(saveLocation, candidateName)) {
715 if (number > kMaxIndex)
718 candidateName = [filteredName stringByAppendingFormat:@" %d%@",
719 number++, extension];
722 return candidateName;
725 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination {
726 NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
727 NSFileManager* fileManager = [NSFileManager defaultManager];
729 if (![pboard containsURLData])
732 NSArray *urls = NULL;
733 NSArray* titles = NULL;
734 [pboard getURLs:&urls andTitles:&titles convertingFilenames:YES];
736 NSString* urlStr = [urls objectAtIndex:0];
737 NSString* nameStr = [titles objectAtIndex:0];
739 NSString* nameWithExtensionStr =
740 UnusedLegalNameForNewDropFile(dropDestination, nameStr, @".webloc");
741 if (!nameWithExtensionStr)
744 NSDictionary* urlDict = [NSDictionary dictionaryWithObject:urlStr
747 [NSURL fileURLWithPath:PathWithBaseURLAndName(dropDestination,
748 nameWithExtensionStr)];
749 [urlDict writeToURL:outputURL
752 if (![fileManager fileExistsAtPath:[outputURL path]])
755 NSDictionary* attr = [NSDictionary dictionaryWithObjectsAndKeys:
756 [NSNumber numberWithBool:YES], NSFileExtensionHidden,
757 [NSNumber numberWithUnsignedLong:'ilht'], NSFileHFSTypeCode,
758 [NSNumber numberWithUnsignedLong:'MACS'], NSFileHFSCreatorCode,
760 [fileManager setAttributes:attr
761 ofItemAtPath:[outputURL path]
763 // Add resource data.
764 OSErr resStatus = WriteURLToNewWebLocFileResourceFork(outputURL, urlStr);
765 OSSTATUS_DCHECK(resStatus == noErr, resStatus);
767 return [NSArray arrayWithObject:nameWithExtensionStr];
770 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
771 return NSDragOperationCopy;
774 - (void)updateToolTipsInRect:(NSRect)cellFrame
775 ofView:(AutocompleteTextField*)controlView {
776 std::vector<LocationBarDecoration*> decorations;
777 std::vector<NSRect> decorationFrames;
779 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
780 [self edgeWidth], &decorations, &decorationFrames,
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]];
790 - (BOOL)hideFocusState {
791 return hideFocusState_;
794 - (void)setHideFocusState:(BOOL)hideFocusState
795 ofView:(AutocompleteTextField*)controlView {
796 if (hideFocusState_ == hideFocusState)
798 hideFocusState_ = hideFocusState;
799 [controlView setNeedsDisplay:YES];
800 NSTextView* fieldEditor =
801 base::mac::ObjCCastStrict<NSTextView>([controlView currentEditor]);
802 [fieldEditor updateInsertionPointStateAndRestartTimer:YES];
805 - (BOOL)showsFirstResponder {
806 return [super showsFirstResponder] && !hideFocusState_;