// Copyright (c) 2009 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. #include "base/logging.h" #import "base/scoped_nsobject.h" #import "chrome/browser/cocoa/bookmark_button.h" #import "chrome/browser/cocoa/bookmark_button_cell.h" #import "third_party/GTM/AppKit/GTMTheme.h" namespace { // Code taken from . // TODO(viettrungluu): Do we want common, standard code for drag hysteresis? const CGFloat kWebDragStartHysteresisX = 5.0; const CGFloat kWebDragStartHysteresisY = 5.0; // The opacity of the bookmark button drag image. const CGFloat kDragImageOpacity = 0.7; } @interface BookmarkButton(Private) // Make a drag image for the button. - (NSImage*)dragImage; @end // @interface BookmarkButton(Private) @implementation BookmarkButton @synthesize draggable = draggable_; @synthesize delegate = delegate_; - (id)initWithFrame:(NSRect)frame { if ((self = [super initWithFrame:frame])) { draggable_ = YES; } return self; } // By default, NSButton ignores middle-clicks. - (void)otherMouseUp:(NSEvent*)event { [self performClick:self]; } - (void)beginDrag:(NSEvent*)event { // Starting drag. Never start another drag until another mouse down. mayDragStart_ = NO; if (delegate_) { // Ask our delegate to fill the pasteboard for us. NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; [delegate_ fillPasteboard:pboard forDragOfButton:self]; // At the moment, moving bookmarks causes their buttons (like me!) // to be destroyed and rebuilt. Make sure we don't go away while on // the stack. [self retain]; CGFloat yAt = [self bounds].size.height; NSSize dragOffset = NSMakeSize(0.0, 0.0); [self dragImage:[self dragImage] at:NSMakePoint(0, yAt) offset:dragOffset event:event pasteboard:pboard source:self slideBack:YES]; // And we're done. [self autorelease]; } else { // Avoid blowing up, but we really shouldn't get here. NOTREACHED(); } } - (void)draggedImage:(NSImage*)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation { beingDragged_ = NO; [[self cell] setHighlighted:NO]; } - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { return isLocal ? NSDragOperationCopy | NSDragOperationMove : NSDragOperationCopy; } - (void)mouseUp:(NSEvent*)theEvent { // Make sure that we can't start a drag until we see a mouse down again. mayDragStart_ = NO; // This conditional is never true (DnD loops in Cocoa eat the mouse // up) but I added it in case future versions of Cocoa do unexpected // things. if (beingDragged_) { NOTREACHED(); return [super mouseUp:theEvent]; } // There are non-drag cases where a mouseUp: may happen // (e.g. mouse-down, cmd-tab to another application, move mouse, // mouse-up). So we check. NSPoint viewLocal = [self convertPoint:[theEvent locationInWindow] fromView:[[self window] contentView]]; if (NSPointInRect(viewLocal, [self bounds])) { [self performClick:self]; } else { [[self cell] setHighlighted:NO]; } } // Mimic "begin a click" operation visually. Do NOT follow through // with normal button event handling. - (void)mouseDown:(NSEvent*)theEvent { mayDragStart_ = YES; [[self cell] setHighlighted:YES]; initialMouseDownLocation_ = [theEvent locationInWindow]; } // Return YES if we have crossed a threshold of movement after // mouse-down when we should begin a drag. Else NO. - (BOOL)hasCrossedDragThreshold:(NSEvent*)theEvent { NSPoint currentLocation = [theEvent locationInWindow]; if ((abs(currentLocation.x - initialMouseDownLocation_.x) > kWebDragStartHysteresisX) || (abs(currentLocation.y - initialMouseDownLocation_.y) > kWebDragStartHysteresisY)) { return YES; } else { return NO; } } - (void)mouseDragged:(NSEvent*)theEvent { if (beingDragged_) { [super mouseDragged:theEvent]; } else if (draggable_ && mayDragStart_ && [self hasCrossedDragThreshold:theEvent]) { [self beginDrag:theEvent]; } } @end @implementation BookmarkButton(Private) - (NSImage*)dragImage { NSRect bounds = [self bounds]; // Grab the image from the screen and put it in an |NSImage|. We can't use // this directly since we need to clip it and set its opacity. This won't work // if the source view is clipped. Fortunately, we don't display clipped // bookmark buttons. [self lockFocus]; scoped_nsobject bitmap([[NSBitmapImageRep alloc] initWithFocusedViewRect:bounds]); [self unlockFocus]; scoped_nsobject image([[NSImage alloc] initWithSize:[bitmap size]]); [image addRepresentation:bitmap]; // Make an autoreleased |NSImage|, which will be returned, and draw into it. // By default, the |NSImage| will be completely transparent. NSImage* dragImage = [[[NSImage alloc] initWithSize:[bitmap size]] autorelease]; [dragImage lockFocus]; // Draw the image with the appropriate opacity, clipping it tightly. GradientButtonCell* cell = static_cast([self cell]); DCHECK([cell isKindOfClass:[GradientButtonCell class]]); [[cell clipPathForFrame:bounds inView:self] setClip]; [image drawAtPoint:NSMakePoint(0, 0) fromRect:NSMakeRect(0, 0, NSWidth(bounds), NSHeight(bounds)) operation:NSCompositeSourceOver fraction:kDragImageOpacity]; [dragImage unlockFocus]; return dragImage; } @end // @implementation BookmarkButton(Private)