xref: /haiku/src/kits/shared/ShakeTrackingFilter.cpp (revision 2b76973fa2401f7a5edf68e6470f3d3210cbcff3)
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