root/src/kits/shared/ShakeTrackingFilter.cpp
/*
 * Copyright 2009, Alexandre Deckner, alex@zappotek.com
 * Distributed under the terms of the MIT License.
 */

/*!
        \class ShakeTrackingFilter
        \brief A simple mouse shake detection filter
        *
        * A simple mouse filter that detects quick mouse shakes.
        *
        * It's detecting rough edges (u-turns) in the mouse movement
        * and counts them within a time window.
        * You can configure the message sent, the u-turn count threshold
        * and the time threshold.
        * It sends the count along with the message.
        * For now, detection is limited within the view bounds, but
        * it might be modified to accept a BRegion mask.
        *
*/


#include <ShakeTrackingFilter.h>

#include <Message.h>
#include <Messenger.h>
#include <MessageRunner.h>
#include <View.h>


const uint32 kMsgCancel = 'Canc';


ShakeTrackingFilter::ShakeTrackingFilter(BView* targetView, uint32 messageWhat,
        uint32 countThreshold, bigtime_t timeThreshold)
        :
        BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE),
        fTargetView(targetView),
        fMessageWhat(messageWhat),
        fCancelRunner(NULL),
        fLowPass(8),
        fLastDelta(0, 0),
        fCounter(0),
        fCountThreshold(countThreshold),
        fTimeThreshold(timeThreshold)
{
}


ShakeTrackingFilter::~ShakeTrackingFilter()
{
        delete fCancelRunner;
}


filter_result
ShakeTrackingFilter::Filter(BMessage* message, BHandler** /*_target*/)
{
        if (fTargetView == NULL)
                return B_DISPATCH_MESSAGE;

        switch (message->what) {
                case B_MOUSE_MOVED:
                {
                        BPoint position;
                        message->FindPoint("be:view_where", &position);

                        // TODO: allow using BRegion masks
                        if (!fTargetView->Bounds().Contains(position))
                                return B_DISPATCH_MESSAGE;

                        fLowPass.Input(position - fLastPosition);

                        BPoint delta = fLowPass.Output();

                        // normalized dot product
                        float norm = delta.x * delta.x + delta.y * delta.y;
                        if (norm > 0.01) {
                                delta.x /= norm;
                                delta.y /= norm;
                        }

                        norm = fLastDelta.x * fLastDelta.x + fLastDelta.y * fLastDelta.y;
                        if (norm > 0.01) {
                                fLastDelta.x /= norm;
                                fLastDelta.y /= norm;
                        }

                        float dot = delta.x * fLastDelta.x + delta.y * fLastDelta.y;

                        if (dot < 0.0) {
                                if (fCounter == 0) {
                                        BMessage * cancelMessage = new BMessage(kMsgCancel);
                                        fCancelRunner = new BMessageRunner(BMessenger(fTargetView),
                                                cancelMessage, fTimeThreshold, 1);
                                }

                                fCounter++;

                                if (fCounter >= fCountThreshold) {
                                        BMessage shakeMessage(fMessageWhat);
                                        shakeMessage.AddUInt32("count", fCounter);
                                        BMessenger messenger(fTargetView);
                                        messenger.SendMessage(&shakeMessage);
                                }
                        }

                        fLastDelta = fLowPass.Output();
                        fLastPosition = position;

                        return B_DISPATCH_MESSAGE;
                }

                case kMsgCancel:
                        delete fCancelRunner;
                        fCancelRunner = NULL;
                        fCounter = 0;
                        return B_SKIP_MESSAGE;

                default:
                        break;
        }

        return B_DISPATCH_MESSAGE;
}


//      #pragma mark -


LowPassFilter::LowPassFilter(uint32 size)
        :
        fSize(size)
{
        fPoints = new BPoint[fSize];
}


LowPassFilter::~LowPassFilter()
{
        delete [] fPoints;
}


void
LowPassFilter::Input(const BPoint& p)
{
        // A fifo buffer that maintains a sum of its elements
        fSum -= fPoints[0];
        for (uint32 i = 0; i < fSize - 1; i++)
                fPoints[i] = fPoints[i + 1];
        fPoints[fSize - 1] = p;
        fSum += p;
}


BPoint
LowPassFilter::Output() const
{
        return BPoint(fSum.x / (float) fSize, fSum.y / (float) fSize);
}