/****************************************************************************
 *   Copyright (c) 2017 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
 *                                                                          *
 *   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 <atomic>
# include <cctype>
# include <boost/algorithm/string/predicate.hpp>
# include <Inventor/SoPickedPoint.h>
# include <Inventor/actions/SoGetBoundingBoxAction.h>
# include <Inventor/details/SoDetail.h>
# include <Inventor/draggers/SoCenterballDragger.h>
# include <Inventor/misc/SoChildList.h>
# include <Inventor/nodes/SoAnnotation.h>
# include <Inventor/nodes/SoCube.h>
# include <Inventor/nodes/SoDrawStyle.h>
# include <Inventor/nodes/SoPickStyle.h>
# include <Inventor/nodes/SoSeparator.h>
# include <Inventor/nodes/SoShapeHints.h>
# include <Inventor/nodes/SoSurroundScale.h>
# include <Inventor/nodes/SoSwitch.h>
# include <Inventor/nodes/SoTransform.h>
# include <Inventor/sensors/SoNodeSensor.h>
# include <Inventor/SoPickedPoint.h>
# include <QApplication>
# include <QMenu>
# include <QCheckBox>
#endif

#include <boost/range.hpp>
#include <App/ElementNamingUtils.h>
#include <App/Document.h>
#include <Base/BoundBoxPy.h>
#include <Base/MatrixPy.h>
#include <Base/PlacementPy.h>
#include <Base/Tools.h>

#include "Action.h"
#include "MainWindow.h"
#include "ViewProviderLink.h"
#include "ViewProviderLinkPy.h"
#include "Application.h"
#include "BitmapFactory.h"
#include "Control.h"
#include "LinkViewPy.h"
#include "Selection.h"
#include "SoFCCSysDragger.h"
#include "SoFCUnifiedSelection.h"
#include "TaskCSysDragger.h"
#include "TaskElementColors.h"
#include "View3DInventor.h"
#include "ViewParams.h"
#include "ViewProviderGeometryObject.h"

#include "ActionFunction.h"
#include "Command.h"
#include "DlgObjectSelection.h"

FC_LOG_LEVEL_INIT("App::Link", true, true)

using namespace Gui;
using namespace Base;

using CharRange = boost::iterator_range<const char*>;
////////////////////////////////////////////////////////////////////////////

static inline bool appendPathSafe(SoPath *path, SoNode *node) {
    if(path->getLength()) {
        SoNode * tail = path->getTail();
        const SoChildList * children = tail->getChildren();
        if(!children || children->find((void *)node)<0)
            return false;
    }
    path->append(node);
    return true;
}

#ifdef FC_DEBUG
#define appendPath(_path,_node)  \
do{\
    if(!appendPathSafe(_path,_node))\
        FC_ERR("LinkView: coin path error");\
}while(0)
#else
#define appendPath(_path, _node) (_path)->append(_node)
#endif

////////////////////////////////////////////////////////////////////////////
class Gui::LinkInfo {

public:
    std::atomic<int> ref;

    using Connection = boost::signals2::scoped_connection;
    Connection connChangeIcon;

    ViewProviderDocumentObject *pcLinked;
    std::unordered_set<Gui::LinkOwner*> links;

    using Pointer = LinkInfoPtr;

    SoNodeSensor sensor;
    SoNodeSensor switchSensor;
    SoNodeSensor childSensor;
    SoNodeSensor transformSensor;

    std::array<CoinPtr<SoSeparator>,LinkView::SnapshotMax> pcSnapshots;
    std::array<CoinPtr<SoSwitch>,LinkView::SnapshotMax> pcSwitches;
    CoinPtr<SoSwitch> pcLinkedSwitch;

    // for group type view providers
    CoinPtr<SoGroup> pcChildGroup;
    using NodeMap = std::unordered_map<SoNode *, Pointer>;
    NodeMap nodeMap;

    std::map<qint64, QIcon> iconMap;

    static ViewProviderDocumentObject *getView(App::DocumentObject *obj) {
        if(obj && obj->isAttachedToDocument()) {
            Document *pDoc = Application::Instance->getDocument(obj->getDocument());
            if(pDoc) {
                ViewProvider *vp = pDoc->getViewProvider(obj);
                if(vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()))
                    return static_cast<ViewProviderDocumentObject*>(vp);
            }
        }
        return nullptr;
    }

    static Pointer get(App::DocumentObject *obj, Gui::LinkOwner *owner) {
        return get(getView(obj),owner);
    }

    static Pointer get(ViewProviderDocumentObject *vp, LinkOwner *owner) {
        if(!vp)
            return Pointer();

        auto ext = vp->getExtensionByType<ViewProviderLinkObserver>(true);
        if(!ext) {
            ext = new ViewProviderLinkObserver();
            ext->initExtension(vp);
        }
        if(!ext->linkInfo) {
            // extension can be created automatically when restored from document,
            // with an empty linkInfo. So we need to check here.
            ext->linkInfo = Pointer(new LinkInfo(vp));
            ext->linkInfo->update();
        }
        if(owner)
            ext->linkInfo->links.insert(owner);
        return ext->linkInfo;
    }

    static void sensorCB(void *data, SoSensor *) {
        static_cast<LinkInfo*>(data)->update();
    }

    static void switchSensorCB(void *data, SoSensor *) {
        static_cast<LinkInfo*>(data)->updateSwitch();
    }

    static void childSensorCB(void *data, SoSensor *) {
        static_cast<LinkInfo*>(data)->updateChildren();
    }

    static void transformSensorCB(void *data, SoSensor *) {
        auto self = static_cast<LinkInfo*>(data);
        for(size_t i=0;i<self->pcSnapshots.size();++i)  {
            if(self->pcSnapshots[i] && i!=LinkView::SnapshotTransform)
                self->getSnapshot(i,true);
        }
    }

    LinkInfo(ViewProviderDocumentObject *vp)
        :ref(0),pcLinked(vp)
    {
        FC_LOG("new link to " << pcLinked->getObject()->getFullName());
        //NOLINTBEGIN
        connChangeIcon = vp->signalChangeIcon.connect(
                std::bind(&LinkInfo::slotChangeIcon,this));
        //NOLINTEND
        vp->forceUpdate(true);
        sensor.setFunction(sensorCB);
        sensor.setData(this);
        switchSensor.setFunction(switchSensorCB);
        switchSensor.setData(this);
        childSensor.setFunction(childSensorCB);
        childSensor.setData(this);
        transformSensor.setFunction(transformSensorCB);
        transformSensor.setData(this);
    }

    ~LinkInfo() = default;

    bool checkName(const char *name) const {
        return isLinked() && strcmp(name,getLinkedName())==0;
    }

    void remove(LinkOwner *owner) {
        links.erase(owner);
    }

    bool isLinked() const {
        return pcLinked && pcLinked->getObject() &&
           pcLinked->getObject()->isAttachedToDocument();
    }

    const char *getLinkedName() const {
        return pcLinked->getObject()->getDagKey();
    }

    const char *getLinkedLabel() const {
        return pcLinked->getObject()->Label.getValue();
    }

    const char *getLinkedNameSafe() const {
        if(isLinked())
            return getLinkedName();
        return "<nil>";
    }

    const char *getDocName() const {
        return pcLinked->getDocument()->getDocument()->getName();
    }

    void detach(bool unlink) {
        FC_LOG("link detach " << getLinkedNameSafe());
        auto me = LinkInfoPtr(this);
        if(unlink) {
            while(!links.empty()) {
                auto link = *links.begin();
                links.erase(links.begin());
                link->unlink(me);
            }
        }
        sensor.detach();
        switchSensor.detach();
        childSensor.detach();
        transformSensor.detach();
        for(const auto &node : pcSnapshots) {
            if(node)
                coinRemoveAllChildren(node);
        }
        for(const auto &node : pcSwitches) {
            if(node)
                coinRemoveAllChildren(node);
        }
        pcLinkedSwitch.reset();
        if(pcChildGroup) {
            coinRemoveAllChildren(pcChildGroup);
            pcChildGroup.reset();
        }
        pcLinked = nullptr;
        connChangeIcon.disconnect();
    }

    void updateSwitch(SoSwitch *node=nullptr) {
        if(!isLinked() || !pcLinkedSwitch)
            return;
        int index = pcLinkedSwitch->whichChild.getValue();
        for(size_t i=0;i<pcSwitches.size();++i) {
            if(!pcSwitches[i] || (node && node!=pcSwitches[i]))
                continue;
            int count = pcSwitches[i]->getNumChildren();
            if((index<0 && i==LinkView::SnapshotChild) || !count)
                pcSwitches[i]->whichChild = -1;
            else if(count>pcLinked->getDefaultMode())
                pcSwitches[i]->whichChild = pcLinked->getDefaultMode();
            else
                pcSwitches[i]->whichChild = 0;
        }
    }

    inline void addref() {
        ++ref;
    }

    inline void release(){
        int r = --ref;
        assert(r>=0);
        if(r==0)
            delete this;
        else if(r==1) {
            if(pcLinked) {
                FC_LOG("link release " << getLinkedNameSafe());
                auto ext = pcLinked->getExtensionByType<ViewProviderLinkObserver>(true);
                if(ext && ext->linkInfo == this) {
                    pcLinked->forceUpdate(false);
                    detach(true);
                    ext->linkInfo.reset();
                }
            }
        }
    }

    // VC2013 has trouble with template argument dependent lookup in
    // namespace. Have to put the below functions in global namespace.
    //
    // However, gcc seems to behave the opposite, hence the conditional
    // compilation  here.
    //
#if defined(_MSC_VER)
    friend void Gui::intrusive_ptr_add_ref(LinkInfo *px);
    friend void Gui::intrusive_ptr_release(LinkInfo *px);
#else
    friend inline void intrusive_ptr_add_ref(LinkInfo *px) { px->addref(); }
    friend inline void intrusive_ptr_release(LinkInfo *px) { px->release(); }
#endif

    bool isVisible() const {
        if(!isLinked())
            return true;
        int indices[] = {LinkView::SnapshotTransform, LinkView::SnapshotVisible};
        for(int idx : indices) {
            if(!pcSwitches[idx])
                continue;
            if(pcSwitches[idx]->whichChild.getValue()==-1)
                return false;
        }
        return true;
    }

    void setVisible(bool visible) {
        if(!isLinked())
            return;
        int indices[] = {LinkView::SnapshotTransform, LinkView::SnapshotVisible};
        for(int idx : indices) {
            if(!pcSwitches[idx])
                continue;
            if(!visible)
                pcSwitches[idx]->whichChild = -1;
            else if(pcSwitches[idx]->getNumChildren()>pcLinked->getDefaultMode())
                pcSwitches[idx]->whichChild = pcLinked->getDefaultMode();
        }
    }

    SoSeparator *getSnapshot(int type, bool update=false) {
        if(type<0 || type>=LinkView::SnapshotMax)
            return nullptr;

        SoSeparator *root;
        if(!isLinked() || !(root=pcLinked->getRoot()))
            return nullptr;

        if(sensor.getAttachedNode()!=root) {
            sensor.detach();
            sensor.attach(root);
        }

        auto &pcSnapshot = pcSnapshots[type];
        auto &pcModeSwitch = pcSwitches[type];
        if(pcSnapshot) {
            if(!update)
                return pcSnapshot;
        }else{
            if(ViewParams::instance()->getUseSelectionRoot())
                pcSnapshot = new SoFCSelectionRoot;
            else
                pcSnapshot = new SoSeparator;
            pcSnapshot->boundingBoxCaching = SoSeparator::OFF;
            pcSnapshot->renderCaching = SoSeparator::OFF;
            std::ostringstream ss;
            ss << pcLinked->getObject()->getNameInDocument()
                << "(" << type << ')';
            pcSnapshot->setName(ss.str().c_str());
            pcModeSwitch = new SoSwitch;
        }

        pcLinkedSwitch.reset();

        coinRemoveAllChildren(pcSnapshot);
        pcModeSwitch->whichChild = -1;
        coinRemoveAllChildren(pcModeSwitch);

        SoSwitch *pcUpdateSwitch = pcModeSwitch;

        auto childRoot = pcLinked->getChildRoot();

        for(int i=0,count=root->getNumChildren();i<count;++i) {
            SoNode *node = root->getChild(i);
            if(node==pcLinked->getTransformNode()) {
                if(type!=LinkView::SnapshotTransform)
                    pcSnapshot->addChild(node);
                else {
                    auto transform = pcLinked->getTransformNode();
                    const auto &scale = transform->scaleFactor.getValue();
                    if(scale[0]!=1.0 || scale[1]!=1.0 || scale[2]!=1.0) {
                        auto trans = new SoTransform;
                        pcSnapshot->addChild(trans);
                        trans->scaleFactor.setValue(scale);
                        trans->scaleOrientation = transform->scaleOrientation;
                        if(transformSensor.getAttachedNode()!=transform) {
                            transformSensor.detach();
                            transformSensor.attach(transform);
                        }
                    }
                }
                continue;
            } else if(node!=pcLinked->getModeSwitch()) {
                pcSnapshot->addChild(node);
                continue;
            }

            pcLinkedSwitch = static_cast<SoSwitch*>(node);
            if(switchSensor.getAttachedNode() != pcLinkedSwitch) {
                switchSensor.detach();
                switchSensor.attach(pcLinkedSwitch);
                pcUpdateSwitch = nullptr;
            }

            pcSnapshot->addChild(pcModeSwitch);
            for(int i=0,count=pcLinkedSwitch->getNumChildren();i<count;++i) {
                auto child = pcLinkedSwitch->getChild(i);
                if(pcChildGroup && child==childRoot)
                    pcModeSwitch->addChild(pcChildGroup);
                else
                    pcModeSwitch->addChild(child);
            }
        }
        updateSwitch(pcUpdateSwitch);
        return pcSnapshot;
    }

    void updateData(const App::Property *prop) {
        LinkInfoPtr me(this);
        for(auto link : links)
            link->onLinkedUpdateData(me,prop);
        // update();
    }

    void update() {
        if(!isLinked() || pcLinked->isRestoring())
            return;

        updateChildren();

        for(size_t i=0;i<pcSnapshots.size();++i)
            if(pcSnapshots[i])
                getSnapshot(i,true);
    }

    void updateChildren() {
        if(!isLinked())
            return;

        if(!pcLinked->getChildRoot()) {
            childSensor.detach();
            if(pcChildGroup)
                coinRemoveAllChildren(pcChildGroup);
            return;
        }

        if(childSensor.getAttachedNode() != pcLinked->getChildRoot()) {
            childSensor.detach();
            childSensor.attach(pcLinked->getChildRoot());
        }
        if(!pcChildGroup)
            pcChildGroup = new SoGroup;
        else
            coinRemoveAllChildren(pcChildGroup);

        NodeMap nodeMap;

        for(auto child : pcLinked->claimChildren3D()) {
            Pointer info = get(child,nullptr);
            if(!info) continue;
            SoNode *node = info->getSnapshot(LinkView::SnapshotChild);
            if(!node) continue;
            nodeMap[node] = info;
            pcChildGroup->addChild(node);
        }

        // Use swap instead of clear() here to avoid potential link
        // destruction
        this->nodeMap.swap(nodeMap);
    }

    bool getElementPicked(bool addname, int type,
            const SoPickedPoint *pp, std::ostream &str) const
    {
        if(!pp || !isLinked() || !pcLinked->isSelectable())
            return false;

        if(addname)
            str << getLinkedName() <<'.';

        auto pcSwitch = pcSwitches[type];
        if(pcChildGroup && pcSwitch && pcSwitch->whichChild.getValue()>=0 &&
            pcSwitch->getChild(pcSwitch->whichChild.getValue())==pcChildGroup)
        {
            SoPath *path = pp->getPath();
            int index = path->findNode(pcChildGroup);
            if(index<=0)
                return false;
            auto it = nodeMap.find(path->getNode(index+1));
            if(it==nodeMap.end())
                return false;
            return it->second->getElementPicked(true,LinkView::SnapshotChild,pp,str);
        }else{
            std::string subname;
            if(!pcLinked->getElementPicked(pp,subname))
                return false;
            str<<subname;
        }
        return true;
    }

    static const char *checkSubname(App::DocumentObject *obj, const char *subname) {
#define CHECK_NAME(_name,_end) do{\
            if(!_name) return 0;\
            const char *_n = _name;\
            for(;*subname && *_n; ++subname,++_n)\
                if(*subname != *_n) break;\
            if(*_n || (*subname!=0 && *subname!=_end))\
                    return 0;\
            if(*subname == _end) ++subname;\
        }while(0)

        // if(subname[0] == '*') {
        //     ++subname;
        //     CHECK_NAME(obj->getDocument()->getName(),'*');
        // }
        CHECK_NAME(obj->getNameInDocument(),'.');
        return subname;
    }

    bool getDetail(bool checkname, int type, const char* subname,
            SoDetail *&det, SoFullPath *path) const
    {
        if(!isLinked())
            return false;

        if(checkname) {
            subname = checkSubname(pcLinked->getObject(),subname);
            if(!subname)
                return false;
        }

        if(pcSnapshots[type]->findChild(pcSwitches[type]) < 0) {
            if(path) {
                if(!appendPathSafe(path,pcSnapshots[type]))
                    return false;
            }
            // this is possible in case of editing, where the switch node
            // of the linked view object is temparaly removed from its root
            return true;
        }
        int len = 0;
        if(path) {
            len = path->getLength();
            if(!appendPathSafe(path,pcSnapshots[type]))
                return false;
            appendPath(path,pcSwitches[type]);
        }
        if(*subname == 0)
            return true;

        auto pcSwitch = pcSwitches[type];
        if(!pcChildGroup || !pcSwitch || pcSwitch->whichChild.getValue()<0 ||
            pcSwitch->getChild(pcSwitch->whichChild.getValue())!=pcChildGroup)
        {
            return pcLinked->getDetailPath(subname,path,false,det);
        }
        if(path){
            appendPath(path,pcChildGroup);
            if(pcLinked->getChildRoot())
                type = LinkView::SnapshotChild;
            else
                type = LinkView::SnapshotVisible;
        }

        // Special handling of nodes with childRoot, especially geo feature
        // group. It's object hierarchy in the tree view (i.e. in subname) is
        // difference from its coin hierarchy. All objects under a geo feature
        // group is visually grouped directly under the group's childRoot,
        // even though some object has secondary hierarchy in subname. E.g.
        //
        // Body
        //   |--Pad
        //       |--Sketch
        //
        //  Both Sketch and Pad's coin nodes are grouped directly under Body as,
        //
        // Body
        //   |--Pad
        //   |--Sketch

        const char *dot = strchr(subname,'.');
        const char *nextsub = subname;
        if(!dot)
            return false;
        auto geoGroup = pcLinked->getObject();
        auto sobj = geoGroup;
        while(true) {
            std::string objname = std::string(nextsub,dot-nextsub+1);
            if(!geoGroup->getSubObject(objname.c_str())) {
                // this object is not found under the geo group, abort.
                break;
            }
            // Object found under geo group, remember this subname
            subname = nextsub;

            auto ssobj = sobj->getSubObject(objname.c_str());
            if(!ssobj) {
                FC_ERR("invalid sub name " << nextsub << " of object " << sobj->getFullName());
                return false;
            }
            sobj = ssobj;
            auto vp = Application::Instance->getViewProvider(sobj);
            if(!vp) {
                FC_ERR("cannot find view provider of " << sobj->getFullName());
                return false;
            }
            if(vp->getChildRoot()) {
                // In case the children is also a geo group, it will visually
                // hold all of its own children, so stop going further down.
                break;
            }
            // new style mapped sub-element
            if(Data::isMappedElement(dot+1))
                break;
            auto next = strchr(dot+1,'.');
            if(!next) {
                // no dot any more, the following must be a sub-element
                break;
            }
            nextsub = dot+1;
            dot = next;
        }

        for(const auto& v : nodeMap) {
            if(v.second->getDetail(true,type,subname,det,path))
                return true;
        }
        if(path)
            path->truncate(len);
        return false;
    }

    void slotChangeIcon() {
        iconMap.clear();
        if(!isLinked())
            return;
        LinkInfoPtr me(this);
        for(auto link : links)
            link->onLinkedIconChange(me);
    }

    QIcon getIcon(QPixmap px) {
        static int iconSize = -1;
        if(iconSize < 0)
            iconSize = QApplication::style()->standardPixmap(QStyle::SP_DirClosedIcon).width();

        if(!isLinked())
            return QIcon();

        if(px.isNull())
            return pcLinked->getIcon();
        QIcon &iconLink = iconMap[px.cacheKey()];
        if(iconLink.isNull()) {
            QIcon icon = pcLinked->getIcon();
            iconLink = QIcon();
            iconLink.addPixmap(BitmapFactory().merge(icon.pixmap(iconSize, iconSize, QIcon::Normal, QIcon::Off),
                px,BitmapFactoryInst::BottomLeft), QIcon::Normal, QIcon::Off);
            iconLink.addPixmap(BitmapFactory().merge(icon.pixmap(iconSize, iconSize, QIcon::Normal, QIcon::On ),
                px,BitmapFactoryInst::BottomLeft), QIcon::Normal, QIcon::On);
        }
        return iconLink;
    }
};

#if defined(_MSC_VER)
namespace Gui
{
    void intrusive_ptr_add_ref(Gui::LinkInfo* px)
    {
        px->addref();
    }

    void intrusive_ptr_release(Gui::LinkInfo* px)
    {
        px->release();
    }
}
#endif

////////////////////////////////////////////////////////////////////////////////////

EXTENSION_TYPESYSTEM_SOURCE(Gui::ViewProviderLinkObserver,Gui::ViewProviderExtension)

ViewProviderLinkObserver::ViewProviderLinkObserver() {
    // TODO: any better way to get deleted automatically?
    m_isPythonExtension = true;
    initExtensionType(ViewProviderLinkObserver::getExtensionClassTypeId());
}

ViewProviderLinkObserver::~ViewProviderLinkObserver() {
    if(linkInfo) {
        linkInfo->detach(true);
        linkInfo.reset();
    }
}

bool ViewProviderLinkObserver::isLinkVisible() const {
    if(linkInfo)
        return linkInfo->isVisible();
    return true;
}

void ViewProviderLinkObserver::setLinkVisible(bool visible) {
    if(linkInfo)
        linkInfo->setVisible(visible);
}

void ViewProviderLinkObserver::extensionBeforeDelete() {
    if(linkInfo)
        linkInfo->detach(false);
}

void ViewProviderLinkObserver::extensionReattach(App::DocumentObject *) {
    if(linkInfo) {
        linkInfo->pcLinked =
            Base::freecad_dynamic_cast<ViewProviderDocumentObject>(getExtendedContainer());
        linkInfo->update();
    }
}

void ViewProviderLinkObserver::extensionOnChanged(const App::Property *prop) {
    (void)prop;
}

void ViewProviderLinkObserver::extensionModeSwitchChange() {
    auto owner = freecad_dynamic_cast<ViewProviderDocumentObject>(getExtendedContainer());
    if(owner && linkInfo)
        linkInfo->updateSwitch();
}

void ViewProviderLinkObserver::extensionUpdateData(const App::Property *prop) {
    if(linkInfo && linkInfo->pcLinked && linkInfo->pcLinked->getObject() &&
       prop != &linkInfo->pcLinked->getObject()->Visibility)
        linkInfo->updateData(prop);
}

void ViewProviderLinkObserver::extensionFinishRestoring() {
    if(linkInfo) {
        FC_TRACE("linked finish restoing");
        linkInfo->update();
    }
}

class LinkView::SubInfo : public LinkOwner {
public:
    LinkInfoPtr linkInfo;
    LinkView &handle;
    CoinPtr<SoSeparator> pcNode;
    CoinPtr<SoTransform> pcTransform;
    std::set<std::string> subElements;

    friend LinkView;

    SubInfo(LinkView &handle):handle(handle) {
        pcNode = new SoFCSelectionRoot(true);
        pcTransform = new SoTransform;
        pcNode->addChild(pcTransform);
    }

    ~SubInfo() override {
        unlink();
        auto root = handle.getLinkRoot();
        if(root) {
            int idx = root->findChild(pcNode);
            if(idx>=0)
                root->removeChild(idx);
        }
    }

    void onLinkedIconChange(LinkInfoPtr) override {
        if(handle.autoSubLink && handle.subInfo.size()==1)
            handle.onLinkedIconChange(handle.linkInfo);
    }

    void unlink(LinkInfoPtr info=LinkInfoPtr()) override {
        (void)info;
        if(linkInfo) {
            linkInfo->remove(this);
            linkInfo.reset();
        }
        coinRemoveAllChildren(pcNode);
        pcNode->addChild(pcTransform);
    }

    void link(App::DocumentObject *obj) {
        if(isLinked() && linkInfo->pcLinked->getObject()==obj)
            return;
        unlink();
        linkInfo = LinkInfo::get(obj,this);
        if(linkInfo)
            pcNode->addChild(linkInfo->getSnapshot(LinkView::SnapshotTransform));
    }

    bool isLinked() const{
        return linkInfo && linkInfo->isLinked();
    }
};

//////////////////////////////////////////////////////////////////////////////////

class LinkView::Element : public LinkOwner {
public:
    LinkInfoPtr linkInfo;
    LinkView &handle;
    CoinPtr<SoSwitch> pcSwitch;
    CoinPtr<SoFCSelectionRoot> pcRoot;
    CoinPtr<SoTransform> pcTransform;
    int groupIndex = -1;
    bool isGroup = false;

    friend LinkView;

    Element(LinkView &handle):handle(handle) {
        pcTransform = new SoTransform;
        pcRoot = new SoFCSelectionRoot(true);
        pcSwitch = new SoSwitch;
        pcSwitch->addChild(pcRoot);
        pcSwitch->whichChild = 0;
    }

    ~Element() override {
        unlink();
        auto root = handle.getLinkRoot();
        if(root) {
            int idx = root->findChild(pcRoot);
            if(idx>=0)
                root->removeChild(idx);
        }
    }

    void unlink(LinkInfoPtr info=LinkInfoPtr()) override{
        (void)info;
        if(linkInfo) {
            linkInfo->remove(this);
            linkInfo.reset();
        }
        coinRemoveAllChildren(pcRoot);
    }

    void link(App::DocumentObject *obj) {
        if(isLinked() && linkInfo->pcLinked->getObject()==obj)
            return;
        unlink();
        linkInfo = LinkInfo::get(obj,this);
        if(isLinked())
            pcRoot->addChild(linkInfo->getSnapshot(handle.childType));
    }

    bool isLinked() const{
        return linkInfo && linkInfo->isLinked();
    }
};

///////////////////////////////////////////////////////////////////////////////////

TYPESYSTEM_SOURCE(Gui::LinkView,Base::BaseClass)

LinkView::LinkView()
    :nodeType(SnapshotTransform)
    ,childType((SnapshotType)-1),autoSubLink(true)
{
    pcLinkRoot = new SoFCSelectionRoot;
}

LinkView::~LinkView() {
    unlink(linkInfo);
    unlink(linkOwner);
}

PyObject *LinkView::getPyObject()
{
    if (PythonObject.is(Py::_None()))
        PythonObject = Py::Object(new LinkViewPy(this),true);
    return Py::new_reference_to(PythonObject);
}

void LinkView::setInvalid() {
    if (!PythonObject.is(Py::_None())){
        auto obj = static_cast<Base::PyObjectBase*>(PythonObject.ptr());
        obj->setInvalid();
        obj->DecRef();
    }else
        delete this;
}

Base::BoundBox3d _getBoundBox(ViewProviderDocumentObject *vpd, SoNode *rootNode) {
    auto doc = vpd->getDocument();
    if(!doc)
        LINK_THROW(Base::RuntimeError,"no document");
    Gui::MDIView* view = doc->getViewOfViewProvider(vpd);
    if(!view)
        LINK_THROW(Base::RuntimeError,"no view");

    Gui::View3DInventorViewer* viewer = static_cast<Gui::View3DInventor*>(view)->getViewer();
    SoGetBoundingBoxAction bboxAction(viewer->getSoRenderManager()->getViewportRegion());
    bboxAction.apply(rootNode);
    auto bbox = bboxAction.getBoundingBox();
    float minX,minY,minZ,maxX,maxY,maxZ;
    bbox.getMax().getValue(maxX,maxY,maxZ);
    bbox.getMin().getValue(minX,minY,minZ);
    return Base::BoundBox3d(minX,minY,minZ,maxX,maxY,maxZ);
}

Base::BoundBox3d LinkView::getBoundBox(ViewProviderDocumentObject *vpd) const {
    if(!vpd) {
        if(!linkOwner || !linkOwner->isLinked())
            LINK_THROW(Base::ValueError,"no ViewProvider");
        vpd = linkOwner->pcLinked;
    }
    return _getBoundBox(vpd,pcLinkRoot);
}

ViewProviderDocumentObject *LinkView::getOwner() const {
    if(linkOwner && linkOwner->isLinked())
        return linkOwner->pcLinked;
    return nullptr;
}

void LinkView::setOwner(ViewProviderDocumentObject *vpd) {
    unlink(linkOwner);
    linkOwner = LinkInfo::get(vpd,this);
}

bool LinkView::isLinked() const{
    return linkInfo && linkInfo->isLinked();
}

void LinkView::setDrawStyle(int style, double lineWidth, double pointSize) {
    if(!pcDrawStyle) {
        if(!style)
            return;
        pcDrawStyle = new SoDrawStyle;
        pcDrawStyle->style = SoDrawStyle::FILLED;
        pcLinkRoot->insertChild(pcDrawStyle,0);
    }
    if(!style) {
        pcDrawStyle->setOverride(false);
        return;
    }
    pcDrawStyle->lineWidth = lineWidth;
    pcDrawStyle->pointSize = pointSize;
    switch(style) {
    case 2:
        pcDrawStyle->linePattern = 0xf00f;
        break;
    case 3:
        pcDrawStyle->linePattern = 0x0f0f;
        break;
    case 4:
        pcDrawStyle->linePattern = 0xff88;
        break;
    default:
        pcDrawStyle->linePattern = 0xffff;
    }
    pcDrawStyle->setOverride(true);
}

void LinkView::renderDoubleSide(bool enable) {
    if(enable) {
        if(!pcShapeHints) {
            pcShapeHints = new SoShapeHints;
            pcShapeHints->vertexOrdering = SoShapeHints::CLOCKWISE;
            pcShapeHints->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE;
            pcLinkRoot->insertChild(pcShapeHints,0);
        }
        pcShapeHints->setOverride(true);
    }else if(pcShapeHints)
        pcShapeHints->setOverride(false);
}

void LinkView::setMaterial(int index, const App::Material *material) {
    if(index < 0) {
        if(!material) {
            pcLinkRoot->removeColorOverride();
            return;
        }
        App::Color c = material->diffuseColor;
        c.a = material->transparency;
        pcLinkRoot->setColorOverride(c);
        for(int i=0;i<getSize();++i)
            setMaterial(i,nullptr);
    }else if(index >= (int)nodeArray.size())
        LINK_THROW(Base::ValueError,"LinkView: material index out of range");
    else {
        auto &info = *nodeArray[index];
        if(!material) {
            info.pcRoot->removeColorOverride();
            return;
        }
        App::Color c = material->diffuseColor;
        c.a = material->transparency;
        info.pcRoot->setColorOverride(c);
    }
}

void LinkView::setLink(App::DocumentObject *obj, const std::vector<std::string> &subs) {
    setLinkViewObject(Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
            Application::Instance->getViewProvider(obj)),subs);

}

void LinkView::setLinkViewObject(ViewProviderDocumentObject *vpd,
        const std::vector<std::string> &subs)
{
    if(!isLinked() || linkInfo->pcLinked != vpd) {
        unlink(linkInfo);
        linkInfo = LinkInfo::get(vpd,this);
        if(!linkInfo)
            return;
    }
    subInfo.clear();
    for(const auto &sub : subs) {
        if(sub.empty()) continue;
        const char *subelement = Data::findElementName(sub.c_str());
        std::string subname = sub.substr(0,subelement-sub.c_str());
        auto it = subInfo.find(subname);
        if(it == subInfo.end()) {
            it = subInfo.insert(std::make_pair(subname,std::unique_ptr<SubInfo>())).first;
            it->second = std::make_unique<SubInfo>(*this);
        }
        if(subelement[0])
            it->second->subElements.insert(subelement);
    }
    updateLink();
}

void LinkView::setTransform(SoTransform *pcTransform, const Base::Matrix4D &mat) {
    double dMtrx[16];
    mat.getGLMatrix(dMtrx);
    pcTransform->setMatrix(SbMatrix(dMtrx[0], dMtrx[1], dMtrx[2],  dMtrx[3],
                                    dMtrx[4], dMtrx[5], dMtrx[6],  dMtrx[7],
                                    dMtrx[8], dMtrx[9], dMtrx[10], dMtrx[11],
                                    dMtrx[12],dMtrx[13],dMtrx[14], dMtrx[15]));
}

void LinkView::setSize(int _size) {
    size_t size = _size<0?0:(size_t)_size;
    if(childType<0 && size==nodeArray.size())
        return;
    resetRoot();
    if(!size || childType>=0) {
        nodeArray.clear();
        nodeMap.clear();
        if(!size && childType<0) {
            if(pcLinkedRoot)
                pcLinkRoot->addChild(pcLinkedRoot);
            return;
        }
        childType = SnapshotContainer;
    }
    if(size<nodeArray.size()) {
        for(size_t i=size;i<nodeArray.size();++i)
            nodeMap.erase(nodeArray[i]->pcSwitch);
        nodeArray.resize(size);
    }
    for(const auto &info : nodeArray)
        pcLinkRoot->addChild(info->pcSwitch);

    while(nodeArray.size()<size) {
        nodeArray.push_back(std::make_unique<Element>(*this));
        auto &info = *nodeArray.back();
        info.pcRoot->addChild(info.pcTransform);
        if(pcLinkedRoot)
            info.pcRoot->addChild(pcLinkedRoot);
        pcLinkRoot->addChild(info.pcSwitch);
        nodeMap.emplace(info.pcSwitch,(int)nodeArray.size()-1);
    }
}

void LinkView::resetRoot() {
    coinRemoveAllChildren(pcLinkRoot);
    if(pcTransform)
        pcLinkRoot->addChild(pcTransform);
    if(pcShapeHints)
        pcLinkRoot->addChild(pcShapeHints);
    if(pcDrawStyle)
        pcLinkRoot->addChild(pcDrawStyle);
}

void LinkView::setChildren(const std::vector<App::DocumentObject*> &children,
        const boost::dynamic_bitset<> &vis, SnapshotType type)
{
    if(children.empty()) {
        if(!nodeArray.empty()) {
            nodeArray.clear();
            nodeMap.clear();
            childType = SnapshotContainer;
            resetRoot();
            if(pcLinkedRoot)
                pcLinkRoot->addChild(pcLinkedRoot);
        }
        return;
    }

    if(type<0 || type>=SnapshotMax)
        LINK_THROW(Base::ValueError,"invalid children type");

    resetRoot();

    if(childType<0)
        nodeArray.clear();
    childType = type;

    if(nodeArray.size() > children.size())
        nodeArray.resize(children.size());
    else
        nodeArray.reserve(children.size());

    std::map<App::DocumentObject*, size_t> groups;
    for(size_t i=0;i<children.size();++i) {
        auto obj = children[i];
        if(nodeArray.size()<=i) {
            nodeArray.push_back(std::make_unique<Element>(*this));
        }
        auto &info = *nodeArray[i];
        info.isGroup = false;
        info.groupIndex = -1;
        info.pcSwitch->whichChild = (vis.size()<=i||vis[i])?0:-1;
        info.link(obj);
        if(obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(),false)) {
            info.isGroup = true;
            coinRemoveAllChildren(info.pcRoot);
            groups.emplace(obj,i);
        }
    }
    nodeMap.clear();
    for(size_t i=0;i<nodeArray.size();++i) {
        auto &info = *nodeArray[i];
        nodeMap.emplace(info.pcSwitch,i);
        if(info.isLinked() && !groups.empty()) {
            auto iter = groups.find(App::GroupExtension::getGroupOfObject(
                            info.linkInfo->pcLinked->getObject()));
            if(iter != groups.end()) {
                info.groupIndex = iter->second;
                auto &groupInfo = *nodeArray[iter->second];
                groupInfo.pcRoot->addChild(info.pcSwitch);
                continue;
            }
        }
        pcLinkRoot->addChild(info.pcSwitch);
    }
}

std::vector<ViewProviderDocumentObject*> LinkView::getChildren() const {
    std::vector<ViewProviderDocumentObject*> ret;
    for(const auto &info : nodeArray) {
        if(info->isLinked())
            ret.push_back(info->linkInfo->pcLinked);
    }
    return ret;
}

void LinkView::setTransform(int index, const Base::Matrix4D &mat) {
    if(index<0) {
        if(!pcTransform) {
            pcTransform = new SoTransform;
            pcLinkRoot->insertChild(pcTransform,0);
        }
        setTransform(pcTransform,mat);
        return;
    }
    if(index<0 || index>=(int)nodeArray.size())
        LINK_THROW(Base::ValueError,"LinkView: index out of range");
    setTransform(nodeArray[index]->pcTransform,mat);
}

void LinkView::setElementVisible(int idx, bool visible) {
    if(idx>=0 && idx<(int)nodeArray.size())
        nodeArray[idx]->pcSwitch->whichChild = visible?0:-1;
}

bool LinkView::isElementVisible(int idx) const {
    if(idx>=0 && idx<(int)nodeArray.size())
        return nodeArray[idx]->pcSwitch->whichChild.getValue()>=0;
    return false;
}

ViewProviderDocumentObject *LinkView::getLinkedView() const {
    auto link = linkInfo;
    if(autoSubLink && subInfo.size()==1)
        link = subInfo.begin()->second->linkInfo;
    return link?link->pcLinked:nullptr;
}

std::vector<std::string> LinkView::getSubNames() const {
    std::vector<std::string> ret;
    for(const auto &v : subInfo) {
        if(v.second->subElements.empty()) {
            ret.push_back(v.first);
            continue;
        }
        for(const auto &s : v.second->subElements)
            ret.push_back(v.first+s);
    }
    return ret;
}

void LinkView::setNodeType(SnapshotType type, bool sublink) {
    autoSubLink = sublink;
    if(nodeType==type)
        return;
    if(type>=SnapshotMax ||
       (type<0 && type!=SnapshotContainer && type!=SnapshotContainerTransform))
        LINK_THROW(Base::ValueError,"LinkView: invalid node type");

    if(nodeType>=0 && type<0) {
        if(pcLinkedRoot) {
            SoSelectionElementAction action(SoSelectionElementAction::None,true);
            action.apply(pcLinkedRoot);
        }
        replaceLinkedRoot(CoinPtr<SoSeparator>(new SoFCSelectionRoot));
    }else if(nodeType<0 && type>=0) {
        if(isLinked())
            replaceLinkedRoot(linkInfo->getSnapshot(type));
        else
            replaceLinkedRoot(nullptr);
    }
    nodeType = type;
    updateLink();
}

void LinkView::replaceLinkedRoot(SoSeparator *root) {
    if(root==pcLinkedRoot)
        return;
    if(nodeArray.empty()) {
        if(pcLinkedRoot && root)
            pcLinkRoot->replaceChild(pcLinkedRoot,root);
        else if(root)
            pcLinkRoot->addChild(root);
        else
            resetRoot();
    }else if(childType<0) {
        if(pcLinkedRoot && root) {
            for(const auto &info : nodeArray)
                info->pcRoot->replaceChild(pcLinkedRoot,root);
        }else if(root) {
            for(const auto &info : nodeArray)
                info->pcRoot->addChild(root);
        }else{
            for(const auto &info : nodeArray)
                info->pcRoot->removeChild(pcLinkedRoot);
        }
    }
    pcLinkedRoot = root;
}

void LinkView::onLinkedIconChange(LinkInfoPtr info) {
    if(info==linkInfo && info!=linkOwner && linkOwner && linkOwner->isLinked())
        linkOwner->pcLinked->signalChangeIcon();
}

void LinkView::onLinkedUpdateData(LinkInfoPtr info, const App::Property *prop) {
    if(info!=linkInfo || !linkOwner || !linkOwner->isLinked() || info==linkOwner)
        return;
    auto ext = linkOwner->pcLinked->getObject()->getExtensionByType<App::LinkBaseExtension>(true);
    if (ext && !(prop->getType() & App::Prop_Output) &&
            !prop->testStatus(App::Property::Output))
    {
        // propagate the signalChangedObject to potentially multiple levels
        // of links, to inform tree view of children change, and other
        // parent objects about the change. But we need to be careful to not
        // touch the object if the property of change is marked as output.
        ext->_LinkTouched.touch();
    }else{
        // In case the owner object does not have link extension, here is a
        // trick to link the signalChangedObject from linked object to the
        // owner
        linkOwner->pcLinked->getDocument()->signalChangedObject(
                *linkOwner->pcLinked,linkOwner->pcLinked->getObject()->Label);
    }
}

void LinkView::updateLink() {
    if(!isLinked())
        return;

    if(linkOwner && linkOwner->isLinked() && linkOwner->pcLinked->isRestoring()) {
        FC_TRACE("restoring '" << linkOwner->pcLinked->getObject()->getFullName() << "'");
        return;
    }

    // TODO: is it a good idea to clear any selection here?
    pcLinkRoot->resetContext();

    if(nodeType >= 0) {
        replaceLinkedRoot(linkInfo->getSnapshot(nodeType));
        return;
    }

    // rebuild link sub objects tree
    CoinPtr<SoSeparator> linkedRoot = pcLinkedRoot;
    if(!linkedRoot)
        linkedRoot = new SoFCSelectionRoot;
    else{
        SoSelectionElementAction action(SoSelectionElementAction::None,true);
        action.apply(linkedRoot);
        coinRemoveAllChildren(linkedRoot);
    }

    SoTempPath path(10);
    path.ref();
    appendPath(&path,linkedRoot);
    auto obj = linkInfo->pcLinked->getObject();
    for(const auto &v : subInfo) {
        auto &sub = *v.second;
        Base::Matrix4D mat;
        App::DocumentObject *sobj = obj->getSubObject(
                v.first.c_str(), nullptr, &mat, nodeType==SnapshotContainer);
        if(!sobj) {
            sub.unlink();
            continue;
        }
        sub.link(sobj);
        linkedRoot->addChild(sub.pcNode);
        setTransform(sub.pcTransform,mat);

        if(!sub.subElements.empty()) {
            path.truncate(1);
            appendPath(&path,sub.pcNode);
            SoSelectionElementAction action(SoSelectionElementAction::Append,true);
            for(const auto &subelement : sub.subElements) {
                path.truncate(2);
                SoDetail *det = nullptr;
                if(!sub.linkInfo->getDetail(false,SnapshotTransform,subelement.c_str(),det,&path))
                    continue;
                action.setElement(det);
                action.apply(&path);
                delete det;
            }
        }
    }
    path.unrefNoDelete();
    replaceLinkedRoot(linkedRoot);
}

bool LinkView::linkGetElementPicked(const SoPickedPoint *pp, std::string &subname) const
{
    std::ostringstream ss;
    CoinPtr<SoPath> path = pp->getPath();
    if(!nodeArray.empty()) {
        auto idx = path->findNode(pcLinkRoot);
        if (idx < 0 || idx + 2 >= path->getLength()) {
            return false;
        }
        auto node = path->getNode(idx+1);
        auto it = nodeMap.find(node);
        if(it == nodeMap.end() || !isElementVisible(it->second)) {
            return false;
        }
        int nodeIdx = it->second;
        ++idx;
        while(nodeArray[nodeIdx]->isGroup) {
            auto &info = *nodeArray[nodeIdx];
            if(!info.isLinked()) {
                return false;
            }
            ss << info.linkInfo->getLinkedName() << '.';
            idx += 2;
            if(idx>=path->getLength()) {
                return false;
            }
            auto iter = nodeMap.find(path->getNode(idx));
            if(iter == nodeMap.end() || !isElementVisible(iter->second)) {
                return false;
            }
            nodeIdx = iter->second;
        }

        auto &info = *nodeArray[nodeIdx];
        if (!info.linkInfo) {
            ss << it->second << '.';
        }
        else {
            ss << info.linkInfo->getLinkedName() << '.';
        }

        if(info.isLinked()) {
            if (!info.linkInfo->getElementPicked(false, childType, pp, ss)) {
                return false;
            }
            subname = ss.str();
            return true;
        }
    }

    if(!isLinked()) {
        return false;
    }

    if(nodeType >= 0) {
        if(linkInfo->getElementPicked(false,nodeType,pp,ss)) {
            subname = ss.str();
            return true;
        }
        return false;
    }
    auto idx = path->findNode(pcLinkedRoot);
    if(idx<0 || idx+1>=path->getLength()) {
        return false;
    }
    auto node = path->getNode(idx+1);
    for(const auto &v : subInfo) {
        auto &sub = *v.second;
        if (node != sub.pcNode) {
            continue;
        }

        std::ostringstream ss2;
        if(!sub.linkInfo->getElementPicked(false,SnapshotTransform,pp,ss2)) {
            return false;
        }
        const std::string &element = ss2.str();
        if(!sub.subElements.empty()) {
            if(sub.subElements.find(element)==sub.subElements.end()) {
                auto pos = element.find('.');
                if (pos == std::string::npos ||
                    sub.subElements.find(element.c_str() + pos + 1) == sub.subElements.end()) {
                    return false;
                }
            }
        }
        if (!autoSubLink || subInfo.size() > 1) {
            ss << v.first;
        }
        ss << element;
        subname = ss.str();
        return true;
    }
    return false;
}

bool LinkView::getGroupHierarchy(int index, SoFullPath *path) const {
    if(index > (int)nodeArray.size())
        return false;
    auto &info = *nodeArray[index];
    if(info.groupIndex>=0 && !getGroupHierarchy(info.groupIndex,path))
        return false;
    appendPath(path,info.pcSwitch);
    appendPath(path,info.pcRoot);
    return true;
}

bool LinkView::linkGetDetailPath(const char *subname, SoFullPath *path, SoDetail *&det) const
{
    if(!subname || *subname==0)
        return true;
    auto len = path->getLength();
    if(nodeArray.empty()) {
        if(!appendPathSafe(path,pcLinkRoot))
            return false;
    } else {
        int idx = -1;
        if (subname[0]>='0' && subname[0]<='9') {
            idx = App::LinkBaseExtension::getArrayIndex(subname,&subname);
        } else {
            while(true) {
                const char *dot = strchr(subname,'.');
                if(!dot)
                    break;
                int i = 0;
                if (subname[0] == '$') {
                    CharRange name(subname+1,dot);
                    for(const auto &info : nodeArray) {
                        if(info->isLinked() && boost::equals(name,info->linkInfo->getLinkedLabel())) {
                            idx = i;
                            break;
                        }
                        ++i;
                    }
                } else {
                    CharRange name(subname,dot);
                    for(const auto &info : nodeArray) {
                        if(info->isLinked() && boost::equals(name,info->linkInfo->getLinkedName())) {
                            idx = i;
                            break;
                        }
                        ++i;
                    }
                }

                if(idx<0)
                    return false;

                subname = dot+1;
                if(!subname[0] || nodeArray[idx]->isGroup==0)
                    break;
                idx = -1;
            }
        }

        if(idx<0 || idx>=(int)nodeArray.size())
            return false;
        auto &info = *nodeArray[idx];
        if(!appendPathSafe(path,pcLinkRoot))
            return false;
        if(info.groupIndex>=0 && !getGroupHierarchy(info.groupIndex,path))
            return false;
        appendPath(path,info.pcSwitch);
        appendPath(path,info.pcRoot);

        if(*subname == 0)
            return true;

        if(info.isLinked()) {
            info.linkInfo->getDetail(false,childType,subname,det,path);
            return true;
        }
    }
    if(isLinked()) {
        if(nodeType >= 0) {
            if(linkInfo->getDetail(false,nodeType,subname,det,path))
                return true;
        }else {
            appendPath(path,pcLinkedRoot);
            for(const auto &v : subInfo) {
                auto &sub = *v.second;
                if(!sub.isLinked())
                    continue;
                const char *nextsub;
                if(autoSubLink && subInfo.size()==1)
                    nextsub = subname;
                else{
                    if(!boost::algorithm::starts_with(subname,v.first))
                        continue;
                    nextsub = subname+v.first.size();
                    if(*nextsub != '.')
                        continue;
                    ++nextsub;
                }
                if(*nextsub && !sub.subElements.empty() &&
                   sub.subElements.find(nextsub)==sub.subElements.end())
                    break;
                appendPath(path,sub.pcNode);
                len = path->getLength();
                if(sub.linkInfo->getDetail(false,SnapshotTransform,nextsub,det,path))
                    return true;
                break;
            }
        }
    }
    path->truncate(len);
    return false;
}

void LinkView::unlink(LinkInfoPtr info) {
    if(!info)
        return;
    if(info == linkOwner) {
        linkOwner->remove(this);
        linkOwner.reset();
    }
    if(info != linkInfo)
        return;
    if(linkInfo) {
        linkInfo->remove(this);
        linkInfo.reset();
    }
    pcLinkRoot->resetContext();
    if(pcLinkedRoot) {
        if(nodeArray.empty())
            resetRoot();
        else {
            for(const auto &info : nodeArray) {
                int idx;
                if(info->isLinked() &&
                   (idx=info->pcRoot->findChild(pcLinkedRoot))>=0)
                    info->pcRoot->removeChild(idx);
            }
        }
        pcLinkedRoot.reset();
    }
    subInfo.clear();
    return;
}

QIcon LinkView::getLinkedIcon(QPixmap px) const {
    auto link = linkInfo;
    if(autoSubLink && subInfo.size()==1)
        link = subInfo.begin()->second->linkInfo;
    if(!link || !link->isLinked())
        return QIcon();
    return link->getIcon(px);
}

bool LinkView::hasSubs() const {
    return isLinked() && !subInfo.empty();
}

///////////////////////////////////////////////////////////////////////////////////

PROPERTY_SOURCE(Gui::ViewProviderLink, Gui::ViewProviderDocumentObject)

static const char *_LinkIcon = "Link";
// static const char *_LinkArrayIcon = "LinkArray";
static const char *_LinkGroupIcon = "LinkGroup";
static const char *_LinkElementIcon = "LinkElement";

ViewProviderLink::ViewProviderLink()
    :linkType(LinkTypeNone),hasSubName(false),hasSubElement(false)
    ,useCenterballDragger(false),childVp(nullptr),overlayCacheKey(0)
{
    sPixmap = _LinkIcon;

    ADD_PROPERTY_TYPE(Selectable, (true), " Link", App::Prop_None, 0);

    ADD_PROPERTY_TYPE(OverrideMaterial, (false), " Link", App::Prop_None, "Override linked object's material");

    App::Material mat(App::Material::DEFAULT);
    mat.diffuseColor.setPackedValue(ViewParams::instance()->getDefaultLinkColor());
    ADD_PROPERTY_TYPE(ShapeMaterial, (mat), " Link", App::Prop_None, 0);
    ShapeMaterial.setStatus(App::Property::MaterialEdit, true);

    ADD_PROPERTY_TYPE(DrawStyle,((long int)0), " Link", App::Prop_None, "");
    static const char* DrawStyleEnums[]= {"None","Solid","Dashed","Dotted","Dashdot",nullptr};
    DrawStyle.setEnums(DrawStyleEnums);

    int lwidth = ViewParams::instance()->getDefaultShapeLineWidth();
    ADD_PROPERTY_TYPE(LineWidth,(lwidth), " Link", App::Prop_None, "");

    static App::PropertyFloatConstraint::Constraints sizeRange = {1.0,64.0,1.0};
    LineWidth.setConstraints(&sizeRange);

    ADD_PROPERTY_TYPE(PointSize,(lwidth), " Link", App::Prop_None, "");
    PointSize.setConstraints(&sizeRange);

    ADD_PROPERTY(MaterialList,());
    MaterialList.setStatus(App::Property::NoMaterialListEdit, true);

    ADD_PROPERTY(OverrideMaterialList,());
    ADD_PROPERTY(OverrideColorList,());

    ADD_PROPERTY(ChildViewProvider, (""));
    ChildViewProvider.setStatus(App::Property::Hidden,true);

    DisplayMode.setStatus(App::Property::Status::Hidden, true);

    linkView = new LinkView;
}

ViewProviderLink::~ViewProviderLink()
{
    linkView->setInvalid();
}

bool ViewProviderLink::isSelectable() const {
    return !pcDragger && Selectable.getValue();
}

void ViewProviderLink::attach(App::DocumentObject *pcObj) {
    SoNode *node = linkView->getLinkRoot();
    node->setName(pcObj->getFullName().c_str());
    addDisplayMaskMode(node,"Link");
    if(childVp) {
        childVpLink = LinkInfo::get(childVp,nullptr);
        node = childVpLink->getSnapshot(LinkView::SnapshotTransform);
    }
    addDisplayMaskMode(node,"ChildView");
    setDisplayMaskMode("Link");
    inherited::attach(pcObj);
    checkIcon();
    if(pcObj->isDerivedFrom(App::LinkElement::getClassTypeId()))
        hide();
    linkView->setOwner(this);

}

void ViewProviderLink::reattach(App::DocumentObject *obj) {
    linkView->setOwner(this);
    if(childVp)
        childVp->reattach(obj);
    ViewProviderDocumentObject::reattach(obj);
}

std::vector<std::string> ViewProviderLink::getDisplayModes() const
{
    std::vector<std::string> StrList = inherited::getDisplayModes();
    StrList.emplace_back("Link");
    StrList.emplace_back("ChildView");
    return StrList;
}

QIcon ViewProviderLink::getIcon() const {
    auto ext = getLinkExtension();
    if(ext) {
        auto link = ext->getLinkedObjectValue();
        if(link && link!=getObject()) {
            QPixmap overlay = getOverlayPixmap();
            overlayCacheKey = overlay.cacheKey();
            QIcon icon = linkView->getLinkedIcon(overlay);
            if(!icon.isNull())
                return icon;
        }
    }
    overlayCacheKey = 0;
    return Gui::BitmapFactory().pixmap(sPixmap);
}

QPixmap ViewProviderLink::getOverlayPixmap() const {
    auto ext = getLinkExtension();
    int px = 12 * getMainWindow()->devicePixelRatioF();
    if(ext && ext->getLinkedObjectProperty() && ext->_getElementCountValue())
        return BitmapFactory().pixmapFromSvg("LinkArrayOverlay", QSizeF(px,px));
    else if(hasSubElement)
        return BitmapFactory().pixmapFromSvg("LinkSubElement", QSizeF(px,px));
    else if(hasSubName)
        return BitmapFactory().pixmapFromSvg("LinkSubOverlay", QSizeF(px,px));
    else
        return BitmapFactory().pixmapFromSvg("LinkOverlay", QSizeF(px,px));
}

void ViewProviderLink::onChanged(const App::Property* prop) {
    if(prop==&ChildViewProvider) {
        childVp = freecad_dynamic_cast<ViewProviderDocumentObject>(ChildViewProvider.getObject().get());
        if(childVp && getObject()) {
            if(strcmp(childVp->getTypeId().getName(),getObject()->getViewProviderName())!=0
                    && !childVp->allowOverride(*getObject()))
            {
                FC_ERR("Child view provider type '" << childVp->getTypeId().getName()
                        << "' does not support " << getObject()->getFullName());
            } else {
                childVp->setPropertyPrefix("ChildViewProvider.");
                childVp->Visibility.setValue(getObject()->Visibility.getValue());
                childVp->attach(getObject());
                childVp->updateView();
                childVp->setActiveMode();
                if(pcModeSwitch->getNumChildren()>1){
                    childVpLink = LinkInfo::get(childVp,nullptr);
                    pcModeSwitch->replaceChild(1,childVpLink->getSnapshot(LinkView::SnapshotTransform));
                }
            }
        }
    }else if(!isRestoring()) {
        if (prop == &OverrideMaterial || prop == &ShapeMaterial ||
            prop == &MaterialList || prop == &OverrideMaterialList)
        {
            applyMaterial();
        }else if(prop == &OverrideColorList) {
            applyColors();
        }else if(prop==&DrawStyle || prop==&PointSize || prop==&LineWidth) {
            if(!DrawStyle.getValue())
                linkView->setDrawStyle(0);
            else
                linkView->setDrawStyle(DrawStyle.getValue(),LineWidth.getValue(),PointSize.getValue());
        }
    }

    inherited::onChanged(prop);
}

bool ViewProviderLink::setLinkType(App::LinkBaseExtension *ext) {
    auto propLink = ext->getLinkedObjectProperty();
    if(!propLink)
        return false;
    LinkType type;
    if(hasSubName)
        type = LinkTypeSubs;
    else
        type = LinkTypeNormal;
    if(linkType != type)
        linkType = type;
    switch(type) {
    case LinkTypeSubs:
        linkView->setNodeType(ext->linkTransform()?LinkView::SnapshotContainer:
                LinkView::SnapshotContainerTransform);
        break;
    case LinkTypeNormal:
        linkView->setNodeType(ext->linkTransform()?LinkView::SnapshotVisible:
                LinkView::SnapshotTransform);
        break;
    default:
        break;
    }
    return true;
}

App::LinkBaseExtension *ViewProviderLink::getLinkExtension() {
    if(!pcObject || !pcObject->isAttachedToDocument())
        return nullptr;
    return pcObject->getExtensionByType<App::LinkBaseExtension>(true);
}

const App::LinkBaseExtension *ViewProviderLink::getLinkExtension() const{
    if(!pcObject || !pcObject->isAttachedToDocument())
        return nullptr;
    return const_cast<App::DocumentObject*>(pcObject)->getExtensionByType<App::LinkBaseExtension>(true);
}

void ViewProviderLink::updateData(const App::Property *prop) {
    if(childVp)
        childVp->updateData(prop);
    if(!isRestoring() && !pcObject->isRestoring()) {
        auto ext = getLinkExtension();
        if(ext) updateDataPrivate(getLinkExtension(),prop);
    }
    return inherited::updateData(prop);
}

static inline bool canScale(const Base::Vector3d &v) {
    return fabs(v.x)>1e-7 && fabs(v.y)>1e-7 && fabs(v.z)>1e-7;
}

void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App::Property *prop) {
    if(!prop)
        return;
    if(prop == &ext->_ChildCache) {
        updateElementList(ext);
    } else if(prop == &ext->_LinkTouched) {
        if(linkView->hasSubs())
            linkView->updateLink();
        applyColors();
        checkIcon(ext);
    }else if(prop==ext->getColoredElementsProperty()) {
        if(!prop->testStatus(App::Property::User3))
            applyColors();
    }else if(prop==ext->getScaleProperty() || prop==ext->getScaleVectorProperty()) {
        if(!prop->testStatus(App::Property::User3)) {
            const auto &v = ext->getScaleVector();
            if(canScale(v))
                pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
            SbMatrix matrix = convert(ext->getTransform(false));
            linkView->renderDoubleSide(matrix.det3() < 1e-7);
        }
    }else if(prop == ext->getPlacementProperty() || prop == ext->getLinkPlacementProperty()) {
        auto propLinkPlacement = ext->getLinkPlacementProperty();
        if(!propLinkPlacement || propLinkPlacement == prop) {
            const auto &pla = static_cast<const App::PropertyPlacement*>(prop)->getValue();
            ViewProviderGeometryObject::updateTransform(pla, pcTransform);
            const auto &v = ext->getScaleVector();
            if(canScale(v))
                pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
            SbMatrix matrix = convert(ext->getTransform(false));
            linkView->renderDoubleSide(matrix.det3() < 1e-7);
        }
    }else if(prop == ext->getLinkCopyOnChangeGroupProperty()) {
        if (auto group = ext->getLinkCopyOnChangeGroupValue()) {
            auto vp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
                    Application::Instance->getViewProvider(group));
            if (vp) {
                vp->hide();
                vp->ShowInTree.setValue(false);
            }
        }
    }else if(prop == ext->getLinkedObjectProperty()) {

        if(!prop->testStatus(App::Property::User3)) {
            std::vector<std::string> subs;
            const char *subname = ext->getSubName();
            std::string sub;
            if(subname)
                sub = subname;
            hasSubElement = false;
            for(const auto &s : ext->getSubElements()) {
                if(s.empty()) continue;
                hasSubElement = true;
                subs.push_back(sub+s);
            }

            if(subs.empty() && !sub.empty())
                subs.push_back(sub);

            hasSubName = !subs.empty();
            setLinkType(ext);

            auto obj = ext->getLinkedObjectValue();
            linkView->setLink(obj,subs);

            if(ext->_getShowElementValue())
                updateElementList(ext);
            else
                updateDataPrivate(ext,ext->_getElementCountProperty());

            // applyColors();
            signalChangeIcon();
        }
    }else if(prop == ext->getLinkTransformProperty()) {
        setLinkType(ext);
        applyColors();
    }else if(prop==ext->_getElementCountProperty()) {
        if(!ext->_getShowElementValue()) {
            linkView->setSize(ext->_getElementCountValue());
            updateDataPrivate(ext,ext->getVisibilityListProperty());
            updateDataPrivate(ext,ext->getPlacementListProperty());
        }
    }else if(prop == ext->_getShowElementProperty()) {
        if(!ext->_getShowElementValue()) {

            auto linked = freecad_dynamic_cast<ViewProviderDocumentObject>(getLinkedView(true,ext));
            if(linked && linked->getDocument()==getDocument())
                linked->hide();

            const auto &elements = ext->_getElementListValue();
            // elements is about to be collapsed, preserve the materials
            if(!elements.empty()) {
                std::vector<App::Material> materials;
                boost::dynamic_bitset<> overrideMaterials;
                overrideMaterials.resize(elements.size(),false);
                bool overrideMaterial = false;
                bool hasMaterial = false;
                materials.reserve(elements.size());
                for(size_t i=0;i<elements.size();++i) {
                    auto element = freecad_dynamic_cast<App::LinkElement>(elements[i]);
                    if(!element) continue;
                    auto vp = freecad_dynamic_cast<ViewProviderLink>(
                            Application::Instance->getViewProvider(element));
                    if(!vp) continue;
                    overrideMaterial = overrideMaterial || vp->OverrideMaterial.getValue();
                    hasMaterial = overrideMaterial || hasMaterial
                        || vp->ShapeMaterial.getValue()!=ShapeMaterial.getValue();
                    materials.push_back(vp->ShapeMaterial.getValue());
                    overrideMaterials[i] = vp->OverrideMaterial.getValue();
                }
                if(!overrideMaterial)
                    overrideMaterials.clear();
                OverrideMaterialList.setStatus(App::Property::User3,true);
                OverrideMaterialList.setValue(overrideMaterials);
                OverrideMaterialList.setStatus(App::Property::User3,false);
                if(!hasMaterial)
                    materials.clear();
                MaterialList.setStatus(App::Property::User3,true);
                MaterialList.setValue(materials);
                MaterialList.setStatus(App::Property::User3,false);

                linkView->setSize(ext->_getElementCountValue());
                updateDataPrivate(ext,ext->getVisibilityListProperty());
                applyMaterial();
                applyColors();
            }
        }
    }else if(prop==ext->getScaleListProperty() || prop==ext->getPlacementListProperty()) {
        if(!prop->testStatus(App::Property::User3) &&
            linkView->getSize() &&
            !ext->_getShowElementValue())
        {
            auto propPlacements = ext->getPlacementListProperty();
            auto propScales = ext->getScaleListProperty();
            if(propPlacements && linkView->getSize()) {
                const auto &touched =
                    prop==propScales?propScales->getTouchList():propPlacements->getTouchList();
                if(touched.empty()) {
                    for(int i=0;i<linkView->getSize();++i) {
                        Base::Matrix4D mat;
                        if(propPlacements && propPlacements->getSize()>i)
                            mat = (*propPlacements)[i].toMatrix();
                        if(propScales && propScales->getSize()>i && canScale((*propScales)[i])) {
                            Base::Matrix4D s;
                            s.scale((*propScales)[i]);
                            mat *= s;
                        }
                        linkView->setTransform(i,mat);
                    }
                }else{
                    for(int i : touched) {
                        if(i<0 || i>=linkView->getSize())
                            continue;
                        Base::Matrix4D mat;
                        if(propPlacements && propPlacements->getSize()>i)
                            mat = (*propPlacements)[i].toMatrix();
                        if(propScales && propScales->getSize()>i && canScale((*propScales)[i])) {
                            Base::Matrix4D s;
                            s.scale((*propScales)[i]);
                            mat *= s;
                        }
                        linkView->setTransform(i,mat);
                    }
                }
            }
        }
    }else if(prop == ext->getVisibilityListProperty()) {
        const auto &vis = ext->getVisibilityListValue();
        for(size_t i=0;i<(size_t)linkView->getSize();++i) {
            if(vis.size()>i)
                linkView->setElementVisible(i,vis[i]);
            else
                linkView->setElementVisible(i,true);
        }
    }else if(prop == ext->_getElementListProperty()) {
        if(ext->_getShowElementValue())
            updateElementList(ext);
    }
}

void ViewProviderLink::updateElementList(App::LinkBaseExtension *ext) {
    const auto &elements = ext->_getElementListValue();
    if(OverrideMaterialList.getSize() || MaterialList.getSize()) {
        int i=-1;
        for(auto obj : elements) {
            ++i;
            auto vp = freecad_dynamic_cast<ViewProviderLink>(
                    Application::Instance->getViewProvider(obj));
            if(!vp) continue;
            if(OverrideMaterialList.getSize()>i)
                vp->OverrideMaterial.setValue(OverrideMaterialList[i]);
            if(MaterialList.getSize()>i)
                vp->ShapeMaterial.setValue(MaterialList[i]);
        }
        OverrideMaterialList.setSize(0);
        MaterialList.setSize(0);
    }
    linkView->setChildren(elements, ext->getVisibilityListValue());
    applyColors();
}

void ViewProviderLink::checkIcon(const App::LinkBaseExtension *ext) {
    if(!ext) {
        ext = getLinkExtension();
        if(!ext)
            return;
    }
    const char *icon;
    auto element = freecad_dynamic_cast<App::LinkElement>(getObject());
    if(element)
        icon = _LinkElementIcon;
    else if(!ext->getLinkedObjectProperty() && ext->getElementListProperty())
        icon = _LinkGroupIcon;
    // else if(ext->_getElementCountValue())
    //     icon = _LinkArrayIcon;
    else
        icon = _LinkIcon;
    qint64 cacheKey = 0;
    if(getObject()->getLinkedObject(false)!=getObject())
        cacheKey = getOverlayPixmap().cacheKey();
    if(icon!=sPixmap || cacheKey!=overlayCacheKey) {
        sPixmap = icon;
        signalChangeIcon();
    }
}

void ViewProviderLink::applyMaterial() {
    if(OverrideMaterial.getValue())
        linkView->setMaterial(-1,&ShapeMaterial.getValue());
    else {
        for(int i=0;i<linkView->getSize();++i) {
            if(MaterialList.getSize()>i &&
               OverrideMaterialList.getSize()>i && OverrideMaterialList[i])
                linkView->setMaterial(i,&MaterialList[i]);
            else
                linkView->setMaterial(i,nullptr);
        }
        linkView->setMaterial(-1,nullptr);
    }
}

void ViewProviderLink::finishRestoring() {
    FC_TRACE("finish restoring");
    auto ext = getLinkExtension();
    if(!ext)
        return;
    linkView->setDrawStyle(DrawStyle.getValue(),LineWidth.getValue(),PointSize.getValue());
    updateDataPrivate(ext,ext->getLinkedObjectProperty());
    if(ext->getLinkPlacementProperty())
        updateDataPrivate(ext,ext->getLinkPlacementProperty());
    else
        updateDataPrivate(ext,ext->getPlacementProperty());
    updateDataPrivate(ext,ext->_getElementCountProperty());
    if(ext->getPlacementListProperty())
        updateDataPrivate(ext,ext->getPlacementListProperty());
    else
        updateDataPrivate(ext,ext->getScaleListProperty());
    updateDataPrivate(ext,ext->_getElementListProperty());
    applyMaterial();
    applyColors();

    // TODO: notify the tree. This is ugly, any other way?
    getDocument()->signalChangedObject(*this,ext->_LinkTouched);

    if(childVp)
        childVp->finishRestoring();
}

bool ViewProviderLink::hasElements(const App::LinkBaseExtension *ext) const {
    if(!ext) {
        ext = getLinkExtension();
        if(!ext)
            return false;
    }
    const auto &elements = ext->getElementListValue();
    return !elements.empty() && (int)elements.size()==ext->_getElementCountValue();
}

bool ViewProviderLink::isGroup(const App::LinkBaseExtension *ext, bool plainGroup) const {
    if(!ext) {
        ext = getLinkExtension();
        if(!ext)
            return false;
    }
    return (plainGroup && ext->linkedPlainGroup())
        || (ext->getElementListProperty() && !ext->getLinkedObjectProperty());
}

ViewProvider *ViewProviderLink::getLinkedView(
        bool real,const App::LinkBaseExtension *ext) const
{
    if(!ext)
        ext = getLinkExtension();
    auto obj = ext&&real?ext->getTrueLinkedObject(true):
        getObject()->getLinkedObject(true);
    if(obj && obj!=getObject())
        return Application::Instance->getViewProvider(obj);
    return nullptr;
}

std::vector<App::DocumentObject*> ViewProviderLink::claimChildren() const {
    auto ext = getLinkExtension();
    std::vector<App::DocumentObject*> ret;

    if(ext && !ext->_getShowElementValue() && ext->_getElementCountValue()) {
        // in array mode without element objects, we'd better not show the
        // linked object's children to avoid inconsistent behavior on selection.
        // We claim the linked object instead
        if(ext) {
            auto obj = ext->getLinkedObjectValue();
            if(obj) ret.push_back(obj);
        }
    } else if(hasElements(ext) || isGroup(ext)) {
        ret = ext->getElementListValue();
        if (ext->_getElementCountValue()
                && ext->getLinkClaimChildValue()
                && ext->getLinkedObjectValue())
            ret.insert(ret.begin(), ext->getLinkedObjectValue());
    } else if(!hasSubName) {
        auto linked = getLinkedView(true);
        if(linked) {
            ret = linked->claimChildren();
            if (ext->getLinkClaimChildValue() && ext->getLinkedObjectValue())
                ret.insert(ret.begin(), ext->getLinkedObjectValue());
        }
    }
    if (ext && ext->getLinkCopyOnChangeGroupValue())
        ret.insert(ret.begin(), ext->getLinkCopyOnChangeGroupValue());
    return ret;
}

bool ViewProviderLink::canDragObject(App::DocumentObject* obj) const {
    auto ext = getLinkExtension();
    if(isGroup(ext))
        return true;
    if(hasElements(ext))
        return false;
    auto linked = getLinkedView(false,ext);
    if(linked)
        return linked->canDragObject(obj);
    return false;
}

bool ViewProviderLink::canDragObjects() const {
    auto ext = getLinkExtension();
    if(isGroup(ext))
        return true;
    if(hasElements(ext))
        return false;
    auto linked = getLinkedView(false,ext);
    if(linked)
        return linked->canDragObjects();
    return false;
}

void ViewProviderLink::dragObject(App::DocumentObject* obj) {
    auto ext = getLinkExtension();
    if(isGroup(ext)) {
        const auto &objs = ext->getElementListValue();
        for(size_t i=0;i<objs.size();++i) {
            if(obj==objs[i]) {
                ext->setLink(i,nullptr);
                break;
            }
        }
        return;
    }
    if(hasElements(ext))
        return;
    auto linked = getLinkedView(false);
    if(linked)
        linked->dragObject(obj);
}

bool ViewProviderLink::canDropObjects() const {
    auto ext = getLinkExtension();
    if(isGroup(ext))
        return true;
    if(hasElements(ext))
        return false;
    if(hasSubElement)
        return true;
    else if(hasSubName)
        return false;
    auto linked = getLinkedView(false,ext);
    if(linked)
        return linked->canDropObjects();
    return true;
}

bool ViewProviderLink::canDropObjectEx(App::DocumentObject *obj,
        App::DocumentObject *owner, const char *subname,
        const std::vector<std::string> &subElements) const
{
    if(pcObject == obj || pcObject == owner)
        return false;
    auto ext = getLinkExtension();
    if(isGroup(ext))
        return true;
    if(!ext || !ext->getLinkedObjectProperty() || hasElements(ext))
        return false;
    if(!hasSubName && linkView->isLinked()) {
        auto linked = getLinkedView(false,ext);
        if(linked) {
            auto linkedVdp = freecad_dynamic_cast<ViewProviderDocumentObject>(linked);
            if(linkedVdp) {
                if(linkedVdp->getObject()==obj || linkedVdp->getObject()==owner)
                    return false;
            }
            return linked->canDropObjectEx(obj,owner,subname,subElements);
        }
    }
    if(obj->getDocument() != getObject()->getDocument() &&
       !freecad_dynamic_cast<App::PropertyXLink>(ext->getLinkedObjectProperty()))
        return false;

    return true;
}

std::string ViewProviderLink::dropObjectEx(App::DocumentObject* obj,
    App::DocumentObject *owner, const char *subname,
    const std::vector<std::string> &subElements)
{
    auto ext = getLinkExtension();
    if (!ext)
        return std::string();

    if(isGroup(ext)) {
        size_t size = ext->getElementListValue().size();
        ext->setLink(size,obj);
        return std::to_string(size)+".";
    }

    if(!ext->getLinkedObjectProperty() || hasElements(ext))
        return std::string();

    if(!hasSubName) {
        auto linked = getLinkedView(false,ext);
        if(linked)
            return linked->dropObjectEx(obj,owner,subname,subElements);
    }
    if(owner) {
        if(!ext->getSubElements().empty())
            ext->setLink(-1,owner,subname,subElements);
        else
            ext->setLink(-1,owner,subname);
    } else if(!ext->getSubElements().empty())
        ext->setLink(-1,obj,nullptr,subElements);
    else
        ext->setLink(-1,obj,nullptr);
    return std::string();
}

bool ViewProviderLink::canDragAndDropObject(App::DocumentObject* obj) const {
    auto ext = getLinkExtension();
    if(!ext)
        return true;
    if(isGroup(ext)) {
        return ext->getLinkModeValue()<App::LinkBaseExtension::LinkModeAutoLink &&
               obj->getDocument()==getObject()->getDocument();
    }
    if(!ext->getLinkedObjectProperty() || hasElements(ext))
        return false;
    if(!hasSubName) {
        auto linked = getLinkedView(false,ext);
        if(linked)
            return linked->canDragAndDropObject(obj);
    }
    return false;
}

bool ViewProviderLink::getElementPicked(const SoPickedPoint *pp, std::string &subname) const {
    if(!isSelectable()) {
        return false;
    }
    auto ext = getLinkExtension();
    if (!ext) {
        return false;
    }
    if(childVpLink && childVp) {
        auto path = pp->getPath();
        int idx = path->findNode(childVpLink->getSnapshot(LinkView::SnapshotTransform));
        if(idx>=0) {
            return childVp->getElementPicked(pp, subname);
        }
    }
    bool ret = linkView->linkGetElementPicked(pp,subname);
    if(!ret) {
        return ret;
    }
    if(isGroup(ext,true)) {
        const char *sub = nullptr;
        int idx = App::LinkBaseExtension::getArrayIndex(subname.c_str(),&sub);
        if(idx>=0 ) {
            --sub;
            assert(*sub == '.');
            const auto &elements = ext->_getElementListValue();
            subname.replace(0,sub-subname.c_str(),elements[idx]->getNameInDocument());
        }
    }
    return ret;
}

bool ViewProviderLink::getDetailPath(
        const char *subname, SoFullPath *pPath, bool append, SoDetail *&det) const
{
    auto ext = getLinkExtension();
    if(!ext)
        return false;

    auto len = pPath->getLength();
    if(append) {
        appendPath(pPath,pcRoot);
        appendPath(pPath,pcModeSwitch);
    }
    if(childVpLink && getDefaultMode()==1) {
        if(childVpLink->getDetail(false,LinkView::SnapshotTransform,subname,det,pPath))
            return true;
        pPath->truncate(len);
        return false;
    }
    std::string _subname;
    if(subname && subname[0]) {
        if (auto linked = ext->getLinkedObjectValue()) {
            if (const char *dot = strchr(subname,'.')) {
                if(subname[0]=='$') {
                    CharRange sub(subname+1, dot);
                    if (!boost::equals(sub, linked->Label.getValue()))
                        dot = nullptr;
                } else {
                    CharRange sub(subname, dot);
                    if (!boost::equals(sub, linked->getNameInDocument()))
                        dot = nullptr;
                }
                if (dot && linked->getSubObject(dot+1))
                    subname = dot+1;
            }
        }

        if (isGroup(ext,true) || hasElements(ext) || ext->getElementCountValue()) {
            int index = ext->getElementIndex(subname,&subname);
            if(index>=0) {
                _subname = std::to_string(index)+'.'+subname;
                subname = _subname.c_str();
            }
        }
    }
    if(linkView->linkGetDetailPath(subname,pPath,det))
        return true;
    pPath->truncate(len);
    return false;
}

bool ViewProviderLink::onDelete(const std::vector<std::string> &) {
    auto element = freecad_dynamic_cast<App::LinkElement>(getObject());
    if (element && !element->canDelete())
        return false;
    auto ext = getLinkExtension();
    if (ext->isLinkMutated()) {
        auto linked = ext->getLinkedObjectValue();
        auto doc = ext->getContainer()->getDocument();
        if (linked->getDocument() == doc) {
            std::deque<std::string> objs;
            for (auto obj : ext->getOnChangeCopyObjects(nullptr, linked)) {
                if (obj->getDocument() == doc) {
                    // getOnChangeCopyObjects() returns object in depending
                    // order. So we delete it in reverse to avoid error
                    // reported by some parent object failing to find child
                    objs.emplace_front(obj->getNameInDocument());
                }
            }
            for (const auto &name : objs)
                doc->removeObject(name.c_str());
        }
    }
    return true;
}

bool ViewProviderLink::canDelete(App::DocumentObject *obj) const {
    auto ext = getLinkExtension();
    if(isGroup(ext) || hasElements(ext) || hasSubElement)
        return true;
    auto linked = getLinkedView(false,ext);
    if(linked)
        return linked->canDelete(obj);
    return false;
}

bool ViewProviderLink::linkEdit(const App::LinkBaseExtension *ext) const {
    if(!ext)
        ext = getLinkExtension();
    if(!ext ||
       (!ext->_getShowElementValue() && ext->_getElementCountValue()) ||
       hasElements(ext) ||
       isGroup(ext) ||
       hasSubName)
    {
        return false;
    }
    return linkView->isLinked();
}

bool ViewProviderLink::doubleClicked() {
    if(linkEdit())
        return linkView->getLinkedView()->doubleClicked();
    return getDocument()->setEdit(this,ViewProvider::Transform);
}

void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const char* member)
{
    auto ext = getLinkExtension();
    if (!ext)
        return;

    _setupContextMenu(ext, menu, receiver, member);
    Gui::ActionFunction* func = nullptr;

    if (ext->isLinkedToConfigurableObject()) {
        auto src = ext->getLinkCopyOnChangeSourceValue();
        if (!src) src = ext->getLinkedObjectValue();
        if (src && !ext->getOnChangeCopyObjects(nullptr, src).empty()) {
            QAction *act = menu->addAction(
                    QObject::tr("Setup configurable object"));
            act->setToolTip(QObject::tr(
                        "Select which object to copy or exclude when configuration changes. "
                        "All external linked objects are excluded by default."));
            act->setData(-1);
            if (!func) func = new Gui::ActionFunction(menu);
            func->trigger(act, [ext](){
                try {
                    std::vector<App::DocumentObject*> excludes;
                    auto src = ext->getLinkCopyOnChangeSourceValue();
                    if (!src)
                        src = ext->getLinkedObjectValue();
                    auto objs = ext->getOnChangeCopyObjects(&excludes, src);
                    if (objs.empty())
                        return;
                    DlgObjectSelection dlg({src}, excludes, getMainWindow());
                    dlg.setMessage(QObject::tr(
                                "Please select which objects to copy when the configuration is changed"));
                    auto box = new QCheckBox(QObject::tr("Apply to all"), &dlg);
                    box->setToolTip(QObject::tr("Apply the setting to all links. Or, uncheck this\n"
                                                "option to apply only to this link."));
                    box->setChecked(App::LinkParams::getCopyOnChangeApplyToAll());
                    dlg.addCheckBox(box);
                    if(dlg.exec()!=QDialog::Accepted)
                        return;

                    bool applyAll = box->isChecked();
                    App::LinkParams::setCopyOnChangeApplyToAll(applyAll);

                    App::Link::OnChangeCopyOptions options {App::Link::OnChangeCopyOptions::None};
                    if (applyAll)
                        options |= App::Link::OnChangeCopyOptions::ApplyAll;

                    App::AutoTransaction guard("Setup configurable object");
                    auto sels = dlg.getSelections(DlgObjectSelection::SelectionOptions::InvertSort);
                    for (const auto & exclude : excludes) {
                        auto iter = std::lower_bound(sels.begin(), sels.end(), exclude);
                        if (iter == sels.end() || *iter != exclude) {
                            ext->setOnChangeCopyObject(exclude, options);
                        } else
                            sels.erase(iter);
                    }
                    options |= App::Link::OnChangeCopyOptions::Exclude;
                    for (auto obj : sels)
                        ext->setOnChangeCopyObject(obj, options);
                    if (!applyAll)
                        ext->monitorOnChangeCopyObjects(ext->getOnChangeCopyObjects());
                    else {
                        std::set<App::LinkBaseExtension*> exts;
                        for (auto o : App::Document::getDependencyList(objs)) {
                            if (auto ext = o->getExtensionByType<App::LinkBaseExtension>(true))
                                exts.insert(ext);
                        }
                        for (auto ext : exts)
                            ext->monitorOnChangeCopyObjects(ext->getOnChangeCopyObjects());
                    }
                    Command::updateActive();
                } catch (Base::Exception &e) {
                    e.ReportException();
                }
            });
        }

        if (ext->getLinkCopyOnChangeValue() == 0) {
            auto submenu = menu->addMenu(QObject::tr("Copy on change"));
            auto act = submenu->addAction(QObject::tr("Enable"));
            act->setToolTip(QObject::tr(
                        "Enable auto copy of linked object when its configuration is changed"));
            act->setData(-1);
            if (!func) func = new Gui::ActionFunction(menu);
            func->trigger(act, [ext](){
                try {
                    App::AutoTransaction guard("Enable Link copy on change");
                    ext->getLinkCopyOnChangeProperty()->setValue(1);
                    Command::updateActive();
                } catch (Base::Exception &e) {
                    e.ReportException();
                }
            });
            act = submenu->addAction(QObject::tr("Tracking"));
            act->setToolTip(QObject::tr(
                        "Copy the linked object when its configuration is changed.\n"
                        "Also auto redo the copy if the original linked object is changed.\n"));
            act->setData(-1);
            func->trigger(act, [ext](){
                try {
                    App::AutoTransaction guard("Enable Link tracking");
                    ext->getLinkCopyOnChangeProperty()->setValue(3);
                    Command::updateActive();
                } catch (Base::Exception &e) {
                    e.ReportException();
                }
            });
        }
    }

    if (ext->getLinkCopyOnChangeValue() != 2
            && ext->getLinkCopyOnChangeValue() != 0) {
        QAction *act = menu->addAction(
                QObject::tr("Disable copy on change"));
        act->setData(-1);
        if (!func) func = new Gui::ActionFunction(menu);
        func->trigger(act, [ext](){
            try {
                App::AutoTransaction guard("Disable copy on change");
                ext->getLinkCopyOnChangeProperty()->setValue((long)0);
                Command::updateActive();
            } catch (Base::Exception &e) {
                e.ReportException();
            }
        });
    }

    if (ext->isLinkMutated()) {
        QAction* act = menu->addAction(QObject::tr("Refresh configurable object"));
        act->setToolTip(QObject::tr(
                    "Synchronize the original configurable source object by\n"
                    "creating a new deep copy. Note that any changes made to\n"
                    "the current copy will be lost.\n"));
        act->setData(-1);
        if (!func) func = new Gui::ActionFunction(menu);
        func->trigger(act, [ext](){
            try {
                App::AutoTransaction guard("Link refresh");
                ext->syncCopyOnChange();
                Command::updateActive();
            } catch (Base::Exception &e) {
                e.ReportException();
            }
        });
    }
}

void ViewProviderLink::_setupContextMenu(
        App::LinkBaseExtension *ext, QMenu* menu, QObject* receiver, const char* member)
{
    if(linkEdit(ext)) {
        if (auto linkvp = Base::freecad_dynamic_cast<ViewProviderLink>(linkView->getLinkedView()))
            linkvp->_setupContextMenu(ext, menu, receiver, member);
        else
            linkView->getLinkedView()->setupContextMenu(menu,receiver,member);
    }

    if(ext->getLinkedObjectProperty()
            && ext->_getShowElementProperty()
            && ext->_getElementCountValue() > 1)
    {
        auto action = menu->addAction(QObject::tr("Toggle array elements"), [ext] {
            try {
                App::AutoTransaction guard(QT_TRANSLATE_NOOP("Command", "Toggle array elements"));
                ext->getShowElementProperty()->setValue(!ext->getShowElementValue());
                Command::updateActive();
            } catch (Base::Exception &e) {
                e.ReportException();
            }
        });
        action->setToolTip(QObject::tr(
                    "Change whether show each link array element as individual objects"));
    }

    if((ext->getPlacementProperty() && !ext->getPlacementProperty()->isReadOnly())
            || (ext->getLinkPlacementProperty() && !ext->getLinkPlacementProperty()->isReadOnly()))
    {
        bool found = false;
        const auto actions = menu->actions();
        for(auto action : actions) {
            if(action->data().toInt() == ViewProvider::Transform) {
                found = true;
                break;
            }
        }
        if (!found) {
            QIcon iconObject = mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap("Std_TransformManip.svg"));
            QAction* act = menu->addAction(iconObject, QObject::tr("Transform"), receiver, member);
            act->setToolTip(QObject::tr("Transform at the origin of the placement"));
            act->setData(QVariant((int)ViewProvider::Transform));
        }
    }

    if(ext->getColoredElementsProperty()) {
        bool found = false;
        const auto actions = menu->actions();
        for(auto action : actions) {
            if(action->data().toInt() == ViewProvider::Color) {
                action->setText(QObject::tr("Override colors..."));
                found = true;
                break;
            }
        }
        if(!found) {
            QAction* act = menu->addAction(QObject::tr("Override colors..."), receiver, member);
            act->setData(QVariant((int)ViewProvider::Color));
        }
    }

    auto cmd = Application::Instance->commandManager().getCommandByName("Std_LinkSelectLinked");
    menu->addAction(cmd->getAction()->action());
}

bool ViewProviderLink::initDraggingPlacement() {
    Base::PyGILStateLocker lock;
    try {
        auto* proxy = getPropertyByName("Proxy");
        if (proxy && proxy->is<App::PropertyPythonObject>()) {
            Py::Object feature = static_cast<App::PropertyPythonObject*>(proxy)->getValue();
            const char *fname = "initDraggingPlacement";
            if (feature.hasAttr(fname)) {
                Py::Callable method(feature.getAttr(fname));
                Py::Tuple arg;
                Py::Object ret(method.apply(arg));
                if(ret.isTuple()) {
                    PyObject *pymat,*pypla,*pybbox;
                    if(!PyArg_ParseTuple(ret.ptr(),"O!O!O!",&Base::MatrixPy::Type, &pymat,
                                &Base::PlacementPy::Type, &pypla,
                                &Base::BoundBoxPy::Type, &pybbox)) {
                        FC_ERR("initDraggingPlacement() expects return of type tuple(matrix,placement,boundbox)");
                        return false;
                    }
                    dragCtx = std::make_unique<DraggerContext>();
                    dragCtx->initialPlacement = *static_cast<Base::PlacementPy*>(pypla)->getPlacementPtr();
                    dragCtx->preTransform = *static_cast<Base::MatrixPy*>(pymat)->getMatrixPtr();
                    dragCtx->bbox = *static_cast<Base::BoundBoxPy*>(pybbox)->getBoundBoxPtr();
                    return true;
                }else if(!ret.isTrue())
                    return false;
            }
        }
    } catch (Py::Exception&) {
        Base::PyException e;
        e.ReportException();
        return false;
    }

    auto ext = getLinkExtension();
    if(!ext) {
        FC_ERR("no link extension");
        return false;
    }
    if(!ext->hasPlacement()) {
        FC_ERR("no placement");
        return false;
    }
    auto doc = Application::Instance->editDocument();
    if(!doc) {
        FC_ERR("no editing document");
        return false;
    }

    dragCtx = std::make_unique<DraggerContext>();

    dragCtx->preTransform = doc->getEditingTransform();
    doc->setEditingTransform(dragCtx->preTransform);

    const auto &pla = ext->getPlacementProperty()?
        ext->getPlacementValue():ext->getLinkPlacementValue();

    // Cancel out our own transformation from the editing transform, because
    // the dragger is meant to change our transformation.
    dragCtx->preTransform *= pla.inverse().toMatrix();

    dragCtx->bbox = getBoundingBox(nullptr,false);
    // The returned bounding box is before our own transform, but we still need
    // to scale it to get the correct center.
    auto scale = ext->getScaleVector();
    dragCtx->bbox.ScaleX(scale.x);
    dragCtx->bbox.ScaleY(scale.y);
    dragCtx->bbox.ScaleZ(scale.z);

    auto modifier = QApplication::queryKeyboardModifiers();
    // Determine the dragger base position
    // if CTRL key is down, force to use bound box center,
    // if SHIFT key is down, force to use origine,
    // if not a sub link, use origine,
    // else (e.g. group, array, sub link), use bound box center
    if(modifier != Qt::ShiftModifier
            && ((ext->getLinkedObjectValue() && !linkView->hasSubs())
                || modifier == Qt::ControlModifier))
    {
        App::PropertyPlacement *propPla = nullptr;
        if(ext->getLinkTransformValue() && ext->getLinkedObjectValue()) {
            propPla = Base::freecad_dynamic_cast<App::PropertyPlacement>(
                    ext->getLinkedObjectValue()->getPropertyByName("Placement"));
        }
        if(propPla) {
            dragCtx->initialPlacement = pla * propPla->getValue();
            dragCtx->mat *= propPla->getValue().inverse().toMatrix();
        } else
            dragCtx->initialPlacement = pla;

    } else {
        auto offset = dragCtx->bbox.GetCenter();

        // This determines the initial placement of the dragger. We place it at the
        // center of our bounding box.
        dragCtx->initialPlacement = pla * Base::Placement(offset, Base::Rotation());

        // dragCtx->mat is to transform the dragger placement to our own placement.
        // Since the dragger is placed at the center, we set the transformation by
        // moving the same amount in reverse direction.
        dragCtx->mat.move(Vector3d() - offset);
    }

    return true;
}

ViewProvider *ViewProviderLink::startEditing(int mode) {
    if(mode==ViewProvider::Color) {
        auto ext = getLinkExtension();
        if(!ext || !ext->getColoredElementsProperty()) {
            if(linkEdit(ext))
                return linkView->getLinkedView()->startEditing(mode);
        }
        return inherited::startEditing(mode);
    }

    static thread_local bool _pendingTransform;
    static thread_local Base::Matrix4D  _editingTransform;

    auto doc = Application::Instance->editDocument();

    if(mode==ViewProvider::Transform) {
        if(_pendingTransform && doc)
            doc->setEditingTransform(_editingTransform);

        if(!initDraggingPlacement())
            return nullptr;
        if(useCenterballDragger)
            pcDragger = CoinPtr<SoCenterballDragger>(new SoCenterballDragger);
        else
            pcDragger = CoinPtr<SoFCCSysDragger>(new SoFCCSysDragger);
        updateDraggingPlacement(dragCtx->initialPlacement,true);
        pcDragger->addStartCallback(dragStartCallback, this);
        pcDragger->addFinishCallback(dragFinishCallback, this);
        pcDragger->addMotionCallback(dragMotionCallback, this);
        return inherited::startEditing(mode);
    }

    if(!linkEdit()) {
        FC_ERR("unsupported edit mode " << mode);
        return nullptr;
    }

    // TODO: the 0x8000 mask here is for caller to disambiguate the intention
    // here, whether they want to, say transform the link itself or the linked
    // object. Use of a mask here will allow forwarding those editing modes that
    // are supported by both the link and the linked object, such as transform
    // and set color. We need to find a better place to declare this constant.
    mode &= ~0x8000;

    if(!doc) {
        FC_ERR("no editing document");
        return nullptr;
    }

    // We are forwarding the editing request to linked object. We need to
    // adjust the editing transformation.
    Base::Matrix4D mat;
    auto linked = getObject()->getLinkedObject(true,&mat,false);
    if(!linked || linked==getObject()) {
        FC_ERR("no linked object");
        return nullptr;
    }
    auto vpd = freecad_dynamic_cast<ViewProviderDocumentObject>(
                Application::Instance->getViewProvider(linked));
    if(!vpd) {
        FC_ERR("no linked viewprovider");
        return nullptr;
    }
    // Amend the editing transformation with the link transformation.
    // But save it first in case the linked object reroute the editing request
    // back to us.
    _editingTransform = doc->getEditingTransform();
    doc->setEditingTransform(doc->getEditingTransform()*mat);
    Base::FlagToggler<> guard(_pendingTransform);
    return vpd->startEditing(mode);
}

bool ViewProviderLink::setEdit(int ModNum)
{
    if (ModNum == ViewProvider::Color) {
        auto ext = getLinkExtension();
        if(!ext || !ext->getColoredElementsProperty())
            return false;
        TaskView::TaskDialog *dlg = Control().activeDialog();
        if (dlg) {
            Control().showDialog(dlg);
            return false;
        }
        Selection().clearSelection();
        return true;
    }
    return inherited::setEdit(ModNum);
}

void ViewProviderLink::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum)
{
    if (ModNum == ViewProvider::Color) {
        Gui::Control().showDialog(new TaskElementColors(this));
        return;
    }

    if (pcDragger && viewer)
    {
        auto rootPickStyle = new SoPickStyle();
        rootPickStyle->style = SoPickStyle::UNPICKABLE;
        static_cast<SoFCUnifiedSelection*>(
                viewer->getSceneGraph())->insertChild(rootPickStyle, 0);

        if(useCenterballDragger) {
            auto dragger = static_cast<SoCenterballDragger*>(pcDragger.get());
            auto group = new SoAnnotation;
            auto pickStyle = new SoPickStyle;
            pickStyle->setOverride(true);
            group->addChild(pickStyle);
            group->addChild(pcDragger);

            // Because the dragger is not grouped with the actual geometry,
            // we use an invisible cube sized by the bounding box obtained from
            // initDraggingPlacement() to scale the centerball dragger properly

            auto * ss = static_cast<SoSurroundScale*>(dragger->getPart("surroundScale", TRUE));
            ss->numNodesUpToContainer = 3;
            ss->numNodesUpToReset = 2;

            auto *geoGroup = new SoGroup;
            group->addChild(geoGroup);
            auto *style = new SoDrawStyle;
            style->style.setValue(SoDrawStyle::INVISIBLE);
            style->setOverride(TRUE);
            geoGroup->addChild(style);
            auto *cube = new SoCube;
            geoGroup->addChild(cube);
            auto length = std::max(std::max(dragCtx->bbox.LengthX(),
                        dragCtx->bbox.LengthY()), dragCtx->bbox.LengthZ());
            cube->width = length;
            cube->height = length;
            cube->depth = length;

            viewer->setupEditingRoot(group,&dragCtx->preTransform);
        } else {
            auto dragger = static_cast<SoFCCSysDragger*>(pcDragger.get());
            dragger->draggerSize.setValue(ViewParams::instance()->getDraggerScale());
            dragger->setUpAutoScale(viewer->getSoRenderManager()->getCamera());
            viewer->setupEditingRoot(pcDragger,&dragCtx->preTransform);

            auto task = new TaskCSysDragger(this, dragger);
            Gui::Control().showDialog(task);
        }
    }
}

void ViewProviderLink::unsetEditViewer(Gui::View3DInventorViewer* viewer)
{
    SoNode *child = static_cast<SoFCUnifiedSelection*>(viewer->getSceneGraph())->getChild(0);
    if (child && child->isOfType(SoPickStyle::getClassTypeId()))
        static_cast<SoFCUnifiedSelection*>(viewer->getSceneGraph())->removeChild(child);
    pcDragger.reset();
    dragCtx.reset();
    Gui::Control().closeDialog();
}

Base::Placement ViewProviderLink::currentDraggingPlacement() const
{
    // if there isn't an active dragger return a default placement
    if (!pcDragger)
        return Base::Placement();

    SbVec3f v;
    SbRotation r;
    if (useCenterballDragger) {
        auto dragger = static_cast<SoCenterballDragger*>(pcDragger.get());
        v = dragger->center.getValue();
        r = dragger->rotation.getValue();
    }
    else {
        auto dragger = static_cast<SoFCCSysDragger*>(pcDragger.get());
        v = dragger->translation.getValue();
        r = dragger->rotation.getValue();
    }

    float q1,q2,q3,q4;
    r.getValue(q1,q2,q3,q4);
    return Base::Placement(Base::Vector3d(v[0],v[1],v[2]),Base::Rotation(q1,q2,q3,q4));
}

void ViewProviderLink::enableCenterballDragger(bool enable) {
    if(enable == useCenterballDragger)
        return;
    if(pcDragger)
        LINK_THROW(Base::RuntimeError,"Cannot change dragger during dragging");
    useCenterballDragger = enable;
}

void ViewProviderLink::updateDraggingPlacement(const Base::Placement &pla, bool force) {
    if(pcDragger && (force || currentDraggingPlacement()!=pla)) {
        const auto &pos = pla.getPosition();
        const auto &rot = pla.getRotation();
        FC_LOG("updating dragger placement (" << pos.x << ", " << pos.y << ", " << pos.z << ')');
        if(useCenterballDragger) {
            auto dragger = static_cast<SoCenterballDragger*>(pcDragger.get());
            SbBool wasenabled = dragger->enableValueChangedCallbacks(FALSE);
            SbMatrix matrix;
            matrix = convert(pla.toMatrix());
            dragger->center.setValue(SbVec3f(0,0,0));
            dragger->setMotionMatrix(matrix);
            if (wasenabled) {
                dragger->enableValueChangedCallbacks(TRUE);
                dragger->valueChanged();
            }
        }else{
            auto dragger = static_cast<SoFCCSysDragger*>(pcDragger.get());
            dragger->translation.setValue(SbVec3f(pos.x,pos.y,pos.z));
            dragger->rotation.setValue(rot[0],rot[1],rot[2],rot[3]);
        }
    }
}

bool ViewProviderLink::callDraggerProxy(const char *fname, bool update) {
    if(!pcDragger)
        return false;
    Base::PyGILStateLocker lock;
    try {
        auto* proxy = getPropertyByName("Proxy");
        if (proxy && proxy->is<App::PropertyPythonObject>()) {
            Py::Object feature = static_cast<App::PropertyPythonObject*>(proxy)->getValue();
            if (feature.hasAttr(fname)) {
                Py::Callable method(feature.getAttr(fname));
                Py::Tuple args;
                if(method.apply(args).isTrue())
                    return true;
            }
        }
    } catch (Py::Exception&) {
        Base::PyException e;
        e.ReportException();
        return true;
    }

    if(update) {
        auto ext = getLinkExtension();
        if(ext) {
            const auto &pla = currentDraggingPlacement();
            auto prop = ext->getLinkPlacementProperty();
            if(!prop)
                prop = ext->getPlacementProperty();
            if(prop) {
                auto plaNew = pla * Base::Placement(dragCtx->mat);
                if(prop->getValue()!=plaNew)
                    prop->setValue(plaNew);
            }
            updateDraggingPlacement(pla);
        }
    }
    return false;
}

void ViewProviderLink::dragStartCallback(void *data, SoDragger *) {
    auto me = static_cast<ViewProviderLink*>(data);
    me->dragCtx->initialPlacement = me->currentDraggingPlacement();
    if(!me->callDraggerProxy("onDragStart",false)) {
        me->dragCtx->cmdPending = true;
        me->getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Link Transform"));
    }else
        me->dragCtx->cmdPending = false;
}

void ViewProviderLink::dragFinishCallback(void *data, SoDragger *) {
    auto me = static_cast<ViewProviderLink*>(data);
    me->callDraggerProxy("onDragEnd",true);
    if(me->dragCtx->cmdPending) {
        if(me->currentDraggingPlacement() == me->dragCtx->initialPlacement)
            me->getDocument()->abortCommand();
        else
            me->getDocument()->commitCommand();
    }
}

void ViewProviderLink::dragMotionCallback(void *data, SoDragger *) {
    auto me = static_cast<ViewProviderLink*>(data);
    me->callDraggerProxy("onDragMotion",true);
}

void ViewProviderLink::updateLinks(ViewProvider *vp) {
    try {
        auto ext = vp->getExtensionByType<ViewProviderLinkObserver>(true);
        if (ext && ext->linkInfo)
            ext->linkInfo->update();
    }
    catch (const Base::TypeError &e) {
        e.ReportException();
    }
    catch (const Base::ValueError &e) {
        e.ReportException();
    }
}

PyObject *ViewProviderLink::getPyObject() {
    if (!pyViewObject)
        pyViewObject = new ViewProviderLinkPy(this);
    pyViewObject->IncRef();
    return pyViewObject;
}

PyObject *ViewProviderLink::getPyLinkView() {
    return linkView->getPyObject();
}

std::map<std::string, App::Color> ViewProviderLink::getElementColors(const char *subname) const {
    bool isPrefix = true;
    if(!subname)
        subname = "";
    else {
        auto len = strlen(subname);
        isPrefix = !len || subname[len-1]=='.';
    }
    std::map<std::string, App::Color> colors;
    auto ext = getLinkExtension();
    if(!ext || ! ext->getColoredElementsProperty())
        return colors;
    const auto &subs = ext->getColoredElementsProperty()->getShadowSubs();
    int size = OverrideColorList.getSize();

    std::string wildcard(subname);
    if(wildcard == "Face" || wildcard == "Face*" || wildcard.empty()) {
        if(wildcard.size()==4 || OverrideMaterial.getValue()) {
            App::Color c = ShapeMaterial.getValue().diffuseColor;
            c.a = ShapeMaterial.getValue().transparency;
            colors["Face"] = c;
            if(wildcard.size()==4)
                return colors;
        }
        if(!wildcard.empty())
            wildcard.resize(4);
    }else if(wildcard == "Edge*")
        wildcard.resize(4);
    else if(wildcard == "Vertex*")
        wildcard.resize(5);
    else if(wildcard == ViewProvider::hiddenMarker()+"*")
        wildcard.resize(ViewProvider::hiddenMarker().size());
    else
        wildcard.clear();

    int i=-1;
    if(!wildcard.empty()) {
        for(const auto &sub : subs) {
            if(++i >= size)
                break;
            auto pos = sub.oldName.rfind('.');
            if(pos == std::string::npos)
                pos = 0;
            else
                ++pos;
            const char *element = sub.oldName.c_str()+pos;
            if(boost::starts_with(element,wildcard))
                colors[sub.oldName] = OverrideColorList[i];
            else if(!element[0] && wildcard=="Face")
                colors[sub.oldName.substr(0,element-sub.oldName.c_str())+wildcard] = OverrideColorList[i];
        }

        // In case of multi-level linking, we recursively call into each level,
        // and merge the colors
        auto vp = this;
        while(true) {
            if(wildcard!=ViewProvider::hiddenMarker() && vp->OverrideMaterial.getValue()) {
                auto color = ShapeMaterial.getValue().diffuseColor;
                color.a = ShapeMaterial.getValue().transparency;
                colors.emplace(wildcard,color);
            }
            auto link = vp->getObject()->getLinkedObject(false);
            if(!link || link==vp->getObject())
                break;
            auto next = freecad_dynamic_cast<ViewProviderLink>(
                    Application::Instance->getViewProvider(link));
            if(!next)
                break;
            for(const auto &v : next->getElementColors(subname))
                colors.insert(v);
            vp = next;
        }
        if(wildcard!=ViewProvider::hiddenMarker()) {
            // Get collapsed array color override.
            auto ext = vp->getLinkExtension();
            if(ext->_getElementCountValue() && !ext->_getShowElementValue()) {
                const auto &overrides = vp->OverrideMaterialList.getValues();
                int i=-1;
                for(const auto &mat : vp->MaterialList.getValues()) {
                    if(++i>=(int)overrides.size())
                        break;
                    if(!overrides[i])
                        continue;
                    auto color = mat.diffuseColor;
                    color.a = mat.transparency;
                    colors.emplace(std::to_string(i)+"."+wildcard,color);
                }
            }
        }
        return colors;
    }

    int element_count = ext->getElementCountValue();

    for(const auto &sub : subs) {
        if(++i >= size)
            break;

        int offset = 0;

        if(!sub.oldName.empty() && element_count && !std::isdigit(sub.oldName[0])) {
            // For checking and expanding color override of array base
            if(!subname[0]) {
                std::ostringstream ss;
                ss << "0." << sub.oldName;
                if(getObject()->getSubObject(ss.str().c_str())) {
                    for(int j=0;j<element_count;++j) {
                        ss.str("");
                        ss << j << '.' << sub.oldName;
                        colors.emplace(ss.str(),OverrideColorList[i]);
                    }
                    continue;
                }
            } else if (std::isdigit(subname[0])) {
                const char *dot = strchr(subname,'.');
                if(dot)
                    offset = dot-subname+1;
            }
        }

        if(isPrefix) {
            if(!boost::starts_with(sub.newName,subname+offset)
                    && !boost::starts_with(sub.oldName,subname+offset))
                continue;
        }else if(sub.newName!=subname+offset && sub.oldName!=subname+offset)
            continue;

        if(offset)
            colors.emplace(std::string(subname,offset)+sub.oldName, OverrideColorList[i]);
        else
            colors[sub.oldName] = OverrideColorList[i];
    }

    if(!subname[0])
        return colors;

    bool found = true;
    if(colors.empty()) {
        found = false;
        colors.emplace(subname,App::Color());
    }
    std::map<std::string, App::Color> ret;
    for(const auto &v : colors) {
        const char *pos = nullptr;
        auto sobj = getObject()->resolve(v.first.c_str(),nullptr,nullptr,&pos);
        if(!sobj || !pos)
            continue;
        auto link = sobj->getLinkedObject(true);
        if(!link || link==getObject())
            continue;
        auto vp = Application::Instance->getViewProvider(sobj->getLinkedObject(true));
        if(!vp)
            continue;
        for(const auto &v2 : vp->getElementColors(!pos[0]?"Face":pos)) {
            std::string name;
            if(pos[0])
                name = v.first.substr(0,pos-v.first.c_str())+v2.first;
            else
                name = v.first;
            ret[name] = found?v.second:v2.second;
        }
    }
    return ret;
}

void ViewProviderLink::setElementColors(const std::map<std::string, App::Color> &colorMap) {
    auto ext = getLinkExtension();
    if(!ext || ! ext->getColoredElementsProperty())
        return;

    // For checking and collapsing array element color
    std::map<std::string,std::map<int,App::Color> > subMap;
    int element_count = ext->getElementCountValue();

    std::vector<std::string> subs;
    std::vector<App::Color> colors;
    App::Color faceColor;
    bool hasFaceColor = false;
    for(const auto &v : colorMap) {
        if(!hasFaceColor && v.first == "Face") {
            hasFaceColor = true;
            faceColor = v.second;
            continue;
        }

        if(element_count && !v.first.empty() && std::isdigit(v.first[0])) {
            // In case of array, check if there are override of the same
            // sub-element for every array element. And collapse those overrides
            // into one without the index.
            const char *dot = strchr(v.first.c_str(),'.');
            if(dot) {
                subMap[dot+1][std::atoi(v.first.c_str())] = v.second;
                continue;
            }
        }
        subs.push_back(v.first);
        colors.push_back(v.second);
    }
    for(auto &v : subMap) {
        if(element_count == (int)v.second.size()) {
            App::Color firstColor = v.second.begin()->second;
            subs.push_back(v.first);
            colors.push_back(firstColor);
            for(auto it=v.second.begin();it!=v.second.end();) {
                if(it->second==firstColor)
                    it = v.second.erase(it);
                else
                    ++it;
            }
        }
        std::ostringstream ss;
        for(const auto &colorInfo : v.second) {
            ss.str("");
            ss << colorInfo.first << '.' << v.first;
            subs.push_back(ss.str());
            colors.push_back(colorInfo.second);
        }
    }

    auto prop = ext->getColoredElementsProperty();
    if(subs!=prop->getSubValues() || colors!=OverrideColorList.getValues()) {
        prop->setStatus(App::Property::User3,true);
        prop->setValue(getObject(),subs);
        prop->setStatus(App::Property::User3,false);
        OverrideColorList.setValues(colors);
    }
    if(hasFaceColor) {
        auto mat = ShapeMaterial.getValue();
        mat.diffuseColor = faceColor;
        mat.transparency = faceColor.a;
        ShapeMaterial.setStatus(App::Property::User3,true);
        ShapeMaterial.setValue(mat);
        ShapeMaterial.setStatus(App::Property::User3,false);
    }
    OverrideMaterial.setValue(hasFaceColor);
}

void ViewProviderLink::applyColors() {
    auto ext = getLinkExtension();
    if(!ext || ! ext->getColoredElementsProperty())
        return;

    SoSelectionElementAction action(SoSelectionElementAction::Color,true);
    // reset color and visibility first
    action.apply(linkView->getLinkRoot());

    std::map<std::string, std::map<std::string,App::Color> > colorMap;
    std::set<std::string> hideList;
    auto colors = getElementColors();
    colors.erase("Face");
    for(const auto &v : colors) {
        const char *subname = v.first.c_str();
        const char *element = nullptr;
        auto sobj = getObject()->resolve(subname,nullptr,nullptr,&element);
        if(!sobj || !element)
            continue;
        if(ViewProvider::hiddenMarker() == element)
            hideList.emplace(subname,element-subname);
        else
            colorMap[std::string(subname,element-subname)][element] = v.second;
    }

    SoTempPath path(10);
    path.ref();
    for(auto &v : colorMap) {
        action.swapColors(v.second);
        if(v.first.empty()) {
            action.apply(linkView->getLinkRoot());
            continue;
        }
        SoDetail *det=nullptr;
        path.truncate(0);
        if(getDetailPath(v.first.c_str(), &path, false, det))
            action.apply(&path);
        delete det;
    }

    action.setType(SoSelectionElementAction::Hide);
    for(const auto &sub : hideList) {
        SoDetail *det=nullptr;
        path.truncate(0);
        if(!sub.empty() && getDetailPath(sub.c_str(), &path, false, det))
            action.apply(&path);
        delete det;
    }
    path.unrefNoDelete();
}

void ViewProviderLink::setOverrideMode(const std::string &mode) {
    auto ext = getLinkExtension();
    if(!ext)
        return;
    auto obj = ext->getTrueLinkedObject(false);
    if(obj && obj!=getObject()) {
        auto vp = Application::Instance->getViewProvider(obj);
        vp->setOverrideMode(mode);
    }
    if(childVp)
        childVp->setOverrideMode(mode);
}

void ViewProviderLink::onBeforeChange(const App::Property *prop) {
    if(prop == &ChildViewProvider) {
        if(childVp) {
            childVp->beforeDelete();
            pcModeSwitch->replaceChild(1,linkView->getLinkRoot());
            childVpLink.reset();
            childVp = nullptr;
        }
    }
    inherited::onBeforeChange(prop);
}

static bool isExcludedProperties(const char *name) {
#define CHECK_EXCLUDE_PROP(_name) if(strcmp(name,#_name)==0) return true;
    CHECK_EXCLUDE_PROP(Proxy);
    return false;
}

App::Property *ViewProviderLink::getPropertyByName(const char *name) const {
    auto prop = inherited::getPropertyByName(name);
    if(prop || isExcludedProperties(name))
        return prop;
    if(childVp) {
        prop = childVp->getPropertyByName(name);
        if(prop && !prop->testStatus(App::Property::Hidden))
            return prop;
        prop = nullptr;
    }
    if(pcObject && pcObject->canLinkProperties()) {
        auto linked = getLinkedViewProvider(nullptr,true);
        if(linked && linked!=this)
            prop = linked->getPropertyByName(name);
    }
    return prop;
}

void ViewProviderLink::getPropertyMap(std::map<std::string,App::Property*> &Map) const {
    inherited::getPropertyMap(Map);
    if(!childVp)
        return;
    std::map<std::string,App::Property*> childMap;
    childVp->getPropertyMap(childMap);
    for(const auto &v : childMap) {
        auto ret = Map.insert(v);
        if(!ret.second) {
            auto myProp = ret.first->second;
            if(myProp->testStatus(App::Property::Hidden))
                ret.first->second = v.second;
        }
    }
}

void ViewProviderLink::getPropertyList(std::vector<App::Property*> &List) const {
    std::map<std::string,App::Property*> Map;
    getPropertyMap(Map);
    List.reserve(List.size()+Map.size());
    for(const auto &v:Map)
        List.push_back(v.second);
}

ViewProviderDocumentObject *ViewProviderLink::getLinkedViewProvider(
        std::string *subname, bool recursive) const
{
    auto self = const_cast<ViewProviderLink*>(this);
    auto ext = getLinkExtension();
    if(!ext)
        return self;
    App::DocumentObject *linked = nullptr;
    if(!recursive) {
        linked = ext->getLink();
        const char *s = ext->getSubName();
        if(subname && s)
            *subname = s;
    } else
        linked = ext->getTrueLinkedObject(recursive);
    if(!linked)
        return self;
    auto res = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
            Application::Instance->getViewProvider(linked));
    if(res)
        return res;
    return self;
}

void ViewProviderLink::setTransformation(const Base::Matrix4D &rcMatrix)
{
    inherited::setTransformation(rcMatrix);
    auto ext = getLinkExtension();
    if(ext) {
        if (ext->getScaleVectorProperty())
            updateDataPrivate(getLinkExtension(),ext->getScaleVectorProperty());
        else
            updateDataPrivate(getLinkExtension(),ext->getScaleProperty());
    }
}

void ViewProviderLink::setTransformation(const SbMatrix &rcMatrix)
{
    inherited::setTransformation(rcMatrix);
    auto ext = getLinkExtension();
    if(ext) {
        if (ext->getScaleVectorProperty())
            updateDataPrivate(getLinkExtension(),ext->getScaleVectorProperty());
        else
            updateDataPrivate(getLinkExtension(),ext->getScaleProperty());
    }
}

////////////////////////////////////////////////////////////////////////////////////////

namespace Gui {
PROPERTY_SOURCE_TEMPLATE(Gui::ViewProviderLinkPython, Gui::ViewProviderLink)
template class GuiExport ViewProviderFeaturePythonT<ViewProviderLink>;
}
