Commit 4f61f537 authored by rsesek@chromium.org's avatar rsesek@chromium.org

[Mac] "Refactor" DraggableButton into a mixin and impl.

This is because DraggableButton inherits from NSButton when a future class will
need it to inherit from NSPopupButton. Because ObjC lacks multiple inheritance,
and we want to avoid runtime trickery, this is the result.

BUG=none
TEST=Dragging download shelf and bookmark bar items works as before. Partially covered by unit tests.

Review URL: http://codereview.chromium.org/7462018

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@96433 0039d316-1c4b-4281-b951-d872f2087c98
parent a195ec36
......@@ -337,8 +337,8 @@ void RecordAppLaunch(Profile* profile, GURL url) {
[[self view] setFrame:NSMakeRect(0, 0, initialWidth_, 0)];
// Complete init of the "off the side" button, as much as we can.
[offTheSideButton_ setDraggable:NO];
[offTheSideButton_ setActsOnMouseDown:YES];
[offTheSideButton_.draggableButton setDraggable:NO];
[offTheSideButton_.draggableButton setActsOnMouseDown:YES];
// We are enabled by default.
barIsEnabled_ = YES;
......@@ -1116,7 +1116,7 @@ void RecordAppLaunch(Profile* profile, GURL url) {
if (node->is_folder()) {
[button setTarget:self];
[button setAction:@selector(openBookmarkFolderFromButton:)];
[button setActsOnMouseDown:YES];
[[button draggableButton] setActsOnMouseDown:YES];
// If it has a title, and it will be truncated, show full title in
// tooltip.
NSString* title = base::SysUTF16ToNSString(node->GetTitle());
......@@ -1194,8 +1194,8 @@ void RecordAppLaunch(Profile* profile, GURL url) {
frame.origin.x = [[self buttonView] bounds].size.width - frame.size.width;
frame.origin.x -= bookmarks::kBookmarkHorizontalPadding;
BookmarkButton* button = [[BookmarkButton alloc] initWithFrame:frame];
[button setDraggable:NO];
[button setActsOnMouseDown:YES];
[[button draggableButton] setDraggable:NO];
[[button draggableButton] setActsOnMouseDown:YES];
otherBookmarksButton_.reset(button);
view_id_util::SetID(button, VIEW_ID_OTHER_BOOKMARKS);
......
......@@ -4,6 +4,8 @@
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_button.h"
#include <cmath>
#include "base/logging.h"
#import "base/memory/scoped_nsobject.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
......@@ -122,23 +124,23 @@ BookmarkButton* gDraggedButton = nil; // Weak
[super updateTrackingAreas];
}
- (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis {
- (DraggableButtonResult)deltaIndicatesDragStartWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis
indicates:(BOOL*)result {
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];
(yDelta <= -yHysteresis) && // Bottom of hysteresis box was hit.
(std::abs(yDelta) / std::abs(xDelta)) >= kDownProportion) {
*result = NO;
return kDraggableButtonMixinDidWork;
}
return kDraggableButtonImplUseBase;
}
......@@ -218,7 +220,7 @@ BookmarkButton* gDraggedButton = nil; // Weak
}
// Overridden to release bar visibility.
- (void)endDrag {
- (DraggableButtonResult)endDrag {
gDraggedButton = nil;
// visibilityDelegate_ can be nil if we're detached, and that's fine.
......@@ -226,7 +228,8 @@ BookmarkButton* gDraggedButton = nil; // Weak
withAnimation:YES
delay:YES];
visibilityDelegate_ = nil;
[super endDrag];
return kDraggableButtonImplUseBase;
}
- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
......@@ -255,9 +258,10 @@ BookmarkButton* gDraggedButton = nil; // Weak
}
}
- (void)performMouseDownAction:(NSEvent*)theEvent {
- (DraggableButtonResult)performMouseDownAction:(NSEvent*)theEvent {
[[self target] performSelector:[self action] withObject:self];
self.actionHasFired = YES;
self.draggableButton.actionHasFired = YES;
return kDraggableButtonMixinDidWork;
}
// BookmarkButtonCell. We redirect this information to our delegate.
......@@ -291,10 +295,11 @@ BookmarkButton* gDraggedButton = nil; // Weak
}
// This only gets called after a click that wasn't a drag, and only on folders.
- (void)secondaryMouseUpAction:(BOOL)wasInside {
- (DraggableButtonResult)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)) {
if (wasInside &&
self.draggableButton.durationMouseWasDown > kShortClickLength) {
[[self target] performSelector:[self action] withObject:self];
} else {
// Mouse tracked out of button during menu track. Hide menus.
......@@ -302,6 +307,7 @@ BookmarkButton* gDraggedButton = nil; // Weak
[delegate_ bookmarkDragDidEnd:self
operation:NSDragOperationNone];
}
return kDraggableButtonMixinDidWork;
}
@end
......
......@@ -30,7 +30,7 @@
NSCell* cell = [self cell];
DCHECK([cell respondsToSelector:@selector(isMouseOverButtonPart)]);
if ([reinterpret_cast<DownloadItemCell*>(cell) isMouseOverButtonPart]) {
[super mouseDown:event];
[self.draggableButton mouseDown:event];
} else {
// Hold a reference to our controller in case the download completes and we
// represent a file that's auto-removed (e.g. a theme).
......
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// 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_DRAGGABLE_BUTTON_H_
#define CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_H_
#pragma once
#import <Cocoa/Cocoa.h>
#import "base/memory/scoped_nsobject.h"
#import "chrome/browser/ui/cocoa/draggable_button_mixin.h"
// Class for buttons that can be drag sources. If the mouse is clicked and moved
// more than a given distance, this class will call |-beginDrag:| instead of
// |-performClick:|. Subclasses should override these two methods.
@interface DraggableButton : NSButton {
@interface DraggableButton : NSButton<DraggableButtonMixin> {
@private
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_;
scoped_nsobject<DraggableButtonImpl> draggableButtonImpl_;
}
@property NSTimeInterval durationMouseWasDown;
@property NSTimeInterval whenMouseDown;
// Whether the action has already fired for this click.
@property(nonatomic) BOOL actionHasFired;
// 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;
// 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;
@end // @interface DraggableButton
@interface DraggableButton (Private)
// Resets the draggable state of the button after dragging is finished. This is
// called by DraggableButton when the beginDrag call returns, it should not be
// 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;
@property(readonly, nonatomic) DraggableButtonImpl* draggableButton;
@end
@end // @interface DraggableButton(Private)
#endif // CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_H_
......@@ -5,214 +5,38 @@
#import "chrome/browser/ui/cocoa/draggable_button.h"
#include "base/logging.h"
#import "base/memory/scoped_nsobject.h"
namespace {
// Code taken from <http://codereview.chromium.org/180036/diff/3001/3004>.
// TODO(viettrungluu): Do we want common, standard code for drag hysteresis?
const CGFloat kWebDragStartHysteresisX = 5.0;
const CGFloat kWebDragStartHysteresisY = 5.0;
const CGFloat kDragExpirationTimeout = 0.45;
}
@implementation DraggableButton
@synthesize draggable = draggable_;
@synthesize actsOnMouseDown = actsOnMouseDown_;
@synthesize durationMouseWasDown = durationMouseWasDown_;
@synthesize actionHasFired = actionHasFired_;
@synthesize whenMouseDown = whenMouseDown_;
- (id)initWithFrame:(NSRect)frame {
if ((self = [super initWithFrame:frame])) {
draggable_ = YES;
actsOnMouseDown_ = NO;
actionHasFired_ = NO;
draggableButtonImpl_.reset(
[[DraggableButtonImpl alloc] initWithButton:self]);
}
return self;
}
- (id)initWithCoder:(NSCoder*)coder {
if ((self = [super initWithCoder:coder])) {
draggable_ = YES;
actsOnMouseDown_ = NO;
actionHasFired_ = NO;
draggableButtonImpl_.reset(
[[DraggableButtonImpl alloc] initWithButton:self]);
}
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
withExpiration:(NSDate*)expiration
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis {
if ([mouseDownEvent type] != NSLeftMouseDown) {
return NO;
}
NSEvent* nextEvent = nil;
NSEvent* firstEvent = nil;
NSEvent* dragEvent = nil;
NSEvent* mouseUp = nil;
BOOL dragIt = NO;
while ((nextEvent = [[self window]
nextEventMatchingMask:(NSLeftMouseUpMask | NSLeftMouseDraggedMask)
untilDate:expiration
inMode:NSEventTrackingRunLoopMode
dequeue:YES]) != nil) {
if (firstEvent == nil) {
firstEvent = nextEvent;
}
if ([nextEvent type] == NSLeftMouseDragged) {
float deltax = [nextEvent locationInWindow].x -
[mouseDownEvent locationInWindow].x;
float deltay = [nextEvent locationInWindow].y -
[mouseDownEvent locationInWindow].y;
dragEvent = nextEvent;
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) {
mouseUp = nextEvent;
break;
}
}
// Since we've been dequeuing the events (If we don't, we'll never see
// the mouse up...), we need to push some of the events back on.
// It makes sense to put the first and last drag events and the mouse
// up if there was one.
if (mouseUp != nil) {
[NSApp postEvent:mouseUp atStart:YES];
}
if (dragEvent != nil) {
[NSApp postEvent:dragEvent atStart:YES];
}
if (firstEvent != mouseUp && firstEvent != dragEvent) {
[NSApp postEvent:firstEvent atStart:YES];
}
return dragIt;
}
- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent
withExpiration:(NSDate*)expiration {
return [self dragShouldBeginFromMouseDown:mouseDownEvent
withExpiration:expiration
xHysteresis:kWebDragStartHysteresisX
yHysteresis:kWebDragStartHysteresisY];
- (DraggableButtonImpl*)draggableButton {
return draggableButtonImpl_.get();
}
- (void)mouseUp:(NSEvent*)theEvent {
durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_;
if (actionHasFired_)
return;
if (!draggable_) {
if ([draggableButtonImpl_ mouseUp:theEvent] == kDraggableButtonMixinCallSuper)
[super mouseUp:theEvent];
return;
}
// 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];
}
}
- (void)secondaryMouseUpAction:(BOOL)wasInside {
// Override if you want to do any extra work on mouseUp, after a mouseDown
// action has already fired.
}
- (void)performMouseDownAction:(NSEvent*)theEvent {
int eventMask = NSLeftMouseUpMask;
[[self target] performSelector:[self action] withObject:self];
actionHasFired_ = YES;
while (1) {
theEvent = [[self window] nextEventMatchingMask:eventMask];
if (!theEvent)
continue;
NSPoint mouseLoc = [self convertPoint:[theEvent locationInWindow]
fromView:nil];
BOOL isInside = [self mouse:mouseLoc inRect:[self bounds]];
[self highlight:isInside];
switch ([theEvent type]) {
case NSLeftMouseUp:
durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_;
[self secondaryMouseUpAction:isInside];
break;
default:
/* Ignore any other kind of event. */
break;
}
}
[self highlight:NO];
}
// 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_) {
NSDate* date = [NSDate dateWithTimeIntervalSinceNow:kDragExpirationTimeout];
if ([self dragShouldBeginFromMouseDown:theEvent
withExpiration:date]) {
[self beginDrag:theEvent];
[self endDrag];
} else {
if (actsOnMouseDown_) {
[self performMouseDownAction:theEvent];
} else {
[super mouseDown:theEvent];
}
}
} else {
if (actsOnMouseDown_) {
[self performMouseDownAction:theEvent];
} else {
[super mouseDown:theEvent];
}
if ([draggableButtonImpl_ mouseDown:theEvent] ==
kDraggableButtonMixinCallSuper) {
[super mouseDown:theEvent];
}
}
......@@ -221,8 +45,4 @@ const CGFloat kDragExpirationTimeout = 0.45;
NOTREACHED();
}
- (void)endDrag {
[self highlight:NO];
}
@end // @interface DraggableButton
// 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_DRAGGABLE_BUTTON_MIXIN_H_
#define CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_MIXIN_H_
#pragma once
#import <Cocoa/Cocoa.h>
// The design of this class is extraordinarily poor. Apologies to all clients in
// advance. Unfortunately, the lack of multiple inheritance and our desire to
// avoid runtime hacks makes this convoluted dance necessary.
//
// Buttons that want to be draggable should implement the Mixin protocol below
// and keep an instance of the Impl as an ivar. The button should forward mouse
// events to the impl, which will tell the button whether or not to call super
// and let the event be handled normally.
//
// If the impl decides to do work on the event, methods of the mixin protocol
// may be called. Some of the methods declared in that protocol have base
// implementations. If the method is not implemented by the button, that base
// implementation will be called. Otherwise, the button's implementation will
// be called first and the DraggableButtonResult will be used to determine
// whether the base implementation should be called. This requires the client to
// understand what the base does.
enum DraggableButtonResult {
// Return values for Impl methods.
kDraggableButtonImplDidWork,
kDraggableButtonMixinCallSuper,
// Return values for Mixin methods.
kDraggableButtonMixinDidWork,
kDraggableButtonImplUseBase,
};
// Mixin Protocol //////////////////////////////////////////////////////////////
// Buttons that make use of the below impl need to conform to this protocol.
@protocol DraggableButtonMixin
@required
// Called when a drag should start. Implement 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 implementing this method.
- (void)beginDrag:(NSEvent*)dragEvent;
@optional
// Called if the actsOnMouseDown property is set. Fires the button's action and
// tracks the click.
- (DraggableButtonResult)performMouseDownAction:(NSEvent*)theEvent;
// Implement if you want to do any extra work on mouseUp, after a mouseDown
// action has already fired.
- (DraggableButtonResult)secondaryMouseUpAction:(BOOL)wasInside;
// Resets the draggable state of the button after dragging is finished. This is
// called by DraggableButtonImpl when the beginDrag call returns.
- (DraggableButtonResult)endDrag;
// Decides whether to treat the click as a cue to start dragging, or to instead
// call the mouseDown/mouseUp handler as appropriate. Implement if you want to
// do something tricky when making the decision.
- (DraggableButtonResult)deltaIndicatesDragStartWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis
indicates:(BOOL*)result;
// Decides if there is enough information to stop tracking the mouse.
// It's deltaIndicatesDragStartWithXDelta, however, that decides whether it's a
// drag or not. Implement if you want to do something tricky when making the
// decision.
- (DraggableButtonResult)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis
indicates:(BOOL*)result;
@end
// Impl Interface //////////////////////////////////////////////////////////////
// Implementation of the drag and drop logic. NSButton Mixin subclasses should
// forward their mouse events to this, which in turn will call out to the mixin
// protocol.
@interface DraggableButtonImpl : NSObject {
@private
// The button for which this class is implementing stuff.
NSButton<DraggableButtonMixin>* button_;
// Is this a draggable type of button?
BOOL draggable_;
// Has the action already fired for this click?
BOOL actionHasFired_;
// Does button action happen on mouse down when possible?
BOOL actsOnMouseDown_;
NSTimeInterval durationMouseWasDown_;
NSTimeInterval whenMouseDown_;
}
@property(nonatomic) NSTimeInterval durationMouseWasDown;
@property(nonatomic) NSTimeInterval whenMouseDown;
// Whether the action has already fired for this click.
@property(nonatomic) BOOL actionHasFired;
// 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;
// Designated initializer.
- (id)initWithButton:(NSButton<DraggableButtonMixin>*)button;
// NSResponder implementation. NSButton subclasses should invoke these methods
// and only call super if the return value indicates such.
- (DraggableButtonResult)mouseDown:(NSEvent*)event;
- (DraggableButtonResult)mouseUp:(NSEvent*)event;
@end
#endif // CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_MIXIN_H_
// 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.
#import "chrome/browser/ui/cocoa/draggable_button.h"
#include <cmath>
#include "base/logging.h"
#import "base/memory/scoped_nsobject.h"
namespace {
// Code taken from <http://codereview.chromium.org/180036/diff/3001/3004>.
// TODO(viettrungluu): Do we want common, standard code for drag hysteresis?
const CGFloat kWebDragStartHysteresisX = 5.0;
const CGFloat kWebDragStartHysteresisY = 5.0;
const CGFloat kDragExpirationTimeout = 0.45;
}
// Private /////////////////////////////////////////////////////////////////////
@interface DraggableButtonImpl (Private)
- (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis;
- (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis;
- (void)performMouseDownAction:(NSEvent*)theEvent;
- (void)endDrag;
- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent
withExpiration:(NSDate*)expiration;
- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent
withExpiration:(NSDate*)expiration
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis;
@end
// Implementation //////////////////////////////////////////////////////////////
@implementation DraggableButtonImpl
@synthesize draggable = draggable_;
@synthesize actsOnMouseDown = actsOnMouseDown_;
@synthesize durationMouseWasDown = durationMouseWasDown_;
@synthesize actionHasFired = actionHasFired_;
@synthesize whenMouseDown = whenMouseDown_;
- (id)initWithButton:(NSButton<DraggableButtonMixin>*)button {
if ((self = [super init])) {
button_ = button;
draggable_ = YES;
actsOnMouseDown_ = NO;
actionHasFired_ = NO;
}
return self;
}
// NSButton/NSResponder Implementations ////////////////////////////////////////
- (DraggableButtonResult)mouseUp:(NSEvent*)theEvent {
durationMouseWasDown_ = [theEvent timestamp] - whenMouseDown_;
if (actionHasFired_)
return kDraggableButtonImplDidWork;
if (!draggable_)
return kDraggableButtonMixinCallSuper;
// There are non-drag cases where a |-mouseUp:| may happen (e.g. mouse-down,
// cmd-tab to another application, move mouse, mouse-up), so check.
NSPoint viewLocal = [button_ convertPoint:[theEvent locationInWindow]
fromView:[[button_ window] contentView]];
if (NSPointInRect(viewLocal, [button_ bounds]))
[button_ performClick:self];
return kDraggableButtonImplDidWork;
}
// Mimic "begin a click" operation visually. Do NOT follow through with normal
// button event handling.
- (DraggableButtonResult)mouseDown:(NSEvent*)theEvent {
[[NSCursor arrowCursor] set];
whenMouseDown_ = [theEvent timestamp];
actionHasFired_ = NO;
if (draggable_) {
NSDate* date = [NSDate dateWithTimeIntervalSinceNow:kDragExpirationTimeout];
if ([self dragShouldBeginFromMouseDown:theEvent
withExpiration:date]) {
[button_ beginDrag:theEvent];
[self endDrag];
} else {
if (actsOnMouseDown_) {
[self performMouseDownAction:theEvent];
return kDraggableButtonImplDidWork;
} else {
return kDraggableButtonMixinCallSuper;
}
}
} else {
if (actsOnMouseDown_) {
[self performMouseDownAction:theEvent];
return kDraggableButtonImplDidWork;
} else {
return kDraggableButtonMixinCallSuper;
}
}
return kDraggableButtonImplDidWork;
}
// Idempotent Helpers //////////////////////////////////////////////////////////
- (BOOL)deltaIndicatesDragStartWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis {
if ([button_ respondsToSelector:@selector(deltaIndicatesDragStartWithXDelta:
yDelta:
xHysteresis:
yHysteresis:
indicates:)]) {
BOOL indicates = NO;
DraggableButtonResult result = [button_
deltaIndicatesDragStartWithXDelta:xDelta
yDelta:yDelta
xHysteresis:xHysteresis
yHysteresis:yHysteresis
indicates:&indicates];
if (result != kDraggableButtonImplUseBase)
return indicates;
}
return (std::abs(xDelta) >= xHysteresis) || (std::abs(yDelta) >= yHysteresis);
}
- (BOOL)deltaIndicatesConclusionReachedWithXDelta:(float)xDelta
yDelta:(float)yDelta
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis {
if ([button_ respondsToSelector:
@selector(deltaIndicatesConclusionReachedWithXDelta:
yDelta:
xHysteresis:
yHysteresis:
indicates:)]) {
BOOL indicates = NO;
DraggableButtonResult result = [button_
deltaIndicatesConclusionReachedWithXDelta:xDelta
yDelta:yDelta
xHysteresis:xHysteresis
yHysteresis:yHysteresis
indicates:&indicates];
if (result != kDraggableButtonImplUseBase)
return indicates;
}
return (std::abs(xDelta) >= xHysteresis) || (std::abs(yDelta) >= yHysteresis);
}
- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent
withExpiration:(NSDate*)expiration {
return [self dragShouldBeginFromMouseDown:mouseDownEvent
withExpiration:expiration
xHysteresis:kWebDragStartHysteresisX
yHysteresis:kWebDragStartHysteresisY];
}
// Implementation Details //////////////////////////////////////////////////////
// Determine whether a mouse down should turn into a drag; started as copy of
// NSTableView code.
- (BOOL)dragShouldBeginFromMouseDown:(NSEvent*)mouseDownEvent
withExpiration:(NSDate*)expiration
xHysteresis:(float)xHysteresis
yHysteresis:(float)yHysteresis {
if ([mouseDownEvent type] != NSLeftMouseDown) {
return NO;
}
NSEvent* nextEvent = nil;
NSEvent* firstEvent = nil;
NSEvent* dragEvent = nil;
NSEvent* mouseUp = nil;
BOOL dragIt = NO;
while ((nextEvent = [[button_ window]
nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask
untilDate:expiration
inMode:NSEventTrackingRunLoopMode
dequeue:YES]) != nil) {
if (firstEvent == nil) {
firstEvent = nextEvent;
}
if ([nextEvent type] == NSLeftMouseDragged) {
float deltax = [nextEvent locationInWindow].x -
[mouseDownEvent locationInWindow].x;
float deltay = [nextEvent locationInWindow].y -
[mouseDownEvent locationInWindow].y;
dragEvent = nextEvent;
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) {
mouseUp = nextEvent;
break;
}
}
// Since we've been dequeuing the events (If we don't, we'll never see
// the mouse up...), we need to push some of the events back on.
// It makes sense to put the first and last drag events and the mouse
// up if there was one.
if (mouseUp != nil) {
[NSApp postEvent:mouseUp atStart:YES];
}
if (dragEvent != nil) {
[NSApp postEvent:dragEvent atStart:YES];
}
if (firstEvent != mouseUp && firstEvent != dragEvent) {
[NSApp postEvent:firstEvent atStart:YES];
}
return dragIt;
}
- (void)secondaryMouseUpAction:(BOOL)wasInside {
if ([button_ respondsToSelector:_cmd])
[button_ secondaryMouseUpAction:wasInside];
// No actual implementation yet.
}
- (void)performMouseDownAction:(NSEvent*)event {
if ([button_ respondsToSelector:_cmd] &&
[button_ performMouseDownAction:event] != kDraggableButtonImplUseBase) {
return;
}
int eventMask = NSLeftMouseUpMask;
[[button_ target] performSelector:[button_ action] withObject:self];
actionHasFired_ = YES;
while (1) {
event = [[button_ window] nextEventMatchingMask:eventMask];
if (!event)
continue;
NSPoint mouseLoc = [button_ convertPoint:[event locationInWindow]
fromView:nil];
BOOL isInside = [button_ mouse:mouseLoc inRect:[button_ bounds]];
[button_ highlight:isInside];
switch ([event type]) {
case NSLeftMouseUp:
durationMouseWasDown_ = [event timestamp] - whenMouseDown_;
[self secondaryMouseUpAction:isInside];
break;
default:
// Ignore any other kind of event.
break;
}
}
[button_ highlight:NO];
}
- (void)endDrag {
if ([button_ respondsToSelector:_cmd] &&
[button_ endDrag] != kDraggableButtonImplUseBase) {
return;
}
[button_ highlight:NO];
}
@end
......@@ -2445,6 +2445,8 @@
'browser/ui/cocoa/download/download_util_mac.mm',
'browser/ui/cocoa/draggable_button.h',
'browser/ui/cocoa/draggable_button.mm',
'browser/ui/cocoa/draggable_button_mixin.h',
'browser/ui/cocoa/draggable_button_mixin.mm',
'browser/ui/cocoa/drag_util.h',
'browser/ui/cocoa/drag_util.mm',
'browser/ui/cocoa/encoding_menu_controller_delegate_mac.h',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment