/***************************************************************************
 *   Copyright (c) 2010 Werner Mayer <wmayer[at]users.sourceforge.net>     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include "PreCompiled.h"

#ifndef _PreComp_
# include <algorithm>
# include <QMenu>
# include <QTimer>
# include <Inventor/SoPickedPoint.h>
# include <Inventor/details/SoFaceDetail.h>
# include <Inventor/details/SoPointDetail.h>
# include <Inventor/events/SoLocation2Event.h>
# include <Inventor/events/SoMouseButtonEvent.h>
# include <Inventor/nodes/SoBaseColor.h>
# include <Inventor/nodes/SoCamera.h>
# include <Inventor/nodes/SoCoordinate3.h>
# include <Inventor/nodes/SoDirectionalLight.h>
# include <Inventor/nodes/SoDrawStyle.h>
# include <Inventor/nodes/SoFaceSet.h>
# include <Inventor/nodes/SoLineSet.h>
# include <Inventor/nodes/SoMarkerSet.h>
# include <Inventor/nodes/SoPickStyle.h>
# include <Inventor/nodes/SoSeparator.h>
# include <Inventor/nodes/SoShapeHints.h>
# include <boost/bind.hpp>
#endif

#include "MeshEditor.h"
#include "SoFCMeshObject.h"
#include "SoPolygon.h"
#include <App/Document.h>
#include <Mod/Mesh/App/MeshFeature.h>
#include <Mod/Mesh/App/Core/Algorithm.h>
#include <Mod/Mesh/App/Core/Triangulation.h>
#include <Gui/Application.h>
#include <Gui/WaitCursor.h>
#include <Gui/View3DInventor.h>
#include <Gui/View3DInventorViewer.h>

using namespace MeshGui;

PROPERTY_SOURCE(MeshGui::ViewProviderFace, Gui::ViewProviderDocumentObject)

ViewProviderFace::ViewProviderFace() : mesh(0), current_index(-1)
{
    pcCoords = new SoCoordinate3();
    pcCoords->ref();
    pcCoords->point.setNum(0);
    pcFaces = new SoFaceSet;
    pcFaces->ref();
    pcMeshPick = new SoFCMeshPickNode();
    pcMeshPick->ref();
}

ViewProviderFace::~ViewProviderFace()
{
    pcCoords->unref();
    pcFaces->unref();
    pcMeshPick->unref();
}

void ViewProviderFace::attach(App::DocumentObject* obj)
{
    ViewProviderDocumentObject::attach(obj);

    pcMeshPick->mesh.setValue(static_cast<Mesh::Feature*>(obj)->Mesh.getValuePtr());

    // Draw markers
    SoGroup* markers = new SoGroup();
    SoDrawStyle* pointStyle = new SoDrawStyle();
    pointStyle->style = SoDrawStyle::POINTS;
    pointStyle->pointSize = 8.0f;
    markers->addChild(pointStyle);

    SoBaseColor * markcol = new SoBaseColor;
    markcol->rgb.setValue(1.0f, 1.0f, 0.0f);
    SoPointSet* marker = new SoPointSet();
    markers->addChild(markcol);
    markers->addChild(pcCoords);
    markers->addChild(marker);

    // Draw face
    SoGroup* faces = new SoGroup();
    SoDrawStyle* faceStyle = new SoDrawStyle();
    faceStyle->style = SoDrawStyle::FILLED;
    faces->addChild(faceStyle);

    SoShapeHints * flathints = new SoShapeHints;
    //flathints->vertexOrdering = SoShapeHints::COUNTERCLOCKWISE ;
    //flathints->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE;
    faces->addChild(flathints);

    SoBaseColor* basecol = new SoBaseColor;
    if (mesh) {
        App::Color col = mesh->ShapeColor.getValue();
        basecol->rgb.setValue(col.r, col.g, col.b);
    }
    else {
        basecol->rgb.setValue(1.0f, 0.0f, 0.0f);
    }

    faces->addChild(basecol);
    faces->addChild(pcCoords);
    faces->addChild(pcFaces);

    SoGroup* face_marker = new SoGroup();
    face_marker->addChild(faces);
    face_marker->addChild(markers);

    addDisplayMaskMode(markers, "Marker");
    addDisplayMaskMode(face_marker, "Face");
    setDisplayMode("Marker");
}

void ViewProviderFace::setDisplayMode(const char* ModeName)
{
    if (strcmp(ModeName, "Face") == 0)
        setDisplayMaskMode("Face");
    else if (strcmp(ModeName, "Marker") == 0)
        setDisplayMaskMode("Marker");
    ViewProviderDocumentObject::setDisplayMode(ModeName);
}

const char* ViewProviderFace::getDefaultDisplayMode() const
{
    return "Marker";
}

std::vector<std::string> ViewProviderFace::getDisplayModes(void) const
{
    std::vector<std::string> modes;
    modes.push_back("Marker");
    modes.push_back("Face");
    return modes;
}

SoPickedPoint* ViewProviderFace::getPickedPoint(const SbVec2s& pos, const SoQtViewer* viewer) const
{
    SoSeparator* root = new SoSeparator;
    root->ref();
    root->addChild(viewer->getHeadlight());
    root->addChild(viewer->getCamera());
    root->addChild(this->pcMeshPick);

    SoRayPickAction rp(viewer->getViewportRegion());
    rp.setPoint(pos);
    rp.apply(root);
    root->unref();

    // returns a copy of the point
    SoPickedPoint* pick = rp.getPickedPoint();
    //return (pick ? pick->copy() : 0); // needs the same instance of CRT under MS Windows
    return (pick ? new SoPickedPoint(*pick) : 0);
}

// ----------------------------------------------------------------------

/* TRANSLATOR MeshGui::MeshFaceAddition */

MeshFaceAddition::MeshFaceAddition(Gui::View3DInventor* parent)
  : QObject(parent), faceView(new MeshGui::ViewProviderFace())
{
}

MeshFaceAddition::~MeshFaceAddition()
{
    delete faceView;
}

void MeshFaceAddition::startEditing(MeshGui::ViewProviderMesh* vp)
{
    Gui::View3DInventor* view = static_cast<Gui::View3DInventor*>(parent());
    Gui::View3DInventorViewer* viewer = view->getViewer();
    viewer->setEditing(true);
    viewer->setRedirectToSceneGraph(true);

    faceView->mesh = vp;
    faceView->attach(vp->getObject());
    viewer->addViewProvider(faceView);
    //faceView->mesh->startEditing();
    viewer->addEventCallback(SoEvent::getClassTypeId(),
        MeshFaceAddition::addFacetCallback, this);
}

void MeshFaceAddition::finishEditing()
{
    Gui::View3DInventor* view = static_cast<Gui::View3DInventor*>(parent());
    Gui::View3DInventorViewer* viewer = view->getViewer();
    viewer->setEditing(false);
    viewer->setRedirectToSceneGraph(false);

    viewer->removeViewProvider(faceView);
    //faceView->mesh->finishEditing();
    viewer->removeEventCallback(SoEvent::getClassTypeId(),
        MeshFaceAddition::addFacetCallback, this);
    this->deleteLater();
}

void MeshFaceAddition::addFace()
{
    Mesh::Feature* mf = static_cast<Mesh::Feature*>(faceView->mesh->getObject());
    App::Document* doc = mf->getDocument();
    doc->openTransaction("Add triangle");
    Mesh::MeshObject* mesh = mf->Mesh.startEditing();
    MeshCore::MeshFacet f;
    f._aulPoints[0] = faceView->index[0];
    f._aulPoints[1] = faceView->index[1];
    f._aulPoints[2] = faceView->index[2];
    std::vector<MeshCore::MeshFacet> faces;
    faces.push_back(f);
    mesh->addFacets(faces);
    mf->Mesh.finishEditing();
    doc->commitTransaction();

    clearPoints();
}

void MeshFaceAddition::clearPoints()
{
    faceView->index.clear();
    faceView->current_index = -1;
    faceView->pcCoords->point.setNum(0);
    faceView->setDisplayMode("Marker");
}

void MeshFaceAddition::flipNormal()
{
    if (faceView->index.size() < 3)
        return;
    std::swap(faceView->index[0], faceView->index[1]);
    SbVec3f v1 = faceView->pcCoords->point[0];
    SbVec3f v2 = faceView->pcCoords->point[1];
    faceView->pcCoords->point.set1Value(0, v2);
    faceView->pcCoords->point.set1Value(1, v1);
}

bool MeshFaceAddition::addMarkerPoint()
{
    if (faceView->current_index < 0)
        return false;
    if (faceView->index.size() >= 3)
        return false;
    faceView->index.push_back(faceView->current_index);
    faceView->current_index = -1;
    if (faceView->index.size() == 3)
        faceView->setDisplayMode("Face");
    return true;
}

void MeshFaceAddition::showMarker(SoPickedPoint* pp)
{
    const SbVec3f& vec = pp->getPoint();
    const SoDetail* detail = pp->getDetail();
    if (detail) {
        if (detail->isOfType(SoFaceDetail::getClassTypeId())) {
            const SoFaceDetail* fd = static_cast<const SoFaceDetail*>(detail);
            Mesh::Feature* mf = static_cast<Mesh::Feature*>(faceView->mesh->getObject());
            const MeshCore::MeshFacetArray& facets = mf->Mesh.getValuePtr()->getKernel().GetFacets();
            const MeshCore::MeshPointArray& points = mf->Mesh.getValuePtr()->getKernel().GetPoints();
            // is the face index valid?
            int face_index = fd->getFaceIndex();
            if (face_index >= (int)facets.size())
                return;
            // is a border facet picked? 
            MeshCore::MeshFacet f = facets[face_index];
            if (!f.HasOpenEdge()) {
                // check if a neighbour facet is at the border
                bool ok=false;
                for (int i=0; i<3; i++) {
                    if (facets[f._aulNeighbours[i]].HasOpenEdge()) {
                        f = facets[f._aulNeighbours[i]];
                        ok = true;
                        break;
                    }
                }
                if (!ok)
                    return;
            }

            int point_index = -1;
            float distance = FLT_MAX;
            Base::Vector3f pnt;
            SbVec3f face_pnt;

            for (int i=0; i<3; i++) {
                int index = (int)f._aulPoints[i];
                if (std::find(faceView->index.begin(), faceView->index.end(), index) != faceView->index.end())
                    continue; // already inside
                if (f._aulNeighbours[i] == ULONG_MAX ||
                    f._aulNeighbours[(i+2)%3] == ULONG_MAX) {
                    pnt = points[index];
                    float len = Base::DistanceP2(pnt, Base::Vector3f(vec[0],vec[1],vec[2]));
                    if (len < distance) {
                        distance = len;
                        point_index = index;
                        face_pnt.setValue(pnt.x,pnt.y,pnt.z);
                    }
                }
            }

            if (point_index < 0)
                return; // picked point is rejected

            int num = faceView->pcCoords->point.getNum();
            if (faceView->current_index >= 0) {
                num = std::max<int>(num-1, 0);
            }
            faceView->current_index = point_index;
            faceView->pcCoords->point.set1Value(num, face_pnt);
            return;
        }
    }
}

void MeshFaceAddition::addFacetCallback(void * ud, SoEventCallback * n)
{
    MeshFaceAddition* that = reinterpret_cast<MeshFaceAddition*>(ud);
    ViewProviderFace* face =  that->faceView;
    Gui::View3DInventorViewer* view  = reinterpret_cast<Gui::View3DInventorViewer*>(n->getUserData());

    const SoEvent* ev = n->getEvent();
    if (ev->getTypeId() == SoLocation2Event::getClassTypeId()) {
        // set as handled
        n->getAction()->setHandled();
        n->setHandled();
        if (face->index.size() < 3) {
            SoPickedPoint * point = face->getPickedPoint(ev->getPosition(), view);
            if (point) {
                that->showMarker(point);
                delete point;
            }
        }
    }
    else if (ev->getTypeId() == SoMouseButtonEvent::getClassTypeId()) {
        // set as handled
        n->getAction()->setHandled();
        n->setHandled();
        const SoMouseButtonEvent * mbe = static_cast<const SoMouseButtonEvent *>(ev);
        if (mbe->getButton() == SoMouseButtonEvent::BUTTON1 && mbe->getState() == SoButtonEvent::DOWN) {
            that->addMarkerPoint();
        }
        else if (mbe->getButton() == SoMouseButtonEvent::BUTTON1 && mbe->getState() == SoButtonEvent::UP) {
            if (face->index.size() == 3) {
                QMenu menu;
                QAction* add = menu.addAction(MeshFaceAddition::tr("Add triangle"));
                QAction* swp = menu.addAction(MeshFaceAddition::tr("Flip normal"));
                QAction* clr = menu.addAction(MeshFaceAddition::tr("Clear"));
                QAction* act = menu.exec(QCursor::pos());
                if (act == add) {
                    QTimer::singleShot(300, that, SLOT(addFace()));
                }
                else if (act == swp) {
                    QTimer::singleShot(300, that, SLOT(flipNormal()));
                }
                else if (act == clr) {
                    QTimer::singleShot(300, that, SLOT(clearPoints()));
                }
            }
        }
        else if (mbe->getButton() == SoMouseButtonEvent::BUTTON2 && mbe->getState() == SoButtonEvent::UP) {
            QMenu menu;
            QAction* fin = menu.addAction(MeshFaceAddition::tr("Finish"));
            QAction* act = menu.exec(QCursor::pos());
            if (act == fin) {
                QTimer::singleShot(300, that, SLOT(finishEditing()));
            }
        }
    }
}

// ----------------------------------------------------------------------

namespace MeshGui {
    // for sorting of elements
    struct NofFacetsCompare : public std::binary_function<const std::vector<unsigned long>&, 
                                                          const std::vector<unsigned long>&, bool>
    {
        bool operator () (const std::vector<unsigned long> &rclC1, 
                          const std::vector<unsigned long> &rclC2)
        {
            return rclC1.size() < rclC2.size();
        }
    };
}

/* TRANSLATOR MeshGui::MeshFillHole */

MeshFillHole::MeshFillHole(MeshHoleFiller& hf, Gui::View3DInventor* parent)
  : QObject(parent), myMesh(0), myNumPoints(0), myHoleFiller(hf)
{
    myBoundariesRoot = new SoSeparator;
    myBoundariesRoot->ref();
    myBoundaryRoot = new SoSeparator;
    myBoundaryRoot->ref();
    myBoundariesGroup = new SoSeparator();
    myBoundariesGroup->ref();
    myBridgeRoot = new SoSeparator;
    myBridgeRoot->ref();

    SoDrawStyle* pointStyle = new SoDrawStyle();
    pointStyle->style = SoDrawStyle::POINTS;
    pointStyle->pointSize = 8.0f;
    myBridgeRoot->addChild(pointStyle);

    SoBaseColor * markcol = new SoBaseColor;
    markcol->rgb.setValue(1.0f, 1.0f, 0.0f);
    SoPointSet* marker = new SoPointSet();
    myBridgeRoot->addChild(markcol);

    myVertex = new SoCoordinate3();
    myBridgeRoot->addChild(myVertex);
    myBridgeRoot->addChild(new SoPointSet);
}

MeshFillHole::~MeshFillHole()
{
    myBoundariesRoot->unref();
    myBoundariesGroup->unref();
    myBoundaryRoot->unref();
    myBridgeRoot->unref();
}

void MeshFillHole::startEditing(MeshGui::ViewProviderMesh* vp)
{
    this->myMesh = static_cast<Mesh::Feature*>(vp->getObject());

    Gui::View3DInventor* view = static_cast<Gui::View3DInventor*>(parent());
    Gui::View3DInventorViewer* viewer = view->getViewer();
    viewer->setEditing(true);
    //viewer->setRedirectToSceneGraph(true);
    viewer->addEventCallback(SoEvent::getClassTypeId(),
        MeshFillHole::fileHoleCallback, this);
    myConnection = App::GetApplication().signalChangedObject.connect(
        boost::bind(&MeshFillHole::slotChangedObject, this, _1, _2));

    myBoundariesRoot->removeAllChildren();
    myBoundariesRoot->addChild(viewer->getHeadlight());
    myBoundariesRoot->addChild(viewer->getCamera());
    myBoundariesRoot->addChild(myBoundariesGroup);
    myBoundaryRoot->removeAllChildren();
    myBoundaryRoot->addChild(viewer->getHeadlight());
    myBoundaryRoot->addChild(viewer->getCamera());
    createPolygons();
    static_cast<SoGroup*>(viewer->getSceneGraph())->addChild(myBridgeRoot);
}

void MeshFillHole::finishEditing()
{
    Gui::View3DInventor* view = static_cast<Gui::View3DInventor*>(parent());
    Gui::View3DInventorViewer* viewer = view->getViewer();
    viewer->setEditing(false);
    //viewer->setRedirectToSceneGraph(false);
    viewer->removeEventCallback(SoEvent::getClassTypeId(),
        MeshFillHole::fileHoleCallback, this);
    myConnection.disconnect();
    this->deleteLater();
    static_cast<SoGroup*>(viewer->getSceneGraph())->removeChild(myBridgeRoot);
}

void MeshFillHole::closeBridge()
{
    // Do the hole-filling
    Gui::WaitCursor wc;
    TBoundary::iterator it = std::find(myPolygon.begin(), myPolygon.end(), myVertex1);
    TBoundary::iterator jt = std::find(myPolygon.begin(), myPolygon.end(), myVertex2);
    if (it != myPolygon.end() && jt != myPolygon.end()) {
        // which iterator comes first
        if (jt < it)
            std::swap(it, jt);
        // split the boundary into two loops and take the shorter one
        std::list<TBoundary> bounds;
        TBoundary loop1, loop2;
        loop1.insert(loop1.end(), myPolygon.begin(), it);
        loop1.insert(loop1.end(), jt, myPolygon.end());
        loop2.insert(loop2.end(), it, jt);
        // this happens when myVertex1 == myVertex2
        if (loop2.empty())
            bounds.push_back(loop1);
        else if (loop1.size() < loop2.size())
            bounds.push_back(loop1);
        else
            bounds.push_back(loop2);

        App::Document* doc = myMesh->getDocument();
        doc->openTransaction("Bridge && Fill hole");
        Mesh::MeshObject* pMesh = myMesh->Mesh.startEditing();
        bool ok = myHoleFiller.fillHoles(*pMesh, bounds, myVertex1, myVertex2);
        myMesh->Mesh.finishEditing();
        if (ok)
            doc->commitTransaction();
        else
            doc->abortTransaction();
    }
}

void MeshFillHole::slotChangedObject(const App::DocumentObject& Obj, const App::Property& Prop)
{
    if (&Obj == myMesh && strcmp(Prop.getName(),"Mesh") == 0) {
        myBoundariesGroup->removeAllChildren();
        myVertex->point.setNum(0);
        myNumPoints = 0;
        myPolygon.clear();

        createPolygons();
    }
}

void MeshFillHole::createPolygons()
{
    Gui::WaitCursor wc;
    myPolygons.clear();

    SoPickStyle* pickStyle = new SoPickStyle();
    pickStyle->style = SoPickStyle::BOUNDING_BOX;
    myBoundariesGroup->addChild(pickStyle);
    myBoundaryRoot->addChild(pickStyle);

    // get mesh kernel
    const MeshCore::MeshKernel & rMesh = this->myMesh->Mesh.getValue().getKernel();

    // get the mesh boundaries as an array of point indices
    std::list<std::vector<unsigned long> > borders;
    MeshCore::MeshAlgorithm cAlgo(rMesh);
    MeshCore::MeshPointIterator p_iter(rMesh);
    cAlgo.GetMeshBorders(borders);
    cAlgo.SplitBoundaryLoops(borders);

    // sort the borders in ascending order of the number of edges
    borders.sort(NofFacetsCompare());

    int32_t count=0;
    for (std::list<std::vector<unsigned long> >::iterator it = 
        borders.begin(); it != borders.end(); ++it) {
        if (it->front() == it->back())
            it->pop_back();
        count += it->size();
    }

    SoCoordinate3* coords = new SoCoordinate3();
    myBoundariesGroup->addChild(coords);
    myBoundaryRoot->addChild(coords);

    coords->point.setNum(count);
    int32_t index = 0;
    for (std::list<std::vector<unsigned long> >::iterator it = 
        borders.begin(); it != borders.end(); ++it) {
        SoPolygon* polygon = new SoPolygon();
        polygon->startIndex = index;
        polygon->numVertices = it->size();
        myBoundariesGroup->addChild(polygon);
        myPolygons[polygon] = *it;
        for (std::vector<unsigned long>::iterator jt = it->begin();
            jt != it->end(); ++jt) {
            p_iter.Set(*jt);
            coords->point.set1Value(index++,p_iter->x,p_iter->y,p_iter->z);
        }
    }
}

SoNode* MeshFillHole::getPickedPolygon(const SoRayPickAction& action/*SoNode* root, const SbVec2s& pos, const SoQtViewer* viewer*/) const
{
    SoPolygon* poly = 0;
    const SoPickedPointList & points = action.getPickedPointList();
    for (int i=0; i < points.getLength(); i++) {
        const SoPickedPoint * point = points[i];
        if (point && point->getPath()->getTail()->getTypeId() == MeshGui::SoPolygon::getClassTypeId()) {
            // we have something picked, now check if it was an SoPolygon node
            SoPolygon* node = static_cast<SoPolygon*>(point->getPath()->getTail());
            if (!poly) {
                poly = node;
            }
            // check which polygon has less edges
            else if (node->numVertices.getValue() < poly->numVertices.getValue()) {
                poly = node;
            }
        }
    }

    return poly;
}

float MeshFillHole::findClosestPoint(const SbLine& ray, const TBoundary& polygon,
                                     unsigned long& vertex_index, SbVec3f& closestPoint) const
{
    // now check which vertex of the polygon is closest to the ray
    float minDist = FLT_MAX;
    vertex_index = ULONG_MAX;

    const MeshCore::MeshKernel & rMesh = myMesh->Mesh.getValue().getKernel();
    const MeshCore::MeshPointArray& pts = rMesh.GetPoints();
    for (TBoundary::const_iterator it = polygon.begin(); it != polygon.end(); ++it) {
        SbVec3f vertex;
        const Base::Vector3f& v = pts[*it];
        vertex.setValue(v.x,v.y,v.z);
        SbVec3f point = ray.getClosestPoint(vertex);
        float distance = (vertex-point).sqrLength();
        if (distance < minDist) {
            minDist = distance;
            vertex_index = *it;
            closestPoint = vertex;
        }
    }

    return minDist;
}

void MeshFillHole::fileHoleCallback(void * ud, SoEventCallback * n)
{
    MeshFillHole* self = reinterpret_cast<MeshFillHole*>(ud);
    Gui::View3DInventorViewer* view  = reinterpret_cast<Gui::View3DInventorViewer*>(n->getUserData());

    const SoEvent* ev = n->getEvent();
    if (ev->getTypeId() == SoLocation2Event::getClassTypeId()) {
        n->setHandled();
        SoRayPickAction rp(view->getViewportRegion());
        rp.setPoint(ev->getPosition());
        rp.setPickAll(true);
        if (self->myNumPoints == 0)
            rp.apply(self->myBoundariesRoot);
        else
            rp.apply(self->myBoundaryRoot);
        SoNode* node = self->getPickedPolygon(rp);
        if (node) {
            std::map<SoNode*, TBoundary>::iterator it = self->myPolygons.find(node);
            if (it != self->myPolygons.end()) {
                // now check which vertex of the polygon is closest to the ray
                unsigned long vertex_index;
                SbVec3f closestPoint;
                float minDist = self->findClosestPoint(rp.getLine(), it->second, vertex_index, closestPoint);
                if (minDist < 1.0f) {
                    if (self->myNumPoints == 0)
                        self->myVertex->point.set1Value(0, closestPoint);
                    else
                        self->myVertex->point.set1Value(1, closestPoint);
                }
            }
        }
    }
    else if (ev->getTypeId() == SoMouseButtonEvent::getClassTypeId()) {
        n->setHandled();
        const SoMouseButtonEvent * mbe = static_cast<const SoMouseButtonEvent *>(ev);
        if (mbe->getButton() == SoMouseButtonEvent::BUTTON1 && mbe->getState() == SoButtonEvent::DOWN) {
        }
        else if (mbe->getButton() == SoMouseButtonEvent::BUTTON1 && mbe->getState() == SoButtonEvent::UP) {
            if (self->myNumPoints > 1)
                return;
            SoRayPickAction rp(view->getViewportRegion());
            rp.setPoint(ev->getPosition());
            rp.setPickAll(true);
            if (self->myNumPoints == 0)
                rp.apply(self->myBoundariesRoot);
            else
                rp.apply(self->myBoundaryRoot);
            SoNode* node = self->getPickedPolygon(rp);
            if (node) {
                std::map<SoNode*, TBoundary>::iterator it = self->myPolygons.find(node);
                if (it != self->myPolygons.end()) {
                    // now check which vertex of the polygon is closest to the ray
                    unsigned long vertex_index;
                    SbVec3f closestPoint;
                    float minDist = self->findClosestPoint(rp.getLine(), it->second, vertex_index, closestPoint);
                    if (minDist < 1.0f) {
                        if (self->myNumPoints == 0) {
                            self->myBoundaryRoot->addChild(node);
                            self->myVertex->point.set1Value(0, closestPoint);
                            self->myNumPoints = 1;
                            self->myVertex1 = vertex_index;
                        }
                        else {
                            // myVertex2 can be equal to myVertex1 which does a full hole-filling
                            self->myBoundaryRoot->removeChild(node);
                            self->myVertex->point.set1Value(1, closestPoint);
                            self->myNumPoints = 2;
                            self->myVertex2 = vertex_index;
                            self->myPolygon = it->second;
                            QTimer::singleShot(300, self, SLOT(closeBridge()));
                        }
                    }
                }
            }
        }
        else if (mbe->getButton() == SoMouseButtonEvent::BUTTON2 && mbe->getState() == SoButtonEvent::UP) {
            QMenu menu;
            QAction* fin = menu.addAction(MeshFillHole::tr("Finish"));
            QAction* act = menu.exec(QCursor::pos());
            if (act == fin) {
                QTimer::singleShot(300, self, SLOT(finishEditing()));
            }
        }
    }
}

#include "moc_MeshEditor.cpp"
