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 "chrome/common/extensions/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_TOP_LEFT,
36 IDR_OMNIBOX_POPUP_BORDER_TOP,
37 IDR_OMNIBOX_POPUP_BORDER_TOP_RIGHT,
38 IDR_OMNIBOX_POPUP_BORDER_LEFT,
39 IDR_OMNIBOX_POPUP_BORDER_CENTER,
40 IDR_OMNIBOX_POPUP_BORDER_RIGHT,
41 IDR_OMNIBOX_POPUP_BORDER_BOTTOM_LEFT,
42 IDR_OMNIBOX_POPUP_BORDER_BOTTOM,
43 IDR_OMNIBOX_POPUP_BORDER_BOTTOM_RIGHT
46 const ui::NinePartImageIds kNormalBorderImageIds = {
47 IDR_OMNIBOX_BORDER_TOP_LEFT,
48 IDR_OMNIBOX_BORDER_TOP,
49 IDR_OMNIBOX_BORDER_TOP_RIGHT,
50 IDR_OMNIBOX_BORDER_LEFT,
51 IDR_OMNIBOX_BORDER_CENTER,
52 IDR_OMNIBOX_BORDER_RIGHT,
53 IDR_OMNIBOX_BORDER_BOTTOM_LEFT,
54 IDR_OMNIBOX_BORDER_BOTTOM,
55 IDR_OMNIBOX_BORDER_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];
372 ui::DrawNinePartImage(frame,
373 isPopupMode_ ? kPopupBorderImageIds
374 : kNormalBorderImageIds,
375 NSCompositeSourceOver,
379 // Interior contents.
380 [self drawInteriorWithFrame:frame inView:controlView];
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 handled = decoration->AsButtonDecoration()->OnMousePressedWithView(
517 decorationRect, controlView);
519 // Update tracking areas and make sure the button's state is consistent with
520 // the mouse's location (e.g. "normal" if the mouse is no longer over the
521 // decoration, "hover" otherwise).
522 [self setUpTrackingAreasInRect:cellFrame ofView:controlView];
524 handled = decoration->OnMousePressed(decorationRect);
527 return handled ? YES : NO;
530 // Helper method for the |mouseEntered:inView:| and |mouseExited:inView:|
531 // messages. Retrieves the |ButtonDecoration| for the specified event (received
532 // from a tracking area), and returns |NULL| if no decoration matches.
533 - (ButtonDecoration*)getButtonDecorationForEvent:(NSEvent*)theEvent {
534 ButtonDecoration* bd = static_cast<ButtonDecoration*>(
535 [[[[theEvent trackingArea] userInfo] valueForKey:kButtonDecorationKey]
539 std::count(leftDecorations_.begin(), leftDecorations_.end(), bd) ||
540 std::count(rightDecorations_.begin(), rightDecorations_.end(), bd));
545 // Helper method for |setUpTrackingAreasInView|. Creates an |NSDictionary| to
546 // be used as user information to identify which decoration is the source of an
547 // event (from a tracking area).
548 - (NSDictionary*)getDictionaryForButtonDecoration:
549 (ButtonDecoration*)decoration {
554 std::count(leftDecorations_.begin(), leftDecorations_.end(), decoration) ||
555 std::count(rightDecorations_.begin(), rightDecorations_.end(), decoration));
558 dictionaryWithObject:[NSValue valueWithPointer:decoration]
559 forKey:kButtonDecorationKey];
562 - (void)mouseEntered:(NSEvent*)theEvent
563 inView:(AutocompleteTextField*)controlView {
564 ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
566 decoration->SetButtonState(ButtonDecoration::kButtonStateHover);
567 [controlView setNeedsDisplay:YES];
571 - (void)mouseExited:(NSEvent*)theEvent
572 inView:(AutocompleteTextField*)controlView {
573 ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
575 decoration->SetButtonState(ButtonDecoration::kButtonStateNormal);
576 [controlView setNeedsDisplay:YES];
580 - (void)setUpTrackingAreasInRect:(NSRect)frame
581 ofView:(AutocompleteTextField*)view {
582 std::vector<LocationBarDecoration*> decorations;
583 std::vector<NSRect> decorationFrames;
585 NSRect cellRect = [self clickableFrameForFrame:[view bounds]];
586 CalculatePositionsInFrame(cellRect, leftDecorations_, rightDecorations_,
587 [self edgeWidth], &decorations, &decorationFrames,
590 // Remove previously-registered tracking areas, since we'll update them below.
591 for (CrTrackingArea* area in [view trackingAreas]) {
592 if ([[area userInfo] objectForKey:kButtonDecorationKey])
593 [view removeTrackingArea:area];
596 // Setup new tracking areas for the buttons.
597 for (size_t i = 0; i < decorations.size(); ++i) {
598 ButtonDecoration* button = decorations[i]->AsButtonDecoration();
600 // If the button isn't pressed (in which case we want to leave it as-is),
601 // update it's state since we might have missed some entered/exited events
602 // because of the removing/adding of the tracking areas.
603 if (button->GetButtonState() !=
604 ButtonDecoration::kButtonStatePressed) {
605 const NSPoint mouseLocationWindow =
606 [[view window] mouseLocationOutsideOfEventStream];
607 const NSPoint mouseLocation =
608 [view convertPoint:mouseLocationWindow fromView:nil];
609 const BOOL mouseInRect = NSMouseInRect(
610 mouseLocation, decorationFrames[i], [view isFlipped]);
611 button->SetButtonState(mouseInRect ?
612 ButtonDecoration::kButtonStateHover :
613 ButtonDecoration::kButtonStateNormal);
614 [view setNeedsDisplay:YES];
617 NSDictionary* info = [self getDictionaryForButtonDecoration:button];
618 base::scoped_nsobject<CrTrackingArea> area(
619 [[CrTrackingArea alloc] initWithRect:decorationFrames[i]
620 options:NSTrackingMouseEnteredAndExited |
621 NSTrackingActiveAlways
624 [view addTrackingArea:area];
629 // Given a newly created .webloc plist url file, also give it a resource
630 // fork and insert 'TEXT and 'url ' resources holding further copies of the
631 // url data. This is required for apps such as Terminal and Safari to accept it
632 // as a real webloc file when dragged in.
633 // It's expected that the resource fork requirement will go away at some
634 // point and this code can then be deleted.
635 OSErr WriteURLToNewWebLocFileResourceFork(NSURL* file, NSString* urlStr) {
636 ResFileRefNum refNum = kResFileNotOpened;
637 ResFileRefNum prevResRef = CurResFile();
640 HFSUniStr255 resourceForkName;
641 FSGetResourceForkName(&resourceForkName);
643 if (![[NSFileManager defaultManager] fileExistsAtPath:[file path]])
646 if (!CFURLGetFSRef((CFURLRef)file, &fsRef))
649 err = FSCreateResourceFork(&fsRef,
650 resourceForkName.length,
651 resourceForkName.unicode,
655 err = FSOpenResourceFile(&fsRef,
656 resourceForkName.length,
657 resourceForkName.unicode,
658 fsRdWrPerm, &refNum);
662 const char* utf8URL = [urlStr UTF8String];
663 int urlChars = strlen(utf8URL);
665 Handle urlHandle = NewHandle(urlChars);
666 memcpy(*urlHandle, utf8URL, urlChars);
668 Handle textHandle = NewHandle(urlChars);
669 memcpy(*textHandle, utf8URL, urlChars);
671 // Data for the 'drag' resource.
672 // This comes from derezzing webloc files made by the Finder.
673 // It's bigendian data, so it's represented here as chars to preserve
676 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Header.
677 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
678 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x01, 0x00, // 'TEXT', 0, 256
679 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
680 0x75, 0x72, 0x6C, 0x20, 0x00, 0x00, 0x01, 0x00, // 'url ', 0, 256
681 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
683 Handle dragHandle = NewHandleClear(sizeof(dragData));
684 memcpy(*dragHandle, &dragData[0], sizeof(dragData));
686 // Save the resources to the file.
687 ConstStr255Param noName = {0};
688 AddResource(dragHandle, 'drag', 128, noName);
689 AddResource(textHandle, 'TEXT', 256, noName);
690 AddResource(urlHandle, 'url ', 256, noName);
692 CloseResFile(refNum);
693 UseResFile(prevResRef);
697 // Returns the file path for file |name| if saved at NSURL |base|.
698 static NSString* PathWithBaseURLAndName(NSURL* base, NSString* name) {
699 NSString* filteredName =
700 [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
701 return [[NSURL URLWithString:filteredName relativeToURL:base] path];
704 // Returns if there is already a file |name| at dir NSURL |base|.
705 static BOOL FileAlreadyExists(NSURL* base, NSString* name) {
706 NSString* path = PathWithBaseURLAndName(base, name);
707 DCHECK([path hasSuffix:name]);
708 return [[NSFileManager defaultManager] fileExistsAtPath:path];
711 // Takes a destination URL, a suggested file name, & an extension (eg .webloc).
712 // Returns the complete file name with extension you should use.
713 // The name returned will not contain /, : or ?, will not be longer than
714 // kMaxNameLength + length of extension, and will not be a file name that
715 // already exists in that directory. If necessary it will try appending a space
716 // and a number to the name (but before the extension) trying numbers up to and
717 // including kMaxIndex.
718 // If the function gives up it returns nil.
719 static NSString* UnusedLegalNameForNewDropFile(NSURL* saveLocation,
721 NSString *extension) {
723 const int kMaxIndex = 20;
724 const unsigned kMaxNameLength = 64; // Arbitrary.
726 NSString* filteredName = [fileName stringByReplacingOccurrencesOfString:@"/"
728 filteredName = [filteredName stringByReplacingOccurrencesOfString:@":"
730 filteredName = [filteredName stringByReplacingOccurrencesOfString:@"?"
733 if ([filteredName length] > kMaxNameLength)
734 filteredName = [filteredName substringToIndex:kMaxNameLength];
736 NSString* candidateName = [filteredName stringByAppendingString:extension];
738 while (FileAlreadyExists(saveLocation, candidateName)) {
739 if (number > kMaxIndex)
742 candidateName = [filteredName stringByAppendingFormat:@" %d%@",
743 number++, extension];
746 return candidateName;
749 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination {
750 NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
751 NSFileManager* fileManager = [NSFileManager defaultManager];
753 if (![pboard containsURLData])
756 NSArray *urls = NULL;
757 NSArray* titles = NULL;
758 [pboard getURLs:&urls andTitles:&titles convertingFilenames:YES];
760 NSString* urlStr = [urls objectAtIndex:0];
761 NSString* nameStr = [titles objectAtIndex:0];
763 NSString* nameWithExtensionStr =
764 UnusedLegalNameForNewDropFile(dropDestination, nameStr, @".webloc");
765 if (!nameWithExtensionStr)
768 NSDictionary* urlDict = [NSDictionary dictionaryWithObject:urlStr
771 [NSURL fileURLWithPath:PathWithBaseURLAndName(dropDestination,
772 nameWithExtensionStr)];
773 [urlDict writeToURL:outputURL
776 if (![fileManager fileExistsAtPath:[outputURL path]])
779 NSDictionary* attr = [NSDictionary dictionaryWithObjectsAndKeys:
780 [NSNumber numberWithBool:YES], NSFileExtensionHidden,
781 [NSNumber numberWithUnsignedLong:'ilht'], NSFileHFSTypeCode,
782 [NSNumber numberWithUnsignedLong:'MACS'], NSFileHFSCreatorCode,
784 [fileManager setAttributes:attr
785 ofItemAtPath:[outputURL path]
787 // Add resource data.
788 OSErr resStatus = WriteURLToNewWebLocFileResourceFork(outputURL, urlStr);
789 OSSTATUS_DCHECK(resStatus == noErr, resStatus);
791 return [NSArray arrayWithObject:nameWithExtensionStr];
794 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
795 return NSDragOperationCopy;
798 - (void)updateToolTipsInRect:(NSRect)cellFrame
799 ofView:(AutocompleteTextField*)controlView {
800 std::vector<LocationBarDecoration*> decorations;
801 std::vector<NSRect> decorationFrames;
803 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
804 [self edgeWidth], &decorations, &decorationFrames,
807 for (size_t i = 0; i < decorations.size(); ++i) {
808 NSString* tooltip = decorations[i]->GetToolTip();
809 if ([tooltip length] > 0)
810 [controlView addToolTip:tooltip forRect:decorationFrames[i]];
814 - (BOOL)hideFocusState {
815 return hideFocusState_;
818 - (void)setHideFocusState:(BOOL)hideFocusState
819 ofView:(AutocompleteTextField*)controlView {
820 if (hideFocusState_ == hideFocusState)
822 hideFocusState_ = hideFocusState;
823 [controlView setNeedsDisplay:YES];
824 NSTextView* fieldEditor =
825 base::mac::ObjCCastStrict<NSTextView>([controlView currentEditor]);
826 [fieldEditor updateInsertionPointStateAndRestartTimer:YES];
829 - (BOOL)showsFirstResponder {
830 return [super showsFirstResponder] && !hideFocusState_;