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 "extensions/common/feature_switch.h"
15 #include "grit/theme_resources.h"
16 #import "third_party/mozilla/NSPasteboard+Utils.h"
17 #import "ui/base/cocoa/appkit_utils.h"
18 #import "ui/base/cocoa/nsview_additions.h"
19 #import "ui/base/cocoa/tracking_area.h"
20 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.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 = IMAGE_GRID(IDR_TEXTFIELD);
45 // How long to wait for mouse-up on the location icon before assuming
46 // that the user wants to drag.
47 const NSTimeInterval kLocationIconDragTimeout = 0.25;
49 // Calculate the positions for a set of decorations. |frame| is the
50 // overall frame to do layout in, |remaining_frame| will get the
51 // left-over space. |all_decorations| is the set of decorations to
52 // lay out, |decorations| will be set to the decorations which are
53 // visible and which fit, in the same order as |all_decorations|,
54 // while |decoration_frames| will be the corresponding frames.
55 // |x_edge| describes the edge to layout the decorations against
56 // (|NSMinXEdge| or |NSMaxXEdge|). |regular_padding| is the padding
57 // from the edge of |cell_frame| to use when the first visible decoration
58 // is a regular decoration. |action_padding| is the padding to use when the
59 // first decoration is a button decoration, ie. the action box button.
60 // (|kDecorationHorizontalPad| is used between decorations).
61 void CalculatePositionsHelper(
63 const std::vector<LocationBarDecoration*>& all_decorations,
65 CGFloat regular_padding,
66 CGFloat action_padding,
67 std::vector<LocationBarDecoration*>* decorations,
68 std::vector<NSRect>* decoration_frames,
69 NSRect* remaining_frame) {
70 DCHECK(x_edge == NSMinXEdge || x_edge == NSMaxXEdge);
71 DCHECK_EQ(decorations->size(), decoration_frames->size());
73 // The initial padding depends on whether the first visible decoration is
75 bool is_first_visible_decoration = true;
77 for (size_t i = 0; i < all_decorations.size(); ++i) {
78 if (all_decorations[i]->IsVisible()) {
79 CGFloat padding = kDecorationHorizontalPad;
80 if (is_first_visible_decoration) {
81 padding = all_decorations[i]->AsButtonDecoration() ?
82 action_padding : regular_padding;
83 is_first_visible_decoration = false;
86 NSRect padding_rect, available;
88 // Peel off the outside padding.
89 NSDivideRect(frame, &padding_rect, &available, padding, x_edge);
91 // Find out how large the decoration will be in the remaining
93 const CGFloat used_width =
94 all_decorations[i]->GetWidthForSpace(NSWidth(available));
96 if (used_width != LocationBarDecoration::kOmittedWidth) {
97 DCHECK_GT(used_width, 0.0);
98 NSRect decoration_frame;
100 // Peel off the desired width, leaving the remainder in
102 NSDivideRect(available, &decoration_frame, &frame,
105 decorations->push_back(all_decorations[i]);
106 decoration_frames->push_back(decoration_frame);
107 DCHECK_EQ(decorations->size(), decoration_frames->size());
109 // Adjust padding for between decorations.
110 padding = kDecorationHorizontalPad;
115 DCHECK_EQ(decorations->size(), decoration_frames->size());
116 *remaining_frame = frame;
119 // Helper function for calculating placement of decorations w/in the cell.
120 // |frame| is the cell's boundary rectangle, |remaining_frame| will get any
121 // space left after decorations are laid out (for text). |left_decorations| is
122 // a set of decorations for the left-hand side of the cell, |right_decorations|
123 // for the right-hand side. |edge_width| is the width of one vertical edge of
124 // the omnibox, this depends on whether the display is low DPI or high DPI.
125 // |decorations| will contain the resulting visible decorations, and
126 // |decoration_frames| will contain their frames in the same coordinates as
127 // |frame|. Decorations will be ordered left to right. As a convenience returns
128 // the index of the first right-hand decoration.
129 size_t CalculatePositionsInFrame(
131 const std::vector<LocationBarDecoration*>& left_decorations,
132 const std::vector<LocationBarDecoration*>& right_decorations,
134 std::vector<LocationBarDecoration*>* decorations,
135 std::vector<NSRect>* decoration_frames,
136 NSRect* remaining_frame) {
137 decorations->clear();
138 decoration_frames->clear();
140 // Layout |left_decorations| against the LHS.
141 CalculatePositionsHelper(frame, left_decorations, NSMinXEdge,
142 kLeftDecorationXOffset, edge_width,
143 decorations, decoration_frames, &frame);
144 DCHECK_EQ(decorations->size(), decoration_frames->size());
146 // Capture the number of visible left-hand decorations.
147 const size_t left_count = decorations->size();
149 // Layout |right_decorations| against the RHS.
150 CalculatePositionsHelper(frame, right_decorations, NSMaxXEdge,
151 kRightDecorationXOffset, edge_width, decorations,
152 decoration_frames, &frame);
153 DCHECK_EQ(decorations->size(), decoration_frames->size());
155 // Reverse the right-hand decorations so that overall everything is
156 // sorted left to right.
157 std::reverse(decorations->begin() + left_count, decorations->end());
158 std::reverse(decoration_frames->begin() + left_count,
159 decoration_frames->end());
161 *remaining_frame = frame;
167 @interface AutocompleteTextFieldCell ()
168 // Post an OnSetFocus notification to the observer of |controlView|.
169 - (void)focusNotificationFor:(NSEvent*)event
170 ofView:(AutocompleteTextField*)controlView;
173 @implementation AutocompleteTextFieldCell
175 @synthesize isPopupMode = isPopupMode_;
177 - (CGFloat)topTextFrameOffset {
181 - (CGFloat)bottomTextFrameOffset {
185 - (CGFloat)cornerRadius {
186 return kCornerRadius;
189 - (CGFloat)edgeWidth {
190 // The omnibox vertical edge width is 1 pixel both in low DPI and high DPI.
191 return [[self controlView] cr_lineWidth];
194 - (BOOL)shouldDrawBezel {
198 - (CGFloat)lineHeight {
202 - (void)clearDecorations {
203 leftDecorations_.clear();
204 rightDecorations_.clear();
207 - (void)addLeftDecoration:(LocationBarDecoration*)decoration {
208 leftDecorations_.push_back(decoration);
211 - (void)addRightDecoration:(LocationBarDecoration*)decoration {
212 rightDecorations_.push_back(decoration);
215 - (CGFloat)availableWidthInFrame:(const NSRect)frame {
216 std::vector<LocationBarDecoration*> decorations;
217 std::vector<NSRect> decorationFrames;
219 CalculatePositionsInFrame(frame, leftDecorations_, rightDecorations_,
220 [self edgeWidth], &decorations, &decorationFrames,
223 return NSWidth(textFrame);
226 - (NSRect)frameForDecoration:(const LocationBarDecoration*)aDecoration
227 inFrame:(NSRect)cellFrame {
228 // Short-circuit if the decoration is known to be not visible.
229 if (aDecoration && !aDecoration->IsVisible())
232 // Layout the decorations.
233 std::vector<LocationBarDecoration*> decorations;
234 std::vector<NSRect> decorationFrames;
236 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
237 [self edgeWidth], &decorations, &decorationFrames,
240 // Find our decoration and return the corresponding frame.
241 std::vector<LocationBarDecoration*>::const_iterator iter =
242 std::find(decorations.begin(), decorations.end(), aDecoration);
243 if (iter != decorations.end()) {
244 const size_t index = iter - decorations.begin();
245 return decorationFrames[index];
248 // Decorations which are not visible should have been filtered out
249 // at the top, but return |NSZeroRect| rather than a 0-width rect
255 // Overriden to account for the decorations.
256 - (NSRect)textFrameForFrame:(NSRect)cellFrame {
257 // Get the frame adjusted for decorations.
258 std::vector<LocationBarDecoration*> decorations;
259 std::vector<NSRect> decorationFrames;
260 NSRect textFrame = [super textFrameForFrame:cellFrame];
261 CalculatePositionsInFrame(textFrame, leftDecorations_, rightDecorations_,
262 [self edgeWidth], &decorations, &decorationFrames,
265 // NOTE: This function must closely match the logic in
266 // |-drawInteriorWithFrame:inView:|.
271 // Returns the sub-frame where clicks can happen within the cell.
272 - (NSRect)clickableFrameForFrame:(NSRect)cellFrame {
273 return [super textFrameForFrame:cellFrame];
276 - (NSRect)textCursorFrameForFrame:(NSRect)cellFrame {
277 std::vector<LocationBarDecoration*> decorations;
278 std::vector<NSRect> decorationFrames;
281 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
282 [self edgeWidth], &decorations,
283 &decorationFrames, &textFrame);
285 // Determine the left-most extent for the i-beam cursor.
286 CGFloat minX = NSMinX(textFrame);
287 for (size_t index = left_count; index--; ) {
288 if (decorations[index]->AcceptsMousePress())
291 // If at leftmost decoration, expand to edge of cell.
293 minX = NSMinX(cellFrame);
295 minX = NSMinX(decorationFrames[index]) - kDecorationHorizontalPad;
299 // Determine the right-most extent for the i-beam cursor.
300 CGFloat maxX = NSMaxX(textFrame);
301 for (size_t index = left_count; index < decorations.size(); ++index) {
302 if (decorations[index]->AcceptsMousePress())
305 // If at rightmost decoration, expand to edge of cell.
306 if (index == decorations.size() - 1) {
307 maxX = NSMaxX(cellFrame);
309 maxX = NSMaxX(decorationFrames[index]) + kDecorationHorizontalPad;
313 // I-beam cursor covers left-most to right-most.
314 return NSMakeRect(minX, NSMinY(textFrame), maxX - minX, NSHeight(textFrame));
317 - (void)drawWithFrame:(NSRect)frame inView:(NSView*)controlView {
319 const CGFloat lineWidth = [controlView cr_lineWidth];
321 [[self backgroundColor] set];
322 NSRectFillUsingOperation(NSInsetRect(frame, 1, 1), NSCompositeSourceOver);
324 CGFloat insetSize = lineWidth == 0.5 ? 1.5 : 2.0;
325 NSRect fillRect = NSInsetRect(frame, insetSize, insetSize);
326 [[self backgroundColor] set];
327 [[NSBezierPath bezierPathWithRoundedRect:fillRect
328 xRadius:kCornerRadius
329 yRadius:kCornerRadius] fill];
333 ui::DrawNinePartImage(frame,
334 isPopupMode_ ? kPopupBorderImageIds
335 : kNormalBorderImageIds,
336 NSCompositeSourceOver,
340 // Interior contents. Drawn after the border as some of the interior controls
341 // draw over the border.
342 [self drawInteriorWithFrame:frame inView:controlView];
345 if ([self showsFirstResponder]) {
346 NSRect focusRingRect = NSInsetRect(frame, lineWidth, lineWidth);
347 [[[NSColor keyboardFocusIndicatorColor]
348 colorWithAlphaComponent:0.5 / lineWidth] set];
349 NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:focusRingRect
350 xRadius:kCornerRadius
351 yRadius:kCornerRadius];
352 [path setLineWidth:lineWidth * 2.0];
357 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
358 std::vector<LocationBarDecoration*> decorations;
359 std::vector<NSRect> decorationFrames;
362 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
363 [self edgeWidth], &decorations, &decorationFrames,
366 // Draw the decorations.
367 for (size_t i = 0; i < decorations.size(); ++i) {
368 if (decorations[i]) {
369 NSRect background_frame = NSInsetRect(
370 decorationFrames[i], -(kDecorationHorizontalPad + 1) / 2, 2);
371 decorations[i]->DrawWithBackgroundInFrame(
372 background_frame, decorationFrames[i], controlView);
376 // NOTE: This function must closely match the logic in
377 // |-textFrameForFrame:|.
379 // Superclass draws text portion WRT original |cellFrame|.
380 // Even though -isOpaque is NO due to rounded corners, we know that the text
381 // will be drawn on top of an opaque area, therefore it is safe to enable
384 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
385 NSGraphicsContext* context = [NSGraphicsContext currentContext];
386 CGContextRef cgContext = static_cast<CGContextRef>([context graphicsPort]);
387 CGContextSetShouldSmoothFonts(cgContext, true);
388 [super drawInteriorWithFrame:cellFrame inView:controlView];
392 - (LocationBarDecoration*)decorationForEvent:(NSEvent*)theEvent
393 inRect:(NSRect)cellFrame
394 ofView:(AutocompleteTextField*)controlView
396 const BOOL flipped = [controlView isFlipped];
397 const NSPoint location =
398 [controlView convertPoint:[theEvent locationInWindow] fromView:nil];
400 std::vector<LocationBarDecoration*> decorations;
401 std::vector<NSRect> decorationFrames;
403 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
404 [self edgeWidth], &decorations, &decorationFrames,
407 for (size_t i = 0; i < decorations.size(); ++i) {
408 if (NSMouseInRect(location, decorationFrames[i], flipped))
409 return decorations[i];
415 - (NSMenu*)decorationMenuForEvent:(NSEvent*)theEvent
416 inRect:(NSRect)cellFrame
417 ofView:(AutocompleteTextField*)controlView {
418 LocationBarDecoration* decoration =
419 [self decorationForEvent:theEvent inRect:cellFrame ofView:controlView];
421 return decoration->GetMenu();
425 - (BOOL)mouseDown:(NSEvent*)theEvent
426 inRect:(NSRect)cellFrame
427 ofView:(AutocompleteTextField*)controlView {
428 // TODO(groby): Factor this into three pieces - find target for event, handle
429 // delayed focus (for any and all events), execute mouseDown for target.
431 // Check if this mouseDown was the reason the control became firstResponder.
432 // If not, discard focus event.
433 base::scoped_nsobject<NSEvent> focusEvent(focusEvent_.release());
434 if (![theEvent isEqual:focusEvent])
437 LocationBarDecoration* decoration =
438 [self decorationForEvent:theEvent inRect:cellFrame ofView:controlView];
439 if (!decoration || !decoration->AcceptsMousePress())
442 NSRect decorationRect =
443 [self frameForDecoration:decoration inFrame:cellFrame];
445 // If the decoration is draggable, then initiate a drag if the user
446 // drags or holds the mouse down for awhile.
447 if (decoration->IsDraggable()) {
449 [NSDate dateWithTimeIntervalSinceNow:kLocationIconDragTimeout];
450 NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask |
453 inMode:NSEventTrackingRunLoopMode
455 if (!event || [event type] == NSLeftMouseDragged) {
456 NSPasteboard* pboard = decoration->GetDragPasteboard();
459 NSImage* image = decoration->GetDragImage();
462 NSRect dragImageRect = decoration->GetDragImageFrame(decorationRect);
464 // Center under mouse horizontally, with cursor below so the image
466 const NSPoint mousePoint =
467 [controlView convertPoint:[theEvent locationInWindow] fromView:nil];
468 dragImageRect.origin =
469 NSMakePoint(mousePoint.x - NSWidth(dragImageRect) / 2.0,
470 mousePoint.y - NSHeight(dragImageRect));
472 // -[NSView dragImage:at:*] wants the images lower-left point,
473 // regardless of -isFlipped. Converting the rect to window base
474 // coordinates doesn't require any special-casing. Note that
475 // -[NSView dragFile:fromRect:*] takes a rect rather than a
476 // point, likely for this exact reason.
477 const NSPoint dragPoint =
478 [controlView convertRect:dragImageRect toView:nil].origin;
479 [[controlView window] dragImage:image
490 // On mouse-up fall through to mouse-pressed case.
491 DCHECK_EQ([event type], NSLeftMouseUp);
495 if (decoration->AsButtonDecoration()) {
496 ButtonDecoration* button = decoration->AsButtonDecoration();
498 button->SetButtonState(ButtonDecoration::kButtonStatePressed);
499 [controlView setNeedsDisplay:YES];
501 // Track the mouse until the user releases the button.
502 [self trackMouse:theEvent
503 inRect:decorationRect
507 const NSPoint mouseLocation = [[NSApp currentEvent] locationInWindow];
508 const NSPoint point = [controlView convertPoint:mouseLocation fromView:nil];
510 // Post delayed focus notification, if necessary.
511 if (focusEvent.get() && !button->PreventFocus(point))
512 [self focusNotificationFor:focusEvent ofView:controlView];
515 // Set the proper state (hover or normal) once the mouse has been released,
516 // and call |OnMousePressed| if the button was released while the mouse was
517 // within the bounds of the button.
518 if (NSMouseInRect(point, decorationRect, [controlView isFlipped])) {
519 button->SetButtonState(ButtonDecoration::kButtonStateHover);
520 [controlView setNeedsDisplay:YES];
521 handled = decoration->OnMousePressed(
522 [self frameForDecoration:decoration inFrame:cellFrame],
523 NSMakePoint(point.x - decorationRect.origin.x,
524 point.y - decorationRect.origin.y));
526 button->SetButtonState(ButtonDecoration::kButtonStateNormal);
527 [controlView setNeedsDisplay:YES];
531 const NSPoint mouseLocation = [theEvent locationInWindow];
532 const NSPoint point = [controlView convertPoint:mouseLocation fromView:nil];
533 handled = decoration->OnMousePressed(
535 NSMakePoint(point.x - decorationRect.origin.x,
536 point.y - decorationRect.origin.y));
539 return handled ? YES : NO;
542 // Helper method for the |mouseEntered:inView:| and |mouseExited:inView:|
543 // messages. Retrieves the |ButtonDecoration| for the specified event (received
544 // from a tracking area), and returns |NULL| if no decoration matches.
545 - (ButtonDecoration*)getButtonDecorationForEvent:(NSEvent*)theEvent {
546 ButtonDecoration* bd = static_cast<ButtonDecoration*>(
547 [[[[theEvent trackingArea] userInfo] valueForKey:kButtonDecorationKey]
551 std::count(leftDecorations_.begin(), leftDecorations_.end(), bd) ||
552 std::count(rightDecorations_.begin(), rightDecorations_.end(), bd));
557 // Helper method for |setUpTrackingAreasInView|. Creates an |NSDictionary| to
558 // be used as user information to identify which decoration is the source of an
559 // event (from a tracking area).
560 - (NSDictionary*)getDictionaryForButtonDecoration:
561 (ButtonDecoration*)decoration {
566 std::count(leftDecorations_.begin(), leftDecorations_.end(), decoration) ||
567 std::count(rightDecorations_.begin(), rightDecorations_.end(), decoration));
570 dictionaryWithObject:[NSValue valueWithPointer:decoration]
571 forKey:kButtonDecorationKey];
574 - (void)mouseEntered:(NSEvent*)theEvent
575 inView:(AutocompleteTextField*)controlView {
576 ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
578 decoration->SetButtonState(ButtonDecoration::kButtonStateHover);
579 [controlView setNeedsDisplay:YES];
583 - (void)mouseExited:(NSEvent*)theEvent
584 inView:(AutocompleteTextField*)controlView {
585 ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
587 decoration->SetButtonState(ButtonDecoration::kButtonStateNormal);
588 [controlView setNeedsDisplay:YES];
592 - (void)setUpTrackingAreasInRect:(NSRect)frame
593 ofView:(AutocompleteTextField*)view {
594 std::vector<LocationBarDecoration*> decorations;
595 std::vector<NSRect> decorationFrames;
597 NSRect cellRect = [self clickableFrameForFrame:[view bounds]];
598 CalculatePositionsInFrame(cellRect, leftDecorations_, rightDecorations_,
599 [self edgeWidth], &decorations, &decorationFrames,
602 // Remove previously-registered tracking areas, since we'll update them below.
603 for (CrTrackingArea* area in [view trackingAreas]) {
604 if ([[area userInfo] objectForKey:kButtonDecorationKey])
605 [view removeTrackingArea:area];
608 // Setup new tracking areas for the buttons.
609 for (size_t i = 0; i < decorations.size(); ++i) {
610 ButtonDecoration* button = decorations[i]->AsButtonDecoration();
612 // If the button isn't pressed (in which case we want to leave it as-is),
613 // update it's state since we might have missed some entered/exited events
614 // because of the removing/adding of the tracking areas.
615 if (button->GetButtonState() !=
616 ButtonDecoration::kButtonStatePressed) {
617 const NSPoint mouseLocationWindow =
618 [[view window] mouseLocationOutsideOfEventStream];
619 const NSPoint mouseLocation =
620 [view convertPoint:mouseLocationWindow fromView:nil];
621 const BOOL mouseInRect = NSMouseInRect(
622 mouseLocation, decorationFrames[i], [view isFlipped]);
623 button->SetButtonState(mouseInRect ?
624 ButtonDecoration::kButtonStateHover :
625 ButtonDecoration::kButtonStateNormal);
626 [view setNeedsDisplay:YES];
629 NSDictionary* info = [self getDictionaryForButtonDecoration:button];
630 base::scoped_nsobject<CrTrackingArea> area(
631 [[CrTrackingArea alloc] initWithRect:decorationFrames[i]
632 options:NSTrackingMouseEnteredAndExited |
633 NSTrackingActiveAlways
636 [view addTrackingArea:area];
641 // Given a newly created .webloc plist url file, also give it a resource
642 // fork and insert 'TEXT and 'url ' resources holding further copies of the
643 // url data. This is required for apps such as Terminal and Safari to accept it
644 // as a real webloc file when dragged in.
645 // It's expected that the resource fork requirement will go away at some
646 // point and this code can then be deleted.
647 OSErr WriteURLToNewWebLocFileResourceFork(NSURL* file, NSString* urlStr) {
648 ResFileRefNum refNum = kResFileNotOpened;
649 ResFileRefNum prevResRef = CurResFile();
652 HFSUniStr255 resourceForkName;
653 FSGetResourceForkName(&resourceForkName);
655 if (![[NSFileManager defaultManager] fileExistsAtPath:[file path]])
658 if (!CFURLGetFSRef((CFURLRef)file, &fsRef))
661 err = FSCreateResourceFork(&fsRef,
662 resourceForkName.length,
663 resourceForkName.unicode,
667 err = FSOpenResourceFile(&fsRef,
668 resourceForkName.length,
669 resourceForkName.unicode,
670 fsRdWrPerm, &refNum);
674 const char* utf8URL = [urlStr UTF8String];
675 int urlChars = strlen(utf8URL);
677 Handle urlHandle = NewHandle(urlChars);
678 memcpy(*urlHandle, utf8URL, urlChars);
680 Handle textHandle = NewHandle(urlChars);
681 memcpy(*textHandle, utf8URL, urlChars);
683 // Data for the 'drag' resource.
684 // This comes from derezzing webloc files made by the Finder.
685 // It's bigendian data, so it's represented here as chars to preserve
688 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Header.
689 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
690 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x01, 0x00, // 'TEXT', 0, 256
691 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
692 0x75, 0x72, 0x6C, 0x20, 0x00, 0x00, 0x01, 0x00, // 'url ', 0, 256
693 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
695 Handle dragHandle = NewHandleClear(sizeof(dragData));
696 memcpy(*dragHandle, &dragData[0], sizeof(dragData));
698 // Save the resources to the file.
699 ConstStr255Param noName = {0};
700 AddResource(dragHandle, 'drag', 128, noName);
701 AddResource(textHandle, 'TEXT', 256, noName);
702 AddResource(urlHandle, 'url ', 256, noName);
704 CloseResFile(refNum);
705 UseResFile(prevResRef);
709 // Returns the file path for file |name| if saved at NSURL |base|.
710 static NSString* PathWithBaseURLAndName(NSURL* base, NSString* name) {
711 NSString* filteredName =
712 [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
713 return [[NSURL URLWithString:filteredName relativeToURL:base] path];
716 // Returns if there is already a file |name| at dir NSURL |base|.
717 static BOOL FileAlreadyExists(NSURL* base, NSString* name) {
718 NSString* path = PathWithBaseURLAndName(base, name);
719 DCHECK([path hasSuffix:name]);
720 return [[NSFileManager defaultManager] fileExistsAtPath:path];
723 // Takes a destination URL, a suggested file name, & an extension (eg .webloc).
724 // Returns the complete file name with extension you should use.
725 // The name returned will not contain /, : or ?, will not be longer than
726 // kMaxNameLength + length of extension, and will not be a file name that
727 // already exists in that directory. If necessary it will try appending a space
728 // and a number to the name (but before the extension) trying numbers up to and
729 // including kMaxIndex.
730 // If the function gives up it returns nil.
731 static NSString* UnusedLegalNameForNewDropFile(NSURL* saveLocation,
733 NSString *extension) {
735 const int kMaxIndex = 20;
736 const unsigned kMaxNameLength = 64; // Arbitrary.
738 NSString* filteredName = [fileName stringByReplacingOccurrencesOfString:@"/"
740 filteredName = [filteredName stringByReplacingOccurrencesOfString:@":"
742 filteredName = [filteredName stringByReplacingOccurrencesOfString:@"?"
745 if ([filteredName length] > kMaxNameLength)
746 filteredName = [filteredName substringToIndex:kMaxNameLength];
748 NSString* candidateName = [filteredName stringByAppendingString:extension];
750 while (FileAlreadyExists(saveLocation, candidateName)) {
751 if (number > kMaxIndex)
754 candidateName = [filteredName stringByAppendingFormat:@" %d%@",
755 number++, extension];
758 return candidateName;
761 - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination {
762 NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
763 NSFileManager* fileManager = [NSFileManager defaultManager];
765 if (![pboard containsURLData])
768 NSArray *urls = NULL;
769 NSArray* titles = NULL;
770 [pboard getURLs:&urls andTitles:&titles convertingFilenames:YES];
772 NSString* urlStr = [urls objectAtIndex:0];
773 NSString* nameStr = [titles objectAtIndex:0];
775 NSString* nameWithExtensionStr =
776 UnusedLegalNameForNewDropFile(dropDestination, nameStr, @".webloc");
777 if (!nameWithExtensionStr)
780 NSDictionary* urlDict = [NSDictionary dictionaryWithObject:urlStr
783 [NSURL fileURLWithPath:PathWithBaseURLAndName(dropDestination,
784 nameWithExtensionStr)];
785 [urlDict writeToURL:outputURL
788 if (![fileManager fileExistsAtPath:[outputURL path]])
791 NSDictionary* attr = [NSDictionary dictionaryWithObjectsAndKeys:
792 [NSNumber numberWithBool:YES], NSFileExtensionHidden,
793 [NSNumber numberWithUnsignedLong:'ilht'], NSFileHFSTypeCode,
794 [NSNumber numberWithUnsignedLong:'MACS'], NSFileHFSCreatorCode,
796 [fileManager setAttributes:attr
797 ofItemAtPath:[outputURL path]
799 // Add resource data.
800 OSErr resStatus = WriteURLToNewWebLocFileResourceFork(outputURL, urlStr);
801 OSSTATUS_DCHECK(resStatus == noErr, resStatus);
803 return [NSArray arrayWithObject:nameWithExtensionStr];
806 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
807 return NSDragOperationCopy;
810 - (void)updateToolTipsInRect:(NSRect)cellFrame
811 ofView:(AutocompleteTextField*)controlView {
812 std::vector<LocationBarDecoration*> decorations;
813 std::vector<NSRect> decorationFrames;
815 CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
816 [self edgeWidth], &decorations, &decorationFrames,
819 for (size_t i = 0; i < decorations.size(); ++i) {
820 NSString* tooltip = decorations[i]->GetToolTip();
821 if ([tooltip length] > 0)
822 [controlView addToolTip:tooltip forRect:decorationFrames[i]];
826 - (BOOL)hideFocusState {
827 return hideFocusState_;
830 - (void)setHideFocusState:(BOOL)hideFocusState
831 ofView:(AutocompleteTextField*)controlView {
832 if (hideFocusState_ == hideFocusState)
834 hideFocusState_ = hideFocusState;
835 [controlView setNeedsDisplay:YES];
836 NSTextView* fieldEditor =
837 base::mac::ObjCCastStrict<NSTextView>([controlView currentEditor]);
838 [fieldEditor updateInsertionPointStateAndRestartTimer:YES];
841 - (BOOL)showsFirstResponder {
842 return [super showsFirstResponder] && !hideFocusState_;
845 - (void)focusNotificationFor:(NSEvent*)event
846 ofView:(AutocompleteTextField*)controlView {
847 if ([controlView observer]) {
848 const bool controlDown = ([event modifierFlags] & NSControlKeyMask) != 0;
849 [controlView observer]->OnSetFocus(controlDown);
853 - (void)handleFocusEvent:(NSEvent*)event
854 ofView:(AutocompleteTextField*)controlView {
855 // Only intercept left button click. All other events cause immediate focus.
856 if ([event type] == NSLeftMouseDown) {
857 LocationBarDecoration* decoration =
858 [self decorationForEvent:event
859 inRect:[controlView bounds]
861 // Only ButtonDecorations need a delayed focus handling.
862 if (decoration && decoration->AsButtonDecoration()) {
863 focusEvent_.reset([event retain]);
868 // Handle event immediately.
869 [self focusNotificationFor:event ofView:controlView];