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
31const uint32 kMsgCancel = 'Canc';
32
33
34ShakeTrackingFilter::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
50ShakeTrackingFilter::~ShakeTrackingFilter()
51{
52	delete fCancelRunner;
53}
54
55
56filter_result
57ShakeTrackingFilter::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
131LowPassFilter::LowPassFilter(uint32 size)
132	:
133	fSize(size)
134{
135	fPoints = new BPoint[fSize];
136}
137
138
139LowPassFilter::~LowPassFilter()
140{
141	delete [] fPoints;
142}
143
144
145void
146LowPassFilter::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
157BPoint
158LowPassFilter::Output() const
159{
160	return BPoint(fSum.x / (float) fSize, fSum.y / (float) fSize);
161}
162