summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormaf@chromium.org <maf@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-03-05 02:18:36 +0000
committermaf@chromium.org <maf@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-03-05 02:18:36 +0000
commit797587a93927badde544d800c95739320eca04bb (patch)
tree03a10543cec88b7bf0b8419cb4999ae11a7fd626
parent9ad63d2ad848efdda29deef7ce65e6d286d2d478 (diff)
downloadchromium_src-797587a93927badde544d800c95739320eca04bb.zip
chromium_src-797587a93927badde544d800c95739320eca04bb.tar.gz
chromium_src-797587a93927badde544d800c95739320eca04bb.tar.bz2
Major rewrite of BookmarkButton event-handling to support proper menu
tracking on mousedown, sticky and non-sticky menus, drag-down to get menu on draggable folders, etc. Also contains first half of animation support for these UI items. Note that a forthcoming checkin will add live animation during the drag, but that's not covered by this checkin, which adds animation support for the bookmark toolbar buttons, but only in response to completed actions. BUG=72011,70002,72012 Review URL: http://codereview.chromium.org/6594065 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@77022 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/ui/cocoa/animation_utils.h35
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm17
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller_unittest.mm3
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.mm8
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller_unittest.mm2
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view.mm2
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_button.h7
-rw-r--r--chrome/browser/ui/cocoa/bookmarks/bookmark_button.mm97
-rw-r--r--chrome/browser/ui/cocoa/draggable_button.h53
-rw-r--r--chrome/browser/ui/cocoa/draggable_button.mm132
-rw-r--r--chrome/browser/ui/cocoa/gradient_button_cell.mm3
11 files changed, 327 insertions, 32 deletions
diff --git a/chrome/browser/ui/cocoa/animation_utils.h b/chrome/browser/ui/cocoa/animation_utils.h
new file mode 100644
index 0000000..3a34e0e
--- /dev/null
+++ b/chrome/browser/ui/cocoa/animation_utils.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROME_BROWSER_UI_COCOA_ANIMATION_UTILS_H
+#define CHROME_BROWSER_UI_COCOA_ANIMATION_UTILS_H
+#pragma once
+
+#import <Cocoa/Cocoa.h>
+
+// This class is a stack-based helper useful for unit testing of Cocoa UI,
+// and any other situation where you want to temporarily turn off Cocoa
+// animation for the life of a function call or other limited scope.
+// Just declare one of these, and all animations will complete instantly until
+// this goes out of scope and pops our state off the Core Animation stack.
+//
+// Example:
+// MyUnitTest() {
+// WithNoAnimation at_all; // Turn off Cocoa auto animation in this scope.
+
+
+class WithNoAnimation {
+ public:
+ WithNoAnimation() {
+ [NSAnimationContext beginGrouping];
+ [[NSAnimationContext currentContext] setDuration:0.0];
+ }
+
+ ~WithNoAnimation() {
+ [NSAnimationContext endGrouping];
+ }
+};
+
+
+#endif // CHROME_BROWSER_UI_COCOA_ANIMATION_UTILS_H
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
index 847b3cc..cad2d78 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.mm
@@ -332,6 +332,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
// Complete init of the "off the side" button, as much as we can.
[offTheSideButton_ setDraggable:NO];
+ [offTheSideButton_ setActsOnMouseDown:YES];
// We are enabled by default.
barIsEnabled_ = YES;
@@ -1139,6 +1140,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
if (node->is_folder()) {
[button setTarget:self];
[button setAction:@selector(openBookmarkFolderFromButton:)];
+ [button setActsOnMouseDown:YES];
} else {
// Make the button do something
[button setTarget:self];
@@ -1203,6 +1205,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
frame.origin.x -= bookmarks::kBookmarkHorizontalPadding;
BookmarkButton* button = [[BookmarkButton alloc] initWithFrame:frame];
[button setDraggable:NO];
+ [button setActsOnMouseDown:YES];
otherBookmarksButton_.reset(button);
view_id_util::SetID(button, VIEW_ID_OTHER_BOOKMARKS);
@@ -1555,12 +1558,12 @@ void RecordAppLaunch(Profile* profile, GURL url) {
CGFloat delta = desiredSize - frame.size.width;
if (delta) {
frame.size.width = desiredSize;
- [button setFrame:frame];
+ [[button animator] setFrame:frame];
for (NSButton* button in buttons_.get()) {
NSRect buttonFrame = [button frame];
if (buttonFrame.origin.x > frame.origin.x) {
buttonFrame.origin.x += delta;
- [button setFrame:buttonFrame];
+ [[button animator] setFrame:buttonFrame];
}
}
}
@@ -1662,7 +1665,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
// - right-click (and unclick) on it to open context menu
// - move mouse to window titlebar then click-drag it by the titlebar
// http://crbug.com/49333
- return YES;
+ return NO;
default:
break;
}
@@ -2325,7 +2328,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
BookmarkButton* button = [buttons_ objectAtIndex:i];
NSPoint buttonOrigin = [button frame].origin;
buttonOrigin.x += xOffset;
- [button setFrameOrigin:buttonOrigin];
+ [[button animator] setFrameOrigin:buttonOrigin];
}
++displayedButtonCount_;
[buttons_ insertObject:newButton atIndex:buttonIndex];
@@ -2410,7 +2413,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
BookmarkButton* button = [buttons_ objectAtIndex:i];
NSRect frame = [button frame];
frame.origin.x -= xOffset;
- [button setFrameOrigin:frame.origin];
+ [[button animator] setFrameOrigin:frame.origin];
}
} else {
// Move the button from right to left within the bar.
@@ -2420,7 +2423,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
BookmarkButton* button = [buttons_ objectAtIndex:i];
NSRect buttonFrame = [button frame];
buttonFrame.origin.x += xOffset;
- [button setFrameOrigin:buttonFrame.origin];
+ [[button animator] setFrameOrigin:buttonFrame.origin];
}
}
[buttons_ insertObject:movedButton atIndex:toIndex];
@@ -2475,7 +2478,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
BookmarkButton* button = [buttons_ objectAtIndex:i];
NSRect buttonFrame = [button frame];
buttonFrame.origin.x -= xOffset;
- [button setFrame:buttonFrame];
+ [[button animator] setFrame:buttonFrame];
// If this button is showing its menu then we need to move the menu, too.
if (button == [folderController_ parentButton])
[folderController_ offsetFolderMenuWindow:NSMakeSize(xOffset, 0.0)];
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller_unittest.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller_unittest.mm
index c63b362..a2655d6 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller_unittest.mm
@@ -12,6 +12,7 @@
#include "base/sys_string_conversions.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
+#import "chrome/browser/ui/cocoa/animation_utils.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_constants.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_window.h"
@@ -877,6 +878,7 @@ TEST_F(BookmarkBarControllerTest, TestButtonMarch) {
}
TEST_F(BookmarkBarControllerTest, CheckForGrowth) {
+ WithNoAnimation at_all; // Turn off Cocoa auto animation in this scope.
BookmarkModel* model = helper_.profile()->GetBookmarkModel();
GURL gurl1("http://www.google.com");
string16 title1(ASCIIToUTF16("x"));
@@ -1109,6 +1111,7 @@ TEST_F(BookmarkBarControllerTest, TestMenuNodeAndDisable) {
}
TEST_F(BookmarkBarControllerTest, TestDragButton) {
+ WithNoAnimation at_all;
BookmarkModel* model = helper_.profile()->GetBookmarkModel();
GURL gurls[] = { GURL("http://www.google.com/a"),
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.mm
index 5f4a182..9286220 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.mm
@@ -393,6 +393,7 @@ struct LayoutMetrics {
NSString* tooltip = [NSString stringWithFormat:@"%@\n%s", title,
urlString.c_str()];
[button setToolTip:tooltip];
+ [button setAcceptsTrackIn:YES];
}
} else {
[button setEnabled:NO];
@@ -1203,6 +1204,10 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
#pragma mark NSWindowDelegate Functions
- (void)windowWillClose:(NSNotification*)notification {
+ // Also done by the dealloc method, but also doing it here is quicker and
+ // more reliable.
+ [parentButton_ forceButtonBorderToStayOnAlways:NO];
+
// If a "hover open" is pending when the bookmark bar folder is
// closed, be sure it gets cancelled.
[NSObject cancelPreviousPerformRequestsWithTarget:self];
@@ -1238,7 +1243,8 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
[self performSelector:@selector(openBookmarkFolderFromButtonAndCloseOldOne:)
withObject:sender
- afterDelay:bookmarks::kHoverOpenDelay];
+ afterDelay:bookmarks::kHoverOpenDelay
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}
// Called from the BookmarkButton
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller_unittest.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller_unittest.mm
index c1c2441..d357087 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller_unittest.mm
@@ -8,6 +8,7 @@
#include "base/scoped_nsobject.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
+#import "chrome/browser/ui/cocoa/animation_utils.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_constants.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_button_cell.h"
@@ -678,6 +679,7 @@ class BookmarkBarFolderControllerMenuTest : public CocoaTest {
};
TEST_F(BookmarkBarFolderControllerMenuTest, DragMoveBarBookmarkToFolder) {
+ WithNoAnimation at_all;
BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
const BookmarkNode* root = model.GetBookmarkBarNode();
const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view.mm
index 06a5443..b3bb105 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view.mm
@@ -184,7 +184,7 @@
// TODO(port): This should probably return |YES| and the controller should
// slide the existing bookmark buttons interactively to the side to make
// room for the about-to-be-dropped bookmark.
- return NO;
+ return YES;
}
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info {
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_button.h b/chrome/browser/ui/cocoa/bookmarks/bookmark_button.h
index e09c620..c04b3eb 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_button.h
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_button.h
@@ -199,9 +199,12 @@ class ThemeProvider;
NSPoint dragMouseOffset_;
NSPoint dragEndScreenLocation_;
BOOL dragPending_;
+ BOOL acceptsTrackIn_;
+ NSTrackingArea* area_;
}
@property(assign, nonatomic) NSObject<BookmarkButtonDelegate>* delegate;
+@property(assign, nonatomic) BOOL acceptsTrackIn;
// Return the bookmark node associated with this button, or NULL.
- (const BookmarkNode*)bookmarkNode;
@@ -228,6 +231,10 @@ class ThemeProvider;
// be displayed.
- (NSPoint)screenLocationForRemoveAnimation;
+// The BookmarkButton which is currently being dragged, if any.
++ (BookmarkButton*)draggedButton;
+
+
@end // @interface BookmarkButton
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_button.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_button.mm
index dba0f6b..191f371 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_button.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_button.mm
@@ -25,24 +25,37 @@ NSString* const kBookmarkPulseFlagKey = @"BookmarkPulseFlagKey";
};
+namespace {
+// We need a class variable to track the current dragged button to enable
+// proper live animated dragging behavior, and can't do it in the
+// delegate/controller since you can drag a button from one domain to the
+// other (from a "folder" menu, to the main bar, or vice versa).
+BookmarkButton* gDraggedButton = nil; // Weak
+};
+
@interface BookmarkButton(Private)
// Make a drag image for the button.
- (NSImage*)dragImage;
+- (void)installCustomTrackingArea;
+
@end // @interface BookmarkButton(Private)
@implementation BookmarkButton
@synthesize delegate = delegate_;
+@synthesize acceptsTrackIn = acceptsTrackIn_;
- (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]))
+ if ((self = [super initWithFrame:frameRect])) {
view_id_util::SetID(self, VIEW_ID_BOOKMARK_BAR_ELEMENT);
+ [self installCustomTrackingArea];
+ }
return self;
}
@@ -97,12 +110,43 @@ NSString* const kBookmarkPulseFlagKey = @"BookmarkPulseFlagKey";
return point;
}
+
+- (void)updateTrackingAreas {
+ [self installCustomTrackingArea];
+ [super updateTrackingAreas];
+}
+
+- (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta
+ yDelta:(float)yDelta
+ xHysteresis:(float)xHysteresis
+ yHysteresis:(float)yHysteresis {
+ const float kDownProportion = 1.4142135f; // Square root of 2.
+
+ // We want to show a folder menu when you drag down on folder buttons,
+ // so don't classify this as a drag for that case.
+ if ([self isFolder] &&
+ (yDelta <= -yHysteresis) && // Bottom of hysteresis box was hit.
+ (ABS(yDelta)/ABS(xDelta)) >= kDownProportion)
+ return NO;
+
+ return [super deltaIndicatesDragStartWithXDelta:xDelta
+ yDelta:yDelta
+ xHysteresis:xHysteresis
+ yHysteresis:yHysteresis];
+}
+
+
// By default, NSButton ignores middle-clicks.
// But we want them.
- (void)otherMouseUp:(NSEvent*)event {
[self performClick:self];
}
+- (BOOL)acceptsTrackInFrom:(id)sender {
+ return [self isFolder] || [self acceptsTrackIn];
+}
+
+
// Overridden from DraggableButton.
- (void)beginDrag:(NSEvent*)event {
// Don't allow a drag of the empty node.
@@ -114,15 +158,16 @@ NSString* const kBookmarkPulseFlagKey = @"BookmarkPulseFlagKey";
NOTREACHED();
return;
}
- // 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];
+ // Ask our delegate to fill the pasteboard for us.
+ NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
+ [[self delegate] fillPasteboard:pboard forDragOfButton:self];
+
// Lock bar visibility, forcing the overlay to stay visible if we are in
// fullscreen mode.
if ([[self delegate] dragShouldLockBarVisibility]) {
@@ -144,19 +189,27 @@ NSString* const kBookmarkPulseFlagKey = @"BookmarkPulseFlagKey";
dragMouseOffset_ = [self convertPointFromBase:[event locationInWindow]];
dragPending_ = YES;
+ gDraggedButton = self;
+ [[self animator] setHidden: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];
+ [self setHidden:NO];
+
// And we're done.
dragPending_ = NO;
+ gDraggedButton = nil;
+
[self autorelease];
}
// Overridden to release bar visibility.
- (void)endDrag {
+ gDraggedButton = nil;
+
// visibilityDelegate_ can be nil if we're detached, and that's fine.
[visibilityDelegate_ releaseBarVisibilityForOwner:self
withAnimation:YES
@@ -179,6 +232,7 @@ NSString* const kBookmarkPulseFlagKey = @"BookmarkPulseFlagKey";
- (void)draggedImage:(NSImage *)anImage
endedAt:(NSPoint)aPoint
operation:(NSDragOperation)operation {
+ gDraggedButton = nil;
// Inform delegate of drag source that we're finished dragging,
// so it can close auto-opened bookmark folders etc.
[delegate_ bookmarkDragDidEnd:self];
@@ -202,10 +256,43 @@ NSString* const kBookmarkPulseFlagKey = @"BookmarkPulseFlagKey";
[delegate_ mouseExitedButton:self event:event];
}
++ (BookmarkButton*)draggedButton {
+ return gDraggedButton;
+}
+
+// This only gets called after a click that wasn't a drag, and only on folders.
+- (void)secondaryMouseUpAction:(BOOL)wasInside {
+ const NSTimeInterval kShortClickLength = 0.5;
+ // Long clicks that end over the folder button result in the menu hiding.
+ if (wasInside && ([self durationMouseWasDown] > kShortClickLength)) {
+ [[self target] performSelector:[self action] withObject:self];
+ } else {
+ // Mouse tracked out of button during menu track. Hide menus.
+ if (!wasInside)
+ [delegate_ bookmarkDragDidEnd:self];
+ }
+}
+
@end
@implementation BookmarkButton(Private)
+- (void)installCustomTrackingArea {
+ if (area_)
+ return;
+
+ NSTrackingAreaOptions options = NSTrackingActiveInActiveApp |
+ NSTrackingMouseEnteredAndExited | NSTrackingEnabledDuringMouseDrag |
+ NSTrackingInVisibleRect;
+
+ area_ = [[NSTrackingArea alloc] initWithRect:[self bounds]
+ options:options
+ owner:self
+ userInfo:nil];
+ [self addTrackingArea:area_];
+}
+
+
- (NSImage*)dragImage {
NSRect bounds = [self bounds];
@@ -223,7 +310,7 @@ NSString* const kBookmarkPulseFlagKey = @"BookmarkPulseFlagKey";
// 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];
+ [[[NSImage alloc] initWithSize:[bitmap size]] autorelease];
[dragImage lockFocus];
// Draw the image with the appropriate opacity, clipping it tightly.
diff --git a/chrome/browser/ui/cocoa/draggable_button.h b/chrome/browser/ui/cocoa/draggable_button.h
index 7f58255..ab74c2b 100644
--- a/chrome/browser/ui/cocoa/draggable_button.h
+++ b/chrome/browser/ui/cocoa/draggable_button.h
@@ -9,18 +9,64 @@
// |-performClick:|. Subclasses should override these two methods.
@interface DraggableButton : NSButton {
@private
- BOOL draggable_; // Is this a draggable type of button?
+ BOOL draggable_; // Is this a draggable type of button?
+ BOOL actionHasFired_; // Has the action already fired for this click?
+ BOOL actsOnMouseDown_; // Does button action happen on mouse down when
+ // possible?
+ NSTimeInterval durationMouseWasDown_;
+ NSTimeInterval whenMouseDown_;
}
// Enable or disable dragability for special buttons like "Other Bookmarks".
@property(nonatomic) BOOL draggable;
+// If it has a popup menu, for example, we want to perform the action on mouse
+// down, if possible (as long as user still gets chance to drag, if
+// appropriate).
+@property(nonatomic) BOOL actsOnMouseDown;
+
// Called when a drag should start. Subclasses must override this to do any
// pasteboard manipulation and begin the drag, usually with
// -dragImage:at:offset:event:. Subclasses must call one of the blocking
// -drag* methods of NSView when overriding this method.
- (void)beginDrag:(NSEvent*)dragEvent;
+// Called internally. Default impl only returns YES if sender==self.
+// Override if your subclass wants to accept being tracked into while a
+// click is being tracked on another DraggableButton. Needed to support
+// buttons being used as fake menu items or menu titles, as BookmarkButton does.
+- (BOOL)acceptsTrackInFrom:(id)sender;
+
+// Override if you want to do any extra work on mouseUp, after a mouseDown
+// action has already fired.
+- (void)secondaryMouseUpAction:(BOOL)wasInside;
+
+// This is called internally.
+// Decides if we now have enough information to stop tracking the mouse.
+// It's the function below, deltaIndicatesDragStartWithXDelta. however, that
+// decides whether it's a drag or not.
+// Override if you want to do something tricky when making the decision.
+// Default impl returns YES if ABS(xDelta) or ABS(yDelta) >= their respective
+// hysteresis limit.
+- (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta
+ yDelta:(float)yDelta
+ xHysteresis:(float)xHysteresis
+ yHysteresis:(float)yHysteresis;
+
+// This is called internally.
+// Decides whether we should treat the click as a cue to start dragging, or
+// instead call the mouseDown/mouseUp handler as appropriate.
+// Override if you want to do something tricky when making the decision.
+// Default impl returns YES if ABS(xDelta) or ABS(yDelta) >= their respective
+// hysteresis limit.
+- (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta
+ yDelta:(float)yDelta
+ xHysteresis:(float)xHysteresis
+ yHysteresis:(float)yHysteresis;
+
+
+@property(nonatomic) NSTimeInterval durationMouseWasDown;
+
@end // @interface DraggableButton
@interface DraggableButton (Private)
@@ -30,4 +76,9 @@
// called by the subclass.
- (void)endDrag;
+// Called internally if the actsOnMouseDown property is set.
+// Fires the button's action and tracks the click.
+- (void)performMouseDownAction:(NSEvent*)theEvent;
+
+
@end // @interface DraggableButton(Private)
diff --git a/chrome/browser/ui/cocoa/draggable_button.mm b/chrome/browser/ui/cocoa/draggable_button.mm
index 923476b..654ae25 100644
--- a/chrome/browser/ui/cocoa/draggable_button.mm
+++ b/chrome/browser/ui/cocoa/draggable_button.mm
@@ -20,10 +20,14 @@ const CGFloat kDragExpirationTimeout = 1.0;
@implementation DraggableButton
@synthesize draggable = draggable_;
+@synthesize actsOnMouseDown = actsOnMouseDown_;
+@synthesize durationMouseWasDown = durationMouseWasDown_;
- (id)initWithFrame:(NSRect)frame {
if ((self = [super initWithFrame:frame])) {
draggable_ = YES;
+ actsOnMouseDown_ = NO;
+ actionHasFired_ = NO;
}
return self;
}
@@ -31,10 +35,27 @@ const CGFloat kDragExpirationTimeout = 1.0;
- (id)initWithCoder:(NSCoder*)coder {
if ((self = [super initWithCoder:coder])) {
draggable_ = YES;
+ actsOnMouseDown_ = NO;
+ actionHasFired_ = NO;
}
return self;
}
+- (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta
+ yDelta:(float)yDelta
+ xHysteresis:(float)xHysteresis
+ yHysteresis:(float)yHysteresis {
+ return (ABS(xDelta) >= xHysteresis) || (ABS(yDelta) >= yHysteresis);
+}
+
+- (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta
+ yDelta:(float)yDelta
+ xHysteresis:(float)xHysteresis
+ yHysteresis:(float)yHysteresis {
+ return (ABS(xDelta) >= xHysteresis) || (ABS(yDelta) >= yHysteresis);
+}
+
+
// Determine whether a mouse down should turn into a drag; started as copy of
// NSTableView code.
- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent
@@ -60,17 +81,19 @@ const CGFloat kDragExpirationTimeout = 1.0;
firstEvent = nextEvent;
}
if ([nextEvent type] == NSLeftMouseDragged) {
- float deltax = ABS([nextEvent locationInWindow].x -
- [mouseDownEvent locationInWindow].x);
- float deltay = ABS([nextEvent locationInWindow].y -
- [mouseDownEvent locationInWindow].y);
+ float deltax = [nextEvent locationInWindow].x -
+ [mouseDownEvent locationInWindow].x;
+ float deltay = [nextEvent locationInWindow].y -
+ [mouseDownEvent locationInWindow].y;
dragEvent = nextEvent;
- if (deltax >= xHysteresis) {
- dragIt = YES;
- break;
- }
- if (deltay >= yHysteresis) {
- dragIt = YES;
+ if ([self deltaIndicatesConclusionReachedWithXDelta:deltax
+ yDelta:deltay
+ xHysteresis:xHysteresis
+ yHysteresis:yHysteresis]) {
+ dragIt = [self deltaIndicatesDragStartWithXDelta:deltax
+ yDelta:deltay
+ xHysteresis:xHysteresis
+ yHysteresis:yHysteresis];
break;
}
} else if ([nextEvent type] == NSLeftMouseUp) {
@@ -105,6 +128,11 @@ const CGFloat kDragExpirationTimeout = 1.0;
}
- (void)mouseUp:(NSEvent*)theEvent {
+ durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_;
+
+ if (actionHasFired_)
+ return;
+
if (!draggable_) {
[super mouseUp:theEvent];
return;
@@ -120,21 +148,97 @@ const CGFloat kDragExpirationTimeout = 1.0;
}
}
+- (void)secondaryMouseUpAction:(BOOL)wasInside {
+ // Override if you want to do any extra work on mouseUp, after a mouseDown
+ // action has already fired.
+}
+
+- (BOOL)acceptsTrackInFrom:(id)sender {
+ return (sender == self);
+}
+
+- (void)performMouseDownAction:(NSEvent*)theEvent {
+ int eventMask = NSLeftMouseUpMask | NSMouseEnteredMask | NSMouseExitedMask;
+
+ [[self target] performSelector:[self action] withObject:self];
+ actionHasFired_ = YES;
+
+ DraggableButton* insideBtn = nil;
+
+ while (1) {
+ theEvent = [[self window] nextEventMatchingMask:eventMask];
+ NSPoint mouseLoc = [self convertPoint:[theEvent locationInWindow]
+ fromView:nil];
+ BOOL isInside = [self mouse:mouseLoc inRect:[self bounds]];
+
+ switch ([theEvent type]) {
+ case NSMouseEntered:
+ case NSMouseExited: {
+ NSView* trackedView = (NSView*)[[theEvent trackingArea] owner];
+ if (trackedView && [trackedView isKindOfClass:[self class]]) {
+ DraggableButton *btn = static_cast<DraggableButton*>(trackedView);
+ if (![btn acceptsTrackInFrom:self])
+ break;
+ if ([theEvent type] == NSMouseEntered) {
+ [[NSCursor arrowCursor] set];
+ [[btn cell] mouseEntered:theEvent];
+ insideBtn = btn;
+ } else {
+ [[btn cell] mouseExited:theEvent];
+ if (insideBtn == btn)
+ insideBtn = nil;
+ }
+ }
+ break;
+ }
+ case NSLeftMouseUp: {
+ if (!isInside && insideBtn && insideBtn != self) {
+ // Has tracked onto another DraggableButton menu item, and released,
+ // so click it.
+ [insideBtn performClick:self];
+ }
+ durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_;
+ [self secondaryMouseUpAction:isInside];
+ [[self cell] mouseExited:theEvent];
+ [[insideBtn cell] mouseExited:theEvent];
+ return;
+ break;
+ }
+ default:
+ /* Ignore any other kind of event. */
+ break;
+ }
+ }
+}
+
// Mimic "begin a click" operation visually. Do NOT follow through
// with normal button event handling.
- (void)mouseDown:(NSEvent*)theEvent {
+ [[NSCursor arrowCursor] set];
+
+ whenMouseDown_ = [theEvent timestamp];
+ actionHasFired_ = NO;
+
if (draggable_) {
- [[self cell] setHighlighted:YES];
NSDate* date = [NSDate dateWithTimeIntervalSinceNow:kDragExpirationTimeout];
if ([self dragShouldBeginFromMouseDown:theEvent
withExpiration:date]) {
[self beginDrag:theEvent];
[self endDrag];
} else {
- [super mouseDown:theEvent];
+ if (actsOnMouseDown_) {
+ [self performMouseDownAction:theEvent];
+ } else {
+ [super mouseDown:theEvent];
+ }
+
}
} else {
- [super mouseDown:theEvent];
+ if (actsOnMouseDown_) {
+ [self performMouseDownAction:theEvent];
+ } else {
+ [super mouseDown:theEvent];
+ }
}
}
@@ -144,7 +248,7 @@ const CGFloat kDragExpirationTimeout = 1.0;
}
- (void)endDrag {
- [[self cell] setHighlighted:NO];
+ [self highlight:NO];
}
@end // @interface DraggableButton
diff --git a/chrome/browser/ui/cocoa/gradient_button_cell.mm b/chrome/browser/ui/cocoa/gradient_button_cell.mm
index ce2ea01..86f4bfe 100644
--- a/chrome/browser/ui/cocoa/gradient_button_cell.mm
+++ b/chrome/browser/ui/cocoa/gradient_button_cell.mm
@@ -319,9 +319,6 @@ static const NSTimeInterval kAnimationContinuousCycleDuration = 0.4;
- (void)setShowsBorderOnlyWhileMouseInside:(BOOL)showOnly {
[super setShowsBorderOnlyWhileMouseInside:showOnly];
if (showOnly) {
- if (trackingArea_.get()) {
- [self setShowsBorderOnlyWhileMouseInside:NO];
- }
[self updateTrackingAreas];
} else {
if (trackingArea_) {