blob: ac53430054a76e5daabb58effdaf1c1972367fa9 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h"
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_logging.h"
#include "chrome/browser/search/search.h"
#import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field.h"
#import "chrome/browser/ui/cocoa/location_bar/button_decoration.h"
#import "chrome/browser/ui/cocoa/location_bar/location_bar_decoration.h"
#import "chrome/browser/ui/cocoa/nsview_additions.h"
#import "chrome/common/extensions/feature_switch.h"
#import "third_party/mozilla/NSPasteboard+Utils.h"
#import "ui/base/cocoa/tracking_area.h"
using extensions::FeatureSwitch;
namespace {
// Matches the clipping radius of |GradientButtonCell|.
const CGFloat kCornerRadius = 3.0;
// How far to inset the left-hand decorations from the field's bounds.
const CGFloat kLeftDecorationXOffset = 5.0;
NSString* const kButtonDecorationKey = @"ButtonDecoration";
// How far to inset the right-hand decorations from the field's bounds.
// TODO(shess): Why is this different from |kLeftDecorationXOffset|?
// |kDecorationOuterXOffset|?
CGFloat RightDecorationXOffset() {
const CGFloat kRightDecorationXOffset = 5.0;
const CGFloat kScriptBadgeRightDecorationXOffset = 9.0;
if (FeatureSwitch::script_badges()->IsEnabled()) {
return kScriptBadgeRightDecorationXOffset;
} else {
return kRightDecorationXOffset;
}
}
// The amount of padding on either side reserved for drawing
// decorations. [Views has |kItemPadding| == 3.]
CGFloat DecorationHorizontalPad() {
const CGFloat kDecorationHorizontalPad = 3.0;
const CGFloat kScriptBadgeDecorationHorizontalPad = 9.0;
return FeatureSwitch::script_badges()->IsEnabled() ?
kScriptBadgeDecorationHorizontalPad : kDecorationHorizontalPad;
}
// How long to wait for mouse-up on the location icon before assuming
// that the user wants to drag.
const NSTimeInterval kLocationIconDragTimeout = 0.25;
// Calculate the positions for a set of decorations. |frame| is the
// overall frame to do layout in, |remaining_frame| will get the
// left-over space. |all_decorations| is the set of decorations to
// lay out, |decorations| will be set to the decorations which are
// visible and which fit, in the same order as |all_decorations|,
// while |decoration_frames| will be the corresponding frames.
// |x_edge| describes the edge to layout the decorations against
// (|NSMinXEdge| or |NSMaxXEdge|). |regular_padding| is the padding
// from the edge of |cell_frame| to use when the first visible decoration
// is a regular decoration. |action_padding| is the padding to use when the
// first decoration is a button decoration, ie. the action box button.
// (|DecorationHorizontalPad()| is used between decorations).
void CalculatePositionsHelper(
NSRect frame,
const std::vector<LocationBarDecoration*>& all_decorations,
NSRectEdge x_edge,
CGFloat regular_padding,
CGFloat action_padding,
std::vector<LocationBarDecoration*>* decorations,
std::vector<NSRect>* decoration_frames,
NSRect* remaining_frame) {
DCHECK(x_edge == NSMinXEdge || x_edge == NSMaxXEdge);
DCHECK_EQ(decorations->size(), decoration_frames->size());
// The initial padding depends on whether the first visible decoration is
// a button or not.
bool is_first_visible_decoration = true;
for (size_t i = 0; i < all_decorations.size(); ++i) {
if (all_decorations[i]->IsVisible()) {
CGFloat padding = DecorationHorizontalPad();
if (is_first_visible_decoration) {
padding = all_decorations[i]->AsButtonDecoration() ?
action_padding : regular_padding;
is_first_visible_decoration = false;
}
NSRect padding_rect, available;
// Peel off the outside padding.
NSDivideRect(frame, &padding_rect, &available, padding, x_edge);
// Find out how large the decoration will be in the remaining
// space.
const CGFloat used_width =
all_decorations[i]->GetWidthForSpace(NSWidth(available));
if (used_width != LocationBarDecoration::kOmittedWidth) {
DCHECK_GT(used_width, 0.0);
NSRect decoration_frame;
// Peel off the desired width, leaving the remainder in
// |frame|.
NSDivideRect(available, &decoration_frame, &frame,
used_width, x_edge);
decorations->push_back(all_decorations[i]);
decoration_frames->push_back(decoration_frame);
DCHECK_EQ(decorations->size(), decoration_frames->size());
// Adjust padding for between decorations.
padding = DecorationHorizontalPad();
}
}
}
DCHECK_EQ(decorations->size(), decoration_frames->size());
*remaining_frame = frame;
}
// Helper function for calculating placement of decorations w/in the cell.
// |frame| is the cell's boundary rectangle, |remaining_frame| will get any
// space left after decorations are laid out (for text). |left_decorations| is
// a set of decorations for the left-hand side of the cell, |right_decorations|
// for the right-hand side. |edge_width| is the width of one vertical edge of
// the omnibox, this depends on whether the display is low DPI or high DPI.
// |decorations| will contain the resulting visible decorations, and
// |decoration_frames| will contain their frames in the same coordinates as
// |frame|. Decorations will be ordered left to right. As a convenience returns
// the index of the first right-hand decoration.
size_t CalculatePositionsInFrame(
NSRect frame,
const std::vector<LocationBarDecoration*>& left_decorations,
const std::vector<LocationBarDecoration*>& right_decorations,
CGFloat edge_width,
std::vector<LocationBarDecoration*>* decorations,
std::vector<NSRect>* decoration_frames,
NSRect* remaining_frame) {
decorations->clear();
decoration_frames->clear();
// Layout |left_decorations| against the LHS.
CalculatePositionsHelper(frame, left_decorations, NSMinXEdge,
kLeftDecorationXOffset, kLeftDecorationXOffset,
decorations, decoration_frames, &frame);
DCHECK_EQ(decorations->size(), decoration_frames->size());
// Capture the number of visible left-hand decorations.
const size_t left_count = decorations->size();
// Layout |right_decorations| against the RHS.
CalculatePositionsHelper(frame, right_decorations, NSMaxXEdge,
RightDecorationXOffset(), edge_width, decorations,
decoration_frames, &frame);
DCHECK_EQ(decorations->size(), decoration_frames->size());
// Reverse the right-hand decorations so that overall everything is
// sorted left to right.
std::reverse(decorations->begin() + left_count, decorations->end());
std::reverse(decoration_frames->begin() + left_count,
decoration_frames->end());
*remaining_frame = frame;
if (FeatureSwitch::script_badges()->IsEnabled()) {
// Keep the padding distance between the right-most decoration and the edit
// box, so that any decoration background isn't overwritten by the edit
// box's background.
NSRect dummy;
NSDivideRect(frame, &dummy, remaining_frame,
DecorationHorizontalPad(), NSMaxXEdge);
}
return left_count;
}
} // namespace
@implementation AutocompleteTextFieldCell
- (CGFloat)topTextFrameOffset {
return 3.0;
}
- (CGFloat)bottomTextFrameOffset {
return 3.0;
}
- (CGFloat)cornerRadius {
return kCornerRadius;
}
- (CGFloat)edgeWidth {
// The omnibox vertical edge width is 1 pixel both in low DPI and high DPI.
return [[self controlView] cr_lineWidth];
}
- (BOOL)shouldDrawBezel {
return YES;
}
- (CGFloat)lineHeight {
return 17;
}
- (void)clearDecorations {
leftDecorations_.clear();
rightDecorations_.clear();
}
- (void)addLeftDecoration:(LocationBarDecoration*)decoration {
leftDecorations_.push_back(decoration);
}
- (void)addRightDecoration:(LocationBarDecoration*)decoration {
rightDecorations_.push_back(decoration);
}
- (CGFloat)availableWidthInFrame:(const NSRect)frame {
std::vector<LocationBarDecoration*> decorations;
std::vector<NSRect> decorationFrames;
NSRect textFrame;
CalculatePositionsInFrame(frame, leftDecorations_, rightDecorations_,
[self edgeWidth], &decorations, &decorationFrames,
&textFrame);
return NSWidth(textFrame);
}
- (NSRect)frameForDecoration:(const LocationBarDecoration*)aDecoration
inFrame:(NSRect)cellFrame {
// Short-circuit if the decoration is known to be not visible.
if (aDecoration && !aDecoration->IsVisible())
return NSZeroRect;
// Layout the decorations.
std::vector<LocationBarDecoration*> decorations;
std::vector<NSRect> decorationFrames;
NSRect textFrame;
CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
[self edgeWidth], &decorations, &decorationFrames,
&textFrame);
// Find our decoration and return the corresponding frame.
std::vector<LocationBarDecoration*>::const_iterator iter =
std::find(decorations.begin(), decorations.end(), aDecoration);
if (iter != decorations.end()) {
const size_t index = iter - decorations.begin();
return decorationFrames[index];
}
// Decorations which are not visible should have been filtered out
// at the top, but return |NSZeroRect| rather than a 0-width rect
// for consistency.
NOTREACHED();
return NSZeroRect;
}
// Overriden to account for the decorations.
- (NSRect)textFrameForFrame:(NSRect)cellFrame {
// Get the frame adjusted for decorations.
std::vector<LocationBarDecoration*> decorations;
std::vector<NSRect> decorationFrames;
NSRect textFrame = [super textFrameForFrame:cellFrame];
CalculatePositionsInFrame(textFrame, leftDecorations_, rightDecorations_,
[self edgeWidth], &decorations, &decorationFrames,
&textFrame);
// NOTE: This function must closely match the logic in
// |-drawInteriorWithFrame:inView:|.
return textFrame;
}
// Returns the sub-frame where clicks can happen within the cell.
- (NSRect)clickableFrameForFrame:(NSRect)cellFrame {
return [super textFrameForFrame:cellFrame];
}
- (NSRect)textCursorFrameForFrame:(NSRect)cellFrame {
std::vector<LocationBarDecoration*> decorations;
std::vector<NSRect> decorationFrames;
NSRect textFrame;
size_t left_count =
CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
[self edgeWidth], &decorations,
&decorationFrames, &textFrame);
// Determine the left-most extent for the i-beam cursor.
CGFloat minX = NSMinX(textFrame);
for (size_t index = left_count; index--; ) {
if (decorations[index]->AcceptsMousePress())
break;
// If at leftmost decoration, expand to edge of cell.
if (!index) {
minX = NSMinX(cellFrame);
} else {
minX = NSMinX(decorationFrames[index]) - DecorationHorizontalPad();
}
}
// Determine the right-most extent for the i-beam cursor.
CGFloat maxX = NSMaxX(textFrame);
for (size_t index = left_count; index < decorations.size(); ++index) {
if (decorations[index]->AcceptsMousePress())
break;
// If at rightmost decoration, expand to edge of cell.
if (index == decorations.size() - 1) {
maxX = NSMaxX(cellFrame);
} else {
maxX = NSMaxX(decorationFrames[index]) + DecorationHorizontalPad();
}
}
// I-beam cursor covers left-most to right-most.
return NSMakeRect(minX, NSMinY(textFrame), maxX - minX, NSHeight(textFrame));
}
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
std::vector<LocationBarDecoration*> decorations;
std::vector<NSRect> decorationFrames;
NSRect workingFrame;
CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
[self edgeWidth], &decorations, &decorationFrames,
&workingFrame);
// Draw the decorations.
for (size_t i = 0; i < decorations.size(); ++i) {
if (decorations[i]) {
NSRect background_frame = NSInsetRect(
decorationFrames[i], -(DecorationHorizontalPad() + 1) / 2, 2);
decorations[i]->DrawWithBackgroundInFrame(
background_frame, decorationFrames[i], controlView);
}
}
// NOTE: This function must closely match the logic in
// |-textFrameForFrame:|.
// Superclass draws text portion WRT original |cellFrame|.
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (LocationBarDecoration*)decorationForEvent:(NSEvent*)theEvent
inRect:(NSRect)cellFrame
ofView:(AutocompleteTextField*)controlView
{
const BOOL flipped = [controlView isFlipped];
const NSPoint location =
[controlView convertPoint:[theEvent locationInWindow] fromView:nil];
std::vector<LocationBarDecoration*> decorations;
std::vector<NSRect> decorationFrames;
NSRect textFrame;
CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
[self edgeWidth], &decorations, &decorationFrames,
&textFrame);
for (size_t i = 0; i < decorations.size(); ++i) {
if (NSMouseInRect(location, decorationFrames[i], flipped))
return decorations[i];
}
return NULL;
}
- (NSMenu*)decorationMenuForEvent:(NSEvent*)theEvent
inRect:(NSRect)cellFrame
ofView:(AutocompleteTextField*)controlView {
LocationBarDecoration* decoration =
[self decorationForEvent:theEvent inRect:cellFrame ofView:controlView];
if (decoration)
return decoration->GetMenu();
return nil;
}
- (BOOL)mouseDown:(NSEvent*)theEvent
inRect:(NSRect)cellFrame
ofView:(AutocompleteTextField*)controlView {
LocationBarDecoration* decoration =
[self decorationForEvent:theEvent inRect:cellFrame ofView:controlView];
if (!decoration || !decoration->AcceptsMousePress())
return NO;
NSRect decorationRect =
[self frameForDecoration:decoration inFrame:cellFrame];
// If the decoration is draggable, then initiate a drag if the user
// drags or holds the mouse down for awhile.
if (decoration->IsDraggable()) {
NSDate* timeout =
[NSDate dateWithTimeIntervalSinceNow:kLocationIconDragTimeout];
NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask |
NSLeftMouseUpMask)
untilDate:timeout
inMode:NSEventTrackingRunLoopMode
dequeue:YES];
if (!event || [event type] == NSLeftMouseDragged) {
NSPasteboard* pboard = decoration->GetDragPasteboard();
DCHECK(pboard);
NSImage* image = decoration->GetDragImage();
DCHECK(image);
NSRect dragImageRect = decoration->GetDragImageFrame(decorationRect);
// Center under mouse horizontally, with cursor below so the image
// can be seen.
const NSPoint mousePoint =
[controlView convertPoint:[theEvent locationInWindow] fromView:nil];
dragImageRect.origin =
NSMakePoint(mousePoint.x - NSWidth(dragImageRect) / 2.0,
mousePoint.y - NSHeight(dragImageRect));
// -[NSView dragImage:at:*] wants the images lower-left point,
// regardless of -isFlipped. Converting the rect to window base
// coordinates doesn't require any special-casing. Note that
// -[NSView dragFile:fromRect:*] takes a rect rather than a
// point, likely for this exact reason.
const NSPoint dragPoint =
[controlView convertRect:dragImageRect toView:nil].origin;
[[controlView window] dragImage:image
at:dragPoint
offset:NSZeroSize
event:theEvent
pasteboard:pboard
source:self
slideBack:YES];
return YES;
}
// On mouse-up fall through to mouse-pressed case.
DCHECK_EQ([event type], NSLeftMouseUp);
}
bool handled;
if (decoration->AsButtonDecoration()) {
handled = decoration->AsButtonDecoration()->OnMousePressedWithView(
decorationRect, controlView);
// Update tracking areas and make sure the button's state is consistent with
// the mouse's location (e.g. "normal" if the mouse is no longer over the
// decoration, "hover" otherwise).
[self setUpTrackingAreasInRect:cellFrame ofView:controlView];
} else {
handled = decoration->OnMousePressed(decorationRect);
}
return handled ? YES : NO;
}
// Helper method for the |mouseEntered:inView:| and |mouseExited:inView:|
// messages. Retrieves the |ButtonDecoration| for the specified event (received
// from a tracking area), and returns |NULL| if no decoration matches.
- (ButtonDecoration*)getButtonDecorationForEvent:(NSEvent*)theEvent {
ButtonDecoration* bd = static_cast<ButtonDecoration*>(
[[[[theEvent trackingArea] userInfo] valueForKey:kButtonDecorationKey]
pointerValue]);
CHECK(!bd ||
std::count(leftDecorations_.begin(), leftDecorations_.end(), bd) ||
std::count(rightDecorations_.begin(), rightDecorations_.end(), bd));
return bd;
}
// Helper method for |setUpTrackingAreasInView|. Creates an |NSDictionary| to
// be used as user information to identify which decoration is the source of an
// event (from a tracking area).
- (NSDictionary*)getDictionaryForButtonDecoration:
(ButtonDecoration*)decoration {
if (!decoration)
return nil;
DCHECK(
std::count(leftDecorations_.begin(), leftDecorations_.end(), decoration) ||
std::count(rightDecorations_.begin(), rightDecorations_.end(), decoration));
return [NSDictionary
dictionaryWithObject:[NSValue valueWithPointer:decoration]
forKey:kButtonDecorationKey];
}
- (void)mouseEntered:(NSEvent*)theEvent
inView:(AutocompleteTextField*)controlView {
ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
if (decoration) {
decoration->SetButtonState(ButtonDecoration::kButtonStateHover);
[controlView setNeedsDisplay:YES];
}
}
- (void)mouseExited:(NSEvent*)theEvent
inView:(AutocompleteTextField*)controlView {
ButtonDecoration* decoration = [self getButtonDecorationForEvent:theEvent];
if (decoration) {
decoration->SetButtonState(ButtonDecoration::kButtonStateNormal);
[controlView setNeedsDisplay:YES];
}
}
- (void)setUpTrackingAreasInRect:(NSRect)frame
ofView:(AutocompleteTextField*)view {
std::vector<LocationBarDecoration*> decorations;
std::vector<NSRect> decorationFrames;
NSRect textFrame;
NSRect cellRect = [self clickableFrameForFrame:[view bounds]];
CalculatePositionsInFrame(cellRect, leftDecorations_, rightDecorations_,
[self edgeWidth], &decorations, &decorationFrames,
&textFrame);
// Remove previously-registered tracking areas, since we'll update them below.
for (CrTrackingArea* area in [view trackingAreas]) {
if ([[area userInfo] objectForKey:kButtonDecorationKey])
[view removeTrackingArea:area];
}
// Setup new tracking areas for the buttons.
for (size_t i = 0; i < decorations.size(); ++i) {
ButtonDecoration* button = decorations[i]->AsButtonDecoration();
if (button) {
// If the button isn't pressed (in which case we want to leave it as-is),
// update it's state since we might have missed some entered/exited events
// because of the removing/adding of the tracking areas.
if (button->GetButtonState() !=
ButtonDecoration::kButtonStatePressed) {
const NSPoint mouseLocationWindow =
[[view window] mouseLocationOutsideOfEventStream];
const NSPoint mouseLocation =
[view convertPoint:mouseLocationWindow fromView:nil];
const BOOL mouseInRect = NSMouseInRect(
mouseLocation, decorationFrames[i], [view isFlipped]);
button->SetButtonState(mouseInRect ?
ButtonDecoration::kButtonStateHover :
ButtonDecoration::kButtonStateNormal);
[view setNeedsDisplay:YES];
}
NSDictionary* info = [self getDictionaryForButtonDecoration:button];
base::scoped_nsobject<CrTrackingArea> area(
[[CrTrackingArea alloc] initWithRect:decorationFrames[i]
options:NSTrackingMouseEnteredAndExited |
NSTrackingActiveAlways
owner:view
userInfo:info]);
[view addTrackingArea:area];
}
}
}
// Given a newly created .webloc plist url file, also give it a resource
// fork and insert 'TEXT and 'url ' resources holding further copies of the
// url data. This is required for apps such as Terminal and Safari to accept it
// as a real webloc file when dragged in.
// It's expected that the resource fork requirement will go away at some
// point and this code can then be deleted.
OSErr WriteURLToNewWebLocFileResourceFork(NSURL* file, NSString* urlStr) {
ResFileRefNum refNum = kResFileNotOpened;
ResFileRefNum prevResRef = CurResFile();
FSRef fsRef;
OSErr err = noErr;
HFSUniStr255 resourceForkName;
FSGetResourceForkName(&resourceForkName);
if (![[NSFileManager defaultManager] fileExistsAtPath:[file path]])
return fnfErr;
if (!CFURLGetFSRef((CFURLRef)file, &fsRef))
return fnfErr;
err = FSCreateResourceFork(&fsRef,
resourceForkName.length,
resourceForkName.unicode,
0);
if (err)
return err;
err = FSOpenResourceFile(&fsRef,
resourceForkName.length,
resourceForkName.unicode,
fsRdWrPerm, &refNum);
if (err)
return err;
const char* utf8URL = [urlStr UTF8String];
int urlChars = strlen(utf8URL);
Handle urlHandle = NewHandle(urlChars);
memcpy(*urlHandle, utf8URL, urlChars);
Handle textHandle = NewHandle(urlChars);
memcpy(*textHandle, utf8URL, urlChars);
// Data for the 'drag' resource.
// This comes from derezzing webloc files made by the Finder.
// It's bigendian data, so it's represented here as chars to preserve
// byte order.
char dragData[] = {
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Header.
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x01, 0x00, // 'TEXT', 0, 256
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x75, 0x72, 0x6C, 0x20, 0x00, 0x00, 0x01, 0x00, // 'url ', 0, 256
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
Handle dragHandle = NewHandleClear(sizeof(dragData));
memcpy(*dragHandle, &dragData[0], sizeof(dragData));
// Save the resources to the file.
ConstStr255Param noName = {0};
AddResource(dragHandle, 'drag', 128, noName);
AddResource(textHandle, 'TEXT', 256, noName);
AddResource(urlHandle, 'url ', 256, noName);
CloseResFile(refNum);
UseResFile(prevResRef);
return noErr;
}
// Returns the file path for file |name| if saved at NSURL |base|.
static NSString* PathWithBaseURLAndName(NSURL* base, NSString* name) {
NSString* filteredName =
[name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return [[NSURL URLWithString:filteredName relativeToURL:base] path];
}
// Returns if there is already a file |name| at dir NSURL |base|.
static BOOL FileAlreadyExists(NSURL* base, NSString* name) {
NSString* path = PathWithBaseURLAndName(base, name);
DCHECK([path hasSuffix:name]);
return [[NSFileManager defaultManager] fileExistsAtPath:path];
}
// Takes a destination URL, a suggested file name, & an extension (eg .webloc).
// Returns the complete file name with extension you should use.
// The name returned will not contain /, : or ?, will not be longer than
// kMaxNameLength + length of extension, and will not be a file name that
// already exists in that directory. If necessary it will try appending a space
// and a number to the name (but before the extension) trying numbers up to and
// including kMaxIndex.
// If the function gives up it returns nil.
static NSString* UnusedLegalNameForNewDropFile(NSURL* saveLocation,
NSString *fileName,
NSString *extension) {
int number = 1;
const int kMaxIndex = 20;
const unsigned kMaxNameLength = 64; // Arbitrary.
NSString* filteredName = [fileName stringByReplacingOccurrencesOfString:@"/"
withString:@"-"];
filteredName = [filteredName stringByReplacingOccurrencesOfString:@":"
withString:@"-"];
filteredName = [filteredName stringByReplacingOccurrencesOfString:@"?"
withString:@"-"];
if ([filteredName length] > kMaxNameLength)
filteredName = [filteredName substringToIndex:kMaxNameLength];
NSString* candidateName = [filteredName stringByAppendingString:extension];
while (FileAlreadyExists(saveLocation, candidateName)) {
if (number > kMaxIndex)
return nil;
else
candidateName = [filteredName stringByAppendingFormat:@" %d%@",
number++, extension];
}
return candidateName;
}
- (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination {
NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
NSFileManager* fileManager = [NSFileManager defaultManager];
if (![pboard containsURLData])
return NULL;
NSArray *urls = NULL;
NSArray* titles = NULL;
[pboard getURLs:&urls andTitles:&titles convertingFilenames:YES];
NSString* urlStr = [urls objectAtIndex:0];
NSString* nameStr = [titles objectAtIndex:0];
NSString* nameWithExtensionStr =
UnusedLegalNameForNewDropFile(dropDestination, nameStr, @".webloc");
if (!nameWithExtensionStr)
return NULL;
NSDictionary* urlDict = [NSDictionary dictionaryWithObject:urlStr
forKey:@"URL"];
NSURL* outputURL =
[NSURL fileURLWithPath:PathWithBaseURLAndName(dropDestination,
nameWithExtensionStr)];
[urlDict writeToURL:outputURL
atomically:NO];
if (![fileManager fileExistsAtPath:[outputURL path]])
return NULL;
NSDictionary* attr = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSFileExtensionHidden,
[NSNumber numberWithUnsignedLong:'ilht'], NSFileHFSTypeCode,
[NSNumber numberWithUnsignedLong:'MACS'], NSFileHFSCreatorCode,
nil];
[fileManager setAttributes:attr
ofItemAtPath:[outputURL path]
error:nil];
// Add resource data.
OSErr resStatus = WriteURLToNewWebLocFileResourceFork(outputURL, urlStr);
OSSTATUS_DCHECK(resStatus == noErr, resStatus);
return [NSArray arrayWithObject:nameWithExtensionStr];
}
- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
return NSDragOperationCopy;
}
- (void)updateToolTipsInRect:(NSRect)cellFrame
ofView:(AutocompleteTextField*)controlView {
std::vector<LocationBarDecoration*> decorations;
std::vector<NSRect> decorationFrames;
NSRect textFrame;
CalculatePositionsInFrame(cellFrame, leftDecorations_, rightDecorations_,
[self edgeWidth], &decorations, &decorationFrames,
&textFrame);
for (size_t i = 0; i < decorations.size(); ++i) {
NSString* tooltip = decorations[i]->GetToolTip();
if ([tooltip length] > 0)
[controlView addToolTip:tooltip forRect:decorationFrames[i]];
}
}
- (BOOL)hideFocusState {
return hideFocusState_;
}
- (void)setHideFocusState:(BOOL)hideFocusState
ofView:(AutocompleteTextField*)controlView {
if (hideFocusState_ == hideFocusState)
return;
hideFocusState_ = hideFocusState;
[controlView setNeedsDisplay:YES];
NSTextView* fieldEditor =
base::mac::ObjCCastStrict<NSTextView>([controlView currentEditor]);
[fieldEditor updateInsertionPointStateAndRestartTimer:YES];
}
- (BOOL)showsFirstResponder {
return [super showsFirstResponder] && !hideFocusState_;
}
@end