// Created on: 2012-05-28
//
// Copyright (c) 2011-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.

// prevent disabling some MSVC warning messages by VTK headers
#include <Standard_WarningsDisable.hxx>
#ifdef _WIN32
  #include <vtkWin32RenderWindowInteractor.h>
  #include <vtkWin32OpenGLRenderWindow.h>
#else
  #include <GL/glx.h>

  // Preventing naming collisions between
  // GLX and VTK versions 9.0 and above
  #ifdef AllValues
    #undef AllValues
  #endif

  // Resolve name collisions with X11 headers
  #ifdef Status
    #undef Status
  #endif

  #ifdef Success
    #undef Success
  #endif

  #include <vtkXRenderWindowInteractor.h>
  #include <vtkXOpenGLRenderWindow.h>
#endif
#include <vtkActor.h>
#include <vtkActorCollection.h>
#include <vtkCommand.h>
#include <vtkObjectFactory.h>
#include <vtkSmartPointer.h>
#include <Standard_WarningsRestore.hxx>

#include <IVtkDraw_Interactor.hxx>

#include <IVtkTools_ShapePicker.hxx>
#include <IVtkTools_SubPolyDataFilter.hxx>
#include <IVtkTools_DisplayModeFilter.hxx>
#include <IVtkTools_ShapeObject.hxx>
#include <IVtkTools_ShapeDataSource.hxx>

#include <Message.hxx>
#include <Message_Messenger.hxx>

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

static void ClearHighlightAndSelection(const occ::handle<ShapePipelineMap>& theMap,
                                       const bool                           doHighlighting,
                                       const bool                           doSelection)
{
  if (!doHighlighting && !doSelection)
  {
    return;
  }

  for (ShapePipelineMap::Iterator anIt(*theMap); anIt.More(); anIt.Next())
  {
    const occ::handle<IVtkDraw_HighlightAndSelectionPipeline>& aPL = anIt.Value();

    if (doHighlighting)
    {
      aPL->ClearHighlightFilters();
    }

    if (doSelection)
    {
      aPL->ClearSelectionFilters();
    }
  }
}

vtkStandardNewMacro(IVtkDraw_Interactor)

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

  IVtkDraw_Interactor::IVtkDraw_Interactor()
    :
#ifdef _WIN32
      myWindowId(NULL),
      myMouseInWindow(0)
#else
      myIsLeftButtonPressed(false)
#endif
{
}

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

IVtkDraw_Interactor::~IVtkDraw_Interactor() = default;

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

void IVtkDraw_Interactor::SetShapePicker(const PSelector& theSelector)
{
  mySelector = theSelector;
}

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

void IVtkDraw_Interactor::SetPipelines(const occ::handle<ShapePipelineMap>& thePipelines)
{
  myPipelines = thePipelines;
}

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

void IVtkDraw_Interactor::SetOCCWindow(const occ::handle<Aspect_Window>& theWindow)
{
  myWindow = theWindow;
}

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

const occ::handle<Aspect_Window>& IVtkDraw_Interactor::GetOCCWindow() const
{
  return myWindow;
}

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

bool IVtkDraw_Interactor::IsEnabled() const
{
  return (Enabled != 0);
}

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

void IVtkDraw_Interactor::Initialize()
{
  // Make sure we have a RenderWindow and camera
  if (!this->RenderWindow)
  {
    vtkErrorMacro(<< "No renderer defined!");
    return;
  }

  if (this->Initialized)
  {
    return;
  }

  this->Initialized = 1;

  // Get the info we need from the RenderingWindow
  int* aSize;
#ifdef _WIN32
  vtkWin32OpenGLRenderWindow* aRenWin;
  aRenWin = (vtkWin32OpenGLRenderWindow*)(this->RenderWindow);
  aRenWin->Start();
  aSize = aRenWin->GetSize();
  aRenWin->GetPosition();
  this->myWindowId = aRenWin->GetWindowId();
#else
  vtkXOpenGLRenderWindow* aRenWin;
  aRenWin           = static_cast<vtkXOpenGLRenderWindow*>(this->RenderWindow);
  this->myDisplayId = aRenWin->GetDisplayId();
  this->myWindowId  = aRenWin->GetWindowId();
  aSize             = aRenWin->GetSize();
  aRenWin->Start();
#endif

  this->Enable();
  this->Size[0] = aSize[0];
  this->Size[1] = aSize[1];
}

#ifdef _WIN32
LRESULT CALLBACK WndProc(HWND theHWnd, UINT theUMsg, WPARAM theWParam, LPARAM theLParam);
#endif

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

void IVtkDraw_Interactor::Enable()
{
  if (this->Enabled)
  {
    return;
  }

  // Add event handlers
#ifdef _WIN32
  SetWindowLongPtr(this->myWindowId, GWLP_USERDATA, (LONG_PTR)this);
  SetWindowLongPtr(this->myWindowId, GWLP_WNDPROC, (LONG_PTR)WndProc);
#else
  Tcl_CreateFileHandler(ConnectionNumber(this->myDisplayId),
                        TCL_READABLE,
                        ProcessEvents,
                        (ClientData)this);
#endif

  this->Enabled = 1;
  this->Modified();
}

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

void IVtkDraw_Interactor::MoveTo(int theX, int theY)
{
  // Processing highlighting
  mySelector->Pick(theX, theY, 0.0);
  vtkSmartPointer<vtkActorCollection> anActorCollection = mySelector->GetPickedActors();

  if (anActorCollection)
  {
    // Highlight picked subshapes
    ClearHighlightAndSelection(myPipelines, true, false);
    anActorCollection->InitTraversal();
    while (vtkActor* anActor = anActorCollection->GetNextActor())
    {
      IVtkTools_ShapeDataSource* aDataSource = IVtkTools_ShapeObject::GetShapeSource(anActor);
      if (!aDataSource)
      {
        continue;
      }

      IVtkOCC_Shape::Handle anOccShape = aDataSource->GetShape();
      if (anOccShape.IsNull())
      {
        continue;
      }

      IVtk_IdType                    aShapeID = anOccShape->GetId();
      occ::handle<Message_Messenger> anOutput = Message::DefaultMessenger();
      if (!myPipelines->IsBound(aShapeID))
      {
        anOutput->SendWarning()
          << "Warning: there is no VTK pipeline registered for highlighted shape" << std::endl;
        continue;
      }

      const occ::handle<IVtkDraw_HighlightAndSelectionPipeline>& aPL = myPipelines->Find(aShapeID);

      // Add a subpolydata filter to the highlight pipeline for the shape data source.
      IVtkTools_SubPolyDataFilter* aFilter = aPL->GetHighlightFilter();

      // Set the selected sub-shapes ids to subpolydata filter.
      NCollection_List<IVtk_IdType> aSubShapeIds = mySelector->GetPickedSubShapesIds(aShapeID);

      // Get ids of cells for picked subshapes.
      NCollection_List<IVtk_IdType>           aSubIds;
      NCollection_List<IVtk_IdType>::Iterator aMetaIds(aSubShapeIds);
      for (; aMetaIds.More(); aMetaIds.Next())
      {
        NCollection_List<IVtk_IdType> aSubSubIds = anOccShape->GetSubIds(aMetaIds.Value());
        aSubIds.Append(aSubSubIds);
      }

      aFilter->SetDoFiltering(!aSubIds.IsEmpty());
      aFilter->SetData(aSubIds);
      if (!aFilter->GetInput())
      {
        aFilter->SetInputConnection(aDataSource->GetOutputPort());
      }
      aFilter->Modified();
    }
  }
  this->Render();
}

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

void IVtkDraw_Interactor::OnSelection()
{
  // Processing selection
  vtkSmartPointer<vtkActorCollection> anActorCollection = mySelector->GetPickedActors();

  if (anActorCollection)
  {
    // Highlight picked subshapes.
    ClearHighlightAndSelection(myPipelines, false, true);
    anActorCollection->InitTraversal();
    while (vtkActor* anActor = anActorCollection->GetNextActor())
    {
      IVtkTools_ShapeDataSource* aDataSource = IVtkTools_ShapeObject::GetShapeSource(anActor);
      if (!aDataSource)
      {
        continue;
      }

      IVtkOCC_Shape::Handle anOccShape = aDataSource->GetShape();
      if (anOccShape.IsNull())
      {
        continue;
      }

      IVtk_IdType                    aShapeID = anOccShape->GetId();
      occ::handle<Message_Messenger> anOutput = Message::DefaultMessenger();
      if (!myPipelines->IsBound(aShapeID))
      {
        anOutput->SendWarning() << "Warning: there is no VTK pipeline registered for picked shape"
                                << std::endl;
        continue;
      }

      const occ::handle<IVtkDraw_HighlightAndSelectionPipeline>& aPL = myPipelines->Find(aShapeID);

      // Add a subpolydata filter to the selection pipeline for the shape data source.
      IVtkTools_SubPolyDataFilter* aFilter = aPL->GetSelectionFilter();

      // Set the selected sub-shapes ids to subpolydata filter.
      NCollection_List<IVtk_IdType> aSubShapeIds = mySelector->GetPickedSubShapesIds(aShapeID);

      // Get ids of cells for picked subshapes.
      NCollection_List<IVtk_IdType>           aSubIds;
      NCollection_List<IVtk_IdType>::Iterator aMetaIds(aSubShapeIds);
      for (; aMetaIds.More(); aMetaIds.Next())
      {
        NCollection_List<IVtk_IdType> aSubSubIds = anOccShape->GetSubIds(aMetaIds.Value());
        aSubIds.Append(aSubSubIds);
      }

      aFilter->SetDoFiltering(!aSubIds.IsEmpty());
      aFilter->SetData(aSubIds);
      if (!aFilter->GetInput())
      {
        aFilter->SetInputConnection(aDataSource->GetOutputPort());
      }
      aFilter->Modified();
    }
  }
  this->Render();
}

#ifdef _WIN32

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

void IVtkDraw_Interactor::OnMouseMove(HWND theHWnd, UINT theNFlags, int theX, int theY)
{
  if (!this->Enabled)
  {
    return;
  }

  this->SetEventInformationFlipY(theX, theY, theNFlags & MK_CONTROL, theNFlags & MK_SHIFT);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  if (!this->myMouseInWindow
      && (theX >= 0 && theX < this->Size[0] && theY >= 0 && theY < this->Size[1]))
  {
    this->InvokeEvent(vtkCommand::EnterEvent, NULL);
    this->myMouseInWindow = 1;
    // request WM_MOUSELEAVE generation
    TRACKMOUSEEVENT aTme;
    aTme.cbSize    = sizeof(TRACKMOUSEEVENT);
    aTme.dwFlags   = TME_LEAVE;
    aTme.hwndTrack = theHWnd;
    TrackMouseEvent(&aTme);
  }

  if (!(theNFlags & MK_LBUTTON))
    this->MoveTo(theX, this->Size[1] - theY - 1);

  this->InvokeEvent(vtkCommand::MouseMoveEvent, NULL);
}

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

void IVtkDraw_Interactor::OnMouseWheelForward(HWND, UINT theNFlags, int theX, int theY)
{
  if (!this->Enabled)
  {
    return;
  }

  this->SetEventInformationFlipY(theX, theY, theNFlags & MK_CONTROL, theNFlags & MK_SHIFT);

  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  this->InvokeEvent(vtkCommand::MouseWheelForwardEvent, NULL);
}

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

void IVtkDraw_Interactor::OnMouseWheelBackward(HWND, UINT theNFlags, int theX, int theY)
{
  if (!this->Enabled)
  {
    return;
  }

  this->SetEventInformationFlipY(theX, theY, theNFlags & MK_CONTROL, theNFlags & MK_SHIFT);

  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  this->InvokeEvent(vtkCommand::MouseWheelBackwardEvent, NULL);
}

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

void IVtkDraw_Interactor::OnLButtonDown(HWND theHWnd,
                                        UINT theNFlags,
                                        int  theX,
                                        int  theY,
                                        int  theRepeat)
{
  if (!this->Enabled)
  {
    return;
  }
  SetFocus(theHWnd);
  SetCapture(theHWnd);
  this->SetEventInformationFlipY(theX,
                                 theY,
                                 theNFlags & MK_CONTROL,
                                 theNFlags & MK_SHIFT,
                                 0,
                                 theRepeat);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));

  OnSelection();

  this->InvokeEvent(vtkCommand::LeftButtonPressEvent, NULL);
}

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

void IVtkDraw_Interactor::OnLButtonUp(HWND, UINT theNFlags, int theX, int theY)
{
  if (!this->Enabled)
  {
    return;
  }

  this->SetEventInformationFlipY(theX, theY, theNFlags & MK_CONTROL, theNFlags & MK_SHIFT);

  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  this->InvokeEvent(vtkCommand::LeftButtonReleaseEvent, NULL);
  ReleaseCapture();
}

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

void IVtkDraw_Interactor::OnMButtonDown(HWND theHWnd,
                                        UINT theNFlags,
                                        int  theX,
                                        int  theY,
                                        int  theRepeat)
{
  if (!this->Enabled)
  {
    return;
  }

  SetFocus(theHWnd);
  SetCapture(theHWnd);
  this->SetEventInformationFlipY(theX,
                                 theY,
                                 theNFlags & MK_CONTROL,
                                 theNFlags & MK_SHIFT,
                                 0,
                                 theRepeat);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  this->InvokeEvent(vtkCommand::MiddleButtonPressEvent, NULL);
}

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

void IVtkDraw_Interactor::OnMButtonUp(HWND, UINT theNFlags, int theX, int theY)
{
  if (!this->Enabled)
  {
    return;
  }
  this->SetEventInformationFlipY(theX, theY, theNFlags & MK_CONTROL, theNFlags & MK_SHIFT);

  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  this->InvokeEvent(vtkCommand::MiddleButtonReleaseEvent, NULL);
  ReleaseCapture();
}

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

void IVtkDraw_Interactor::OnRButtonDown(HWND theHWnd,
                                        UINT theNFlags,
                                        int  theX,
                                        int  theY,
                                        int  theRepeat)
{
  if (!this->Enabled)
  {
    return;
  }

  SetFocus(theHWnd);
  SetCapture(theHWnd);
  this->SetEventInformationFlipY(theX,
                                 theY,
                                 theNFlags & MK_CONTROL,
                                 theNFlags & MK_SHIFT,
                                 0,
                                 theRepeat);

  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  this->InvokeEvent(vtkCommand::RightButtonPressEvent, NULL);
}

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

void IVtkDraw_Interactor::OnRButtonUp(HWND, UINT theNFlags, int theX, int theY)
{
  if (!this->Enabled)
  {
    return;
  }
  this->SetEventInformationFlipY(theX, theY, theNFlags & MK_CONTROL, theNFlags & MK_SHIFT);

  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  this->InvokeEvent(vtkCommand::RightButtonReleaseEvent, NULL);
  ReleaseCapture();
}

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

void IVtkDraw_Interactor::OnSize(HWND, UINT, int theX, int theY)
{
  this->UpdateSize(theX, theY);
  if (this->Enabled)
  {
    this->InvokeEvent(vtkCommand::ConfigureEvent, NULL);
  }
}

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

void IVtkDraw_Interactor::OnTimer(HWND, UINT theTimerId)
{
  if (!this->Enabled)
  {
    return;
  }

  int aTid = static_cast<int>(theTimerId);
  this->InvokeEvent(vtkCommand::TimerEvent, (void*)&aTid);

  // Here we deal with one-shot versus repeating timers
  if (this->IsOneShotTimer(aTid))
  {
    KillTimer(this->myWindowId, aTid); //'cause windows timers are always repeating
  }
}

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

LRESULT CALLBACK WndProc(HWND theHWnd, UINT theUMsg, WPARAM theWParam, LPARAM theLParam)
{
  LRESULT              aRes         = 0;
  IVtkDraw_Interactor* anInteractor = 0;

  anInteractor = (IVtkDraw_Interactor*)GetWindowLongPtrW(theHWnd, GWLP_USERDATA);

  if (anInteractor && anInteractor->GetReferenceCount() > 0)
  {
    anInteractor->Register(anInteractor);
    aRes = ViewerWindowProc(theHWnd, theUMsg, theWParam, theLParam, anInteractor);
    anInteractor->UnRegister(anInteractor);
  }

  return aRes;
}

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

LRESULT CALLBACK ViewerWindowProc(HWND                 theHWnd,
                                  UINT                 theMsg,
                                  WPARAM               theWParam,
                                  LPARAM               theLParam,
                                  IVtkDraw_Interactor* theInteractor)
{
  switch (theMsg)
  {
    case WM_CLOSE:
      theInteractor->GetOCCWindow()->Unmap();
      return 0;
    case WM_PAINT:
      theInteractor->Render();
      break;
    case WM_SIZE:
      theInteractor->OnSize(theHWnd, (UINT)theWParam, LOWORD(theLParam), HIWORD(theLParam));
      break;
    case WM_LBUTTONDBLCLK:
      theInteractor->OnLButtonDown(theHWnd,
                                   (UINT)theWParam,
                                   MAKEPOINTS(theLParam).x,
                                   MAKEPOINTS(theLParam).y,
                                   1);
      break;
    case WM_LBUTTONDOWN:
      theInteractor->OnLButtonDown(theHWnd,
                                   (UINT)theWParam,
                                   MAKEPOINTS(theLParam).x,
                                   MAKEPOINTS(theLParam).y,
                                   0);
      break;
    case WM_LBUTTONUP:
      theInteractor->OnLButtonUp(theHWnd,
                                 (UINT)theWParam,
                                 MAKEPOINTS(theLParam).x,
                                 MAKEPOINTS(theLParam).y);
      break;
    case WM_MBUTTONDBLCLK:
      theInteractor->OnMButtonDown(theHWnd,
                                   (UINT)theWParam,
                                   MAKEPOINTS(theLParam).x,
                                   MAKEPOINTS(theLParam).y,
                                   1);
      break;
    case WM_MBUTTONDOWN:
      theInteractor->OnMButtonDown(theHWnd,
                                   (UINT)theWParam,
                                   MAKEPOINTS(theLParam).x,
                                   MAKEPOINTS(theLParam).y,
                                   0);
      break;
    case WM_MBUTTONUP:
      theInteractor->OnMButtonUp(theHWnd,
                                 (UINT)theWParam,
                                 MAKEPOINTS(theLParam).x,
                                 MAKEPOINTS(theLParam).y);
      break;
    case WM_RBUTTONDBLCLK:
      theInteractor->OnRButtonDown(theHWnd,
                                   (UINT)theWParam,
                                   MAKEPOINTS(theLParam).x,
                                   MAKEPOINTS(theLParam).y,
                                   1);
      break;
    case WM_RBUTTONDOWN:
      theInteractor->OnRButtonDown(theHWnd,
                                   (UINT)theWParam,
                                   MAKEPOINTS(theLParam).x,
                                   MAKEPOINTS(theLParam).y,
                                   0);
      break;
    case WM_RBUTTONUP:
      theInteractor->OnRButtonUp(theHWnd,
                                 (UINT)theWParam,
                                 MAKEPOINTS(theLParam).x,
                                 MAKEPOINTS(theLParam).y);
      break;
    case WM_MOUSELEAVE: {
      theInteractor->InvokeEvent(vtkCommand::LeaveEvent, NULL);
      theInteractor->myMouseInWindow = 0;
    }
    break;
    case WM_MOUSEMOVE:
      theInteractor->OnMouseMove(theHWnd,
                                 (UINT)theWParam,
                                 MAKEPOINTS(theLParam).x,
                                 MAKEPOINTS(theLParam).y);
      break;
    case WM_MOUSEWHEEL: {
      POINT pt;
      pt.x = MAKEPOINTS(theLParam).x;
      pt.y = MAKEPOINTS(theLParam).y;
      ::ScreenToClient(theHWnd, &pt);
      if (GET_WHEEL_DELTA_WPARAM(theWParam) > 0)
      {
        theInteractor->OnMouseWheelForward(theHWnd, (UINT)theWParam, pt.x, pt.y);
      }
      else
      {
        theInteractor->OnMouseWheelBackward(theHWnd, (UINT)theWParam, pt.x, pt.y);
      }
    }
    break;
    case WM_TIMER:
      theInteractor->OnTimer(theHWnd, (UINT)theWParam);
      break;
  }
  return DefWindowProcW(theHWnd, theMsg, theWParam, theLParam);
}

#else

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

Display* IVtkDraw_Interactor::GetDisplayId() const
{
  return myDisplayId;
}

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

void IVtkDraw_Interactor::GetMousePosition(int* theX, int* theY)
{
  Window       aRoot, aChild;
  int          aRoot_x, aRoot_y;
  unsigned int aKeys;

  XQueryPointer(this->myDisplayId,
                this->myWindowId,
                &aRoot,
                &aChild,
                &aRoot_x,
                &aRoot_y,
                theX,
                theY,
                &aKeys);
}

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

int IVtkDraw_Interactor::ViewerMainLoop(int theArgNum, const char** /*theArgs*/)
{
  int  aXp, aYp;
  bool aPick = theArgNum > 0;

  static XEvent anEvent;
  XNextEvent(myDisplayId, &anEvent);

  switch (anEvent.type)
  {
    case Expose: {
      if (!this->Enabled)
      {
        return aPick;
      }
      XEvent aResult;
      while (XCheckTypedWindowEvent(this->myDisplayId, this->myWindowId, Expose, &aResult))
      {
        // just getting the expose configure event
        anEvent = aResult;
      }

      this->SetEventSize(anEvent.xexpose.width, anEvent.xexpose.height);

      aXp = anEvent.xexpose.x;
      aYp = this->Size[1] - anEvent.xexpose.y - 1;
      this->SetEventPosition(aXp, aYp);

      // only render if we are currently accepting events
      if (this->Enabled)
      {
        this->InvokeEvent(vtkCommand::ExposeEvent, nullptr);
        this->Render();
      }
    }
    break;

    case MapNotify: {
      // only render if we are currently accepting events
      if (this->Enabled && this->GetRenderWindow()->GetNeverRendered())
      {
        this->Render();
      }
    }
    break;

    case ConfigureNotify: {
      XEvent aResult;
      while (XCheckTypedWindowEvent(this->myDisplayId, this->myWindowId, ConfigureNotify, &aResult))
      {
        // just getting the last configure event
        anEvent = aResult;
      }
      int aWidth  = anEvent.xconfigure.width;
      int aHeight = anEvent.xconfigure.height;
      if (aWidth != this->Size[0] || aHeight != this->Size[1])
      {
        bool toResizeSmaller = aWidth <= this->Size[0] && aHeight <= this->Size[1];
        this->UpdateSize(aWidth, aHeight);
        aXp = anEvent.xbutton.x;
        aYp = anEvent.xbutton.y;

        SetEventPosition(aXp, this->Size[1] - aYp - 1);

        // only render if we are currently accepting events
        if (Enabled)
        {
          this->InvokeEvent(vtkCommand::ConfigureEvent, nullptr);
          if (toResizeSmaller)
          {
            // Don't call Render when the window is resized to be larger:
            //
            // - if the window is resized to be larger, an Expose event will
            //   be triggered by the X server which will trigger a call to Render().
            // - if the window is resized to be smaller, no Expose event will
            //   be triggered by the X server, as no new area become visible.
            //   only in this case, we need to explicitly call Render() in ConfigureNotify.
            this->Render();
          }
        }
      }
    }
    break;

    case ButtonPress: {
      if (!this->Enabled)
      {
        return aPick;
      }

      int aCtrl  = anEvent.xbutton.state & ControlMask ? 1 : 0;
      int aShift = anEvent.xbutton.state & ShiftMask ? 1 : 0;
      int anAlt  = anEvent.xbutton.state & Mod1Mask ? 1 : 0;
      aXp        = anEvent.xbutton.x;
      aYp        = anEvent.xbutton.y;

      // check for double click
      static int aMousePressTime = 0;
      int        aRepeat         = 0;
      // 400 ms threshold by default is probably good to start
      int anEventTime = static_cast<int>(anEvent.xbutton.time);
      if ((anEventTime - aMousePressTime) < 400)
      {
        aMousePressTime -= 2000; // no double click next time
        aRepeat = 1;
      }
      else
      {
        aMousePressTime = anEventTime;
      }

      this->SetEventInformationFlipY(aXp, aYp, aCtrl, aShift, 0, aRepeat);
      this->SetAltKey(anAlt);

      switch (anEvent.xbutton.button)
      {
        case Button1:
          this->OnSelection();
          this->myIsLeftButtonPressed = true;
          this->InvokeEvent(vtkCommand::LeftButtonPressEvent, nullptr);
          break;
        case Button2:
          this->InvokeEvent(vtkCommand::MiddleButtonPressEvent, nullptr);
          break;
        case Button3:
          this->InvokeEvent(vtkCommand::RightButtonPressEvent, nullptr);
          break;
        case Button4:
          this->InvokeEvent(vtkCommand::MouseWheelForwardEvent, nullptr);
          break;
        case Button5:
          this->InvokeEvent(vtkCommand::MouseWheelBackwardEvent, nullptr);
          break;
      }
      this->Render();
    }
    break;

    case ButtonRelease: {
      if (!this->Enabled)
      {
        return aPick;
      }
      int aCtrl  = anEvent.xbutton.state & ControlMask ? 1 : 0;
      int aShift = anEvent.xbutton.state & ShiftMask ? 1 : 0;
      int anAlt  = anEvent.xbutton.state & Mod1Mask ? 1 : 0;
      aXp        = anEvent.xbutton.x;
      aYp        = anEvent.xbutton.y;

      this->SetEventInformationFlipY(aXp, aYp, aCtrl, aShift);
      this->SetAltKey(anAlt);
      switch (anEvent.xbutton.button)
      {
        case Button1:
          this->InvokeEvent(vtkCommand::LeftButtonReleaseEvent, nullptr);
          this->myIsLeftButtonPressed = False;
          break;
        case Button2:
          this->InvokeEvent(vtkCommand::MiddleButtonReleaseEvent, nullptr);
          break;
        case Button3:
          this->InvokeEvent(vtkCommand::RightButtonReleaseEvent, nullptr);
          break;
      }
      this->Render();
    }
    break;

    case EnterNotify: {
      if (this->Enabled)
      {
        XEnterWindowEvent* anEnterEvent = reinterpret_cast<XEnterWindowEvent*>(&anEvent);
        this->SetEventInformationFlipY(anEnterEvent->x,
                                       anEnterEvent->y,
                                       (anEnterEvent->state & ControlMask) != 0,
                                       (anEnterEvent->state & ShiftMask) != 0);

        this->SetAltKey(anEvent.xbutton.state & Mod1Mask ? 1 : 0);
        this->InvokeEvent(vtkCommand::EnterEvent, nullptr);
      }
      this->Render();
    }
    break;

    case LeaveNotify: {
      if (this->Enabled)
      {
        XLeaveWindowEvent* aLeaveEvent = reinterpret_cast<XLeaveWindowEvent*>(&anEvent);
        this->SetEventInformationFlipY(aLeaveEvent->x,
                                       aLeaveEvent->y,
                                       (aLeaveEvent->state & ControlMask) != 0,
                                       (aLeaveEvent->state & ShiftMask) != 0);

        this->SetAltKey(anEvent.xbutton.state & Mod1Mask ? 1 : 0);
        this->InvokeEvent(vtkCommand::LeaveEvent, nullptr);
      }
      this->Render();
    }
    break;

    case MotionNotify: {
      if (!this->Enabled)
      {
        return aPick;
      }

      int aCtrl  = anEvent.xbutton.state & ControlMask ? 1 : 0;
      int aShift = anEvent.xbutton.state & ShiftMask ? 1 : 0;
      int anAlt  = anEvent.xbutton.state & Mod1Mask ? 1 : 0;

      // Note that even though the (x,y) location of the pointer is event structure,
      // we must call XQueryPointer for the hints (motion event compression) to
      // work properly.
      this->GetMousePosition(&aXp, &aYp);
      this->SetEventInformationFlipY(aXp, aYp, aCtrl, aShift);
      this->SetAltKey(anAlt);
      if (!myIsLeftButtonPressed)
        MoveTo(aXp, this->Size[1] - aYp - 1);
      this->InvokeEvent(vtkCommand::MouseMoveEvent, nullptr);
    }
    break;
  }

  return aPick;
}

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

void IVtkDraw_Interactor::ProcessEvents(ClientData theData, int)
{
  IVtkDraw_Interactor* anInteractor = (IVtkDraw_Interactor*)theData;
  // test for X Event
  while (XPending(anInteractor->GetDisplayId()))
  {
    anInteractor->ViewerMainLoop(0, nullptr);
  }
}

#endif
