// Copyright (c) 2010 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/cocoa/bookmark_button.h" #include "base/logging.h" #import "base/scoped_nsobject.h" #include "chrome/browser/bookmarks/bookmark_model.h" #import "chrome/browser/cocoa/bookmark_button_cell.h" #import "chrome/browser/cocoa/browser_window_controller.h" #import "chrome/browser/cocoa/view_id_util.h" #include "chrome/browser/metrics/user_metrics.h" // The opacity of the bookmark button drag image. static const CGFloat kDragImageOpacity = 0.7; namespace bookmark_button { NSString* const kPulseBookmarkButtonNotification = @"PulseBookmarkButtonNotification"; NSString* const kBookmarkKey = @"BookmarkKey"; NSString* const kBookmarkPulseFlagKey = @"BookmarkPulseFlagKey"; }; @interface BookmarkButton(Private) // Make a drag image for the button. - (NSImage*)dragImage; @end // @interface BookmarkButton(Private) @implementation BookmarkButton @synthesize delegate = delegate_; - (id)initWithFrame:(NSRect)frameRect { // BookmarkButton's ViewID may be changed to VIEW_ID_OTHER_BOOKMARKS in // BookmarkBarController, so we can't just override -viewID method to return // it. if ((self = [super initWithFrame:frameRect])) view_id_util::SetID(self, VIEW_ID_BOOKMARK_BAR_ELEMENT); return self; } - (void)dealloc { if ([[self cell] respondsToSelector:@selector(safelyStopPulsing)]) [[self cell] safelyStopPulsing]; view_id_util::UnsetID(self); [super dealloc]; } - (const BookmarkNode*)bookmarkNode { return [[self cell] bookmarkNode]; } - (BOOL)isFolder { const BookmarkNode* node = [self bookmarkNode]; return (node && node->is_folder()); } - (BOOL)isEmpty { return [self bookmarkNode] ? NO : YES; } - (void)setIsContinuousPulsing:(BOOL)flag { [[self cell] setIsContinuousPulsing:flag]; } - (BOOL)isContinuousPulsing { return [[self cell] isContinuousPulsing]; } - (NSPoint)screenLocationForRemoveAnimation { NSPoint point; if (dragPending_) { // Use the position of the mouse in the drag image as the location. point = dragEndScreenLocation_; point.x += dragMouseOffset_.x; if ([self isFlipped]) { point.y += [self bounds].size.height - dragMouseOffset_.y; } else { point.y += dragMouseOffset_.y; } } else { // Use the middle of this button as the location. NSRect bounds = [self bounds]; point = NSMakePoint(NSMidX(bounds), NSMidY(bounds)); point = [self convertPoint:point toView:nil]; point = [[self window] convertBaseToScreen:point]; } return point; } // By default, NSButton ignores middle-clicks. // But we want them. - (void)otherMouseUp:(NSEvent*)event { [self performClick:self]; } // Overridden from DraggableButton. - (void)beginDrag:(NSEvent*)event { // Don't allow a drag of the empty node. // The empty node is a placeholder for "(empty)", to be revisited. if ([self isEmpty]) return; if ([self delegate]) { // Ask our delegate to fill the pasteboard for us. NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; [[self 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]; // Lock bar visibility, forcing the overlay to stay visible if we are in // fullscreen mode. if ([[self delegate] dragShouldLockBarVisibility]) { DCHECK(!visibilityDelegate_); NSWindow* window = [[self delegate] browserWindow]; visibilityDelegate_ = [BrowserWindowController browserWindowControllerForWindow:window]; [visibilityDelegate_ lockBarVisibilityForOwner:self withAnimation:NO delay:NO]; } const BookmarkNode* node = [self bookmarkNode]; const BookmarkNode* parent = node ? node->GetParent() : NULL; BOOL isWithinFolder = parent && parent->type() == BookmarkNode::FOLDER; UserMetrics::RecordAction(UserMetricsAction( isWithinFolder ? "BookmarkBarFolder_DragStart" : "BookmarkBar_DragStart")); dragMouseOffset_ = [self convertPointFromBase:[event locationInWindow]]; dragPending_ = YES; 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. dragPending_ = NO; [self autorelease]; } else { // Avoid blowing up, but we really shouldn't get here. NOTREACHED(); } } // Overridden to release bar visibility. - (void)endDrag { // visibilityDelegate_ can be nil if we're detached, and that's fine. [visibilityDelegate_ releaseBarVisibilityForOwner:self withAnimation:YES delay:YES]; visibilityDelegate_ = nil; [super endDrag]; } - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { NSDragOperation operation = NSDragOperationCopy; if (isLocal) { operation |= NSDragOperationMove; } if ([delegate_ canDragBookmarkButtonToTrash:self]) { operation |= NSDragOperationDelete; } return operation; } - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation { if (operation & NSDragOperationDelete) { dragEndScreenLocation_ = aPoint; [delegate_ didDragBookmarkToTrash:self]; } } // mouseEntered: and mouseExited: are called from our // BookmarkButtonCell. We redirect this information to our delegate. // The controller can then perform menu-like actions (e.g. "hover over // to open menu"). - (void)mouseEntered:(NSEvent*)event { [delegate_ mouseEnteredButton:self event:event]; } // See comments above mouseEntered:. - (void)mouseExited:(NSEvent*)event { [delegate_ mouseExitedButton:self event:event]; } @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)