// Copyright (c) 1998-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#if defined(_WIN32)
  // include windows.h first to have all definitions available
  #include <windows.h>
#endif

#include <WNT_Window.hxx>

#if defined(_WIN32) && !defined(OCCT_UWP)

  #include <Aspect_ScrollDelta.hxx>
  #include <Aspect_WindowDefinitionError.hxx>
  #include <Aspect_WindowInputListener.hxx>
  #include <Message.hxx>
  #include <NCollection_LocalArray.hxx>
  #include <TCollection_ExtendedString.hxx>
  #include <WNT_WClass.hxx>
  #include <WNT_HIDSpaceMouse.hxx>

IMPLEMENT_STANDARD_RTTIEXT(WNT_Window, Aspect_Window)

  #ifndef MOUSEEVENTF_FROMTOUCH
    #define MOUSEEVENTF_FROMTOUCH 0xFF515700
  #endif

//! Auxiliary tool for handling WM_TOUCH events.
//! Dynamically loads functions from User32 available since Win7 and later.
class WNT_Window::TouchInputHelper : public Standard_Transient
{
public:
  typedef BOOL(WINAPI* RegisterTouchWindow_t)(HWND hwnd, ULONG ulFlags);
  typedef BOOL(WINAPI* UnregisterTouchWindow_t)(HWND hwnd);
  typedef BOOL(WINAPI* GetTouchInputInfo_t)(HTOUCHINPUT hTouchInput,
                                            UINT        cInputs,
                                            PTOUCHINPUT pInputs,
                                            int         cbSize);
  typedef BOOL(WINAPI* CloseTouchInputHandle_t)(HTOUCHINPUT hTouchInput);

  typedef NCollection_LocalArray<TOUCHINPUT, 16> InnerTouchArray;

public:
  //! Main constructor.
  TouchInputHelper()
      : myRegisterTouchWindow(NULL),
        myUnregisterTouchWindow(NULL),
        myGetTouchInputInfo(NULL),
        myCloseTouchInputHandle(NULL),
        myIsRegistered(false)
  {
    HMODULE aUser32Module = GetModuleHandleW(L"User32");
    if (aUser32Module != NULL)
    {
      // User32 should be already loaded
      myRegisterTouchWindow =
        (RegisterTouchWindow_t)GetProcAddress(aUser32Module, "RegisterTouchWindow");
      myUnregisterTouchWindow =
        (UnregisterTouchWindow_t)GetProcAddress(aUser32Module, "UnregisterTouchWindow");
      myGetTouchInputInfo = (GetTouchInputInfo_t)GetProcAddress(aUser32Module, "GetTouchInputInfo");
      myCloseTouchInputHandle =
        (CloseTouchInputHandle_t)GetProcAddress(aUser32Module, "CloseTouchInputHandle");
    }
  }

  //! Return TRUE if window has been registered.
  bool IsRegistered() const { return myIsRegistered; }

  //! Register window to receive WM_TOUCH events.
  bool Register(HWND theWin)
  {
    if (myRegisterTouchWindow == NULL)
    {
      return false;
    }

    if (myRegisterTouchWindow(theWin, TWF_FINETOUCH))
    {
      myIsRegistered = true;
      return true;
    }
    // Message::SendTrace() << "RegisterTouchWindow() FAILED";
    return false;
  }

  //! Array of touches retrieved from HTOUCHINPUT.
  class TouchInputInfo : public InnerTouchArray
  {
  public:
    //! Main constructor.
    TouchInputInfo(const TouchInputHelper& theHelper, const MSG& theMsg)
        : InnerTouchArray(0),
          myHelper(&theHelper),
          myHInput((HTOUCHINPUT)theMsg.lParam)
    {
      const int aNbTouches = LOWORD(theMsg.wParam);
      if (aNbTouches > 0 && theHelper.myGetTouchInputInfo != NULL)
      {
        InnerTouchArray::Allocate(aNbTouches);
        TOUCHINPUT* aTouches = InnerTouchArray::operator TOUCHINPUT*();
        if (!theHelper.myGetTouchInputInfo(myHInput, aNbTouches, aTouches, sizeof(TOUCHINPUT)))
        {
          InnerTouchArray::Deallocate();
        }
      }
    }

    //! Destructor.
    ~TouchInputInfo()
    {
      if (myHelper->myCloseTouchInputHandle != NULL)
      {
        myHelper->myCloseTouchInputHandle(myHInput);
      }
    }

  private:
    const TouchInputHelper* myHelper;
    HTOUCHINPUT             myHInput;
  };

private:
  RegisterTouchWindow_t   myRegisterTouchWindow;
  UnregisterTouchWindow_t myUnregisterTouchWindow;
  GetTouchInputInfo_t     myGetTouchInputInfo;
  CloseTouchInputHandle_t myCloseTouchInputHandle;
  bool                    myIsRegistered;
};

//=================================================================================================

WNT_Window::WNT_Window(const char*                    theTitle,
                       const occ::handle<WNT_WClass>& theClass,
                       const WNT_Dword&               theStyle,
                       const int                      thePxLeft,
                       const int                      thePxTop,
                       const int                      thePxWidth,
                       const int                      thePxHeight,
                       const Quantity_NameOfColor     theBackColor,
                       const Aspect_Handle            theParent,
                       const Aspect_Handle            theMenu,
                       void* const                    theClientStruct)
    : Aspect_Window(),
      myWClass(theClass),
      myHWindow(NULL),
      myHParentWindow(NULL),
      myXLeft(thePxLeft),
      myYTop(thePxTop),
      myXRight(thePxLeft + thePxWidth),
      myYBottom(thePxTop + thePxHeight),
      myIsForeign(false)
{
  if (thePxWidth <= 0 || thePxHeight <= 0)
  {
    throw Aspect_WindowDefinitionError("Coordinate(s) out of range");
  }

  DWORD aStyle = theStyle;
  if (theParent && !(theStyle & WS_CHILD))
  {
    aStyle |= WS_CHILD | WS_CLIPSIBLINGS;
  }
  else if (!theParent && !(theStyle & WS_CLIPCHILDREN))
  {
    aStyle |= WS_CLIPCHILDREN;
  }

  // include decorations in the window dimensions to reproduce same behavior of Xw_Window
  RECT aRect;
  aRect.top    = myYTop;
  aRect.bottom = myYBottom;
  aRect.left   = myXLeft;
  aRect.right  = myXRight;
  AdjustWindowRect(&aRect, aStyle, theMenu != NULL ? TRUE : FALSE);
  myXLeft   = aRect.left;
  myYTop    = aRect.top;
  myXRight  = aRect.right;
  myYBottom = aRect.bottom;

  const TCollection_ExtendedString aTitleW(theTitle);
  const TCollection_ExtendedString aClassNameW(myWClass->Name());
  myHWindow = CreateWindowW(aClassNameW.ToWideString(),
                            aTitleW.ToWideString(),
                            aStyle,
                            myXLeft,
                            myYTop,
                            (myXRight - myXLeft),
                            (myYBottom - myYTop),
                            (HWND)theParent,
                            (HMENU)theMenu,
                            (HINSTANCE)myWClass->Instance(),
                            theClientStruct);
  if (!myHWindow)
  {
    throw Aspect_WindowDefinitionError("Unable to create window");
  }

  myHParentWindow = theParent;
  SetBackground(theBackColor);
}

//=================================================================================================

WNT_Window::WNT_Window(const Aspect_Handle theHandle, const Quantity_NameOfColor theBackColor)
    : myHWindow(theHandle),
      myHParentWindow(GetParent((HWND)theHandle)),
      myXLeft(0),
      myYTop(0),
      myXRight(0),
      myYBottom(0),
      myIsForeign(true)
{
  SetBackground(theBackColor);

  WINDOWPLACEMENT aPlace = {};
  aPlace.length          = sizeof(WINDOWPLACEMENT);
  ::GetWindowPlacement((HWND)myHWindow, &aPlace);

  myXLeft   = aPlace.rcNormalPosition.left;
  myYTop    = aPlace.rcNormalPosition.top;
  myXRight  = aPlace.rcNormalPosition.right;
  myYBottom = aPlace.rcNormalPosition.bottom;
}

//=================================================================================================

WNT_Window::~WNT_Window()
{
  if (myHWindow == NULL || myIsForeign)
  {
    return;
  }

  DestroyWindow((HWND)myHWindow);
  myIsForeign = false;
}

//=================================================================================================

void WNT_Window::SetCursor(const Aspect_Handle theCursor) const
{
  ::SetClassLongPtrW((HWND)myHWindow, GCLP_HCURSOR, (LONG_PTR)theCursor);
}

//=================================================================================================

bool WNT_Window::IsMapped() const
{
  if (IsVirtual())
  {
    return true;
  }

  WINDOWPLACEMENT aPlace = {};
  aPlace.length          = sizeof(WINDOWPLACEMENT);
  ::GetWindowPlacement((HWND)myHWindow, &aPlace);
  return !(aPlace.showCmd == SW_HIDE || aPlace.showCmd == SW_MINIMIZE);
}

//=================================================================================================

void WNT_Window::Map() const
{
  if (!IsVirtual())
  {
    Map(SW_SHOW);
  }
}

//=================================================================================================

void WNT_Window::Map(const int theMapMode) const
{
  if (IsVirtual())
  {
    return;
  }

  ::ShowWindow((HWND)myHWindow, theMapMode);
  ::UpdateWindow((HWND)myHWindow);
}

//=================================================================================================

void WNT_Window::Unmap() const
{
  Map(SW_HIDE);
}

//=================================================================================================

Aspect_TypeOfResize WNT_Window::DoResize()
{
  if (IsVirtual())
  {
    return Aspect_TOR_UNKNOWN;
  }

  WINDOWPLACEMENT aPlace = {};
  aPlace.length          = sizeof(WINDOWPLACEMENT);
  GetWindowPlacement((HWND)myHWindow, &aPlace);
  if (aPlace.showCmd == SW_SHOWMINIMIZED)
  {
    return Aspect_TOR_UNKNOWN;
  }

  int aMask = 0;
  if (std::abs((int)aPlace.rcNormalPosition.left - myXLeft) > 2)
  {
    aMask |= 1;
  }
  if (std::abs((int)aPlace.rcNormalPosition.right - myXRight) > 2)
  {
    aMask |= 2;
  }
  if (std::abs((int)aPlace.rcNormalPosition.top - myYTop) > 2)
  {
    aMask |= 4;
  }
  if (std::abs((int)aPlace.rcNormalPosition.bottom - myYBottom) > 2)
  {
    aMask |= 8;
  }

  myXLeft   = aPlace.rcNormalPosition.left;
  myXRight  = aPlace.rcNormalPosition.right;
  myYTop    = aPlace.rcNormalPosition.top;
  myYBottom = aPlace.rcNormalPosition.bottom;
  switch (aMask)
  {
    case 0:
      return Aspect_TOR_NO_BORDER;
    case 1:
      return Aspect_TOR_LEFT_BORDER;
    case 2:
      return Aspect_TOR_RIGHT_BORDER;
    case 4:
      return Aspect_TOR_TOP_BORDER;
    case 5:
      return Aspect_TOR_LEFT_AND_TOP_BORDER;
    case 6:
      return Aspect_TOR_TOP_AND_RIGHT_BORDER;
    case 8:
      return Aspect_TOR_BOTTOM_BORDER;
    case 9:
      return Aspect_TOR_BOTTOM_AND_LEFT_BORDER;
    case 10:
      return Aspect_TOR_RIGHT_AND_BOTTOM_BORDER;
  }
  return Aspect_TOR_UNKNOWN;
}

//=================================================================================================

double WNT_Window::Ratio() const
{
  if (IsVirtual())
  {
    return double(myXRight - myXLeft) / double(myYBottom - myYTop);
  }

  RECT aRect = {};
  GetClientRect((HWND)myHWindow, &aRect);
  return double(aRect.right - aRect.left) / double(aRect.bottom - aRect.top);
}

//=================================================================================================

void WNT_Window::Position(int& theX1, int& theY1, int& theX2, int& theY2) const
{
  if (IsVirtual())
  {
    theX1 = myXLeft;
    theX2 = myXRight;
    theY1 = myYTop;
    theY2 = myYBottom;
    return;
  }

  RECT aRect = {};
  ::GetClientRect((HWND)myHWindow, &aRect);

  POINT aPntLeft, aPntRight;
  aPntLeft.x = aPntLeft.y = 0;
  ::ClientToScreen((HWND)myHWindow, &aPntLeft);
  aPntRight.x = aRect.right;
  aPntRight.y = aRect.bottom;
  ::ClientToScreen((HWND)myHWindow, &aPntRight);

  if (myHParentWindow != NULL)
  {
    ::ScreenToClient((HWND)myHParentWindow, &aPntLeft);
    ::ScreenToClient((HWND)myHParentWindow, &aPntRight);
  }

  theX1 = aPntLeft.x;
  theX2 = aPntRight.x;
  theY1 = aPntLeft.y;
  theY2 = aPntRight.y;
}

//=================================================================================================

void WNT_Window::Size(int& theWidth, int& theHeight) const
{
  if (IsVirtual())
  {
    theWidth  = myXRight - myXLeft;
    theHeight = myYBottom - myYTop;
    return;
  }

  RECT aRect = {};
  ::GetClientRect((HWND)myHWindow, &aRect);
  theWidth  = aRect.right;
  theHeight = aRect.bottom;
}

//=================================================================================================

void WNT_Window::SetPos(const int theX, const int theY, const int theX1, const int theY1)
{
  myXLeft   = theX;
  myYTop    = theY;
  myXRight  = theX1;
  myYBottom = theY1;
}

//=================================================================================================

void WNT_Window::SetTitle(const TCollection_AsciiString& theTitle)
{
  const TCollection_ExtendedString aTitleW(theTitle);
  SetWindowTextW((HWND)myHWindow, aTitleW.ToWideString());
}

//=================================================================================================

void WNT_Window::InvalidateContent(const occ::handle<Aspect_DisplayConnection>&)
{
  if (myHWindow != NULL)
  {
    ::InvalidateRect((HWND)myHWindow, NULL, TRUE);
  }
}

//=================================================================================================

Aspect_VKey WNT_Window::VirtualKeyFromNative(int theKey)
{
  if (theKey >= int('0') && theKey <= int('9'))
  {
    return Aspect_VKey((theKey - int('0')) + Aspect_VKey_0);
  }
  if (theKey >= int('A') && theKey <= int('Z'))
  {
    // main latin alphabet keys
    return Aspect_VKey((theKey - int('A')) + Aspect_VKey_A);
  }
  if (theKey >= VK_F1 && theKey <= VK_F24)
  {
    // special keys
    if (theKey <= VK_F12)
    {
      return Aspect_VKey((theKey - VK_F1) + Aspect_VKey_F1);
    }
    return Aspect_VKey_UNKNOWN;
  }
  if (theKey >= VK_NUMPAD0 && theKey <= VK_NUMPAD9)
  {
    // numpad keys
    return Aspect_VKey((theKey - VK_NUMPAD0) + Aspect_VKey_Numpad0);
  }

  switch (theKey)
  {
    case VK_LBUTTON:
    case VK_RBUTTON:
    case VK_CANCEL:
    case VK_MBUTTON:
    case VK_XBUTTON1:
    case VK_XBUTTON2:
      return Aspect_VKey_UNKNOWN;
    case VK_BACK:
      return Aspect_VKey_Backspace;
    case VK_TAB:
      return Aspect_VKey_Tab;
    case VK_CLEAR:
      return Aspect_VKey_UNKNOWN;
    case VK_RETURN:
      return Aspect_VKey_Enter;
    case VK_SHIFT:
      return Aspect_VKey_Shift;
    case VK_CONTROL:
      return Aspect_VKey_Control;
    case VK_MENU:
      return Aspect_VKey_Alt; // Aspect_VKey_Menu;
    case VK_PAUSE:
    case VK_CAPITAL:
      return Aspect_VKey_UNKNOWN;
    case VK_ESCAPE:
      return Aspect_VKey_Escape;
    case VK_CONVERT:
    case VK_NONCONVERT:
    case VK_ACCEPT:
    case VK_MODECHANGE:
      return Aspect_VKey_UNKNOWN;
    case VK_SPACE:
      return Aspect_VKey_Space;
    case VK_PRIOR:
      return Aspect_VKey_PageUp;
    case VK_NEXT:
      return Aspect_VKey_PageDown;
    case VK_END:
      return Aspect_VKey_End;
    case VK_HOME:
      return Aspect_VKey_Home;
    case VK_LEFT:
      return Aspect_VKey_Left;
    case VK_UP:
      return Aspect_VKey_Up;
    case VK_DOWN:
      return Aspect_VKey_Down;
    case VK_RIGHT:
      return Aspect_VKey_Right;
    case VK_SELECT:
    case VK_PRINT:
    case VK_EXECUTE:
    case VK_SNAPSHOT:
      return Aspect_VKey_UNKNOWN;
    case VK_INSERT:
      return Aspect_VKey_UNKNOWN; // Aspect_VKey_Insert
    case VK_DELETE:
      return Aspect_VKey_Delete;
    case VK_HELP:
    case VK_LWIN:
    case VK_RWIN:
    case VK_APPS:
    case VK_SLEEP:
      return Aspect_VKey_UNKNOWN;
    case VK_MULTIPLY:
      return Aspect_VKey_NumpadMultiply;
    case VK_ADD:
      return Aspect_VKey_NumpadAdd;
    case VK_SEPARATOR:
    case VK_DECIMAL:
      return Aspect_VKey_UNKNOWN;
    case VK_SUBTRACT:
      return Aspect_VKey_NumpadSubtract;
    case VK_DIVIDE:
      return Aspect_VKey_NumpadDivide;
    case VK_NUMLOCK:
      return Aspect_VKey_Numlock;
    case VK_SCROLL:
      return Aspect_VKey_Scroll;
    case VK_LSHIFT:
    case VK_RSHIFT:
    case VK_LCONTROL:
    case VK_RCONTROL:
    case VK_LMENU:
    case VK_RMENU:
      return Aspect_VKey_UNKNOWN;
    case VK_BROWSER_BACK:
      return Aspect_VKey_BrowserBack;
    case VK_BROWSER_FORWARD:
      return Aspect_VKey_BrowserForward;
    case VK_BROWSER_REFRESH:
      return Aspect_VKey_BrowserRefresh;
    case VK_BROWSER_STOP:
      return Aspect_VKey_BrowserStop;
    case VK_BROWSER_SEARCH:
      return Aspect_VKey_BrowserSearch;
    case VK_BROWSER_FAVORITES:
      return Aspect_VKey_BrowserFavorites;
    case VK_BROWSER_HOME:
      return Aspect_VKey_BrowserHome;
    case VK_VOLUME_MUTE:
      return Aspect_VKey_VolumeMute;
    case VK_VOLUME_DOWN:
      return Aspect_VKey_VolumeDown;
    case VK_VOLUME_UP:
      return Aspect_VKey_VolumeUp;
    case VK_MEDIA_NEXT_TRACK:
      return Aspect_VKey_MediaNextTrack;
    case VK_MEDIA_PREV_TRACK:
      return Aspect_VKey_MediaPreviousTrack;
    case VK_MEDIA_STOP:
      return Aspect_VKey_MediaStop;
    case VK_MEDIA_PLAY_PAUSE:
      return Aspect_VKey_MediaPlayPause;
    case VK_OEM_1:
      return Aspect_VKey_Semicolon;
    case VK_OEM_PLUS:
      return Aspect_VKey_Plus;
    case VK_OEM_COMMA:
      return Aspect_VKey_Comma;
    case VK_OEM_MINUS:
      return Aspect_VKey_Minus;
    case VK_OEM_PERIOD:
      return Aspect_VKey_Period;
    case VK_OEM_2:
      return Aspect_VKey_Slash;
    case VK_OEM_3:
      return Aspect_VKey_Tilde;
    case VK_OEM_4:
      return Aspect_VKey_BracketLeft;
    case VK_OEM_5:
      return Aspect_VKey_Backslash;
    case VK_OEM_6:
      return Aspect_VKey_BracketRight;
    case VK_OEM_7:
      return Aspect_VKey_Apostrophe;
  }
  return Aspect_VKey_UNKNOWN;
}

//=================================================================================================

Aspect_VKeyFlags WNT_Window::MouseKeyFlagsFromEvent(WPARAM theKeys)
{
  Aspect_VKeyFlags aFlags = Aspect_VKeyFlags_NONE;
  if ((theKeys & MK_CONTROL) != 0)
  {
    aFlags |= Aspect_VKeyFlags_CTRL;
  }
  if ((theKeys & MK_SHIFT) != 0)
  {
    aFlags |= Aspect_VKeyFlags_SHIFT;
  }
  if (GetKeyState(VK_MENU) < 0)
  {
    aFlags |= Aspect_VKeyFlags_ALT;
  }
  return aFlags;
}

//=================================================================================================

Aspect_VKeyFlags WNT_Window::MouseKeyFlagsAsync()
{
  Aspect_VKeyFlags aFlags = Aspect_VKeyFlags_NONE;
  if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0)
  {
    aFlags |= Aspect_VKeyFlags_CTRL;
  }
  if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
  {
    aFlags |= Aspect_VKeyFlags_SHIFT;
  }
  if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0)
  {
    aFlags |= Aspect_VKeyFlags_ALT;
  }
  return aFlags;
}

//=================================================================================================

Aspect_VKeyMouse WNT_Window::MouseButtonsFromEvent(WPARAM theKeys)
{
  Aspect_VKeyMouse aButtons = Aspect_VKeyMouse_NONE;
  if ((theKeys & MK_LBUTTON) != 0)
  {
    aButtons |= Aspect_VKeyMouse_LeftButton;
  }
  if ((theKeys & MK_MBUTTON) != 0)
  {
    aButtons |= Aspect_VKeyMouse_MiddleButton;
  }
  if ((theKeys & MK_RBUTTON) != 0)
  {
    aButtons |= Aspect_VKeyMouse_RightButton;
  }
  return aButtons;
}

//=================================================================================================

Aspect_VKeyMouse WNT_Window::MouseButtonsAsync()
{
  Aspect_VKeyMouse aButtons  = Aspect_VKeyMouse_NONE;
  const bool       isSwapped = GetSystemMetrics(SM_SWAPBUTTON) != 0;

  if ((GetAsyncKeyState(!isSwapped ? VK_LBUTTON : VK_RBUTTON) & 0x8000) != 0)
  {
    aButtons |= Aspect_VKeyMouse_LeftButton;
  }
  if ((GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0)
  {
    aButtons |= Aspect_VKeyMouse_MiddleButton;
  }
  if ((GetAsyncKeyState(!isSwapped ? VK_RBUTTON : VK_LBUTTON) & 0x8000) != 0)
  {
    aButtons |= Aspect_VKeyMouse_RightButton;
  }
  return aButtons;
}

//=================================================================================================

int WNT_Window::RegisterRawInputDevices(unsigned int theRawDeviceMask)
{
  if (IsVirtual() || myHWindow == NULL)
  {
    return 0;
  }

  // hidusage.h
  enum HidUsagePage
  {
    THE_HID_USAGE_PAGE_GENERIC = 0x01
  }; // HID_USAGE_PAGE_GENERIC

  enum HidUsage
  {
    THE_HID_USAGE_GENERIC_MOUSE                 = 0x02, // HID_USAGE_GENERIC_MOUSE
    THE_HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER = 0x08, // HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER
  };

  int            aNbDevices = 0;
  RAWINPUTDEVICE aRawInDevList[2];
  if ((theRawDeviceMask & RawInputMask_Mouse) != 0)
  {
    // mouse
    RAWINPUTDEVICE& aRawMouse = aRawInDevList[aNbDevices++];
    aRawMouse.usUsagePage     = THE_HID_USAGE_PAGE_GENERIC;
    aRawMouse.usUsage         = THE_HID_USAGE_GENERIC_MOUSE;
    aRawMouse.dwFlags         = RIDEV_INPUTSINK;
    aRawMouse.hwndTarget      = (HWND)myHWindow;
  }
  if ((theRawDeviceMask & RawInputMask_SpaceMouse) != 0)
  {
    // space mouse
    RAWINPUTDEVICE& aRawSpace = aRawInDevList[aNbDevices++];
    aRawSpace.usUsagePage     = THE_HID_USAGE_PAGE_GENERIC;
    aRawSpace.usUsage         = THE_HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER;
    aRawSpace.dwFlags         = 0; // RIDEV_DEVNOTIFY
    aRawSpace.hwndTarget      = (HWND)myHWindow;
  }

  for (int aTryIter = aNbDevices; aTryIter > 0; --aTryIter)
  {
    if (::RegisterRawInputDevices(aRawInDevList, aTryIter, sizeof(aRawInDevList[0])))
    {
      return aTryIter;
    }

    Message::SendTrace(
      aRawInDevList[aTryIter - 1].usUsage == THE_HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER
        ? "Warning: RegisterRawInputDevices() failed to register RAW multi-axis controller input"
        : "Warning: RegisterRawInputDevices() failed to register RAW mouse input");
  }
  return 0;
}

//=================================================================================================

bool WNT_Window::ProcessMessage(Aspect_WindowInputListener& theListener, MSG& theMsg)
{
  if (myTouchInputHelper.IsNull())
  {
    myTouchInputHelper = new TouchInputHelper();
    myTouchInputHelper->Register((HWND)myHWindow);
  }

  switch (theMsg.message)
  {
    case WM_CLOSE: {
      if (theMsg.hwnd == (HWND)myHWindow)
      {
        theListener.ProcessClose();
        return true;
      }
      return false;
    }
    case WM_SETFOCUS:
    case WM_KILLFOCUS: {
      if (theMsg.hwnd == (HWND)myHWindow)
      {
        theListener.ProcessFocus(theMsg.message == WM_SETFOCUS);
        return true;
      }
      return false;
    }
    case WM_PAINT: {
      PAINTSTRUCT aPaint;
      BeginPaint(theMsg.hwnd, &aPaint);
      EndPaint(theMsg.hwnd, &aPaint);
      theListener.ProcessExpose();
      return true;
    }
    case WM_SIZE:
    case WM_MOVE:
    case WM_MOVING:
    case WM_SIZING: {
      theListener.ProcessConfigure(theMsg.message == WM_SIZE);
      return true;
    }
    case WM_KEYUP:
    case WM_KEYDOWN: {
      const Aspect_VKey aVKey = WNT_Window::VirtualKeyFromNative((int)theMsg.wParam);
      if (aVKey != Aspect_VKey_UNKNOWN)
      {
        const double aTimeStamp = theListener.EventTime();
        if (theMsg.message == WM_KEYDOWN)
        {
          theListener.KeyDown(aVKey, aTimeStamp);
        }
        else
        {
          theListener.KeyUp(aVKey, aTimeStamp);
        }
        theListener.ProcessInput();
      }
      return true;
    }
    case WM_LBUTTONUP:
    case WM_MBUTTONUP:
    case WM_RBUTTONUP:
    case WM_LBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_RBUTTONDOWN: {
      const LPARAM anExtraInfo = GetMessageExtraInfo();
      bool         isEmulated  = false;
      if ((anExtraInfo & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH)
      {
        isEmulated = true;
        if (!myTouchInputHelper.IsNull() && myTouchInputHelper->IsRegistered())
        {
          // Message::SendTrace ("Skipping mouse message emulated from touches...");
          break;
        }
      }

      const NCollection_Vec2<int> aPos(LOWORD(theMsg.lParam), HIWORD(theMsg.lParam));
      const Aspect_VKeyFlags      aFlags  = WNT_Window::MouseKeyFlagsFromEvent(theMsg.wParam);
      Aspect_VKeyMouse            aButton = Aspect_VKeyMouse_NONE;
      switch (theMsg.message)
      {
        case WM_LBUTTONUP:
        case WM_LBUTTONDOWN:
          aButton = Aspect_VKeyMouse_LeftButton;
          break;
        case WM_MBUTTONUP:
        case WM_MBUTTONDOWN:
          aButton = Aspect_VKeyMouse_MiddleButton;
          break;
        case WM_RBUTTONUP:
        case WM_RBUTTONDOWN:
          aButton = Aspect_VKeyMouse_RightButton;
          break;
      }
      if (theMsg.message == WM_LBUTTONDOWN || theMsg.message == WM_MBUTTONDOWN
          || theMsg.message == WM_RBUTTONDOWN)
      {
        SetFocus(theMsg.hwnd);
        SetCapture(theMsg.hwnd);
        theListener.PressMouseButton(aPos, aButton, aFlags, isEmulated);
      }
      else
      {
        ReleaseCapture();
        theListener.ReleaseMouseButton(aPos, aButton, aFlags, isEmulated);
      }
      theListener.ProcessInput();
      return true;
    }
    case WM_MOUSEWHEEL: {
      const int              aDelta  = GET_WHEEL_DELTA_WPARAM(theMsg.wParam);
      const double           aDeltaF = double(aDelta) / double(WHEEL_DELTA);
      const Aspect_VKeyFlags aFlags  = WNT_Window::MouseKeyFlagsFromEvent(theMsg.wParam);
      NCollection_Vec2<int>  aPos(int(short(LOWORD(theMsg.lParam))),
                                  int(short(HIWORD(theMsg.lParam))));
      POINT                  aCursorPnt = {aPos.x(), aPos.y()};
      if (ScreenToClient(theMsg.hwnd, &aCursorPnt))
      {
        aPos.SetValues(aCursorPnt.x, aCursorPnt.y);
      }

      if (theMsg.hwnd != (HWND)myHWindow)
      {
        return false;
      }

      theListener.UpdateMouseScroll(Aspect_ScrollDelta(aPos, aDeltaF, aFlags));
      theListener.ProcessInput();
      return true;
    }
    case WM_MOUSEMOVE: {
      NCollection_Vec2<int> aPos(LOWORD(theMsg.lParam), HIWORD(theMsg.lParam));
      Aspect_VKeyMouse      aButtons = WNT_Window::MouseButtonsFromEvent(theMsg.wParam);
      Aspect_VKeyFlags      aFlags   = WNT_Window::MouseKeyFlagsFromEvent(theMsg.wParam);

      // don't make a slide-show from input events - fetch the actual mouse cursor position
      CURSORINFO aCursor;
      aCursor.cbSize = sizeof(aCursor);
      if (::GetCursorInfo(&aCursor) != FALSE)
      {
        POINT aCursorPnt = {aCursor.ptScreenPos.x, aCursor.ptScreenPos.y};
        if (ScreenToClient(theMsg.hwnd, &aCursorPnt))
        {
          // as we override mouse position, we need overriding also mouse state
          aPos.SetValues(aCursorPnt.x, aCursorPnt.y);
          aButtons = WNT_Window::MouseButtonsAsync();
          aFlags   = WNT_Window::MouseKeyFlagsAsync();
        }
      }

      if (theMsg.hwnd != (HWND)myHWindow)
      {
        // mouse move events come also for inactive windows
        return false;
      }

      theListener.UpdateMousePosition(aPos, aButtons, aFlags, false);
      theListener.ProcessInput();
      return true;
    }
    case WM_INPUT: {
      UINT aSize = 0;
      ::GetRawInputData((HRAWINPUT)theMsg.lParam, RID_INPUT, NULL, &aSize, sizeof(RAWINPUTHEADER));
      NCollection_LocalArray<BYTE> aRawData(aSize);
      if (aSize == 0
          || ::GetRawInputData((HRAWINPUT)theMsg.lParam,
                               RID_INPUT,
                               aRawData,
                               &aSize,
                               sizeof(RAWINPUTHEADER))
               != aSize)
      {
        return true;
      }

      const RAWINPUT* aRawInput = (RAWINPUT*)(BYTE*)aRawData;
      if (aRawInput->header.dwType != RIM_TYPEHID)
      {
        return true;
      }

      RID_DEVICE_INFO aDevInfo;
      aDevInfo.cbSize   = sizeof(RID_DEVICE_INFO);
      UINT aDevInfoSize = sizeof(RID_DEVICE_INFO);
      if (::GetRawInputDeviceInfoW(aRawInput->header.hDevice,
                                   RIDI_DEVICEINFO,
                                   &aDevInfo,
                                   &aDevInfoSize)
            != sizeof(RID_DEVICE_INFO)
          || (aDevInfo.hid.dwVendorId != WNT_HIDSpaceMouse::VENDOR_ID_LOGITECH
              && aDevInfo.hid.dwVendorId != WNT_HIDSpaceMouse::VENDOR_ID_3DCONNEXION))
      {
        return true;
      }

      WNT_HIDSpaceMouse aSpaceData(aDevInfo.hid.dwProductId,
                                   aRawInput->data.hid.bRawData,
                                   aRawInput->data.hid.dwSizeHid);
      if (theListener.Update3dMouse(aSpaceData))
      {
        InvalidateContent(occ::handle<Aspect_DisplayConnection>());
      }
      return true;
    }
    case WM_TOUCH: {
      if (theMsg.hwnd != (HWND)myHWindow || myTouchInputHelper.IsNull())
      {
        return false;
      }

      TouchInputHelper::TouchInputInfo aSrcTouches(*myTouchInputHelper, theMsg);
      if (aSrcTouches.Size() < 1)
      {
        break;
      }

      NCollection_Vec2<int> aWinTopLeft, aWinBotRight;
      Position(aWinTopLeft.x(), aWinTopLeft.y(), aWinBotRight.x(), aWinBotRight.y());

      bool hasUpdates = false;
      for (size_t aTouchIter = 0; aTouchIter < aSrcTouches.Size(); ++aTouchIter)
      {
        const TOUCHINPUT& aTouchSrc = aSrcTouches[aTouchIter];
        const size_t      aTouchId  = (size_t)aTouchSrc.dwID;
        // const size_t aDeviceId = (size_t )aTouchSrc.hSource;

        const NCollection_Vec2<int>    aSize = aWinBotRight - aWinTopLeft;
        const NCollection_Vec2<double> aNewPos2d =
          NCollection_Vec2<double>(double(aTouchSrc.x), double(aTouchSrc.y)) * 0.01
          - NCollection_Vec2<double>(aWinTopLeft);
        const NCollection_Vec2<int> aNewPos2i =
          NCollection_Vec2<int>(aNewPos2d + NCollection_Vec2<double>(0.5));
        if ((aTouchSrc.dwFlags & TOUCHEVENTF_DOWN) == TOUCHEVENTF_DOWN)
        {
          if (aNewPos2i.x() >= 0 && aNewPos2i.x() < aSize.x() && aNewPos2i.y() >= 0
              && aNewPos2i.y() < aSize.y())
          {
            hasUpdates = true;
            theListener.AddTouchPoint(aTouchId, aNewPos2d);
          }
        }
        else if ((aTouchSrc.dwFlags & TOUCHEVENTF_MOVE) == TOUCHEVENTF_MOVE)
        {
          const int anOldIndex = theListener.TouchPoints().FindIndex(aTouchId);
          if (anOldIndex != 0)
          {
            hasUpdates = true;
            theListener.UpdateTouchPoint(aTouchId, aNewPos2d);
          }
        }
        else if ((aTouchSrc.dwFlags & TOUCHEVENTF_UP) == TOUCHEVENTF_UP)
        {
          if (theListener.RemoveTouchPoint(aTouchId))
          {
            hasUpdates = true;
          }
        }
      }

      if (hasUpdates)
      {
        InvalidateContent(occ::handle<Aspect_DisplayConnection>());
      }
      return true;
    }
  }
  return false;
}

#endif // _WIN32
