// 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_ #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)mouseDownImpl:(NSEvent*)event; - (DraggableButtonResult)mouseUpImpl:(NSEvent*)event; @end #endif // CHROME_BROWSER_UI_COCOA_DRAGGABLE_BUTTON_MIXIN_H_