Skip to content
Snippets Groups Projects
Commit fc066e96 authored by Andrew Wilson's avatar Andrew Wilson :elephant:
Browse files

BUG: Fix linux events, keys and VR

parent 87ac099d
No related branches found
No related tags found
No related merge requests found
...@@ -30,7 +30,6 @@ ...@@ -30,7 +30,6 @@
#include <vtkRenderer.h> #include <vtkRenderer.h>
#include <vtkRenderWindow.h> #include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkTextActor.h> #include <vtkTextActor.h>
#include <vtkOpenGLRenderWindow.h> #include <vtkOpenGLRenderWindow.h>
...@@ -39,6 +38,8 @@ ...@@ -39,6 +38,8 @@
#ifdef WIN32 #ifdef WIN32
#include <vtkWin32HardwareWindow.h> #include <vtkWin32HardwareWindow.h>
#include <vtkWin32RenderWindowInteractor.h> #include <vtkWin32RenderWindowInteractor.h>
#else
#include "imstkVtkXRenderWindowInteractor2.h"
#endif #endif
namespace imstk namespace imstk
...@@ -53,8 +54,13 @@ VTKViewer::VTKViewer(std::string name) : AbstractVTKViewer(name), ...@@ -53,8 +54,13 @@ VTKViewer::VTKViewer(std::string name) : AbstractVTKViewer(name),
m_vtkInteractorStyle = std::dynamic_pointer_cast<vtkInteractorStyle>(m_interactorStyle); m_vtkInteractorStyle = std::dynamic_pointer_cast<vtkInteractorStyle>(m_interactorStyle);
// Create the interactor // Create the interactor
#ifdef WIN32
vtkNew<vtkRenderWindowInteractor> iren; vtkNew<vtkRenderWindowInteractor> iren;
iren->SetInteractorStyle(m_vtkInteractorStyle.get()); iren->SetInteractorStyle(m_vtkInteractorStyle.get());
#else
vtkSmartPointer<vtkXRenderWindowInteractor2> iren = vtkSmartPointer<vtkXRenderWindowInteractor2>::New();
iren->SetInteractorStyle(m_vtkInteractorStyle.get());
#endif
// Create the RenderWindow // Create the RenderWindow
m_vtkRenderWindow = vtkSmartPointer<vtkRenderWindow>::New(); m_vtkRenderWindow = vtkSmartPointer<vtkRenderWindow>::New();
......
/*=========================================================================
Program: Visualization Toolkit
Module: vtkXRenderWindowInteractor2.cxx
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#include "imstkVtkXRenderWindowInteractor2.h"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/time.h>
#include "vtkActor.h"
#include "vtkCallbackCommand.h"
#include "vtkCommand.h"
#include "vtkInteractorStyle.h"
#include "vtkObjectFactory.h"
#include "vtkRenderWindow.h"
#include "vtkStringArray.h"
#include <vtksys/SystemTools.hxx>
#include <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <climits>
#include <cmath>
#include <map>
#include <set>
#include <sstream>
vtkStandardNewMacro(vtkXRenderWindowInteractor2);
template <int EventType>
int XEventTypeEquals(Display*, XEvent* event, XPointer)
{
return event->type == EventType;
}
struct vtkXRenderWindowInteractor2Timer
{
unsigned long duration;
timeval lastFire;
};
// Map between the X native id to our own integer count id. Note this
// is separate from the TimerMap in the vtkRenderWindowInteractor
// superclass. This is used to avoid passing 64-bit values back
// through the "int" return type of InternalCreateTimer.
class vtkXRenderWindowInteractor2Internals
{
public:
vtkXRenderWindowInteractor2Internals() { this->TimerIdCount = 1; }
~vtkXRenderWindowInteractor2Internals() = default;
// duration is in milliseconds
int CreateLocalTimer(unsigned long duration)
{
int id = this->TimerIdCount++;
this->LocalToTimer[id].duration = duration;
gettimeofday(&this->LocalToTimer[id].lastFire, nullptr);
return id;
}
void DestroyLocalTimer(int id) { this->LocalToTimer.erase(id); }
void GetTimeToNextTimer(timeval& tv)
{
uint64_t lowestDelta = 1000000;
if (!this->LocalToTimer.empty())
{
timeval ctv;
gettimeofday(&ctv, nullptr);
for (auto& timer : this->LocalToTimer)
{
uint64_t delta = (ctv.tv_sec - timer.second.lastFire.tv_sec) * 1000000 + ctv.tv_usec -
timer.second.lastFire.tv_usec;
if (delta < lowestDelta)
{
lowestDelta = delta;
}
}
}
tv.tv_sec = lowestDelta / 1000000;
tv.tv_usec = lowestDelta % 1000000;
}
void FireTimers(vtkXRenderWindowInteractor2* rwi)
{
if (!this->LocalToTimer.empty())
{
timeval ctv;
gettimeofday(&ctv, nullptr);
std::vector<unsigned long> expired;
for (auto& timer : this->LocalToTimer)
{
int64_t delta = (ctv.tv_sec - timer.second.lastFire.tv_sec) * 1000000 + ctv.tv_usec -
timer.second.lastFire.tv_usec;
if (delta / 1000 >= static_cast<int64_t>(timer.second.duration))
{
int timerId = rwi->GetVTKTimerId(timer.first);
rwi->InvokeEvent(vtkCommand::TimerEvent, &timerId);
if (rwi->IsOneShotTimer(timerId))
{
expired.push_back(timer.first);
}
else
{
timer.second.lastFire.tv_sec = ctv.tv_sec;
timer.second.lastFire.tv_usec = ctv.tv_usec;
}
}
}
for (auto exp : expired)
{
this->DestroyLocalTimer(exp);
}
}
}
static std::set<vtkXRenderWindowInteractor2*> Instances;
private:
int TimerIdCount;
std::map<int, vtkXRenderWindowInteractor2Timer> LocalToTimer;
};
std::set<vtkXRenderWindowInteractor2*> vtkXRenderWindowInteractor2Internals::Instances;
// for some reason the X11 def of KeySym is getting messed up
typedef XID vtkKeySym;
//------------------------------------------------------------------------------
vtkXRenderWindowInteractor2::vtkXRenderWindowInteractor2()
{
this->Internal = new vtkXRenderWindowInteractor2Internals;
this->DisplayId = nullptr;
this->WindowId = 0;
this->KillAtom = 0;
this->XdndSource = 0;
this->XdndPositionAtom = 0;
this->XdndDropAtom = 0;
this->XdndActionCopyAtom = 0;
this->XdndStatusAtom = 0;
this->XdndFinishedAtom = 0;
}
//------------------------------------------------------------------------------
vtkXRenderWindowInteractor2::~vtkXRenderWindowInteractor2()
{
this->Disable();
delete this->Internal;
}
//------------------------------------------------------------------------------
// TerminateApp() notifies the event loop to exit.
// The event loop is started by Start() or by one own's method.
// This results in Start() returning to its caller.
void vtkXRenderWindowInteractor2::TerminateApp()
{
if (this->Done)
{
return;
}
this->Done = true;
// Send a VTK_BreakXtLoop ClientMessage event to be sure we pop out of the
// event loop. This "wakes up" the event loop. Otherwise, it might sit idle
// waiting for an event before realizing an exit was requested.
XClientMessageEvent client;
memset(&client, 0, sizeof(client));
client.type = ClientMessage;
// client.serial; //leave zeroed
// client.send_event; //leave zeroed
client.display = this->DisplayId;
client.window = this->WindowId;
client.message_type = XInternAtom(this->DisplayId, "VTK_BreakXtLoop", False);
client.format = 32; // indicates size of data chunks: 8, 16 or 32 bits...
// client.data; //leave zeroed
XSendEvent(client.display, client.window, True, NoEventMask, reinterpret_cast<XEvent*>(&client));
XFlush(client.display);
this->RenderWindow->Finalize();
}
void vtkXRenderWindowInteractor2::ProcessEvents()
{
XEvent event;
while (XPending(this->DisplayId) && !this->Done)
{
XNextEvent(this->DisplayId, &event);
this->DispatchEvent(&event);
}
}
//------------------------------------------------------------------------------
// This will start up the X event loop. If you
// call this method it will loop processing X events until the
// loop is exited.
void vtkXRenderWindowInteractor2::StartEventLoop()
{
std::vector<int> rwiFileDescriptors;
fd_set in_fds;
struct timeval tv;
struct timeval minTv;
for (auto rwi : vtkXRenderWindowInteractor2Internals::Instances)
{
rwi->Done = false;
rwiFileDescriptors.push_back(ConnectionNumber(rwi->DisplayId));
}
bool done = true;
do
{
bool wait = true;
done = true;
minTv.tv_sec = 1000;
minTv.tv_usec = 1000;
XEvent event;
for (auto rwi = vtkXRenderWindowInteractor2Internals::Instances.begin();
rwi != vtkXRenderWindowInteractor2Internals::Instances.end();)
{
if (XPending((*rwi)->DisplayId) == 0)
{
// get how long to wait for the next timer
(*rwi)->Internal->GetTimeToNextTimer(tv);
minTv.tv_sec = std::min(tv.tv_sec, minTv.tv_sec);
minTv.tv_usec = std::min(tv.tv_usec, minTv.tv_usec);
}
else
{
// If events are pending, dispatch them to the right RenderWindowInteractor
XNextEvent((*rwi)->DisplayId, &event);
(*rwi)->DispatchEvent(&event);
wait = false;
}
(*rwi)->FireTimers();
// Check if all RenderWindowInteractors have been terminated
done = done && (*rwi)->Done;
// If current RenderWindowInteractor have been terminated, handle its last event,
// then remove it from the Instance vector
if ((*rwi)->Done)
{
// Empty the event list
while (XPending((*rwi)->DisplayId) != 0)
{
XNextEvent((*rwi)->DisplayId, &event);
(*rwi)->DispatchEvent(&event);
}
// Adjust the file descriptors vector
int rwiPosition =
std::distance(vtkXRenderWindowInteractor2Internals::Instances.begin(), rwi);
rwi = vtkXRenderWindowInteractor2Internals::Instances.erase(rwi);
rwiFileDescriptors.erase(rwiFileDescriptors.begin() + rwiPosition);
}
else
{
++rwi;
}
}
if (wait && !done)
{
// select will wait until 'tv' elapses or something else wakes us
FD_ZERO(&in_fds);
for (auto rwiFileDescriptor : rwiFileDescriptors)
{
FD_SET(rwiFileDescriptor, &in_fds);
}
int maxFileDescriptor =
*std::max_element(rwiFileDescriptors.begin(), rwiFileDescriptors.end());
select(maxFileDescriptor + 1, &in_fds, nullptr, nullptr, &minTv);
}
} while (!done);
}
//------------------------------------------------------------------------------
// Initializes the event handlers without an XtAppContext. This is
// good for when you don't have a user interface, but you still
// want to have mouse interaction.
void vtkXRenderWindowInteractor2::Initialize()
{
if (this->Initialized)
{
return;
}
vtkRenderWindow* ren;
int* size;
// make sure we have a RenderWindow and camera
if (!this->RenderWindow)
{
vtkErrorMacro(<< "No renderer defined!");
return;
}
this->Initialized = 1;
ren = this->RenderWindow;
this->DisplayId = static_cast<Display*>(ren->GetGenericDisplayId());
if (!this->DisplayId)
{
vtkDebugMacro("opening display");
this->DisplayId = XOpenDisplay(nullptr);
vtkDebugMacro("opened display");
ren->SetDisplayId(this->DisplayId);
}
vtkXRenderWindowInteractor2Internals::Instances.insert(this);
size = ren->GetActualSize();
size[0] = ((size[0] > 0) ? size[0] : 300);
size[1] = ((size[1] > 0) ? size[1] : 300);
XSync(this->DisplayId, False);
ren->Start();
ren->End();
this->WindowId = reinterpret_cast<Window>(ren->GetGenericWindowId());
XWindowAttributes attribs;
// Find the current window size
XGetWindowAttributes(this->DisplayId, this->WindowId, &attribs);
size[0] = attribs.width;
size[1] = attribs.height;
ren->SetSize(size[0], size[1]);
this->Enable();
this->Size[0] = size[0];
this->Size[1] = size[1];
}
//------------------------------------------------------------------------------
void vtkXRenderWindowInteractor2::Enable()
{
// avoid cycles of calling Initialize() and Enable()
if (this->Enabled)
{
return;
}
// Add the event handler to the system.
// If we change the types of events processed by this handler, then
// we need to change the Disable() routine to match. In order for Disable()
// to work properly, both the callback function AND the client data
// passed to XtAddEventHandler and XtRemoveEventHandler must MATCH
// PERFECTLY
XSelectInput(this->DisplayId, this->WindowId,
KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | ExposureMask |
StructureNotifyMask | EnterWindowMask | LeaveWindowMask | PointerMotionHintMask |
PointerMotionMask);
// Setup for capturing the window deletion
this->KillAtom = XInternAtom(this->DisplayId, "WM_DELETE_WINDOW", False);
XSetWMProtocols(this->DisplayId, this->WindowId, &this->KillAtom, 1);
// Enable drag and drop
Atom xdndAwareAtom = XInternAtom(this->DisplayId, "XdndAware", False);
char xdndVersion = 5;
XChangeProperty(this->DisplayId, this->WindowId, xdndAwareAtom, XA_ATOM, 32, PropModeReplace,
(unsigned char*)&xdndVersion, 1);
this->XdndPositionAtom = XInternAtom(this->DisplayId, "XdndPosition", False);
this->XdndDropAtom = XInternAtom(this->DisplayId, "XdndDrop", False);
this->XdndActionCopyAtom = XInternAtom(this->DisplayId, "XdndActionCopy", False);
this->XdndStatusAtom = XInternAtom(this->DisplayId, "XdndStatus", False);
this->XdndFinishedAtom = XInternAtom(this->DisplayId, "XdndFinished", False);
this->Enabled = 1;
this->Modified();
}
//------------------------------------------------------------------------------
void vtkXRenderWindowInteractor2::Disable()
{
if (!this->Enabled)
{
return;
}
this->Enabled = 0;
this->Modified();
}
//------------------------------------------------------------------------------
void vtkXRenderWindowInteractor2::PrintSelf(ostream& os, vtkIndent indent)
{
this->Superclass::PrintSelf(os, indent);
}
//------------------------------------------------------------------------------
void vtkXRenderWindowInteractor2::UpdateSize(int x, int y)
{
// if the size changed send this on to the RenderWindow
if ((x != this->Size[0]) || (y != this->Size[1]))
{
this->Size[0] = x;
this->Size[1] = y;
this->RenderWindow->SetSize(x, y);
}
}
//------------------------------------------------------------------------------
void vtkXRenderWindowInteractor2::UpdateSizeNoXResize(int x, int y)
{
// if the size changed send this on to the RenderWindow
if ((x != this->Size[0]) || (y != this->Size[1]))
{
this->Size[0] = x;
this->Size[1] = y;
// static_cast<vtkXOpenGLRenderWindow*>(this->RenderWindow)->SetSizeNoXResize(x, y);
this->RenderWindow->SetSize(x, y);
}
}
//------------------------------------------------------------------------------
void vtkXRenderWindowInteractor2::FireTimers()
{
if (this->GetEnabled())
{
this->Internal->FireTimers(this);
}
}
//------------------------------------------------------------------------------
// X always creates one shot timers
int vtkXRenderWindowInteractor2::InternalCreateTimer(
int vtkNotUsed(timerId), int vtkNotUsed(timerType), unsigned long duration)
{
duration = (duration > 0 ? duration : this->TimerDuration);
int platformTimerId = this->Internal->CreateLocalTimer(duration);
return platformTimerId;
}
//------------------------------------------------------------------------------
int vtkXRenderWindowInteractor2::InternalDestroyTimer(int platformTimerId)
{
this->Internal->DestroyLocalTimer(platformTimerId);
return 1;
}
//------------------------------------------------------------------------------
void vtkXRenderWindowInteractor2::DispatchEvent(XEvent* event)
{
int xp, yp;
switch (event->type)
{
case Expose:
{
if (!this->Enabled)
{
return;
}
XEvent result;
while (XCheckTypedWindowEvent(this->DisplayId, this->WindowId, Expose, &result))
{
// just getting the expose configure event
event = &result;
}
XExposeEvent* exposeEvent = reinterpret_cast<XExposeEvent*>(event);
this->SetEventSize(exposeEvent->width, exposeEvent->height);
xp = exposeEvent->x;
yp = exposeEvent->y;
yp = this->Size[1] - yp - 1;
this->SetEventPosition(xp, yp);
// 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 result;
while (XCheckTypedWindowEvent(this->DisplayId, this->WindowId, ConfigureNotify, &result))
{
// just getting the last configure event
event = &result;
}
int width = (reinterpret_cast<XConfigureEvent*>(event))->width;
int height = (reinterpret_cast<XConfigureEvent*>(event))->height;
if (width != this->Size[0] || height != this->Size[1])
{
bool resizeSmaller = width <= this->Size[0] && height <= this->Size[1];
this->UpdateSizeNoXResize(width, height);
xp = (reinterpret_cast<XButtonEvent*>(event))->x;
yp = (reinterpret_cast<XButtonEvent*>(event))->y;
this->SetEventPosition(xp, this->Size[1] - yp - 1);
// only render if we are currently accepting events
if (this->Enabled)
{
this->InvokeEvent(vtkCommand::ConfigureEvent, nullptr);
if (resizeSmaller)
{
// 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;
}
int ctrl = ((reinterpret_cast<XButtonEvent*>(event))->state & ControlMask) ? 1 : 0;
int shift = ((reinterpret_cast<XButtonEvent*>(event))->state & ShiftMask) ? 1 : 0;
int alt = ((reinterpret_cast<XButtonEvent*>(event))->state & Mod1Mask) ? 1 : 0;
xp = (reinterpret_cast<XButtonEvent*>(event))->x;
yp = (reinterpret_cast<XButtonEvent*>(event))->y;
// check for double click
static int MousePressTime = 0;
int repeat = 0;
// 400 ms threshold by default is probably good to start
int eventTime = static_cast<int>(reinterpret_cast<XButtonEvent*>(event)->time);
if ((eventTime - MousePressTime) < 400)
{
MousePressTime -= 2000; // no double click next time
repeat = 1;
}
else
{
MousePressTime = eventTime;
}
this->SetEventInformationFlipY(xp, yp, ctrl, shift, 0, repeat);
this->SetAltKey(alt);
switch ((reinterpret_cast<XButtonEvent*>(event))->button)
{
case Button1:
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;
}
}
break;
case ButtonRelease:
{
if (!this->Enabled)
{
return;
}
int ctrl = ((reinterpret_cast<XButtonEvent*>(event))->state & ControlMask) ? 1 : 0;
int shift = ((reinterpret_cast<XButtonEvent*>(event))->state & ShiftMask) ? 1 : 0;
int alt = ((reinterpret_cast<XButtonEvent*>(event))->state & Mod1Mask) ? 1 : 0;
xp = (reinterpret_cast<XButtonEvent*>(event))->x;
yp = (reinterpret_cast<XButtonEvent*>(event))->y;
this->SetEventInformationFlipY(xp, yp, ctrl, shift);
this->SetAltKey(alt);
switch ((reinterpret_cast<XButtonEvent*>(event))->button)
{
case Button1:
this->InvokeEvent(vtkCommand::LeftButtonReleaseEvent, nullptr);
break;
case Button2:
this->InvokeEvent(vtkCommand::MiddleButtonReleaseEvent, nullptr);
break;
case Button3:
this->InvokeEvent(vtkCommand::RightButtonReleaseEvent, nullptr);
break;
}
}
break;
case EnterNotify:
{
// Force the keyboard focus to be this render window
XSetInputFocus(this->DisplayId, this->WindowId, RevertToPointerRoot, CurrentTime);
if (this->Enabled)
{
XEnterWindowEvent* e = reinterpret_cast<XEnterWindowEvent*>(event);
this->SetEventInformationFlipY(
e->x, e->y, (e->state & ControlMask) != 0, (e->state & ShiftMask) != 0);
this->SetAltKey(((reinterpret_cast<XButtonEvent*>(event))->state & Mod1Mask) ? 1 : 0);
this->InvokeEvent(vtkCommand::EnterEvent, nullptr);
}
}
break;
case LeaveNotify:
{
if (this->Enabled)
{
XLeaveWindowEvent* e = reinterpret_cast<XLeaveWindowEvent*>(event);
this->SetEventInformationFlipY(
e->x, e->y, (e->state & ControlMask) != 0, (e->state & ShiftMask) != 0);
this->SetAltKey(((reinterpret_cast<XButtonEvent*>(event))->state & Mod1Mask) ? 1 : 0);
this->InvokeEvent(vtkCommand::LeaveEvent, nullptr);
}
}
break;
case KeyPress:
{
if (!this->Enabled)
{
return;
}
int ctrl = ((reinterpret_cast<XButtonEvent*>(event))->state & ControlMask) ? 1 : 0;
int shift = ((reinterpret_cast<XButtonEvent*>(event))->state & ShiftMask) ? 1 : 0;
int alt = ((reinterpret_cast<XButtonEvent*>(event))->state & Mod1Mask) ? 1 : 0;
vtkKeySym ks;
static char buffer[20];
buffer[0] = '\0';
XLookupString(reinterpret_cast<XKeyEvent*>(event), buffer, 20, &ks, nullptr);
xp = (reinterpret_cast<XKeyEvent*>(event))->x;
yp = (reinterpret_cast<XKeyEvent*>(event))->y;
this->SetEventInformationFlipY(xp, yp, ctrl, shift, buffer[0], 1, XKeysymToString(ks));
this->SetAltKey(alt);
this->InvokeEvent(vtkCommand::KeyPressEvent, nullptr);
this->InvokeEvent(vtkCommand::CharEvent, nullptr);
}
break;
case KeyRelease:
{
if (!this->Enabled)
{
return;
}
int ctrl = ((reinterpret_cast<XButtonEvent*>(event))->state & ControlMask) ? 1 : 0;
int shift = ((reinterpret_cast<XButtonEvent*>(event))->state & ShiftMask) ? 1 : 0;
int alt = ((reinterpret_cast<XButtonEvent*>(event))->state & Mod1Mask) ? 1 : 0;
vtkKeySym ks;
static char buffer[20];
buffer[0] = '\0';
XLookupString(reinterpret_cast<XKeyEvent*>(event), buffer, 20, &ks, nullptr);
xp = (reinterpret_cast<XKeyEvent*>(event))->x;
yp = (reinterpret_cast<XKeyEvent*>(event))->y;
this->SetEventInformationFlipY(xp, yp, ctrl, shift, buffer[0], 1, XKeysymToString(ks));
this->SetAltKey(alt);
this->InvokeEvent(vtkCommand::KeyReleaseEvent, nullptr);
}
break;
case MotionNotify:
{
if (!this->Enabled)
{
return;
}
int ctrl = ((reinterpret_cast<XButtonEvent*>(event))->state & ControlMask) ? 1 : 0;
int shift = ((reinterpret_cast<XButtonEvent*>(event))->state & ShiftMask) ? 1 : 0;
int alt = ((reinterpret_cast<XButtonEvent*>(event))->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(&xp, &yp);
this->SetEventInformation(xp, yp, ctrl, shift);
this->SetAltKey(alt);
this->InvokeEvent(vtkCommand::MouseMoveEvent, nullptr);
}
break;
// Selection request for drag and drop has been delivered
case SelectionNotify:
{
// Sanity checks
if (!event->xselection.property || !this->XdndSource)
{
return;
}
// Recover the dropped file
char* data = nullptr;
Atom actualType;
int actualFormat;
unsigned long itemCount, bytesAfter;
XGetWindowProperty(this->DisplayId, event->xselection.requestor, event->xselection.property,
0, LONG_MAX, False, event->xselection.target, &actualType, &actualFormat, &itemCount,
&bytesAfter, (unsigned char**)&data);
// Conversion checks
if ((event->xselection.target != AnyPropertyType && actualType != event->xselection.target) ||
itemCount == 0)
{
return;
}
// Recover filepaths from uris and invoke DropFilesEvent
std::stringstream uris(data);
std::string uri, protocol, hostname, filePath;
std::string unused0, unused1, unused2, unused3;
vtkNew<vtkStringArray> filePaths;
while (std::getline(uris, uri, '\n'))
{
if (vtksys::SystemTools::ParseURL(
uri, protocol, unused0, unused1, hostname, unused3, filePath, true))
{
if (protocol == "file" && (hostname.empty() || hostname == "localhost"))
{
// The uris can be crlf delimited, remove ending \r if any
if (filePath.back() == '\r')
{
filePath.pop_back();
}
// The extracted filepath miss the first slash
filePath.insert(0, "/");
filePaths->InsertNextValue(filePath);
}
}
}
this->InvokeEvent(vtkCommand::DropFilesEvent, filePaths);
XFree(data);
// Inform the source the the drag and drop operation was sucessfull
XEvent reply;
memset(&reply, 0, sizeof(reply));
reply.type = ClientMessage;
reply.xclient.window = event->xclient.data.l[0];
reply.xclient.message_type = this->XdndFinishedAtom;
reply.xclient.format = 32;
reply.xclient.data.l[0] = this->WindowId;
reply.xclient.data.l[1] = itemCount;
reply.xclient.data.l[2] = this->XdndActionCopyAtom;
XSendEvent(this->DisplayId, this->XdndSource, False, NoEventMask, &reply);
XFlush(this->DisplayId);
this->XdndSource = 0;
}
break;
case ClientMessage:
{
if (event->xclient.message_type == this->XdndPositionAtom)
{
// Drag and drop event inside the window
// Recover the position
int xWindow, yWindow;
int xRoot = event->xclient.data.l[2] >> 16;
int yRoot = event->xclient.data.l[2] & 0xffff;
Window root = DefaultRootWindow(this->DisplayId);
Window child;
XTranslateCoordinates(
this->DisplayId, root, this->WindowId, xRoot, yRoot, &xWindow, &yWindow, &child);
// Convert it to VTK compatible location
double location[2];
location[0] = static_cast<double>(xWindow);
location[1] = static_cast<double>(this->Size[1] - yWindow - 1);
this->InvokeEvent(vtkCommand::UpdateDropLocationEvent, location);
// Reply that we are ready to copy the dragged data
XEvent reply;
memset(&reply, 0, sizeof(reply));
reply.type = ClientMessage;
reply.xclient.window = event->xclient.data.l[0];
reply.xclient.message_type = this->XdndStatusAtom;
reply.xclient.format = 32;
reply.xclient.data.l[0] = this->WindowId;
reply.xclient.data.l[1] = 1; // Always accept the dnd with no rectangle
reply.xclient.data.l[2] = 0; // Specify an empty rectangle
reply.xclient.data.l[3] = 0;
reply.xclient.data.l[4] = this->XdndActionCopyAtom;
XSendEvent(this->DisplayId, event->xclient.data.l[0], False, NoEventMask, &reply);
XFlush(this->DisplayId);
}
else if (event->xclient.message_type == this->XdndDropAtom)
{
// Item dropped in the window
// Store the source of the drag and drop
this->XdndSource = event->xclient.data.l[0];
// Ask for a conversion of the selection. This will trigger a SelectioNotify event later.
Atom xdndSelectionAtom = XInternAtom(this->DisplayId, "XdndSelection", False);
XConvertSelection(this->DisplayId, xdndSelectionAtom,
XInternAtom(this->DisplayId, "UTF8_STRING", False), xdndSelectionAtom, this->WindowId,
CurrentTime);
}
else if (static_cast<Atom>(event->xclient.data.l[0]) == this->KillAtom)
{
this->ExitCallback();
}
}
break;
}
}
//------------------------------------------------------------------------------
void vtkXRenderWindowInteractor2::GetMousePosition(int* x, int* y)
{
Window root, child;
int root_x, root_y;
unsigned int keys;
XQueryPointer(this->DisplayId, this->WindowId, &root, &child, &root_x, &root_y, x, y, &keys);
*y = this->Size[1] - *y - 1;
}
//------------------------------------------------------------------------------
// void vtkXRenderWindowInteractor2::Timer(XtPointer client_data, XtIntervalId* id)
// {
// vtkXRenderWindowInteractor2Timer(client_data, id);
// }
//------------------------------------------------------------------------------
// void vtkXRenderWindowInteractor2::Callback(
// Widget w, XtPointer client_data, XEvent* event, Boolean* ctd)
// {
// vtkXRenderWindowInteractor2Callback(w, client_data, event, ctd);
// }
/*=========================================================================
Library: iMSTK
Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
& Imaging in Medicine, Rensselaer Polytechnic Institute.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.txt
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=========================================================================*/
#pragma once
//===========================================================
// now we define the C++ class
#include "vtkRenderWindowInteractor.h"
#include <X11/Xlib.h> // Needed for X types in the public interface
class vtkCallbackCommand;
class vtkXRenderWindowInteractor2Internals;
///
/// \class vtkXRenderWindowInteractor2
///
/// \brief This class exists to fix bugs in VTK 9.0, which are, as typing this
/// already merged into VTK master, this class should be deleted when upgrading
/// to yet to be released VTK 10
///
class vtkXRenderWindowInteractor2 : public vtkRenderWindowInteractor
{
public:
static vtkXRenderWindowInteractor2* New();
vtkTypeMacro(vtkXRenderWindowInteractor2, vtkRenderWindowInteractor);
void PrintSelf(ostream& os, vtkIndent indent) override;
/**
* Initializes the event handlers without an XtAppContext. This is
* good for when you don't have a user interface, but you still
* want to have mouse interaction.
*/
void Initialize() override;
/**
* Break the event loop on 'q','e' keypress. Want more ???
*/
void TerminateApp() override;
/**
* Run the event loop and return. This is provided so that you can
* implement your own event loop but yet use the vtk event handling as
* well.
*/
void ProcessEvents() override;
//@{
/**
* Enable/Disable interactions. By default interactors are enabled when
* initialized. Initialize() must be called prior to enabling/disabling
* interaction. These methods are used when a window/widget is being
* shared by multiple renderers and interactors. This allows a "modal"
* display where one interactor is active when its data is to be displayed
* and all other interactors associated with the widget are disabled
* when their data is not displayed.
*/
void Enable() override;
void Disable() override;
//@}
/**
* Update the Size data member and set the associated RenderWindow's
* size.
*/
void UpdateSize(int, int) override;
/**
* Re-defines virtual function to get mouse position by querying X-server.
*/
void GetMousePosition(int* x, int* y) override;
void DispatchEvent(XEvent*);
protected:
vtkXRenderWindowInteractor2();
~vtkXRenderWindowInteractor2() override;
/**
* Update the Size data member and set the associated RenderWindow's
* size but do not resize the XWindow.
*/
void UpdateSizeNoXResize(int, int);
// Using static here to avoid destroying context when many apps are open:
static int NumAppInitialized;
Display* DisplayId;
Window WindowId;
Atom KillAtom;
int PositionBeforeStereo[2];
vtkXRenderWindowInteractor2Internals* Internal;
// Drag and drop related
Window XdndSource;
Atom XdndPositionAtom;
Atom XdndDropAtom;
Atom XdndActionCopyAtom;
Atom XdndStatusAtom;
Atom XdndFinishedAtom;
//@{
/**
* X-specific internal timer methods. See the superclass for detailed
* documentation.
*/
int InternalCreateTimer(int timerId, int timerType, unsigned long duration) override;
int InternalDestroyTimer(int platformTimerId) override;
//@}
void FireTimers();
/**
* This will start up the X event loop and never return. If you
* call this method it will loop processing X events until the
* application is exited.
*/
void StartEventLoop() override;
private:
vtkXRenderWindowInteractor2(const vtkXRenderWindowInteractor2&) = delete;
void operator=(const vtkXRenderWindowInteractor2&) = delete;
};
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment