Home | History | Annotate | Download | only in cocoa
      1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #import "chrome/browser/ui/cocoa/clickhold_button_cell.h"
      6 
      7 #include "base/logging.h"
      8 
      9 // Minimum and maximum click-hold timeout.
     10 static const NSTimeInterval kMinTimeout = 0.0;
     11 static const NSTimeInterval kMaxTimeout = 5.0;
     12 
     13 // Drag distance threshold to activate click-hold; should be >= 0.
     14 static const CGFloat kDragDistThreshold = 2.5;
     15 
     16 // See |-resetToDefaults| (and header file) for other default values.
     17 
     18 @interface ClickHoldButtonCell (Private)
     19 - (void)resetToDefaults;
     20 @end  // @interface ClickHoldButtonCell (Private)
     21 
     22 @implementation ClickHoldButtonCell
     23 
     24 // Overrides:
     25 
     26 + (BOOL)prefersTrackingUntilMouseUp {
     27   return NO;
     28 }
     29 
     30 - (id)init {
     31   if ((self = [super init]))
     32     [self resetToDefaults];
     33   return self;
     34 }
     35 
     36 - (id)initWithCoder:(NSCoder*)decoder {
     37   if ((self = [super initWithCoder:decoder]))
     38     [self resetToDefaults];
     39   return self;
     40 }
     41 
     42 - (id)initImageCell:(NSImage*)image {
     43   if ((self = [super initImageCell:image]))
     44     [self resetToDefaults];
     45   return self;
     46 }
     47 
     48 - (id)initTextCell:(NSString*)string {
     49   if ((self = [super initTextCell:string]))
     50     [self resetToDefaults];
     51   return self;
     52 }
     53 
     54 - (BOOL)startTrackingAt:(NSPoint)startPoint
     55                  inView:(NSView*)controlView {
     56   return enableClickHold_ ? YES :
     57                             [super startTrackingAt:startPoint
     58                                             inView:controlView];
     59 }
     60 
     61 - (BOOL)continueTracking:(NSPoint)lastPoint
     62                       at:(NSPoint)currentPoint
     63                   inView:(NSView*)controlView {
     64   return enableClickHold_ ? YES :
     65                             [super continueTracking:lastPoint
     66                                                  at:currentPoint
     67                                              inView:controlView];
     68 }
     69 
     70 - (BOOL)trackMouse:(NSEvent*)originalEvent
     71             inRect:(NSRect)cellFrame
     72             ofView:(NSView*)controlView
     73       untilMouseUp:(BOOL)untilMouseUp {
     74   if (!enableClickHold_) {
     75     return [super trackMouse:originalEvent
     76                       inRect:cellFrame
     77                       ofView:controlView
     78                 untilMouseUp:untilMouseUp];
     79   }
     80 
     81   // If doing click-hold, track the mouse ourselves.
     82   NSPoint currPoint = [controlView convertPoint:[originalEvent locationInWindow]
     83                                        fromView:nil];
     84   NSPoint lastPoint = currPoint;
     85   NSPoint firstPoint = currPoint;
     86   NSTimeInterval timeout =
     87       MAX(MIN(clickHoldTimeout_, kMaxTimeout), kMinTimeout);
     88   NSDate* clickHoldBailTime = [NSDate dateWithTimeIntervalSinceNow:timeout];
     89 
     90   if (![self startTrackingAt:currPoint inView:controlView])
     91     return NO;
     92 
     93   enum {
     94     kContinueTrack, kStopClickHold, kStopMouseUp, kStopLeftRect, kStopNoContinue
     95   } state = kContinueTrack;
     96   do {
     97     NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask |
     98                                                    NSLeftMouseUpMask)
     99                                         untilDate:clickHoldBailTime
    100                                            inMode:NSEventTrackingRunLoopMode
    101                                           dequeue:YES];
    102     currPoint = [controlView convertPoint:[event locationInWindow]
    103                                  fromView:nil];
    104 
    105     // Time-out.
    106     if (!event) {
    107       state = kStopClickHold;
    108 
    109     // Drag? (If distance meets threshold.)
    110     } else if (activateOnDrag_ && ([event type] == NSLeftMouseDragged)) {
    111       CGFloat dx = currPoint.x - firstPoint.x;
    112       CGFloat dy = currPoint.y - firstPoint.y;
    113       if ((dx*dx + dy*dy) >= (kDragDistThreshold*kDragDistThreshold))
    114         state = kStopClickHold;
    115 
    116     // Mouse up.
    117     } else if ([event type] == NSLeftMouseUp) {
    118       state = kStopMouseUp;
    119 
    120     // Stop tracking if mouse left frame rectangle (if requested to do so).
    121     } else if (trackOnlyInRect_ && ![controlView mouse:currPoint
    122                                                 inRect:cellFrame]) {
    123       state = kStopLeftRect;
    124 
    125     // Stop tracking if instructed to.
    126     } else if (![self continueTracking:lastPoint
    127                                     at:currPoint
    128                                 inView:controlView]) {
    129       state = kStopNoContinue;
    130     }
    131 
    132     lastPoint = currPoint;
    133   } while (state == kContinueTrack);
    134 
    135   [self stopTracking:lastPoint
    136                   at:lastPoint
    137               inView:controlView
    138            mouseIsUp:NO];
    139 
    140   switch (state) {
    141     case kStopClickHold:
    142       if (clickHoldAction_) {
    143         [static_cast<NSControl*>(controlView) sendAction:clickHoldAction_
    144                                                       to:clickHoldTarget_];
    145       }
    146       return YES;
    147 
    148     case kStopMouseUp:
    149       if ([self action]) {
    150         [static_cast<NSControl*>(controlView) sendAction:[self action]
    151                                                       to:[self target]];
    152       }
    153       return YES;
    154 
    155     case kStopLeftRect:
    156     case kStopNoContinue:
    157       return NO;
    158 
    159     default:
    160       NOTREACHED() << "Unknown terminating state!";
    161   }
    162 
    163   return NO;
    164 }
    165 
    166 // Accessors and mutators:
    167 
    168 @synthesize enableClickHold = enableClickHold_;
    169 @synthesize clickHoldTimeout = clickHoldTimeout_;
    170 @synthesize trackOnlyInRect = trackOnlyInRect_;
    171 @synthesize activateOnDrag = activateOnDrag_;
    172 @synthesize clickHoldTarget = clickHoldTarget_;
    173 @synthesize clickHoldAction = clickHoldAction_;
    174 
    175 @end  // @implementation ClickHoldButtonCell
    176 
    177 @implementation ClickHoldButtonCell (Private)
    178 
    179 // Resets various members to defaults indicated in the header file. (Those
    180 // without indicated defaults are *not* touched.) Please keep the values below
    181 // in sync with the header file, and please be aware of side-effects on code
    182 // which relies on the "published" defaults.
    183 - (void)resetToDefaults {
    184   [self setEnableClickHold:NO];
    185   [self setClickHoldTimeout:0.25];
    186   [self setTrackOnlyInRect:NO];
    187   [self setActivateOnDrag:YES];
    188 }
    189 
    190 @end  // @implementation ClickHoldButtonCell (Private)
    191