///////////////////////////////////////////////////////////////////////////////
// Name:        tests/events/keyboard.cpp
// Purpose:     Test keyboard events
// Author:      Vadim Zeitlin
// Created:     2010-09-05
// Copyright:   (c) 2010 Vadim Zeitlin <vadim@wxwidgets.org>
///////////////////////////////////////////////////////////////////////////////

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

#include "testprec.h"


#if wxUSE_UIACTIONSIMULATOR

#ifndef WX_PRECOMP
    #include "wx/app.h"
    #include "wx/event.h"
    #include "wx/window.h"
#endif // WX_PRECOMP

#include "wx/uiaction.h"
#include "wx/vector.h"

#ifdef __WXGTK__
#include "wx/stopwatch.h"
#endif

namespace
{

// ----------------------------------------------------------------------------
// test window verifying the event generation
// ----------------------------------------------------------------------------

class KeyboardTestWindow : public wxWindow
{
public:
    KeyboardTestWindow(wxWindow *parent)
        : wxWindow(parent, wxID_ANY, wxPoint(0, 0), parent->GetClientSize())
    {
        Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(KeyboardTestWindow::OnKeyDown));
        Connect(wxEVT_CHAR, wxKeyEventHandler(KeyboardTestWindow::OnChar));
        Connect(wxEVT_KEY_UP, wxKeyEventHandler(KeyboardTestWindow::OnKeyUp));
    }

    unsigned GetKeyDownCount() const { return m_keyDownEvents.size(); }
    unsigned GetCharCount() const { return m_charEvents.size(); }
    unsigned GetKeyUpCount() const { return m_keyUpEvents.size(); }

    const wxKeyEvent& GetKeyDownEvent(unsigned n = 0) const
    {
        return m_keyDownEvents[n];
    }
    const wxKeyEvent& GetCharEvent(unsigned n = 0) const
    {
        return m_charEvents[n];
    }
    const wxKeyEvent& GetKeyUpEvent(unsigned n = 0) const
    {
        return m_keyUpEvents[n];
    }

    void ClearEvents()
    {
        m_keyDownEvents =
        m_charEvents =
        m_keyUpEvents = wxVector<wxKeyEvent>();
    }

private:
    void OnKeyDown(wxKeyEvent& event)
    {
        m_keyDownEvents.push_back(event);
        event.Skip();
    }

    void OnChar(wxKeyEvent& event)
    {
        m_charEvents.push_back(event);
        event.Skip();
    }

    void OnKeyUp(wxKeyEvent& event)
    {
        m_keyUpEvents.push_back(event);
        event.Skip();
    }

    wxVector<wxKeyEvent> m_keyDownEvents,
                         m_charEvents,
                         m_keyUpEvents;


    wxDECLARE_NO_COPY_CLASS(KeyboardTestWindow);
};

// Object describing the (main fields of) keyboard event.
struct KeyDesc
{
    KeyDesc(int keycode, int mods = 0)
        : m_keycode(keycode),
          m_mods(mods)
    {
    }

    int m_keycode;
    int m_mods;
};

// Helper for ModKeyDown().
int GetModForKey(int keycode)
{
    switch ( keycode )
    {
        case WXK_CONTROL:   return wxMOD_CONTROL;
        case WXK_SHIFT:     return wxMOD_SHIFT;
        case WXK_ALT:       return wxMOD_ALT;
        default:
            wxFAIL_MSG( "Unknown modifier key" );
    }

    return wxMOD_NONE;
}

// Helper function to allow writing just ModKeyDown(WXK_CONTROL) instead of
// more verbose KeyDesc(WXK_CONTROL, wxMOD_CONTROL).
KeyDesc ModKeyDown(int keycode)
{
    return KeyDesc(keycode, GetModForKey(keycode));
}

// Another helper provided for symmetry with ModKeyDown() only.
KeyDesc ModKeyUp(int keycode)
{
    return KeyDesc(keycode);
}

// Verify that the event object corresponds to our idea of what it should be.
void TestEvent(int line, const wxKeyEvent& ev, const KeyDesc& desc)
{
    // Construct the message we'll display if an assert fails.
    std::string msg;
    const wxEventType t = ev.GetEventType();
    if ( t == wxEVT_KEY_DOWN )
        msg = "key down";
    else if ( t == wxEVT_CHAR )
        msg = "char";
    else if ( t == wxEVT_KEY_UP )
        msg = "key up";
    else
        CPPUNIT_FAIL( "unknown event type" );

    msg += " event at line ";
    msg += wxString::Format("%d", line).mb_str();


    CPPUNIT_ASSERT_EQUAL_MESSAGE( "wrong key code in " + msg,
                                  desc.m_keycode,
                                  ev.GetKeyCode() );

#if wxUSE_UNICODE
    if ( desc.m_keycode < WXK_START )
    {
        // For Latin-1 our key code is the same as Unicode character value.
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "wrong Unicode key in " + msg,
                                      (char)desc.m_keycode,
                                      (char)ev.GetUnicodeKey() );
    }
    else // Special key
    {
        // Key codes above WXK_START don't correspond to printable characters.
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "wrong non-zero Unicode key in " + msg,
                                      0,
                                      (int)ev.GetUnicodeKey() );
    }
#endif // wxUSE_UNICODE

    CPPUNIT_ASSERT_EQUAL_MESSAGE( "wrong modifiers in " + msg,
                                  desc.m_mods,
                                  ev.GetModifiers() );
}

// Call TestEvent() passing it the line number from where it was called: this
// is useful for interpreting the assert failure messages.
#define ASSERT_KEY_EVENT_IS( ev, desc ) TestEvent(__LINE__, ev, desc)

} // anonymous namespace

// --------------------------------------------------------------------------
// test class
// --------------------------------------------------------------------------

class KeyboardEventTestCase : public CppUnit::TestCase
{
public:
    KeyboardEventTestCase() {}

    virtual void setUp() wxOVERRIDE;
    virtual void tearDown() wxOVERRIDE;

private:
    CPPUNIT_TEST_SUITE( KeyboardEventTestCase );
        WXUISIM_TEST( NormalLetter );
        WXUISIM_TEST( NormalSpecial );
        WXUISIM_TEST( CtrlLetter );
        WXUISIM_TEST( CtrlSpecial );
        WXUISIM_TEST( ShiftLetter );
        WXUISIM_TEST( ShiftSpecial );
    CPPUNIT_TEST_SUITE_END();

    void NormalLetter();
    void NormalSpecial();
    void CtrlLetter();
    void CtrlSpecial();
    void ShiftLetter();
    void ShiftSpecial();

    KeyboardTestWindow *m_win;

    wxDECLARE_NO_COPY_CLASS(KeyboardEventTestCase);
};

wxREGISTER_UNIT_TEST(KeyboardEvent);

void KeyboardEventTestCase::setUp()
{
    m_win = new KeyboardTestWindow(wxTheApp->GetTopWindow());
    wxYield();
    m_win->SetFocus();

#ifdef __WXGTK__
    for ( wxStopWatch sw; sw.Time() < 10; )
#endif
        wxYield(); // needed to show the new window

    // The window might get some key up events when it's being shown if the key
    // was pressed when the program was started and released after the window
    // was shown, e.g. this does happen in practice when launching the test
    // from command line. Simply discard all the spurious events so far.
    m_win->ClearEvents();
}

void KeyboardEventTestCase::tearDown()
{
    m_win->Destroy();
}

void KeyboardEventTestCase::NormalLetter()
{
    wxUIActionSimulator sim;
    sim.Char('a');
    wxYield();

    CPPUNIT_ASSERT_EQUAL( 1, m_win->GetKeyDownCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyDownEvent(), 'A' );

    CPPUNIT_ASSERT_EQUAL( 1, m_win->GetCharCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetCharEvent(), 'a' );

    CPPUNIT_ASSERT_EQUAL( 1, m_win->GetKeyUpCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyUpEvent(), 'A' );
}

void KeyboardEventTestCase::NormalSpecial()
{
    wxUIActionSimulator sim;
    sim.Char(WXK_END);
    wxYield();

    CPPUNIT_ASSERT_EQUAL( 1, m_win->GetKeyDownCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyDownEvent(), WXK_END );

    CPPUNIT_ASSERT_EQUAL( 1, m_win->GetCharCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetCharEvent(), WXK_END );

    CPPUNIT_ASSERT_EQUAL( 1, m_win->GetKeyUpCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyUpEvent(), WXK_END );
}

void KeyboardEventTestCase::CtrlLetter()
{
    wxUIActionSimulator sim;
    sim.Char('z', wxMOD_CONTROL);
    wxYield();

    CPPUNIT_ASSERT_EQUAL( 2, m_win->GetKeyDownCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyDownEvent(0),
                         ModKeyDown(WXK_CONTROL) );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyDownEvent(1),
                         KeyDesc('Z', wxMOD_CONTROL) );

    CPPUNIT_ASSERT_EQUAL( 1, m_win->GetCharCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetCharEvent(),
                         KeyDesc('\x1a', wxMOD_CONTROL) );

    CPPUNIT_ASSERT_EQUAL( 2, m_win->GetKeyUpCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyUpEvent(0),
                         KeyDesc('Z', wxMOD_CONTROL) );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyUpEvent(1),
                         ModKeyUp(WXK_CONTROL) );
}

void KeyboardEventTestCase::CtrlSpecial()
{
    wxUIActionSimulator sim;
    sim.Char(WXK_PAGEUP, wxMOD_CONTROL);
    wxYield();

    CPPUNIT_ASSERT_EQUAL( 2, m_win->GetKeyDownCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyDownEvent(0),
                         ModKeyDown(WXK_CONTROL) );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyDownEvent(1),
                         KeyDesc(WXK_PAGEUP, wxMOD_CONTROL) );

    CPPUNIT_ASSERT_EQUAL( 1, m_win->GetCharCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetCharEvent(),
                         KeyDesc(WXK_PAGEUP, wxMOD_CONTROL) );

    CPPUNIT_ASSERT_EQUAL( 2, m_win->GetKeyUpCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyUpEvent(0),
                         KeyDesc(WXK_PAGEUP, wxMOD_CONTROL) );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyUpEvent(1),
                         ModKeyUp(WXK_CONTROL) );
}

void KeyboardEventTestCase::ShiftLetter()
{
    wxUIActionSimulator sim;
    sim.Char('Q', wxMOD_SHIFT);
    wxYield();

    CPPUNIT_ASSERT_EQUAL( 2, m_win->GetKeyDownCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyDownEvent(0),
                         ModKeyDown(WXK_SHIFT) );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyDownEvent(1),
                         KeyDesc('Q', wxMOD_SHIFT) );

    CPPUNIT_ASSERT_EQUAL( 1, m_win->GetCharCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetCharEvent(),
                         KeyDesc('Q', wxMOD_SHIFT) );

    CPPUNIT_ASSERT_EQUAL( 2, m_win->GetKeyUpCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyUpEvent(0),
                         KeyDesc('Q', wxMOD_SHIFT) );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyUpEvent(1),
                         ModKeyUp(WXK_SHIFT) );
}

void KeyboardEventTestCase::ShiftSpecial()
{
    wxUIActionSimulator sim;
    sim.Char(WXK_F3, wxMOD_SHIFT);
    wxYield();

    CPPUNIT_ASSERT_EQUAL( 2, m_win->GetKeyDownCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyDownEvent(0),
                         ModKeyDown(WXK_SHIFT) );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyDownEvent(1),
                         KeyDesc(WXK_F3, wxMOD_SHIFT) );

    CPPUNIT_ASSERT_EQUAL( 1, m_win->GetCharCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetCharEvent(),
                         KeyDesc(WXK_F3, wxMOD_SHIFT) );

    CPPUNIT_ASSERT_EQUAL( 2, m_win->GetKeyUpCount() );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyUpEvent(0),
                         KeyDesc(WXK_F3, wxMOD_SHIFT) );
    ASSERT_KEY_EVENT_IS( m_win->GetKeyUpEvent(1),
                         ModKeyUp(WXK_SHIFT) );
}

#endif // wxUSE_UIACTIONSIMULATOR
