aboutsummaryrefslogtreecommitdiff
path: root/mayaPlug/shaveCursorCtx.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mayaPlug/shaveCursorCtx.cpp')
-rw-r--r--mayaPlug/shaveCursorCtx.cpp1709
1 files changed, 1709 insertions, 0 deletions
diff --git a/mayaPlug/shaveCursorCtx.cpp b/mayaPlug/shaveCursorCtx.cpp
new file mode 100644
index 0000000..7124cde
--- /dev/null
+++ b/mayaPlug/shaveCursorCtx.cpp
@@ -0,0 +1,1709 @@
+// Shave and a Haircut
+// (c) 2019 Epic Games
+// US Patent 6720962
+
+//Qt headers must be included before any others !!!
+#include <QtCore/QEvent>
+#include <QtGui/QMouseEvent>
+#include <QtGui/QTabletEvent>
+#include <QtGui/QWheelEvent>
+
+#if QT_VERSION < 0x050000
+# include <QtGui/QApplication>
+# include <QtGui/QWidget>
+#else
+# include <QtWidgets/QApplication>
+# include <QtWidgets/QWidget>
+#endif
+
+#include <iostream>
+#include <vector>
+using namespace std;
+#include <limits.h>
+
+#include <maya/M3dView.h>
+#include <maya/MCursor.h>
+#include <maya/MDagModifier.h>
+#include <maya/MDagPath.h>
+#include <maya/MEvent.h>
+#include <maya/MEventMessage.h>
+#include <maya/MFnCamera.h>
+#include <maya/MFnDagNode.h>
+#include <maya/MFnTransform.h>
+#include <maya/MGlobal.h>
+#include <maya/MPoint.h>
+#include <maya/MPxContext.h>
+#include <maya/MSelectionList.h>
+#include <maya/MString.h>
+#include <maya/MVector.h>
+
+#include "shaveSDK.h"
+#include "shaveCursorCtx.h"
+#include "shaveBrushManip.h"
+#include "shaveGlobals.h"
+#include "shaveHairShape.h"
+#include "shaveHairUI.h"
+#include "shaveUtil.h"
+#include "shaveStyleCmd.h"
+
+#ifdef OSMac_
+#include "shaveMacCarbon.h"
+#endif
+
+#include <vector>
+
+//
+// Qt Mouse Watcher -- vlad|17Mar2010
+//
+static bool toolActive = false;
+//static bool toolJustMoved = false; //gah, need to set it per node
+static bool gMouseDown = false;
+static bool eventsHappen = false;
+std::vector<shaveHairShape*> shnodes;
+
+bool IsToolActive()
+{
+ return toolActive;
+}
+//bool IsToolJustMoved()
+//{
+// return toolJustMoved;
+//}
+bool GetEventsHappen()
+{
+ return eventsHappen;
+}
+
+void SetEventsHappen()
+{
+ eventsHappen = true;
+ return;
+}
+
+
+void ClearEvents()
+{
+ eventsHappen = false;
+}
+bool IsMouseDown()
+{
+ return gMouseDown;
+}
+
+bool shaveQtMouseWatcher::m_busy = false;
+bool shaveQtMouseWatcher::eventFilter(QObject* obj, QEvent* event)
+{
+ if (event->type() == (QEvent::Type)NotBusyEvent::aType)
+ {
+ m_busy = false;
+ event->accept();
+ }
+ if (event->type() == QEvent::MouseMove ||
+ event->type() == QEvent::TabletMove)
+ {
+ //lets try to dirty flag on any mouse move event
+
+ SetEventsHappen();
+ event->accept();
+
+ QPoint sp;
+ QPoint lp;
+ //couple words about event:
+ //LINUX: free move and move events are fired by view
+ //OSX: drag events are fired by view but free move eviets are fired by
+ //window that view, so local position (5,6) turns into (5,28)
+ //in other words in-view test I did (viewLocation == localLocation) below
+ //is not ok for OSX.
+ bool tablet = false;
+ if (event->type() == QEvent::MouseMove)
+ {
+ m_busy = false; //just an insurrance
+
+ //if(m_busy)
+ //{
+ // return false;
+ //}
+ //if(!m_busy)
+ //{
+ // m_busy = true;
+ // QEvent* bev = new NotBusyEvent();
+ // QApplication::postEvent(qApp->activeWindow(), bev);
+ //}
+
+ SetEventsHappen();
+
+ QMouseEvent* me = (QMouseEvent*)event;
+ sp = me->globalPos();
+ lp = me->pos();
+
+ //printf("--- pos %i %i\n",sp.x(),sp.y());fflush(stdout);
+ }
+ else if (event->type() == QEvent::TabletMove)
+ {
+ SetEventsHappen();
+ if(m_busy)
+ {
+ return false;
+ }
+ if(!m_busy)
+ {
+ m_busy = true;
+ QEvent* bev = new NotBusyEvent();
+ QApplication::postEvent(qApp->activeWindow(), bev);
+ }
+
+ tablet = true;
+
+ QTabletEvent* te = (QTabletEvent*)event;
+ sp = te->globalPos();
+ lp = te->pos();
+
+ //there is pretty consistent
+ //printf("get pos %i %i\n",lp.x(),lp.y());fflush(stdout);
+ //printf("get pos %i %i\n",sp.x(),sp.y());fflush(stdout);
+ }
+ //char buf[200];
+ //sprintf(buf,"lp: %i %i",lp.x(),lp.y());
+ //MGlobal::displayInfo(buf);
+
+ bool inview = false;
+ //shave tracks only in active view, so no need to iterate all
+ //unsigned int nv = M3dView::numberOf3dViews();
+ //for(unsigned int i = 0; i < nv; i++)
+ {
+ //M3dView theview;
+ //MStatus stat = M3dView::get3dView(i,theview);
+
+ MStatus stat;
+ M3dView theview = M3dView::active3dView(&stat);
+ if(stat == MStatus::kSuccess)
+ {
+ //unsigned int vw = theview.portWidth();
+ //unsigned int vh = theview.portHeight();
+ //if(vw == 0 || vh == 0)
+ // continue;
+
+ QWidget* qw = theview.widget(&stat);
+ if(stat == MStatus::kSuccess && qw != NULL)
+ {
+ QPoint wp = qw->mapFromGlobal(sp);
+
+ //char buf[200];
+ //sprintf(buf,"wp%i: %i %i",i,wp.x(),wp.y());
+ //MGlobal::displayInfo(buf);
+
+ // not good for OSX, see note above
+ // additional tests needed
+ //if(lp == wp)
+ //{
+ // inview = true;
+ // //MGlobal::displayInfo(MString("view:") + i);
+ // m_ctx->pointerMoved(theview.window(),lp.x(),lp.y());
+ // break;
+ //}
+ if(wp.x() >= 0 && wp.y() >= 0 &&
+ wp.x() < theview.portWidth() &&
+ wp.y() < theview.portHeight())
+ {
+ inview = true;
+ m_ctx->pointerMoved(theview.window(),wp.x(),wp.y());
+ //printf("set pos %i %i\n",wp.x(),wp.y());fflush(stdout);
+ //break;
+ }
+ }
+ }
+ }
+ if(!inview)
+ {
+ m_ctx->leaveWindow(NULL,0,0); //x,y are not used in leaveWindow
+ }
+ }
+
+ if(incrementaldraw )
+ return false;
+
+ return QObject::eventFilter(obj, event);
+}
+
+static shaveQtMouseWatcher* mv = NULL;
+
+static globalQtViewport20Tracker* vp20t = NULL;
+void CheckGlobalQtViewport20Tracker()
+{
+ if(vp20t == NULL)
+ {
+ vp20t = new globalQtViewport20Tracker();
+ qApp->installEventFilter(vp20t);
+ }
+
+}
+bool globalQtViewport20Tracker::eventFilter(QObject* obj, QEvent* event)
+{
+ if (event->type() == QEvent::MouseMove ||
+ event->type() == QEvent::TabletMove )
+ {
+ //MGlobal::displayInfo("mouse moved");
+ M3dView view = M3dView::active3dView();
+
+ MDagPath camPath;
+ MStatus stat = MStatus::kSuccess;
+ stat = view.getCamera(camPath);
+ if(stat == MStatus::kSuccess)
+ {
+ MFnCamera camFn(camPath);
+ MMatrix tm = camPath.inclusiveMatrixInverse();
+ if(worldToCam != tm)
+ {
+
+ MDagPathArray paths;
+ shaveUtil::getShaveNodes(paths); //it should not trigger ::compute
+ for (unsigned int i = 0; i < paths.length(); i++)
+ {
+ //MGlobal::displayInfo("vp2.0 dirtied");
+ MHWRender::MRenderer::setGeometryDrawDirty(paths[i].node());
+ }
+ worldToCam = tm;
+ }
+ }
+#ifdef OSMac_
+ //need to diryt vp2 on OSX ( for the case of growth obj moved)
+ MDagPathArray paths;
+ shaveUtil::getShaveNodes(paths); //it should not trigger ::compute
+ for (unsigned int i = 0; i < paths.length(); i++)
+ {
+ //MGlobal::displayInfo("vp2.0 dirtied");
+ MHWRender::MRenderer::setGeometryDrawDirty(paths[i].node());
+ }
+#endif
+
+ }
+ if (event->type() == QEvent::MouseButtonRelease)
+ {
+ MDagPathArray paths;
+ shaveUtil::getShaveNodes(paths); //it should not trigger ::compute
+ for (unsigned int i = 0; i < paths.length(); i++)
+ MHWRender::MRenderer::setGeometryDrawDirty(paths[i].node());
+ }
+
+ return QObject::eventFilter(obj, event);
+}
+
+#ifdef GLOBAL_FALLBACK
+
+//static LARGE_INTEGER last={ static_cast<LONGLONG>(0) };
+//LARGE_INTEGER GetLastMoveTime() {return last;}
+
+static qint64 last;
+qint64 GetLastMoveTime() {return last;}
+void SetLastMoveTime(qint64 t) {last = t;}
+
+bool globalQtMouseWatcher::eventFilter(QObject* obj, QEvent* event)
+{
+ //printf("event type %i\n",event->type());fflush(stdout);
+if (event->type() == QEvent::Wheel )eventsHappen = true;
+
+ if (event->type() == QEvent::MouseMove ||
+ event->type() == QEvent::TabletMove )
+// event->type() == QEvent::Wheel )
+ {
+// printf("mouse move (pressed: %s)\n",gMouseDown?"yes":"no");
+
+ if(gMouseDown)
+ {
+ if(!eventsHappen)
+ {
+// printf("flush 01\n");fflush(stdout);
+// qApp->flush();
+ }
+ eventsHappen = true;
+ }
+
+
+ if(!incrementaldraw )
+ event->accept();
+
+ shaveGlobals::getGlobals();
+
+ if((toolActive && !gMouseDown && doFallbackGlob)||(event->type() == QEvent::Wheel && doFallbackGlob ))
+ {
+ eventsHappen = true;
+ //gah, hungs. probably need to make the list of nodes in tool init
+
+ // MFnDagNode nodeFn;
+ // MDagPathArray paths;
+ // shaveUtil::getShaveNodes(paths); //it should not trigger ::compute
+ // for (unsigned int i = 0; i < paths.length(); i++)
+ // {
+ // nodeFn.setObject(paths[i].node());
+
+ // if (nodeFn.typeId() == shaveHairShape::id)
+ // {
+ // shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode();
+ // shape->dirties.BRUSH_JUST_MOVED = 1;
+ // }
+ // }
+ //QueryPerformanceCounter(&last);
+ if(!incrementaldraw )
+ last = GetQTimer().elapsed();
+
+ if(!incrementaldraw )
+ for(unsigned int i = 0; i < shnodes.size(); i++)
+ {
+ if(shnodes[i]->dirties.BRUSH_JUST_MOVED == 0)
+ {
+ shnodes[i]->dirtyDisplay(); //its idiocy
+ shnodes[i]->dirties.BRUSH_JUST_MOVED = 1;
+ }
+ }
+ //if(!incrementaldraw )
+ //if(shaveStyleCmd::redrawOnIdlDone)
+ //{
+ // printf("redrawOnIdle fired\n");fflush(stdout);
+
+ // shaveStyleCmd::redrawOnIdlDone = false;
+ // MGlobal::executeCommandOnIdle("shaveStyle -redrawOnIdle");
+ //}
+// MHWRender::MRenderer::setGeometryDrawDirty(paths[i].node());
+ }
+ qint64 t = GetQTimer().elapsed();
+ SetLastMoveTime(t);
+ ////////////////////////
+ if(incrementaldraw )
+ {
+
+ if(shaveStyleCmd::redrawOnIdlDone)
+ {
+ //printf("redrawOnIdle fired\n");fflush(stdout);
+
+ shaveStyleCmd::redrawOnIdlDone = false;
+ MGlobal::executeCommandOnIdle("shaveStyle -redrawOnIdle");
+
+ //nope. does not make better on tex assigment
+ //MGlobal::executeCommandOnIdle("shaveStyle -fullRedrawOnIdle");
+ }
+ }
+ }
+ if (event->type() == QEvent::MouseButtonPress ||
+ event->type() == QEvent::TabletPress||
+ event->type() == /*QEvent::KeyPress*/ 6)
+ {
+ if(!incrementaldraw )
+ event->accept();
+
+
+// printf("flush 00\n");fflush(stdout);
+// qApp->flush();
+
+ gMouseDown = true;
+ eventsHappen = true;
+
+ MFnDagNode nodeFn;
+ MDagPathArray paths;
+ shaveUtil::getShaveNodes(paths);
+ if(!incrementaldraw )
+ for (unsigned int i = 0; i < paths.length(); i++)
+ {
+ nodeFn.setObject(paths[i].node());
+
+ if (nodeFn.typeId() == shaveHairShape::id)
+ {
+ ////////////////////////
+ //printf("MOUSE DOWN\n"); fflush(stdout);
+ ////////////////////////
+ shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode();
+ shape->dirties.GLOBAL_MOUSE_DOWN = 1;
+
+ MHWRender::MRenderer::setGeometryDrawDirty(paths[i].node());
+ }
+ }
+
+ ////////////////////////////////////////
+ //MSelectionList selection;
+ //MGlobal::getActiveSelectionList(selection);
+
+ //MObject comp;
+ //bool componentMode = (MGlobal::selectionMode()== MGlobal::kSelectComponentMode);
+ //unsigned i;
+ //MDagPath path;
+ //MFnDagNode nodeFn;
+
+ //for (i = 0; i < selection.length(); i++)
+ //{
+ // if (selection.getDagPath(i, path, comp) && path.isValid())
+ // {
+ // path.extendToShape();
+
+ // //
+ // // If we're in component selection mode then ignore any
+ // // nodes which don't have components selected.
+ // //
+ // if (!componentMode || !comp.isNull())
+ // {
+ // nodeFn.setObject(path.node());
+
+ // if (nodeFn.typeId() == shaveHairShape::id)
+ // {
+ // shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode();
+ // shape->dirties.GLOBAL_MOUSE_DOWN = 1;
+ // break;
+ // }
+ // }
+ // }
+ //}
+ ////////////////////////////////////////
+ }
+ if (event->type() == QEvent::MouseButtonRelease ||
+ event->type() == QEvent::TabletRelease||
+ event->type() == /*QEvent::KeyRelease*/ 7)
+ {
+ if(!incrementaldraw )
+ event->accept();
+
+ gMouseDown = false;
+ //eventsHappen = true;
+
+ //shaveGlobals::Globals globals;
+ //shaveGlobals::getGlobals(globals);
+
+ ////////////////////////////////////////
+ //MFnDagNode nodeFn;
+ //MDagPathArray paths;
+ //shaveUtil::getShaveNodes(paths);
+ //for (unsigned int i = 0; i < paths.length(); i++)
+ //{
+ // nodeFn.setObject(paths[i].node());
+
+ // if (nodeFn.typeId() == shaveHairShape::id)
+ // {
+
+ // ////////////////////////
+ // //printf("MOUSE UP\n"); fflush(stdout);
+ // ////////////////////////
+
+ // shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode();
+ // shape->dirties.GLOBAL_MOUSE_DOWN = 0;
+ // shape->dirties.DIRTY_TEXTURE = 1;
+ // shape->dirties.DIRTY_TEXTURE_JOE = 1;
+ // shape->dirtyDisplay();
+
+ // //how to avoid it?
+ // float trigger;
+ // MPlug triggerPlug = nodeFn.findPlug("trigger");
+ // triggerPlug.getValue(trigger);
+ // triggerPlug.setValue(trigger+1.0f);
+ // }
+ //}
+ ///////////////////aaaaaahh///////////////////
+ //MFnDagNode nodeFn;
+ //MDagPathArray paths;
+ //shaveUtil::getShaveNodes(paths);
+ //for (unsigned int i = 0; i < paths.length(); i++)
+ //{
+ // nodeFn.setObject(paths[i].node());
+ // if (nodeFn.typeId() == shaveHairShape::id)
+ // {
+ // shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode();
+ // shape->updateTexLookups();
+ // }
+ //}
+ ////////////////////////////////////////
+ //if(shaveStyleCmd::redrawOnIdlDone)
+ eventsHappen = true;
+ //if(!incrementaldraw )
+ if(doFallbackGlob)
+ {
+ //printf("fullRedrawOnIdle fired\n");fflush(stdout);
+
+ shaveStyleCmd::redrawOnIdlDone = false;
+ MGlobal::executeCommandOnIdle("shaveStyle -fullRedrawOnIdle");
+ }
+
+ ////////////////////////////////////
+ }
+ if(incrementaldraw )
+ return false;
+
+ return QObject::eventFilter(obj, event);
+}
+
+static globalQtMouseWatcher* gmv = NULL;
+
+void CheckAndSetGlobalQtWatcher()
+{
+ if(gmv == NULL)
+ {
+ gmv = new globalQtMouseWatcher();
+ qApp->installEventFilter(gmv);
+ }
+ StartQTimer();
+}
+
+static QElapsedTimer qTimer;
+static bool qTimerStarted = false;
+void StartQTimer()
+{
+ if(!qTimerStarted)
+ {
+ qTimer.start();
+ qTimerStarted = true;
+ }
+}
+QElapsedTimer& GetQTimer()
+{
+ return qTimer;
+}
+#endif
+
+
+#ifdef _WIN32
+MCursor shaveCursorCtx::mDoubleArrowCursor("shaveDoubleArrowCursor.cur");
+#else
+# ifdef OSMac_
+# include "icons/shaveDoubleArrowCursorOSX.xbm"
+# else
+# include "icons/shaveDoubleArrowCursor.xbm"
+# endif
+
+MCursor shaveCursorCtx::mDoubleArrowCursor(
+ shaveDoubleArrowCursor_width,
+ shaveDoubleArrowCursor_height,
+ 8,
+ 4,
+ (unsigned char*)shaveDoubleArrowCursor_bits,
+ (unsigned char*)shaveDoubleArrowCursor_bits
+ );
+#endif
+
+shaveCursorCtx* shaveCursorCtx::mActiveCtx = 0;
+
+
+shaveCursorCtx::shaveCursorCtx()
+: mActiveViewChangedId(0)
+, mBrushSize(0.2f)
+, mBrushStren(1.0f)
+, mDoingStroke(false)
+, mEventWindow(0)
+, mFalloffEnabled(true)
+, mFastBrush(true)
+, mIsActive(false)
+, mIsResizing(false)
+, mManip(0)
+, mResizeKeyPressed(false)
+#ifdef _WIN32
+, mTrackingLeaveWindow(false)
+#endif
+{
+}
+
+
+shaveCursorCtx::~shaveCursorCtx()
+{
+}
+
+
+MStatus shaveCursorCtx::doDrag(MEvent& event)
+{
+#ifdef DO_PROFILE
+ if(!Profile::GetDiagFile())
+ Profile::ProfileStart(NULL);
+ Profile::ProfileDump("shaveCursorCtx::doDrag", NULL);
+#endif
+
+ MStatus st = MS::kSuccess;
+
+ if (!mManip) return st;
+
+ short x;
+ short y;
+
+ event.getPosition(x, y);
+
+ if ((x != mStrokeLastX) || (y != mStrokeLastY))
+ {
+ mStrokeLastX = x;
+ mStrokeLastY = y;
+#ifdef DO_PROFILE
+ Profile::ProfileDump("doDrag 01", NULL);
+#endif
+ M3dView view = M3dView::active3dView();
+
+ if (mIsResizing || !mDoingStroke)
+ {
+ if (!mFastBrush)
+ {
+ // Maya doesn't do refreshes during a drag so we must
+ // force a refresh so that the brush is redrawn at its new
+ // position. (That's not a problem for the fast brush
+ // because it draws itself independent of Maya's refresh.)
+ view.refresh(false, true);
+ }
+
+ return st;
+ }
+#ifdef DO_PROFILE
+ Profile::ProfileDump("doDrag 02", NULL);
+#endif
+ MPoint worldMouse = getWorldPt(view, x, y);
+
+ float maxX = (float)(view.portWidth() - 1);
+ float maxY = (float)(view.portHeight() - 1);
+ VERT screenDelta;
+ VERT screenPos;
+ VERT worldDelta;
+ VERT worldPos;
+
+ screenDelta.x = (float)(x - mStrokeStartX) / maxX;
+ screenDelta.y = (float)(y - mStrokeStartY) / maxY;
+ screenDelta.z = 0.0f;
+
+ screenPos.x = (float)x / maxX;
+ screenPos.y = (float)y / maxY;
+ screenPos.z = 0.0f;
+
+ worldDelta.x = (float)worldMouse.x - mStrokeStartWorld.x;
+ worldDelta.y = (float)worldMouse.y - mStrokeStartWorld.y;
+ worldDelta.z = (float)worldMouse.z - mStrokeStartWorld.z;
+
+ worldPos.x = (float)worldMouse.x;
+ worldPos.y = (float)worldMouse.y;
+ worldPos.z = (float)worldMouse.z;
+
+ //////////////////////
+ screenDelta.x *= getBrushStren();
+ screenDelta.y *= getBrushStren();
+
+ screenPos.x = mStrokeStartX / maxX + screenDelta.x;
+ screenPos.y = mStrokeStartY / maxY + screenDelta.y;
+
+ worldDelta.x *= getBrushStren();
+ worldDelta.y *= getBrushStren();
+ worldDelta.z *= getBrushStren();
+
+ worldPos.x = mStrokeStartWorld.x + worldDelta.x;
+ worldPos.y = mStrokeStartWorld.y + worldDelta.y;
+ worldPos.z = mStrokeStartWorld.z + worldDelta.z;
+ //////////////////////
+ st = strokeDrag(screenPos, worldPos, screenDelta, worldDelta);
+ if (st)
+ {
+ //
+ // Let the hair node know that there have been changes.
+ //
+ mTargetShape->guidesChanged();
+ //
+ // The call above will have let Maya know that a redraw is
+ // required, but Maya won't do one while we're dragging, so we have
+ // to force it.
+ //
+ view.refresh(false, true);
+ //what it try do not force
+ //view.refresh(false, false); //nothing changes
+ }
+ else
+ cleanupStroke();
+
+ mTargetShape->dirtyDisplay();
+
+#if 0
+ //////////////////////
+ //mTargetShape->applyEdits(false);
+ //////////////////////
+ if(mTargetShape)
+ {
+ //SHAVEsculpt_finish();
+ mTargetShape->updateParams();
+ mTargetShape->applyEdits(true);
+ //mTargetShape->getHairNode();
+
+ MSelectionList selection;
+ MGlobal::getActiveSelectionList(selection);
+
+ MObject comp;
+ bool componentMode = (MGlobal::selectionMode()
+ == MGlobal::kSelectComponentMode);
+ unsigned i;
+ MDagPath path;
+ MFnDagNode nodeFn;
+
+ for (i = 0; i < selection.length(); i++)
+ {
+ if (selection.getDagPath(i, path, comp) && path.isValid())
+ {
+ path.extendToShape();
+
+ //
+ // If we're in component selection mode then ignore any
+ // nodes which don't have components selected.
+ //
+ if (!componentMode || !comp.isNull())
+ {
+ nodeFn.setObject(path.node());
+
+
+ float trigger;
+ MPlug triggerPlug = nodeFn.findPlug("trigger");
+ triggerPlug.getValue(trigger);
+ triggerPlug.setValue(trigger+1.0f);
+ }
+ }
+ }
+
+ M3dView::active3dView().refresh();
+ }
+ //////////////////////
+#endif
+ }
+#ifdef DO_PROFILE
+ Profile::ProfileDump("shaveCursorCtx::doDrag - done", NULL);
+#endif
+ return st;
+}
+
+
+MStatus shaveCursorCtx::doPress(MEvent& event)
+{
+ MStatus st = MS::kSuccess;
+
+ mDoingStroke = false;
+
+ if (!mManip) return MS::kSuccess;
+
+ // Save the start of the stroke.
+ //
+ event.getPosition(mStrokeStartX, mStrokeStartY);
+
+ mStrokeLastX = mStrokeStartX;
+ mStrokeLastY = mStrokeStartY;
+
+ //
+ // If the resizing hotkey is pressed, then go into resize mode.
+ //
+ if (mResizeKeyPressed)
+ {
+ mStrokeStartBrushSize = mBrushSize;
+ mIsResizing = true;
+ mDoingStroke = true;
+ }
+ else
+ {
+ //
+ // The first hair node on the selection list is the one we'll be
+ // modifying. There shouldn't be any others but if there are we'll
+ // ignore them because Shave can only handle editing on one hair
+ // node at a time.
+ //
+ MSelectionList selection;
+ MGlobal::getActiveSelectionList(selection);
+
+ MObject comp;
+ bool componentMode = (MGlobal::selectionMode()
+ == MGlobal::kSelectComponentMode);
+ unsigned i;
+ MDagPath path;
+ MFnDagNode nodeFn;
+
+ for (i = 0; i < selection.length(); i++)
+ {
+ if (selection.getDagPath(i, path, comp) && path.isValid())
+ {
+ path.extendToShape();
+
+ //
+ // If we're in component selection mode then ignore any
+ // nodes which don't have components selected.
+ //
+ if (!componentMode || !comp.isNull())
+ {
+ nodeFn.setObject(path.node());
+
+ if (nodeFn.typeId() == shaveHairShape::id)
+ {
+ mTargetShape = (shaveHairShape*)nodeFn.userNode();
+ break;
+ }
+ }
+ }
+ }
+
+ if (i == selection.length()) return MS::kSuccess;
+
+ int rx;
+ int ry;
+
+ mManip->getBrushRadii(rx, ry);
+
+ float aspect = (float)rx / (float)ry;
+ float rSquared = (float)(rx * rx);
+
+ M3dView view = M3dView::active3dView();
+
+ //
+ // Processing the selection list is slow. Let's get the
+ // already-processed selections from the hair node instead.
+ //
+ mTargetShape->makeCurrent(); //maybe better to place it in tool init?
+
+ const shaveHairShape::Guides& cachedGuides = mTargetShape->getGuides().guides;
+
+ SOFTGUIDE guide;
+ int g;
+ int v;
+ MPoint centroid;
+ unsigned numVerts = 0;
+
+ for (g = 0; SHAVEfetch_guide(g, &guide) != -1; g++)
+ {
+ if (!guide.hidden)
+ {
+ float maxWeight = -1.0f;
+
+ //
+ // If we're not in component mode, then we treat every vert as
+ // being selected.
+ //
+ for (v = 1; v < SHAVE_VERTS_PER_GUIDE; v++)
+ {
+ if (!componentMode || (cachedGuides[g].select & (1 << v)))
+ {
+ //
+ // Get the position of the vertex in window coords.
+ //
+ MPoint p(guide.guide[v].x, guide.guide[v].y, guide.guide[v].z);
+ short px;
+ short py;
+
+ view.worldToView(p, px, py);
+
+ //
+ // How far is this vertex from the center of the brush?
+ //
+ float dx = (float)(px - mStrokeStartX);
+ float dy = aspect * (float)(py - mStrokeStartY);
+ float dSquared = dx * dx + dy * dy;
+
+ if (dSquared >= rSquared)
+ guide.select[v] = 0;
+ else
+ {
+ guide.select[v] = 1;
+
+ if (mFalloffEnabled)
+ {
+ guide.weight[v] = 1.0f - dSquared / rSquared;
+
+ if (guide.weight[v] > maxWeight)
+ maxWeight = guide.weight[v];
+ }
+ else
+ {
+ guide.weight[v] = 1.0f;
+ maxWeight = 1.0f;
+ }
+
+ centroid.x += p.x;
+ centroid.y += p.y;
+ centroid.z += p.z;
+ numVerts++;
+ }
+ }
+ else
+ guide.select[v] = 0;
+ }
+
+ //
+ // If any verts on the guide were selected then select the root
+ // vert as well and give it a weight identical to the max
+ // assigned to any of the other verts.
+ //
+ if (maxWeight >= 0.0f)
+ {
+ guide.select[0] = 1;
+ guide.weight[0] = maxWeight;
+ }
+ else
+ guide.select[0] = 0;
+
+ SHAVEput_guideNOCALC(g, &guide);
+ }
+ }
+
+ if (numVerts > 0)
+ {
+ centroid = centroid / (double)numVerts;
+
+ //
+ // How far along the eye vector, between the near and far clipping
+ // planes, does the centroid lie?
+ //
+ short centroidX;
+ short centroidY;
+ MPoint farClip;
+ MPoint nearClip;
+
+ view.worldToView(centroid, centroidX, centroidY);
+ view.viewToWorld(centroidX, centroidY, nearClip, farClip);
+
+ double totalDist = (farClip - nearClip).length();
+
+ //
+ // We should never get a totalDist of zero, but let's be safe.
+ //
+ if (totalDist > 0.0)
+ {
+ double centroidDist = (centroid - nearClip).length();
+
+ mCentroidFraction = centroidDist / totalDist;
+
+ MPoint worldMouse = getWorldPt(
+ view, mStrokeStartX, mStrokeStartY
+ );
+
+ float maxX = (float)(view.portWidth() - 1);
+ float maxY = (float)(view.portHeight() - 1);
+ VERT screenPos;
+ VERT worldPos;
+
+ screenPos.x = (float)mStrokeStartX / maxX;
+ screenPos.y = (float)mStrokeStartY / maxY;
+ screenPos.z = 0.0f;
+
+ worldPos.x = (float)worldMouse.x;
+ worldPos.y = (float)worldMouse.y;
+ worldPos.z = (float)worldMouse.z;
+
+ mStrokeStartWorld = worldPos;
+
+ //
+ // Get the camera's position and viewing vector.
+ //
+ MDagPath cameraPath;
+ view.getCamera(cameraPath);
+
+ MFnCamera cameraFn(cameraPath);
+ MPoint eyePoint = cameraFn.eyePoint(MSpace::kWorld);
+ MVector upDir = cameraFn.upDirection(MSpace::kWorld);
+ MVector viewDir = cameraFn.viewDirection(MSpace::kWorld);
+
+ VERT eyePointAsVERT;
+ eyePointAsVERT.x = (float)eyePoint.x;
+ eyePointAsVERT.y = (float)eyePoint.y;
+ eyePointAsVERT.z = (float)eyePoint.z;
+
+ VERT viewDirAsVERT;
+ viewDirAsVERT.x = (float)viewDir.x;
+ viewDirAsVERT.y = (float)viewDir.y;
+ viewDirAsVERT.z = (float)viewDir.z;
+
+ VERT upDirAsVERT;
+ upDirAsVERT.x = (float)upDir.x;
+ upDirAsVERT.y = (float)upDir.y;
+ upDirAsVERT.z = (float)upDir.z;
+
+ st = strokeBegin(
+ eyePointAsVERT,
+ viewDirAsVERT,
+ upDirAsVERT,
+ screenPos,
+ worldPos
+ );
+
+ if (st)
+ {
+ mDoingStroke = true;
+
+ //
+ // To speed up interaction, we hide a lot of
+ // unnecessary stuff during the stroke, so that redraws
+ // are fast.
+ //
+ //hideHair(); not higging, but decreasing display haircount
+
+ if(mTargetShape)
+ {
+ //mTargetShape->setBrushDown(true); //currently used for higing hair in vp2.0
+ mTargetShape->dirties.BRUSH_MOUSE_DOWN = 1;
+ M3dView::active3dView().refresh(false, true);
+ }
+ }
+ else
+ {
+ //
+ // If beginStroke() returns kEndOfFile it means that
+ // the method succeeded, but the rest of the stroke
+ // is to be ignored. Typically used for instantaneous
+ // actions which occur on mouse-down.
+ //
+ if (st == MS::kEndOfFile)
+ {
+ cleanupStroke();
+ st = MS::kSuccess;
+ }
+ }
+ }
+ }
+ }
+
+
+ return st;
+}
+
+
+MStatus shaveCursorCtx::doRelease(MEvent& event)
+{
+ MStatus st;
+
+ if (!mManip || !mDoingStroke) return MS::kSuccess;
+
+ //
+ // It may be possible for the release to occur at a different position
+ // from the last drag, so let's do a drag update first.
+ //
+ st = doDrag(event);
+
+ if (mIsResizing)
+ {
+ mIsResizing = false;
+
+ //
+ // If the resizing hotkey is no longer pressed, reset the cursor.
+ //
+ if (!mResizeKeyPressed) setCursor(MCursor::defaultCursor);
+
+ //
+ // Update the property sheet to reflect the new brush size.
+ //
+ MGlobal::executeCommand("shaveCursorCtx_updateCommonPropertySheet");
+ }
+ else
+ {
+ if (st) strokeEnd();
+ eventsHappen = false;
+ cleanupStroke();
+ }
+
+ mDoingStroke = false;
+ // come back
+
+ if(mTargetShape)
+ {
+ //mTargetShape->setBrushDown(false); //in vp2.0
+ mTargetShape->dirties.BRUSH_MOUSE_DOWN = 0;
+ M3dView::active3dView().refresh();
+ }
+ return st;
+}
+
+
+void shaveCursorCtx::resizeKeyPressed(bool isPressed)
+{
+ if (mResizeKeyPressed != isPressed)
+ {
+ mResizeKeyPressed = isPressed;
+
+ //
+ // If we're not currently doing a stroke, then set the cursor to
+ // the double-headed arrow when the key pressed, and back to normal
+ // when it is released.
+ //
+ if (!mDoingStroke)
+ {
+ if (isPressed)
+ setCursor(mDoubleArrowCursor);
+ else
+ setCursor(MCursor::defaultCursor);
+ }
+ }
+}
+
+
+void shaveCursorCtx::setBrushSize(float size)
+{
+ if (size != mBrushSize)
+ {
+ mBrushSize = size;
+
+ if (mManip)
+ {
+ mManip->setBrushSize(mBrushSize);
+
+ if (mIsActive) mManip->redrawBrush();
+ }
+ }
+}
+
+
+void shaveCursorCtx::toolOffCleanup()
+{
+ //
+ // Let the hair nodes know that the brush is no longer active.
+ //
+ toolActive = false;
+
+ shaveHairUI::setBrushActive(false);
+ //for vp2.0
+ if (mTargetShape != NULL) mTargetShape->setBrushActive(false);
+
+
+ mIsActive = false;
+
+ MGlobal::executeCommand("shaveBrush_setHotkeys off");
+ uncatchActiveViewEvents();
+ MMessage::removeCallback(mActiveViewChangedId);
+
+ if (mManip)
+ {
+ mManip->setActive(false);
+ mManip = NULL;
+ }
+
+ deleteManipulators();
+ mActiveCtx = 0;
+
+ if (!mFastBrush)
+ {
+ MSelectionList list;
+ MObject dummy;
+ list.add("shaveBrushProxy");
+
+ if (list.length() > 0)
+ {
+ list.getDependNode(0, dummy);
+
+ MDagModifier dagMod;
+ dagMod.deleteNode(dummy);
+ dagMod.doIt();
+ }
+ }
+}
+
+
+void shaveCursorCtx::toolOnSetup(MEvent& event)
+{
+ ////////////////////////////////////
+ toolActive = true;
+#ifdef GLOBAL_FALLBACK
+ shaveStyleCmd::redrawOnIdlDone = true;
+#endif
+ MFnDagNode nodeFn;
+ MDagPathArray paths;
+ shnodes.clear();
+ eventsHappen = true;
+ shaveUtil::getShaveNodes(paths); //it should not trigger ::compute
+ for (unsigned int i = 0; i < paths.length(); i++)
+ {
+ nodeFn.setObject(paths[i].node());
+
+ if (nodeFn.typeId() == shaveHairShape::id)
+ {
+ shaveHairShape* shape = (shaveHairShape*)nodeFn.userNode();
+ shape->dirtyDisplay();
+ shnodes.push_back(shape);
+ }
+ }
+ //////////////////////////////////
+
+ shaveGlobals::Globals g;
+ shaveGlobals::getGlobals(g);
+
+ mFastBrush = g.fastBrush;
+
+ MObject manipObj;
+
+ mActiveCtx = this;
+
+ mManip = (shaveBrushManip*)shaveBrushManip::newManipulator(
+ shaveBrushManip::nodeTypeName,
+ manipObj
+ );
+
+ if (mManip)
+ {
+ mManip->thisObj = manipObj;
+ mManip->setFastBrush(mFastBrush);
+
+ //
+ // %%% We should really find the current pointer position relative
+ // the active view and pass that to the manip as well then
+ // call setActive(). For now we have a kludge that the first
+ // call to setBrushPos() will do the setActive() call for us.
+ //
+ mManip->setBrushSize(mBrushSize);
+ addManipulator(manipObj);
+
+ if (!mFastBrush)
+ {
+ // Create a dummy transform which we can connect the manip to and
+ // move around in response to mouse movements.
+ MStatus st;
+ MDagModifier dagMod;
+ MObject dummy;
+
+ dummy = dagMod.createNode("transform", MObject::kNullObj, &st);
+ if (!st)
+ {
+ MGlobal::displayError(
+ MString("Shave: cannot create proxy transform for brush: ")
+ + st.errorString()
+ );
+ }
+ else
+ {
+ dagMod.renameNode(dummy, "shaveBrushProxy");
+
+ st = dagMod.doIt();
+
+ if (!st)
+ {
+ MGlobal::displayError(
+ MString("Shave: cannot commit proxy transform for brush: ")
+ + st.errorString()
+ );
+ }
+ else
+ {
+ MSelectionList list;
+ list.add("shaveBrushProxy");
+ list.getDagPath(0, mProxyPath);
+ mManip->connectToDependNode(mProxyPath.node());
+ }
+ }
+ }
+ }
+
+ //
+ // Prior to Maya 7.0 there was no ModelPanelSetFocus event, so instead
+ // we watch for whenever the busy state goes false since that happens
+ // whenever the user pops between single-view and four-view.
+ //
+ MString eventName = "ModelPanelSetFocus";
+
+ mActiveViewChangedId = MEventMessage::addEventCallback(
+ eventName, activeViewChanged, this
+ );
+
+ catchActiveViewEvents();
+ MGlobal::executeCommand("shave_prepareForBrushing");
+ MGlobal::executeCommand("shaveBrush_setHotkeys on");
+
+ mIsActive = true;
+
+ //
+ // Let the hair nodes know that a brush is active.
+ //
+ shaveHairUI::setBrushActive(true);
+ //for vp2.0
+ mTargetShape = NULL;
+ {
+ MSelectionList selection;
+ MGlobal::getActiveSelectionList(selection);
+
+ MObject comp;
+ bool componentMode = (MGlobal::selectionMode()
+ == MGlobal::kSelectComponentMode);
+ unsigned i;
+ MDagPath path;
+ MFnDagNode nodeFn;
+
+ for (i = 0; i < selection.length(); i++)
+ {
+ if (selection.getDagPath(i, path, comp) && path.isValid())
+ {
+ path.extendToShape();
+
+ //
+ // If we're in component selection mode then ignore any
+ // nodes which don't have components selected.
+ //
+ if (!componentMode || !comp.isNull())
+ {
+ nodeFn.setObject(path.node());
+
+ if (nodeFn.typeId() == shaveHairShape::id)
+ {
+ mTargetShape = (shaveHairShape*)nodeFn.userNode();
+ break;
+ }
+ }
+ }
+ }
+ }
+ if(mTargetShape)
+ {
+ mTargetShape->setBrushActive(true);
+ //mTargetShape->makeCurrent(); //here to avoid delays on mouse press
+ }
+}
+
+
+//----------------------------------------------------------------
+//
+// Internal Methods
+//
+//----------------------------------------------------------------
+
+void shaveCursorCtx::activeViewChanged(void* clientData)
+{
+ shaveCursorCtx* brushCtx = (shaveCursorCtx*)clientData;
+
+ brushCtx->uncatchActiveViewEvents();
+ brushCtx->catchActiveViewEvents();
+}
+
+
+void shaveCursorCtx::catchActiveViewEvents()
+{
+ M3dView view = M3dView::active3dView();
+
+ if(qApp)
+ {
+ mv = new shaveQtMouseWatcher(this);
+ qApp->installEventFilter(mv);;
+ }
+ else
+ {
+ printf("shaveCursorCtx: qApp is null\n");
+ }
+}
+
+
+void shaveCursorCtx::cleanupStroke()
+{
+ //
+ // Joe says that we don't need to reset the rest pose when brushing,
+ // thus the 'false' in the call below.
+ //
+ if(mTargetShape)
+ {
+ mTargetShape->updateParams();
+ mTargetShape->applyEdits(false);
+ }
+ //unhideHair(); //we do not show/hide hair but decrese haircount
+ mDoingStroke = false;
+}
+
+
+MPoint shaveCursorCtx::getWorldPt(
+ const M3dView& view, short viewX, short viewY
+) const
+{
+ //
+ // Calculate the point along the ray passing through the screen coords
+ // which is at the same depth, relative to the clipping planes, as
+ // the centroid.
+ //
+ MPoint farClip;
+ MPoint nearClip;
+
+ view.viewToWorld(viewX, viewY, nearClip, farClip);
+
+ MVector mouseRay = farClip - nearClip;
+ double mouseDist = mouseRay.length() * mCentroidFraction;
+
+ mouseRay.normalize();
+
+ return nearClip + mouseRay * mouseDist;
+}
+
+
+void shaveCursorCtx::hideHair()
+{
+ //
+ // Completely hide all hairnodes except the target, so that they don't
+ // draw at all.
+ //
+ MDagPathArray paths;
+
+ shaveUtil::getShaveNodes(paths);
+ mHiddenHairNodes.clear();
+
+ unsigned i;
+
+ for (i = 0; i < paths.length(); i++)
+ {
+ if (paths[i].node() != mTargetShape->thisMObject())
+ {
+ MFnDagNode nodeFn(paths[i]);
+ bool isVisible;
+ MPlug plug = nodeFn.findPlug("visibility");
+
+ plug.getValue(isVisible);
+
+ if (isVisible)
+ {
+ plug.setValue(false);
+ mHiddenHairNodes.append(paths[i]);
+ }
+ }
+ }
+
+ //
+ // Turn off hair display on the target.
+ //
+#if 0
+ //
+ // %%% This code *should* work but doesn't, the reason being that
+ // that setting dspyMode dirties both triggerAttr and outputMesh.
+ // Retrieving either one of those will call computeOutputMesh()
+ // which will in turn call SHAVExform, even if the current node
+ // is already in the engine and nothing has changed.
+ //
+ // To get around it shaveHairShape will have to be more clever
+ // about determining whether it needs to do an xform. In the
+ // meantime we use the workaround in the 'else' portion of this
+ // ifdef, which uses a purpose-built method for temporarily
+ // disabling hair display.
+ //
+ short hairDisplayMode;
+ MPlug plug(mTargetShape->thisMObject(), shaveHairShape::dspyMode);
+
+ plug.getValue(hairDisplayMode);
+ mTargetDisplayMode = (shaveHairShape::HairDisplayMode)hairDisplayMode;
+
+ if (mTargetDisplayMode != shaveHairShape::kHairDisplayNone)
+ plug.setValue((short)shaveHairShape::kHairDisplayNone);
+#else
+ mTargetShape->enableHairDisplay(false);
+#endif
+}
+
+void shaveCursorCtx::leaveWindow(MNativeWindowHdl window, int x, int y)
+{
+ M3dView view = M3dView::active3dView();
+
+ if (window == NULL || // vlad|17Mar2010
+ window == view.window())
+ mManip->leaveView();
+}
+
+
+void shaveCursorCtx::motionEventHandler(shaveCursorCtx::EventData* event)
+{
+ switch (event->type)
+ {
+#if defined(_WIN32)
+ case kMouseLeftWindow:
+ mTrackingLeaveWindow = false;
+ case kMouseMoved:
+ {
+ M3dView activeView = M3dView::active3dView();
+ HWND viewWin = activeView.window();
+ HWND viewParentWin = GetParent(viewWin);
+
+ //
+ // For some reason mouse events are delivered to the parent
+ // of the view's window.
+ //
+ if (event->window == viewWin)
+ {
+ if (event->type == kMouseLeftWindow)
+ leaveWindow(viewWin, event->x, event->y);
+ else
+ {
+ //
+ // Have Windows send a WM_MOUSELEAVE message when the
+ // pointer leaves the view.
+ //
+ if (!mTrackingLeaveWindow)
+ {
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof(tme);
+ tme.dwFlags = TME_LEAVE;
+ tme.hwndTrack = viewParentWin;
+ TrackMouseEvent(&tme);
+ mTrackingLeaveWindow = true;
+ }
+
+ pointerMoved(viewWin, event->x, event->y);
+ }
+ }
+ }
+ break;
+#else
+ case kMouseLeftWindow:
+ leaveWindow(event->window, event->x, event->y);
+ break;
+
+ case kMouseMoved:
+ pointerMoved(event->window, event->x, event->y);
+ break;
+#endif
+
+ default:
+ break;
+ }
+
+ delete event;
+}
+
+void shaveCursorCtx::pointerMoved(MNativeWindowHdl window, int x, int y)
+{
+ ////////////////////////
+ //mManip->dirtyDisplay();
+ //return;
+ ////////////////////////
+ M3dView view = M3dView::active3dView();
+
+ if (window == view.window())
+ {
+ if (mIsResizing)
+ {
+ //
+ // Calculate the per-pixel scaling factor along X.
+ //
+ int rx;
+ int ry;
+
+ mManip->getBrushRadii(rx, ry);
+
+ float scalePerPixel = mBrushSize / (float)rx;
+ float minSize = scalePerPixel * 4.5f;
+
+ //
+ // Determine what the new scaling factor must be for the radius
+ // to grow in the X direction by the same number of pixels as
+ // the mouse was dragged.
+ //
+ float newSize = mStrokeStartBrushSize
+ + scalePerPixel * (float)(x - mStrokeStartX);
+
+ if (newSize < minSize)
+ newSize = minSize;
+ else if (newSize > 1.0f)
+ newSize = 1.0f;
+
+ setBrushSize(newSize);
+ }
+ else
+ {
+ mManip->setBrushPos(x, view.portHeight() - 1 - y);
+ view.refresh(false,true);
+ //view.refresh(true,true); //why do we need it here?
+ }
+ }
+
+}
+
+
+void shaveCursorCtx::uncatchActiveViewEvents()
+{
+ if(mv)
+ {
+ delete mv ;
+ mv = NULL;
+ }
+}
+
+
+void shaveCursorCtx::unhideHair()
+{
+ unsigned i;
+
+ for (i = 0; i < mHiddenHairNodes.length(); i++)
+ {
+ MFnDagNode nodeFn(mHiddenHairNodes[i]);
+ MPlug plug = nodeFn.findPlug("visibility");
+
+ plug.setValue(true);
+ }
+
+ mHiddenHairNodes.clear();
+
+ //
+ // Restore the target's hair display.
+ //
+#if 0
+ //
+ // See hideHair() for the reason for this ifdef.
+ //
+ if (mTargetDisplayMode != shaveHairShape::kHairDisplayNone)
+ {
+ MPlug plug(mTargetShape->thisMObject(), shaveHairShape::dspyMode);
+ plug.setValue((short)mTargetDisplayMode);
+ }
+#else
+ mTargetShape->enableHairDisplay(true);
+#endif
+}
+
+
+#ifdef _WIN32
+LRESULT CALLBACK shaveCursorCtx::WinEventHandler(
+ int nCode, WPARAM wParam, LPARAM lParam
+)
+{
+ shaveCursorCtx* brushCtx = getActiveCtx();
+
+ if (brushCtx && (nCode == HC_ACTION))
+ {
+ MSG* msgInfo = (MSG*)lParam;
+ int msgType = LOWORD(msgInfo->message);
+
+ switch (msgType)
+ {
+ case WM_MOUSELEAVE:
+ case WM_MOUSEMOVE:
+ {
+ EventData* event = new EventData;
+
+ if (msgType == WM_MOUSEMOVE)
+ event->type = kMouseMoved;
+ else
+ event->type = kMouseLeftWindow;
+
+ event->window = msgInfo->hwnd;
+
+ RECT rect;
+ GetWindowRect(event->window, &rect);
+
+ event->x = msgInfo->pt.x - rect.left;
+ event->y = msgInfo->pt.y - rect.top;
+
+ brushCtx->motionEventHandler(event);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return CallNextHookEx(0, nCode, wParam, lParam);
+}
+#endif
+
+
+#ifdef LINUX
+void shaveCursorCtx::XEventHandler(
+ Widget widget,
+ XtPointer clientData,
+ XEvent* event,
+ Boolean* continueDispatch)
+{
+ EventData* eventData = new EventData;
+
+ switch (event->type)
+ {
+ case LeaveNotify:
+ eventData->type = kMouseLeftWindow;
+ eventData->window = event->xcrossing.window;
+ eventData->x = event->xcrossing.x;
+ eventData->y = event->xcrossing.y;
+ break;
+
+ case MotionNotify:
+ eventData->type = kMouseMoved;
+ eventData->window = event->xmotion.window;
+ eventData->x = event->xmotion.x;
+ eventData->y = event->xmotion.y;
+ break;
+
+ default:
+ delete eventData;
+ return;
+ }
+
+ shaveCursorCtx* brushCtx = (shaveCursorCtx*)clientData;
+
+ brushCtx->motionEventHandler(eventData);
+}
+#endif