Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Mouse Gestures for Internet Explorer

, 21 Sep 2005
Adding mouse gesture recognition to Internet Explorer.
mousegestures_inst.zip
MouseGestures.exe
mousegestures_inst_1.zip
MouseGestures.exe
mousegestures_src.zip
src
gesture.weights
images
about.bmp
ClickLeftRight.bmp
ClickRightLeft.bmp
down.bmp
downarrow.bmp
downleft.bmp
downright.bmp
downup.bmp
exclamation.bmp
left.bmp
leftarrow.bmp
leftright.bmp
none.bmp
right.bmp
rightarrow.bmp
rightleft.bmp
up.bmp
uparrow.bmp
updown.bmp
upleft.bmp
upright.bmp
wheel.bmp
MouseGestures.rgs
mousegestures_src_1.zip
MouseGestures
BrowserHelperObject.rgs
gesture.weights
images
about.bmp
ClickLeftRight.bmp
ClickRightLeft.bmp
down.bmp
downleft.bmp
downright.bmp
downup.bmp
left.bmp
none.bmp
right.bmp
up.bmp
updown.bmp
upleft.bmp
upright.bmp
wheel.bmp
MouseGestures.def
MouseGestures.dsp
MouseGesturesCfg.rgs
///////////////////////////////////////////////////////////////
//
// TrailWindow.cpp
//
// Created: 28/06/2005
// Copyright (c) 2005 Ralph Hare (ralph.hare@ysgyfarnog.co.uk)
// All rights reserved.
//
// The code and information is provided "as-is" without
// warranty of any kind, either expressed or implied.
//
///////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "TrailWindow.h"
#include "GDIUtils.h"
#include <math.h>

namespace
{
    const int       GUTTER = -1;
    const COLORREF  BACK_COLOUR = RGB( 255, 0, 255 );   // Magenta
    const double    PI = 3.1415926535897932384626433832795;
    const UINT      HIDE_TIMER = 0x05091946;

    inline POINT TransformPoint( const POINT &pt, const SIZE &offset )
    {
        return WTL::CPoint( pt.x + offset.cx, pt.y + offset.cy );
    }

    inline double Distance( const POINT &pt1, const POINT &pt2 )
    {
        return sqrt( pow( pt1.x - pt2.x, 2 ) + pow( pt1.y - pt2.y, 2 ) );
    }

    inline double DegToRad( double deg )
    {
        return deg * ( ( 2 * PI ) / 360.0 );
    }

    inline double RadToDeg( double rad )
    {
        return rad * ( 360.0 / ( 2 * PI ) );
    }

    class EndMarker
    {
    public:
        EndMarker( int penWidth_, WTL::CPoint ptOrigin, double angle )
        {
        //
        // the min pen width is 2 pixels
        //
            int penWidth = std::max( penWidth_, 2 );

            static const double scale = 2.5;
            static const double cos30 = scale * cos( PI / 6 );
            static const double sin30 = scale * sin( PI / 6 );

        //
        // adjust the origin, so the marker is central
        //
            int nx = static_cast< int >( penWidth * 1.5 * cos( angle ) );
            int ny = static_cast< int >( penWidth * 1.5 * sin( angle ) );

            ptOrigin.Offset( nx, ny );

        //
        // calculate the magnitude of each vertex
        //
            double  dx = -penWidth * cos30;
            double  dy = +penWidth * sin30;

            m_pts[ 0 ] = ptOrigin;
            m_pts[ 1 ] = ptOrigin;
            m_pts[ 2 ] = ptOrigin;

        //
        // rotate the vertices
        //
            const double    cosA = cos( angle );
            const double    sinA = sin( angle );

            m_pts[ 1 ].Offset(
                        static_cast< int >( ( dx * cosA ) - ( dy * sinA ) ),
                        static_cast< int >( ( dx * sinA ) + ( dy * cosA ) )
                        );

            m_pts[ 2 ].Offset(
                        static_cast< int >( ( dx * cosA ) - ( -dy * sinA ) ),
                        static_cast< int >( ( dx * sinA ) + ( -dy * cosA ) )
                        );
        }

        void Draw( HDC hDC )
        {
            ::Polygon( hDC, m_pts, 3 );
        }

    public:
        WTL::CPoint m_pts[ 3 ];
    };

    inline double GetAngle( const std::vector< POINT > &path )
    {
        if( path.size() < 2 )
        {
            return 0.0;
        }

        const POINT &pt1 = *( path.end() - 1 ); // == path.back();        
        const POINT &pt2 = *( path.end() - 2 ); // == path.back() - 1;

        double  dx = pt1.x - pt2.x;
        double  dy = pt1.y - pt2.y;

        if( dx == 0.0 )
        {
            return dy > 0 ? ( PI / 2 ) : ( -PI / 2 );
        }
        else if( dy == 0.0 )
        {            
            return dx > 0 ? 0 : PI;
        }

        double  ang = atan( dy / dx );

        if( dx < 0 )
        {
            return ang + ( dy < 0 ? PI : -PI );
        }

        return ang;
    }

    HBRUSH GetBackBrush()
    {
        static WTL::CBrush  backBrush( ::CreateSolidBrush( BACK_COLOUR ) );
        return backBrush;
    }

    HPEN CreatePenEx( int style, int width, COLORREF clr )
    {
        LOGBRUSH    lb = { BS_SOLID, clr, 0 };

        style |= PS_GEOMETRIC | PS_ENDCAP_ROUND | PS_JOIN_ROUND;

        return ::ExtCreatePen( style, width, &lb, 0, NULL );
    }
}

TrailWindow::TrailWindow( COLORREF clr, long thickness, BYTE transparency, long fadeSpeed ) :
    CWindowImpl< TrailWindow >(),
    m_offset( 0, 0 ),
    m_lastPoint( INT_MIN, INT_MIN ),
    m_path(),
    m_memDC(),
    m_bitmap(),
    m_pen( CreatePenEx( 0, thickness, clr ) ),
    m_penEnd( CreatePenEx( 0, 0, clr ) ),
    m_brush( ::CreateSolidBrush( clr ) ),
    m_rcWnd( 0, 0, 0, 0 ),
    m_rcExtents( 0, 0, 0, 0 ),
    m_penWidth( 10 ),
    m_transparency( transparency ),
    m_fadeIncrement( std::max( fadeSpeed, 1L ) ),
    m_sensitivity( 5.0 )
{    
    ::GetWindowRect( ::GetDesktopWindow(), &m_rcWnd );
    m_rcWnd.InflateRect( GUTTER, GUTTER );
        
    if( CWindowImpl< TrailWindow >::Create( 
                                        NULL, 
                                        m_rcWnd, 
                                        NULL, 
                                        WS_POPUP,
                                        WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_LAYERED
                                        ) == NULL )
    {
        ATLASSERT( FALSE );
    }

    SetTransparency( m_transparency );
    ShowWindow( SW_SHOWNA );

    UpdateBitmap();
}

TrailWindow::~TrailWindow()
{
    if( ::IsWindow( m_hWnd ) )
    {
        DestroyWindow();
    }
}

CWndClassInfo & TrailWindow::GetWndClassInfo()
{
//
// ATL Internals pp 419
//
    static CWndClassInfo wc =
    {
        {    
            sizeof( WNDCLASSEX ), 
            0, 
            StartWindowProc, 
            0, 
            0, 
            NULL, 
            NULL, 
            NULL, 
            GetBackBrush(),
            NULL, 
            _T( "TrailWindow" ),
            NULL 
        },
        NULL, 
        NULL, 
        IDC_ARROW, 
        TRUE, 
        0, 
        _T( "TrailWindow" )
    };
    
    return wc;
}

void TrailWindow::SetTransparency( BYTE transparency )
{
    ATLASSERT( m_hWnd && ::IsWindow( m_hWnd ) );

    GDIUtils::SetLayeredWindowAttribute(
                            m_hWnd, 
                            BACK_COLOUR,
                            transparency, 
                            LWA_ALPHA | LWA_COLORKEY
                            );
}

void TrailWindow::DrawTrail( HDC hDC )
{
    if( m_path.empty() == false )
    {
    //
    // get the bounding rect for the trail - allow m_penWidth pixels
    // either side, but don't exceed the limits of the trail window
    //
        long        border = std::max( m_penWidth * 2, 1L );
        WTL::CRect  rcExtents = m_rcExtents;

        rcExtents.left = std::max( m_rcExtents.left - border, m_rcWnd.left );
        rcExtents.right = std::min( m_rcExtents.right + border, m_rcWnd.right );
        rcExtents.top = std::max( m_rcExtents.top - border, m_rcWnd.top );
        rcExtents.bottom = std::min( m_rcExtents.bottom + ( border * 2 ), m_rcWnd.bottom );

    //
    // prepare to draw the trail
    //
        HBITMAP bmOld = m_memDC.SelectBitmap( m_bitmap );
        m_memDC.FillRect( &rcExtents, GetBackBrush() );

        HPEN    penOld = m_memDC.SelectPen( m_pen );
        HBRUSH  brOld = m_memDC.SelectBrush( m_brush );

    //
    // first the dot at the beginning
    //
        long    radius = std::max( m_penWidth / 2, 2L );
        RECT    rcBegin;

        rcBegin.left = m_path.front().x - radius;
        rcBegin.right = m_path.front().x + radius;
        rcBegin.top = m_path.front().y - radius;
        rcBegin.bottom = m_path.front().y + radius;

        m_memDC.Ellipse( &rcBegin );

    //
    // draw the trail 
    //        
        m_memDC.Polyline( &m_path[ 0 ], static_cast< int >( m_path.size() ) );

    //
    // only draw the end marker if the mouse has been moved
    // sufficiently
    //
        if( ( m_path.size() > 5 ) || 
            ( Distance( m_path.front(), m_path.back() ) > m_penWidth ) )
        {
            EndMarker   endMarker( m_penWidth, m_path.back(), GetAngle( m_path ) );

            m_memDC.SelectPen( m_penEnd );
            endMarker.Draw( m_memDC );
        }

    //
    // blit in the memory dc
    //
        ::BitBlt(
            hDC,
            rcExtents.left, 
            rcExtents.top,
            rcExtents.Width(), 
            rcExtents.Height(), 
            m_memDC, 
            rcExtents.left, 
            rcExtents.top, 
            SRCCOPY 
            );

        m_memDC.SelectBrush( brOld );
        m_memDC.SelectPen( penOld );
        m_memDC.SelectBitmap( bmOld );
    }
}

void TrailWindow::Begin( HWND hWnd )
{
//
// store the top-left corner of the window we're drawing over
// (the path we receive is relative to this window, and we'll
// need to transform the coordinates before drawing the trail)
//
    WTL::CRect  rc;
    ::GetWindowRect( hWnd, &rc );
    m_offset = *( reinterpret_cast< SIZE * >( &rc ) );

//
// reset the last point to a point well off the screen, ensuring
// we definitely draw the trail on the first Update
//
    m_lastPoint = WTL::CPoint( INT_MIN, INT_MIN );

//
// show the window, but don't activate it. this means the apparent
// focus remains with the window we're drawing the trail over
//
    SetWindowPos(
            HWND_TOPMOST, 
            0, 0, 0, 0, 
            SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOCOPYBITS 
            );
}

void TrailWindow::End()
{
//
// fade the window
//
    if( m_fadeIncrement != 255 )
    {
        for( int idx = m_transparency; idx > 0; idx -= m_fadeIncrement )
        {
            SetTransparency( static_cast< BYTE >( idx ) );
        }
    }

//
// clear the path, and invalidate the window. this forces a
// redraw before we hide the window (but see the message pump
// below), so it comes up clear next time round
//
    m_path.clear();
    Invalidate( TRUE );

//
// Pump the queue to ensure that the window processes
// the invalidate message before we hide
//
    MSG msg = { 0 };
    while( ::PeekMessage( &msg, m_hWnd, 0, 0, PM_REMOVE ) )
    {
        ::TranslateMessage( &msg );
        ::DispatchMessage( &msg );
    }

//
// reset the transparency
//
    if( m_fadeIncrement != 255 )
    {
        SetTransparency( m_transparency );
    }
}

void TrailWindow::Update( const Path &path )
{
//
// Always add the first point
//
    if( m_path.empty() )
    {
        m_path.push_back( TransformPoint( path.front(), m_offset ) );
        m_lastPoint = path.front();

        m_rcExtents.left = m_path.back().x;
        m_rcExtents.right = m_path.back().x;
        m_rcExtents.top = m_path.back().y;
        m_rcExtents.bottom = m_path.back().y;
    }
    else
    {
    //
    // if we haven't moved more than m_sensitivity pixels 
    // since last time, then there's nothing to draw
    //
        if( Distance( m_lastPoint, path.back() ) < m_sensitivity )
        {
            return;
        }

    //
    // add the new point, and update our bounding rect
    //
        m_path.push_back( TransformPoint( path.back(), m_offset ) );
        m_lastPoint = path.back();

        if( m_path.back().x > m_rcExtents.right )
        {
            m_rcExtents.right = m_path.back().x;
        }
        else if( m_path.back().x < m_rcExtents.left )
        {
            m_rcExtents.left = m_path.back().x;
        }
        
        if( m_path.back().y < m_rcExtents.top )
        {
            m_rcExtents.top = m_path.back().y;
        }
        else if( m_path.back().y > m_rcExtents.bottom )
        {
            m_rcExtents.bottom = m_path.back().y;
        }
    }

//
// draw the trail
//
    WTL::CClientDC  dc( m_hWnd );
    DrawTrail( dc );
}

void TrailWindow::UpdateSettings( COLORREF clr, long thickness, BYTE transparency, long fadeSpeed )
{
    m_pen.DeleteObject();
    m_pen.Attach( CreatePenEx( 0, thickness, clr ) );
    m_penWidth = thickness;

    m_penEnd.DeleteObject();
    m_penEnd.Attach( CreatePenEx( 0, 0, clr ) );

    m_brush.DeleteObject();
    m_brush.CreateSolidBrush( clr );

    m_transparency = transparency;
    SetTransparency( m_transparency );

    m_fadeIncrement = std::max( fadeSpeed, 1L );
}

void TrailWindow::Show()
{
    CriticalSection::Lock   lock( m_csShow );

    SetWindowPos( HWND_TOPMOST, &m_rcWnd, SWP_NOACTIVATE | SWP_NOCOPYBITS );
    KillTimer( HIDE_TIMER );
}

void TrailWindow::Hide()
{
    CriticalSection::Lock   lock( m_csShow );

    SetTimer( HIDE_TIMER, 500, NULL );
}

LRESULT TrailWindow::OnTimer( UINT, WPARAM, LPARAM, BOOL & )
{
    CriticalSection::Lock   lock( m_csShow );

    KillTimer( HIDE_TIMER );
    SetWindowPos(
            HWND_BOTTOM, 
            0, 0, 0, 0, 
            SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOCOPYBITS 
            );
    return 0;
}

LRESULT TrailWindow::OnDisplayChange( UINT, WPARAM wParam, LPARAM, BOOL & )
{
    UpdateBitmap();
    return 0;
}

void TrailWindow::UpdateBitmap()
{
    if( m_memDC.IsNull() == false )
    {
        m_memDC.DeleteDC();
    }

    if( m_bitmap.IsNull() == false )
    {
        m_bitmap.DeleteObject();
    }

    WTL::CClientDC  dc( m_hWnd );
    m_memDC.CreateCompatibleDC( dc );

    ::GetWindowRect( ::GetDesktopWindow(), &m_rcWnd );
    m_rcWnd.InflateRect( GUTTER, GUTTER );

    SetWindowPos( HWND_TOPMOST, &m_rcWnd, SWP_NOACTIVATE );

    m_bitmap.CreateCompatibleBitmap( dc, m_rcWnd.Width(), m_rcWnd.Height() );
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Ralph Hare
Web Developer
United Kingdom United Kingdom
No Biography provided

| Advertise | Privacy | Mobile
Web01 | 2.8.141022.2 | Last Updated 21 Sep 2005
Article Copyright 2003 by Ralph Hare
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid