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