1 /* 2 * Copyright 2009, Alexandre Deckner, alex@zappotek.com 3 * Distributed under the terms of the MIT License. 4 */ 5 6 /*! 7 \class ShakeTrackingFilter 8 \brief A simple mouse shake detection filter 9 * 10 * A simple mouse filter that detects quick mouse shakes. 11 * 12 * It's detecting rough edges (u-turns) in the mouse movement 13 * and counts them within a time window. 14 * You can configure the message sent, the u-turn count threshold 15 * and the time threshold. 16 * It sends the count along with the message. 17 * For now, detection is limited within the view bounds, but 18 * it might be modified to accept a BRegion mask. 19 * 20 */ 21 22 23 #include <ShakeTrackingFilter.h> 24 25 #include <Message.h> 26 #include <Messenger.h> 27 #include <MessageRunner.h> 28 #include <View.h> 29 30 31 const uint32 kMsgCancel = 'Canc'; 32 33 34 ShakeTrackingFilter::ShakeTrackingFilter(BView* targetView, uint32 messageWhat, 35 uint32 countThreshold, bigtime_t timeThreshold) 36 : 37 BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), 38 fTargetView(targetView), 39 fMessageWhat(messageWhat), 40 fCancelRunner(NULL), 41 fLowPass(8), 42 fLastDelta(0, 0), 43 fCounter(0), 44 fCountThreshold(countThreshold), 45 fTimeThreshold(timeThreshold) 46 { 47 } 48 49 50 ShakeTrackingFilter::~ShakeTrackingFilter() 51 { 52 delete fCancelRunner; 53 } 54 55 56 filter_result 57 ShakeTrackingFilter::Filter(BMessage* message, BHandler** /*_target*/) 58 { 59 if (fTargetView == NULL) 60 return B_DISPATCH_MESSAGE; 61 62 switch (message->what) { 63 case B_MOUSE_MOVED: 64 { 65 BPoint position; 66 message->FindPoint("be:view_where", &position); 67 68 // TODO: allow using BRegion masks 69 if (!fTargetView->Bounds().Contains(position)) 70 return B_DISPATCH_MESSAGE; 71 72 fLowPass.Input(position - fLastPosition); 73 74 BPoint delta = fLowPass.Output(); 75 76 // normalized dot product 77 float norm = delta.x * delta.x + delta.y * delta.y; 78 if (norm > 0.01) { 79 delta.x /= norm; 80 delta.y /= norm; 81 } 82 83 norm = fLastDelta.x * fLastDelta.x + fLastDelta.y * fLastDelta.y; 84 if (norm > 0.01) { 85 fLastDelta.x /= norm; 86 fLastDelta.y /= norm; 87 } 88 89 float dot = delta.x * fLastDelta.x + delta.y * fLastDelta.y; 90 91 if (dot < 0.0) { 92 if (fCounter == 0) { 93 BMessage * cancelMessage = new BMessage(kMsgCancel); 94 fCancelRunner = new BMessageRunner(BMessenger(fTargetView), 95 cancelMessage, fTimeThreshold, 1); 96 } 97 98 fCounter++; 99 100 if (fCounter >= fCountThreshold) { 101 BMessage shakeMessage(fMessageWhat); 102 shakeMessage.AddUInt32("count", fCounter); 103 BMessenger messenger(fTargetView); 104 messenger.SendMessage(&shakeMessage); 105 } 106 } 107 108 fLastDelta = fLowPass.Output(); 109 fLastPosition = position; 110 111 return B_DISPATCH_MESSAGE; 112 } 113 114 case kMsgCancel: 115 delete fCancelRunner; 116 fCancelRunner = NULL; 117 fCounter = 0; 118 return B_SKIP_MESSAGE; 119 120 default: 121 break; 122 } 123 124 return B_DISPATCH_MESSAGE; 125 } 126 127 128 // #pragma mark - 129 130 131 LowPassFilter::LowPassFilter(uint32 size) 132 : 133 fSize(size) 134 { 135 fPoints = new BPoint[fSize]; 136 } 137 138 139 LowPassFilter::~LowPassFilter() 140 { 141 delete [] fPoints; 142 } 143 144 145 void 146 LowPassFilter::Input(const BPoint& p) 147 { 148 // A fifo buffer that maintains a sum of its elements 149 fSum -= fPoints[0]; 150 for (uint32 i = 0; i < fSize - 1; i++) 151 fPoints[i] = fPoints[i + 1]; 152 fPoints[fSize - 1] = p; 153 fSum += p; 154 } 155 156 157 BPoint 158 LowPassFilter::Output() const 159 { 160 return BPoint(fSum.x / (float) fSize, fSum.y / (float) fSize); 161 } 162