FreeCAD

Форк
0
/
PropertyLinks.cpp 
4762 строки · 162.8 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de>              *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 of the License, or (at your option) any later version.      *
10
 *                                                                         *
11
 *   This library  is distributed in the hope that it will be useful,      *
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 *   GNU Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23

24
#include "PreCompiled.h"
25

26
#include <QDir>
27
#include <QFileInfo>
28
#include <boost/algorithm/string/predicate.hpp>
29

30
#include <Base/Console.h>
31
#include <Base/Exception.h>
32
#include <Base/Reader.h>
33
#include <Base/Writer.h>
34

35
#include "PropertyLinks.h"
36
#include "Application.h"
37
#include "Document.h"
38
#include "DocumentObject.h"
39
#include "DocumentObjectPy.h"
40
#include "ObjectIdentifier.h"
41

42

43
FC_LOG_LEVEL_INIT("PropertyLinks",true,true)
44

45
using namespace App;
46
using namespace Base;
47
using namespace std;
48
namespace sp = std::placeholders;
49

50
//**************************************************************************
51
//**************************************************************************
52
// PropertyLinkBase
53
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
54

55
TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyLinkBase , App::Property)
56

57
static std::unordered_map<std::string, std::set<PropertyLinkBase*> > _LabelMap;
58
PropertyLinkBase::PropertyLinkBase() = default;
59

60
PropertyLinkBase::~PropertyLinkBase() {
61
    unregisterLabelReferences();
62
    unregisterElementReference();
63
}
64

65
void PropertyLinkBase::setAllowExternal(bool allow) {
66
    setFlag(LinkAllowExternal,allow);
67
}
68

69
void PropertyLinkBase::hasSetValue() {
70
    auto owner = dynamic_cast<DocumentObject*>(getContainer());
71
    if(owner)
72
        owner->clearOutListCache();
73
    Property::hasSetValue();
74
}
75

76
bool PropertyLinkBase::isSame(const Property &other) const
77
{
78
    if(&other == this)
79
        return true;
80
    if(other.isDerivedFrom(PropertyLinkBase::getClassTypeId())
81
        || getScope() != static_cast<const PropertyLinkBase*>(&other)->getScope())
82
        return false;
83

84
    static std::vector<App::DocumentObject*> ret;
85
    static std::vector<std::string> subs;
86
    static std::vector<App::DocumentObject*> ret2;
87
    static std::vector<std::string> subs2;
88

89
    ret.clear();
90
    subs.clear();
91
    ret2.clear();
92
    subs2.clear();
93
    getLinks(ret,true,&subs,false);
94
    static_cast<const PropertyLinkBase *>(&other)->getLinks(ret2,true,&subs2,true);
95

96
    return ret==ret2 && subs==subs2;
97
}
98

99
void PropertyLinkBase::unregisterElementReference() {
100
}
101

102
void PropertyLinkBase::unregisterLabelReferences()
103
{
104
    for(auto &label : _LabelRefs) {
105
        auto it = _LabelMap.find(label);
106
        if(it!=_LabelMap.end()) {
107
            it->second.erase(this);
108
            if(it->second.empty())
109
                _LabelMap.erase(it);
110
        }
111
    }
112
    _LabelRefs.clear();
113
}
114

115
void PropertyLinkBase::getLabelReferences(std::vector<std::string> &subs,const char *subname) {
116
    const char *dot;
117
    for (; (subname = strchr(subname, '$')) != nullptr; subname = dot + 1) {
118
        ++subname;
119
        dot = strchr(subname,'.');
120
        if(!dot) break;
121
        subs.emplace_back(subname,dot-subname);
122
    }
123
}
124

125
void PropertyLinkBase::registerLabelReferences(std::vector<std::string> &&labels, bool reset) {
126
    if(reset)
127
        unregisterLabelReferences();
128
    for(auto &label : labels) {
129
        auto res = _LabelRefs.insert(std::move(label));
130
        if(res.second)
131
            _LabelMap[*res.first].insert(this);
132
    }
133
}
134

135
void PropertyLinkBase::checkLabelReferences(const std::vector<std::string> &subs, bool reset) {
136
    if(reset)
137
        unregisterLabelReferences();
138
    std::vector<std::string> labels;
139
    for(auto &sub : subs) {
140
        labels.clear();
141
        getLabelReferences(labels,sub.c_str());
142
        registerLabelReferences(std::move(labels),false);
143
    }
144
}
145

146
std::string PropertyLinkBase::updateLabelReference(const App::DocumentObject *parent,
147
        const char *subname, App::DocumentObject *obj, const std::string &ref, const char *newLabel)
148
{
149
    if(!obj || !obj->isAttachedToDocument() || !parent || !parent->isAttachedToDocument())
150
        return {};
151

152
    // Because the label is allowed to be the same across different
153
    // hierarchies, we have to search for all occurrences, and make sure the
154
    // referenced sub-object at the found hierarchy is actually the given
155
    // object.
156
    for (const char *pos = subname; ((pos = strstr(pos, ref.c_str())) != nullptr); pos += ref.size()) {
157
        auto sub = std::string(subname,pos+ref.size()-subname);
158
        auto sobj = parent->getSubObject(sub.c_str());
159
        if(sobj == obj) {
160
            sub = subname;
161
            sub.replace(pos+1-subname,ref.size()-2,newLabel);
162
            return sub;
163
        }
164
    }
165
    return {};
166
}
167

168
std::vector<std::pair<Property*, std::unique_ptr<Property> > >
169
PropertyLinkBase::updateLabelReferences(App::DocumentObject *obj, const char *newLabel)
170
{
171
    std::vector<std::pair<Property*,std::unique_ptr<Property> > >  ret;
172
    if(!obj || !obj->isAttachedToDocument())
173
        return ret;
174
    auto it = _LabelMap.find(obj->Label.getStrValue());
175
    if(it == _LabelMap.end())
176
        return ret;
177
    std::string ref("$");
178
    ref += obj->Label.getValue();
179
    ref += '.';
180
    std::vector<PropertyLinkBase*> props;
181
    props.reserve(it->second.size());
182
    props.insert(props.end(),it->second.begin(),it->second.end());
183
    for(auto prop : props) {
184
        if(!prop->getContainer())
185
            continue;
186
        std::unique_ptr<Property> copy(prop->CopyOnLabelChange(obj,ref,newLabel));
187
        if(copy)
188
            ret.emplace_back(prop,std::move(copy));
189
    }
190
    return ret;
191
}
192

193
static std::string propertyName(const Property *prop) {
194
    if(!prop)
195
        return {};
196
    if(!prop->getContainer() || !prop->hasName()) {
197
        auto xlink = Base::freecad_dynamic_cast<const PropertyXLink>(prop);
198
        if(xlink)
199
            return propertyName(xlink->parent());
200
    }
201
    return prop->getFullName();
202
}
203

204
void PropertyLinkBase::updateElementReferences(DocumentObject *feature, bool reverse) {
205
    (void)feature;
206
    (void)reverse;
207
}
208

209
void PropertyLinkBase::_registerElementReference(App::DocumentObject *obj, std::string &sub, ShadowSub &shadow)
210
{
211
    (void)obj;
212
    (void)sub;
213
    (void)shadow;
214
}
215

216
class StringGuard {
217
public:
218
    explicit StringGuard(char *c)
219
        :c(c)
220
    {
221
        v1 = c[0];
222
        v2 = c[1];
223
        c[0] = '.';
224
        c[1] = 0;
225
    }
226
    ~StringGuard()
227
    {
228
        c[0] = v1;
229
        c[1] = v2;
230
    }
231

232
    char *c;
233
    char v1;
234
    char v2;
235
};
236

237
void PropertyLinkBase::restoreLabelReference(const DocumentObject *obj,
238
        std::string &subname, ShadowSub *shadow)
239
{
240
    std::ostringstream ss;
241
    char *sub = &subname[0];
242
    char *next=sub;
243
    for(char *dot=strchr(next,'.');dot;next=dot+1,dot=strchr(next,'.')) {
244
        if(dot!=next && dot[-1]!='@')
245
            continue;
246
        DocumentObject *sobj;
247
        try {
248
            StringGuard guard(dot-1);
249
            sobj = obj->getSubObject(subname.c_str());
250
            if(!sobj) {
251
                FC_ERR("Failed to restore label reference " << obj->getFullName()
252
                        << '.' << ss.str());
253
                return;
254
            }
255
        }catch(...){
256
            throw;
257
        }
258
        ss.write(sub,next-sub);
259
        ss << '$' << sobj->Label.getStrValue() << '.';
260
        sub = dot+1;
261
    }
262
    if(sub == subname.c_str())
263
        return;
264

265
    size_t count = sub-subname.c_str();
266
    const auto &newSub = ss.str();
267
    if(shadow && shadow->second.size()>=count)
268
        shadow->second = newSub + (shadow->second.c_str()+count);
269
    if(shadow && shadow->first.size()>=count)
270
        shadow->first = newSub + (shadow->first.c_str()+count);
271
    subname = newSub + sub;
272
}
273

274
bool PropertyLinkBase::_updateElementReference(DocumentObject *feature,
275
        App::DocumentObject *obj, std::string &sub, ShadowSub &shadow,
276
        bool reverse, bool notify)
277
{
278
    (void)feature;
279
    (void)obj;
280
    (void)reverse;
281
    (void)notify;
282
    shadow.second = sub;
283
    return false;
284
}
285

286
std::pair<DocumentObject*, std::string>
287
PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject *obj,
288
    const DocumentObject *parent, DocumentObject *oldObj, DocumentObject *newObj, const char *subname)
289
{
290
    std::pair<DocumentObject*, std::string> res;
291
    res.first = 0;
292

293
    if(oldObj == obj) {
294
        if(owner == parent) {
295
            res.first = newObj;
296
            if(subname) res.second = subname;
297
            return res;
298
        }
299
        return res;
300
    }
301
    if(!subname || !subname[0])
302
        return res;
303

304
    App::DocumentObject *prev = obj;
305
    std::size_t prevPos = 0;
306
    std::string sub = subname;
307
    for(auto pos=sub.find('.');pos!=std::string::npos;pos=sub.find('.',pos)) {
308
        ++pos;
309
        char c = sub[pos];
310
        sub[pos] = 0;
311
        auto sobj = obj->getSubObject(sub.c_str());
312
        sub[pos] = c;
313
        if(!sobj)
314
            break;
315
        if(sobj == oldObj) {
316
            if(prev == parent) {
317
                if(sub[prevPos] == '$')
318
                    sub.replace(prevPos+1,pos-1-prevPos,newObj->Label.getValue());
319
                else
320
                    sub.replace(prevPos,pos-1-prevPos,newObj->getNameInDocument());
321
                res.first = obj;
322
                res.second = std::move(sub);
323
                return res;
324
            }
325
            break;
326
        }else if(prev == parent)
327
            break;
328
        prev = sobj;
329
        prevPos = pos;
330
    }
331
    return res;
332
}
333

334
std::pair<DocumentObject*,std::vector<std::string> >
335
PropertyLinkBase::tryReplaceLinkSubs(const PropertyContainer *owner,
336
        DocumentObject *obj, const DocumentObject *parent, DocumentObject *oldObj,
337
        DocumentObject *newObj, const std::vector<std::string> &subs)
338
{
339
    std::pair<DocumentObject*,std::vector<std::string> > res;
340
    res.first = 0;
341

342
    auto r = tryReplaceLink(owner,obj,parent,oldObj,newObj);
343
    if(r.first) {
344
        res.first = r.first;
345
        res.second = subs;
346
        return res;
347
    }
348
    for(auto it=subs.begin();it!=subs.end();++it) {
349
        auto r = tryReplaceLink(owner,obj,parent,oldObj,newObj,it->c_str());
350
        if(r.first) {
351
            if(!res.first) {
352
                res.first = r.first;
353
                res.second.insert(res.second.end(),subs.begin(),it);
354
            }
355
            res.second.push_back(std::move(r.second));
356
        }else if(res.first)
357
            res.second.push_back(*it);
358
    }
359
    return res;
360
}
361

362
//**************************************************************************
363
//**************************************************************************
364
// PropertyLinkListBase
365
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
366

367
TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyLinkListBase , App::PropertyLinkBase)
368

369
//**************************************************************************
370
//**************************************************************************
371
// PropertyLink
372
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
373

374
TYPESYSTEM_SOURCE(App::PropertyLink, App::PropertyLinkBase)
375
TYPESYSTEM_SOURCE(App::PropertyLinkChild , App::PropertyLink)
376
TYPESYSTEM_SOURCE(App::PropertyLinkGlobal , App::PropertyLink)
377
TYPESYSTEM_SOURCE(App::PropertyLinkHidden , App::PropertyLink)
378

379
//**************************************************************************
380
// Construction/Destruction
381

382

383
PropertyLink::PropertyLink() = default;
384

385
PropertyLink::~PropertyLink()
386
{
387
    resetLink();
388
}
389

390
//**************************************************************************
391
// Base class implementer
392

393
void PropertyLink::resetLink() {
394
    //in case this property gets dynamically removed
395
#ifndef USE_OLD_DAG
396
    // maintain the back link in the DocumentObject class if it is from a document object
397
    if (_pcScope!=LinkScope::Hidden &&
398
        _pcLink &&
399
        getContainer() &&
400
        getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId()))
401
    {
402
        App::DocumentObject* parent = static_cast<DocumentObject*>(getContainer());
403
        // before accessing internals make sure the object is not about to be destroyed
404
        // otherwise the backlink contains dangling pointers
405
        if (!parent->testStatus(ObjectStatus::Destroy)) {
406
            if (_pcLink)
407
                _pcLink->_removeBackLink(parent);
408
        }
409
    }
410
#endif
411
    _pcLink = nullptr;
412
}
413

414
void PropertyLink::setValue(App::DocumentObject * lValue)
415
{
416
    auto parent = dynamic_cast<App::DocumentObject*>(getContainer());
417
    if(!testFlag(LinkAllowExternal) && parent && lValue && parent->getDocument()!=lValue->getDocument())
418
        throw Base::ValueError("PropertyLink does not support external object");
419

420
    aboutToSetValue();
421
#ifndef USE_OLD_DAG
422
    // maintain the back link in the DocumentObject class if it is from a document object
423
    if (_pcScope!=LinkScope::Hidden && parent) {
424
        // before accessing internals make sure the object is not about to be destroyed
425
        // otherwise the backlink contains dangling pointers
426
        if (!parent->testStatus(ObjectStatus::Destroy)) {
427
            if (_pcLink)
428
                _pcLink->_removeBackLink(parent);
429
            if (lValue)
430
                lValue->_addBackLink(parent);
431
        }
432
    }
433
#endif
434
    _pcLink=lValue;
435
    hasSetValue();
436
}
437

438
App::DocumentObject * PropertyLink::getValue() const
439
{
440
    return _pcLink;
441
}
442

443
App::DocumentObject * PropertyLink::getValue(Base::Type t) const
444
{
445
    return (_pcLink && _pcLink->getTypeId().isDerivedFrom(t)) ? _pcLink : nullptr;
446
}
447

448
PyObject *PropertyLink::getPyObject()
449
{
450
    if (_pcLink)
451
        return _pcLink->getPyObject();
452
    else
453
        Py_Return;
454
}
455

456
void PropertyLink::setPyObject(PyObject *value)
457
{
458
    Base::PyTypeCheck(&value, &DocumentObjectPy::Type);
459
    if (value) {
460
        DocumentObjectPy *pcObject = static_cast<DocumentObjectPy*>(value);
461
        setValue(pcObject->getDocumentObjectPtr());
462
    }
463
    else {
464
        setValue(nullptr);
465
    }
466
}
467

468
void PropertyLink::Save (Base::Writer &writer) const
469
{
470
    writer.Stream() << writer.ind() << "<Link value=\"" <<  (_pcLink?_pcLink->getExportName():"") <<"\"/>" << std::endl;
471
}
472

473
void PropertyLink::Restore(Base::XMLReader &reader)
474
{
475
    // read my element
476
    reader.readElement("Link");
477
    // get the value of my attribute
478
    std::string name = reader.getName(reader.getAttribute("value"));
479

480
    // Property not in a DocumentObject!
481
    assert(getContainer()->isDerivedFrom<App::DocumentObject>() );
482

483
    if (!name.empty()) {
484
        DocumentObject* parent = static_cast<DocumentObject*>(getContainer());
485

486
        App::Document* document = parent->getDocument();
487
        DocumentObject* object = document ? document->getObject(name.c_str()) : nullptr;
488
        if (!object) {
489
            if (reader.isVerbose()) {
490
                Base::Console().Warning("Lost link to '%s' while loading, maybe "
491
                                        "an object was not loaded correctly\n",name.c_str());
492
            }
493
        }
494
        else if (parent == object) {
495
            if (reader.isVerbose()) {
496
                Base::Console().Warning("Object '%s' links to itself, nullify it\n",name.c_str());
497
            }
498
            object = nullptr;
499
        }
500

501
        setValue(object);
502
    }
503
    else {
504
        setValue(nullptr);
505
    }
506
}
507

508
Property *PropertyLink::Copy() const
509
{
510
    PropertyLink *p= new PropertyLink();
511
    p->_pcLink = _pcLink;
512
    return p;
513
}
514

515
void PropertyLink::Paste(const Property &from)
516
{
517
    if (!from.isDerivedFrom(PropertyLink::getClassTypeId()))
518
        throw Base::TypeError("Incompatible property to paste to");
519

520
    setValue(static_cast<const PropertyLink&>(from)._pcLink);
521
}
522

523
void PropertyLink::getLinks(std::vector<App::DocumentObject *> &objs,
524
        bool all, std::vector<std::string> *subs, bool newStyle) const
525
{
526
    (void)newStyle;
527
    (void)subs;
528
    if((all||_pcScope!=LinkScope::Hidden) && _pcLink && _pcLink->isAttachedToDocument())
529
        objs.push_back(_pcLink);
530
}
531

532
void PropertyLink::breakLink(App::DocumentObject *obj, bool clear) {
533
    if(_pcLink == obj || (clear && getContainer()==obj))
534
        setValue(nullptr);
535
}
536

537
bool PropertyLink::adjustLink(const std::set<App::DocumentObject*> &inList) {
538
    (void)inList;
539
    return false;
540
}
541

542
Property *PropertyLink::CopyOnLinkReplace(const App::DocumentObject *parent,
543
        App::DocumentObject *oldObj, App::DocumentObject *newObj) const
544
{
545
    auto res = tryReplaceLink(getContainer(),_pcLink,parent,oldObj,newObj);
546
    if(res.first) {
547
        auto p = new PropertyLink();
548
        p->_pcLink = res.first;
549
        return p;
550
    }
551
    return nullptr;
552
}
553

554
//**************************************************************************
555
// PropertyLinkList
556
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
557

558
TYPESYSTEM_SOURCE(App::PropertyLinkList, App::PropertyLinkListBase)
559
TYPESYSTEM_SOURCE(App::PropertyLinkListChild, App::PropertyLinkList)
560
TYPESYSTEM_SOURCE(App::PropertyLinkListGlobal, App::PropertyLinkList)
561
TYPESYSTEM_SOURCE(App::PropertyLinkListHidden, App::PropertyLinkList)
562

563
//**************************************************************************
564
// Construction/Destruction
565

566

567
PropertyLinkList::PropertyLinkList() = default;
568

569
PropertyLinkList::~PropertyLinkList()
570
{
571
    //in case this property gety dynamically removed
572
#ifndef USE_OLD_DAG
573
    //maintain the back link in the DocumentObject class
574
    if (_pcScope!=LinkScope::Hidden &&
575
        !_lValueList.empty() &&
576
        getContainer() &&
577
        getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId()))
578
    {
579
        App::DocumentObject* parent = static_cast<DocumentObject*>(getContainer());
580
        // before accessing internals make sure the object is not about to be destroyed
581
        // otherwise the backlink contains dangling pointers
582
        if (!parent->testStatus(ObjectStatus::Destroy)) {
583
            for(auto *obj : _lValueList) {
584
                if (obj)
585
                    obj->_removeBackLink(parent);
586
            }
587
        }
588
    }
589
#endif
590

591
}
592

593
void PropertyLinkList::setSize(int newSize)
594
{
595
    for(int i=newSize;i<(int)_lValueList.size();++i) {
596
        auto obj = _lValueList[i];
597
        if (!obj || !obj->isAttachedToDocument())
598
            continue;
599
        _nameMap.erase(obj->getNameInDocument());
600
#ifndef USE_OLD_DAG
601
        if (_pcScope!=LinkScope::Hidden)
602
            obj->_removeBackLink(static_cast<DocumentObject*>(getContainer()));
603
#endif
604
    }
605
    _lValueList.resize(newSize);
606
}
607

608
void PropertyLinkList::setSize(int newSize, const_reference def) {
609
    auto oldSize = getSize();
610
    setSize(newSize);
611
    for(auto i=oldSize;i<newSize;++i)
612
        _lValueList[i] = def;
613
}
614

615
void PropertyLinkList::set1Value(int idx, DocumentObject* const &value) {
616
    DocumentObject *obj = nullptr;
617
    if(idx>=0 && idx<(int)_lValueList.size()) {
618
        obj = _lValueList[idx];
619
        if(obj == value)
620
            return;
621
    }
622

623
    if(!value || !value->isAttachedToDocument())
624
        throw Base::ValueError("invalid document object");
625

626
    _nameMap.clear();
627

628
#ifndef USE_OLD_DAG
629
    if (getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
630
        App::DocumentObject* parent = static_cast<DocumentObject*>(getContainer());
631
        // before accessing internals make sure the object is not about to be destroyed
632
        // otherwise the backlink contains dangling pointers
633
        if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
634
            if(obj)
635
                obj->_removeBackLink(static_cast<DocumentObject*>(getContainer()));
636
            if(value)
637
                value->_addBackLink(static_cast<DocumentObject*>(getContainer()));
638
        }
639
    }
640
#endif
641

642
    inherited::set1Value(idx,value);
643
}
644

645
void PropertyLinkList::setValues(const std::vector<DocumentObject*>& lValue) {
646
    if(lValue.size()==1 && !lValue[0]) {
647
        // one null element means clear, as backward compatibility for old code
648
        setValues(std::vector<DocumentObject*>());
649
        return;
650
    }
651

652
    auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
653
    for(auto obj : lValue) {
654
        if(!obj || !obj->isAttachedToDocument())
655
            throw Base::ValueError("PropertyLinkList: invalid document object");
656
        if(!testFlag(LinkAllowExternal) && parent && parent->getDocument()!=obj->getDocument())
657
            throw Base::ValueError("PropertyLinkList does not support external object");
658
    }
659
    _nameMap.clear();
660

661
#ifndef USE_OLD_DAG
662
    //maintain the back link in the DocumentObject class
663
    if (parent) {
664
        // before accessing internals make sure the object is not about to be destroyed
665
        // otherwise the backlink contains dangling pointers
666
        if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
667
            for(auto *obj : _lValueList) {
668
                if (obj)
669
                    obj->_removeBackLink(parent);
670
            }
671
            for(auto *obj : lValue) {
672
                if (obj)
673
                    obj->_addBackLink(parent);
674
            }
675
        }
676
    }
677
#endif
678
    inherited::setValues(lValue);
679
}
680

681
PyObject *PropertyLinkList::getPyObject()
682
{
683
    int count = getSize();
684
#if 0//FIXME: Should switch to tuple
685
    Py::Tuple sequence(count);
686
#else
687
    Py::List sequence(count);
688
#endif
689
    for (int i = 0; i<count; i++) {
690
        auto obj = _lValueList[i];
691
        if(obj && obj->isAttachedToDocument())
692
            sequence.setItem(i, Py::asObject(_lValueList[i]->getPyObject()));
693
        else
694
            sequence.setItem(i, Py::None());
695
    }
696

697
    return Py::new_reference_to(sequence);
698
}
699

700
DocumentObject *PropertyLinkList::getPyValue(PyObject *item) const
701
{
702
    Base::PyTypeCheck(&item, &DocumentObjectPy::Type);
703

704
    return item ? static_cast<DocumentObjectPy*>(item)->getDocumentObjectPtr() : nullptr;
705
}
706

707
void PropertyLinkList::Save(Base::Writer &writer) const
708
{
709
    writer.Stream() << writer.ind() << "<LinkList count=\"" << getSize() << "\">" << endl;
710
    writer.incInd();
711
    for (int i = 0; i<getSize(); i++) {
712
        DocumentObject* obj = _lValueList[i];
713
        if (obj)
714
            writer.Stream() << writer.ind() << "<Link value=\"" << obj->getExportName() << "\"/>" << endl;
715
        else
716
            writer.Stream() << writer.ind() << "<Link value=\"\"/>" << endl;
717
    }
718

719
    writer.decInd();
720
    writer.Stream() << writer.ind() << "</LinkList>" << endl;
721
}
722

723
void PropertyLinkList::Restore(Base::XMLReader &reader)
724
{
725
    // read my element
726
    reader.readElement("LinkList");
727
    // get the value of my attribute
728
    int count = reader.getAttributeAsInteger("count");
729
    App::PropertyContainer* container = getContainer();
730
    if (!container)
731
        throw Base::RuntimeError("Property is not part of a container");
732
    if (!container->isDerivedFrom<App::DocumentObject>()) {
733
        std::stringstream str;
734
        str << "Container is not a document object ("
735
            << container->getTypeId().getName() << ")";
736
        throw Base::TypeError(str.str());
737
    }
738

739
    std::vector<DocumentObject*> values;
740
    values.reserve(count);
741
    for (int i = 0; i < count; i++) {
742
        reader.readElement("Link");
743
        std::string name = reader.getName(reader.getAttribute("value"));
744
        // In order to do copy/paste it must be allowed to have defined some
745
        // referenced objects in XML which do not exist anymore in the new
746
        // document. Thus, we should silently ignore this.
747
        // Property not in an object!
748
        DocumentObject* father = static_cast<DocumentObject*>(getContainer());
749
        App::Document* document = father->getDocument();
750
        DocumentObject* child = document ? document->getObject(name.c_str()) : nullptr;
751
        if (child)
752
            values.push_back(child);
753
        else if (reader.isVerbose())
754
            FC_WARN("Lost link to " << (document?document->getName():"") << " " << name
755
                    << " while loading, maybe an object was not loaded correctly");
756
    }
757

758
    reader.readEndElement("LinkList");
759

760
    // assignment
761
    setValues(values);
762
}
763

764
Property *PropertyLinkList::CopyOnLinkReplace(const App::DocumentObject *parent,
765
        App::DocumentObject *oldObj, App::DocumentObject *newObj) const
766
{
767
    std::vector<DocumentObject*> links;
768
    bool copied = false;
769
    bool found = false;
770
    for(auto it=_lValueList.begin();it!=_lValueList.end();++it) {
771
        auto res = tryReplaceLink(getContainer(),*it,parent,oldObj,newObj);
772
        if(res.first) {
773
            found = true;
774
            if(!copied) {
775
                copied = true;
776
                links.insert(links.end(),_lValueList.begin(),it);
777
            }
778
            links.push_back(res.first);
779
        } else if(*it == newObj) {
780
            // in case newObj already exists here, we shall remove all existing
781
            // entry, and insert it to take over oldObj's position.
782
            if(!copied) {
783
                copied = true;
784
                links.insert(links.end(),_lValueList.begin(),it);
785
            }
786
        }else if(copied)
787
            links.push_back(*it);
788
    }
789
    if(!found)
790
        return nullptr;
791
    auto p= new PropertyLinkList();
792
    p->_lValueList = std::move(links);
793
    return p;
794
}
795

796
Property *PropertyLinkList::Copy() const
797
{
798
    PropertyLinkList *p = new PropertyLinkList();
799
    p->_lValueList = _lValueList;
800
    return p;
801
}
802

803
void PropertyLinkList::Paste(const Property &from)
804
{
805
    if(!from.isDerivedFrom(PropertyLinkList::getClassTypeId()))
806
        throw Base::TypeError("Incompatible property to paste to");
807

808
    setValues(static_cast<const PropertyLinkList&>(from)._lValueList);
809
}
810

811
unsigned int PropertyLinkList::getMemSize() const
812
{
813
    return static_cast<unsigned int>(_lValueList.size() * sizeof(App::DocumentObject *));
814
}
815

816
DocumentObject *PropertyLinkList::find(const std::string &name, int *pindex) const {
817
    if(_nameMap.empty() || _nameMap.size()>_lValueList.size()) {
818
        _nameMap.clear();
819
        for(int i=0;i<(int)_lValueList.size();++i) {
820
            auto obj = _lValueList[i];
821
            if(obj && obj->isAttachedToDocument())
822
                _nameMap[obj->getNameInDocument()] = i;
823
        }
824
    }
825
    auto it = _nameMap.find(name);
826
    if(it == _nameMap.end())
827
        return nullptr;
828
    if(pindex) *pindex = it->second;
829
    return _lValueList[it->second];
830
}
831

832
void PropertyLinkList::getLinks(std::vector<App::DocumentObject *> &objs,
833
        bool all, std::vector<std::string> *subs, bool newStyle) const
834
{
835
    (void)subs;
836
    (void)newStyle;
837
    if(all||_pcScope!=LinkScope::Hidden) {
838
        objs.reserve(objs.size()+_lValueList.size());
839
        for(auto obj : _lValueList) {
840
            if(obj && obj->isAttachedToDocument())
841
                objs.push_back(obj);
842
        }
843
    }
844
}
845

846
void PropertyLinkList::breakLink(App::DocumentObject *obj, bool clear) {
847
    if(clear && getContainer()==obj) {
848
        setValues({});
849
        return;
850
    }
851
    std::vector<App::DocumentObject*> values;
852
    values.reserve(_lValueList.size());
853
    for(auto o : _lValueList) {
854
        if(o != obj)
855
            values.push_back(o);
856
    }
857
    if(values.size()!=_lValueList.size())
858
        setValues(values);
859
}
860

861
bool PropertyLinkList::adjustLink(const std::set<App::DocumentObject*> &inList) {
862
    (void)inList;
863
    return false;
864
}
865

866

867
//**************************************************************************
868
// PropertyLinkSub
869
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
870

871
TYPESYSTEM_SOURCE(App::PropertyLinkSub, App::PropertyLinkBase)
872
TYPESYSTEM_SOURCE(App::PropertyLinkSubChild, App::PropertyLinkSub)
873
TYPESYSTEM_SOURCE(App::PropertyLinkSubGlobal, App::PropertyLinkSub)
874
TYPESYSTEM_SOURCE(App::PropertyLinkSubHidden, App::PropertyLinkSub)
875

876
//**************************************************************************
877
// Construction/Destruction
878

879

880
PropertyLinkSub::PropertyLinkSub() = default;
881

882
PropertyLinkSub::~PropertyLinkSub()
883
{
884
    //in case this property is dynamically removed
885
#ifndef USE_OLD_DAG
886
    if (_pcLinkSub && getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
887
        App::DocumentObject* parent = static_cast<DocumentObject*>(getContainer());
888
        // before accessing internals make sure the object is not about to be destroyed
889
        // otherwise the backlink contains dangling pointers
890
        if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
891
            if (_pcLinkSub)
892
                _pcLinkSub->_removeBackLink(parent);
893
        }
894
    }
895
#endif
896
}
897

898
void PropertyLinkSub::setSyncSubObject(bool enable)
899
{
900
    _Flags.set((std::size_t)LinkSyncSubObject, enable);
901
}
902

903
void PropertyLinkSub::setValue(App::DocumentObject * lValue,
904
        const std::vector<std::string> &SubList, std::vector<ShadowSub> &&shadows)
905
{
906
    setValue(lValue,std::vector<std::string>(SubList),std::move(shadows));
907
}
908

909
void PropertyLinkSub::setValue(App::DocumentObject * lValue,
910
        std::vector<std::string> &&subs, std::vector<ShadowSub> &&shadows)
911
{
912
    auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
913
    if(lValue) {
914
        if(!lValue->isAttachedToDocument())
915
            throw Base::ValueError("PropertyLinkSub: invalid document object");
916
        if(!testFlag(LinkAllowExternal) && parent && parent->getDocument()!=lValue->getDocument())
917
            throw Base::ValueError("PropertyLinkSub does not support external object");
918
    }
919
    aboutToSetValue();
920
#ifndef USE_OLD_DAG
921
    if(parent) {
922
        // before accessing internals make sure the object is not about to be destroyed
923
        // otherwise the backlink contains dangling pointers
924
        if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
925
            if (_pcLinkSub)
926
                _pcLinkSub->_removeBackLink(parent);
927
            if (lValue)
928
                lValue->_addBackLink(parent);
929
        }
930
    }
931
#endif
932
    _pcLinkSub=lValue;
933
    _cSubList=std::move(subs);
934
    if(shadows.size()==_cSubList.size())
935
        _ShadowSubList = std::move(shadows);
936
    else
937
        updateElementReference(nullptr);
938
    checkLabelReferences(_cSubList);
939
    hasSetValue();
940
}
941

942
App::DocumentObject * PropertyLinkSub::getValue() const
943
{
944
    return _pcLinkSub;
945
}
946

947
const std::vector<std::string>& PropertyLinkSub::getSubValues() const
948
{
949
    return _cSubList;
950
}
951

952
static inline const std::string &getSubNameWithStyle(const std::string &subName,
953
        const PropertyLinkBase::ShadowSub &shadow, bool newStyle)
954
{
955
    if(!newStyle) {
956
        if(!shadow.second.empty())
957
            return shadow.second;
958
    }else if(!shadow.first.empty())
959
        return shadow.first;
960
    return subName;
961
}
962

963
std::vector<std::string> PropertyLinkSub::getSubValues(bool newStyle) const {
964
    assert(_cSubList.size() == _ShadowSubList.size());
965
    std::vector<std::string> ret;
966
    ret.reserve(_cSubList.size());
967
    for(size_t i=0;i<_ShadowSubList.size();++i)
968
        ret.push_back(getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle));
969
    return ret;
970
}
971

972
std::vector<std::string> PropertyLinkSub::getSubValuesStartsWith(const char* starter, bool newStyle) const
973
{
974
    (void)newStyle;
975

976
    std::vector<std::string> temp;
977
    for(const auto & it : _cSubList) {
978
        if(strncmp(starter, it.c_str(), strlen(starter)) == 0) {
979
            temp.push_back(it);
980
        }
981
    }
982
    return temp;
983
}
984

985
App::DocumentObject * PropertyLinkSub::getValue(Base::Type t) const
986
{
987
    return (_pcLinkSub && _pcLinkSub->getTypeId().isDerivedFrom(t)) ? _pcLinkSub : nullptr;
988
}
989

990
PyObject *PropertyLinkSub::getPyObject()
991
{
992
    Py::Tuple tup(2);
993
    Py::List list(static_cast<int>(_cSubList.size()));
994
    if (_pcLinkSub) {
995
        tup[0] = Py::asObject(_pcLinkSub->getPyObject());
996
        for(unsigned int i = 0;i<_cSubList.size(); i++)
997
            list[i] = Py::String(_cSubList[i]);
998
        tup[1] = list;
999
        return Py::new_reference_to(tup);
1000
    }
1001
    else {
1002
        return Py::new_reference_to(Py::None());
1003
    }
1004
}
1005

1006
void PropertyLinkSub::setPyObject(PyObject *value)
1007
{
1008
    if (PyObject_TypeCheck(value, &(DocumentObjectPy::Type))) {
1009
        DocumentObjectPy  *pcObject = static_cast<DocumentObjectPy*>(value);
1010
        setValue(pcObject->getDocumentObjectPtr());
1011
    }
1012
    else if (PyTuple_Check(value) || PyList_Check(value)) {
1013
        Py::Sequence seq(value);
1014
        if(seq.size() == 0)
1015
            setValue(nullptr);
1016
        else if(seq.size()!=2)
1017
            throw Base::ValueError("Expect input sequence of size 2");
1018
        else if (PyObject_TypeCheck(seq[0].ptr(), &(DocumentObjectPy::Type))) {
1019
            DocumentObjectPy  *pcObj = static_cast<DocumentObjectPy*>(seq[0].ptr());
1020
            static const char *errMsg = "type of second element in tuple must be str or sequence of str";
1021
            PropertyString propString;
1022
            if (seq[1].isString()) {
1023
                std::vector<std::string> vals;
1024
                propString.setPyObject(seq[1].ptr());
1025
                vals.emplace_back(propString.getValue());
1026
                setValue(pcObj->getDocumentObjectPtr(),std::move(vals));
1027
            }
1028
            else if (seq[1].isSequence()) {
1029
                Py::Sequence list(seq[1]);
1030
                std::vector<std::string> vals(list.size());
1031
                unsigned int i=0;
1032
                for (Py::Sequence::iterator it = list.begin();it!=list.end();++it,++i) {
1033
                    if(!(*it).isString())
1034
                        throw Base::TypeError(errMsg);
1035
                    propString.setPyObject((*it).ptr());
1036
                    vals[i] = propString.getValue();
1037
                }
1038
                setValue(pcObj->getDocumentObjectPtr(),std::move(vals));
1039
            }
1040
            else {
1041
                throw Base::TypeError(errMsg);
1042
            }
1043
        }
1044
        else {
1045
            std::string error = std::string("type of first element in tuple must be 'DocumentObject', not ");
1046
            error += seq[0].ptr()->ob_type->tp_name;
1047
            throw Base::TypeError(error);
1048
        }
1049
    }
1050
    else if(Py_None == value) {
1051
        setValue(nullptr);
1052
    }
1053
    else {
1054
        std::string error = std::string("type must be 'DocumentObject', 'NoneType' or ('DocumentObject',['String',]) not ");
1055
        error += value->ob_type->tp_name;
1056
        throw Base::TypeError(error);
1057
    }
1058
}
1059

1060
static bool updateLinkReference(App::PropertyLinkBase *prop,
1061
        App::DocumentObject *feature, bool reverse, bool notify,
1062
        App::DocumentObject *link, std::vector<std::string> &subs, std::vector<int> &mapped,
1063
        std::vector<PropertyLinkBase::ShadowSub> &shadows)
1064
{
1065
    if(!feature) {
1066
        shadows.clear();
1067
        prop->unregisterElementReference();
1068
    }
1069
    shadows.resize(subs.size());
1070
    if(!link || !link->isAttachedToDocument())
1071
        return false;
1072
    auto owner = dynamic_cast<DocumentObject*>(prop->getContainer());
1073
    if(owner && owner->isRestoring())
1074
        return false;
1075
    int i=0;
1076
    bool touched = false;
1077
    for(auto &sub : subs) {
1078
        if(prop->_updateElementReference(
1079
                    feature,link,sub,shadows[i++],reverse,notify&&!touched))
1080
            touched = true;
1081
    }
1082
    if(!touched)
1083
        return false;
1084
    for(int idx : mapped) {
1085
        if(idx<(int)subs.size() && !shadows[idx].first.empty())
1086
            subs[idx] = shadows[idx].first;
1087
    }
1088
    mapped.clear();
1089
    if(owner && feature)
1090
        owner->onUpdateElementReference(prop);
1091
    return true;
1092
}
1093

1094
void PropertyLinkSub::afterRestore() {
1095
    _ShadowSubList.resize(_cSubList.size());
1096
    if(!testFlag(LinkRestoreLabel) ||!_pcLinkSub || !_pcLinkSub->isAttachedToDocument())
1097
        return;
1098
    setFlag(LinkRestoreLabel,false);
1099
    for(std::size_t i=0;i<_cSubList.size();++i)
1100
        restoreLabelReference(_pcLinkSub,_cSubList[i],&_ShadowSubList[i]);
1101
}
1102

1103
void PropertyLinkSub::onContainerRestored() {
1104
    unregisterElementReference();
1105
    if(!_pcLinkSub || !_pcLinkSub->isAttachedToDocument())
1106
        return;
1107
    for(std::size_t i=0;i<_cSubList.size();++i)
1108
        _registerElementReference(_pcLinkSub,_cSubList[i],_ShadowSubList[i]);
1109
}
1110

1111
void PropertyLinkSub::updateElementReference(DocumentObject *feature, bool reverse, bool notify) {
1112
    if(!updateLinkReference(this,feature,reverse,notify,_pcLinkSub,_cSubList,_mapped,_ShadowSubList))
1113
        return;
1114
    if(notify)
1115
        hasSetValue();
1116
}
1117

1118
bool PropertyLinkSub::referenceChanged() const {
1119
    return !_mapped.empty();
1120
}
1121

1122
std::string PropertyLinkBase::importSubName(Base::XMLReader &reader, const char *sub, bool &restoreLabel) {
1123
    if(!reader.doNameMapping())
1124
        return sub;
1125
    std::ostringstream str;
1126
    for(const char *dot=strchr(sub,'.');dot;sub=dot+1,dot=strchr(sub,'.')) {
1127
        size_t count = dot-sub;
1128
        const char *tail = ".";
1129
        if(count && dot[-1] == '@') {
1130
            // tail=='@' means we are exporting a label reference. So retain
1131
            // this marker so that the label can be restored in afterRestore().
1132
            tail = "@.";
1133
            --count;
1134
            restoreLabel = true;
1135
        }
1136
        str << reader.getName(std::string(sub,count).c_str()) << tail;
1137
    }
1138
    str << sub;
1139
    return str.str();
1140
}
1141

1142
const char *PropertyLinkBase::exportSubName(std::string &output,
1143
        const App::DocumentObject *obj, const char *sub, bool first_obj)
1144
{
1145
    std::ostringstream str;
1146
    const char *res = sub;
1147

1148
    if(!sub || !sub[0])
1149
        return res;
1150

1151
    bool touched = false;
1152
    if(first_obj) {
1153
        auto dot = strchr(sub,'.');
1154
        if(!dot)
1155
            return res;
1156
        const char *hash;
1157
        for(hash=sub;hash<dot && *hash!='#';++hash) {}
1158
        App::Document *doc = nullptr;
1159
        if(*hash == '#')
1160
            doc = GetApplication().getDocument(std::string(sub,hash-sub).c_str());
1161
        else {
1162
            hash = nullptr;
1163
            if(obj && obj->isAttachedToDocument())
1164
                doc = obj->getDocument();
1165
        }
1166
        if(!doc) {
1167
            FC_ERR("Failed to get document for the first object in " << sub);
1168
            return res;
1169
        }
1170
        obj = doc->getObject(std::string(sub,dot-sub).c_str());
1171
        if(!obj || !obj->isAttachedToDocument())
1172
            return res;
1173
        if(hash) {
1174
            if(!obj->isExporting())
1175
                str << doc->getName() << '#';
1176
            sub = hash+1;
1177
        }
1178
    }else if(!obj || !obj->isAttachedToDocument())
1179
        return res;
1180

1181
    for(const char *dot=strchr(sub,'.');dot;sub=dot+1,dot=strchr(sub,'.')) {
1182
        // name with trailing '.'
1183
        auto name = std::string(sub,dot-sub+1);
1184
        if(first_obj)
1185
            first_obj = false;
1186
        else
1187
            obj = obj->getSubObject(name.c_str());
1188
        if(!obj || !obj->isAttachedToDocument()) {
1189
            FC_WARN("missing sub object '" << name << "' in '" << sub <<"'");
1190
            break;
1191
        }
1192
        if(obj->isExporting()) {
1193
            if(name[0] == '$') {
1194
                if(name.compare(1,name.size()-2,obj->Label.getValue())!=0) {
1195
                    str << obj->getExportName(true) << "@.";
1196
                    touched = true;
1197
                    continue;
1198
                }
1199
            } else if(name.compare(0,name.size()-1,obj->getNameInDocument())==0) {
1200
                str << obj->getExportName(true) << '.';
1201
                touched = true;
1202
                continue;
1203
            }
1204
        }
1205
        str << name;
1206
    }
1207
    if(!touched)
1208
        return res;
1209
    str << sub;
1210
    output = str.str();
1211
    return output.c_str();
1212
}
1213

1214
App::DocumentObject *PropertyLinkBase::tryImport(const App::Document *doc,
1215
            const App::DocumentObject *obj, const std::map<std::string,std::string> &nameMap)
1216
{
1217
    if(doc && obj && obj->isAttachedToDocument())  {
1218
        auto it = nameMap.find(obj->getExportName(true));
1219
        if(it!=nameMap.end()) {
1220
            obj = doc->getObject(it->second.c_str());
1221
            if(!obj)
1222
                FC_THROWM(Base::RuntimeError,"Cannot find import object " << it->second);
1223
        }
1224
    }
1225
    return const_cast<DocumentObject*>(obj);
1226
}
1227

1228
std::string PropertyLinkBase::tryImportSubName(const App::DocumentObject *obj, const char *_subname,
1229
        const App::Document *doc, const std::map<std::string,std::string> &nameMap)
1230
{
1231
    if(!doc || !obj || !obj->isAttachedToDocument())
1232
        return {};
1233

1234
    std::ostringstream ss;
1235
    std::string subname(_subname);
1236
    char *sub = &subname[0];
1237
    char *next = sub;
1238
    for(char *dot=strchr(next,'.');dot;next=dot+1,dot=strchr(next,'.')) {
1239
        StringGuard guard(dot);
1240
        auto sobj = obj->getSubObject(subname.c_str());
1241
        if(!sobj) {
1242
            FC_ERR("Failed to restore label reference " << obj->getFullName() << '.' << subname);
1243
            return {};
1244
        }
1245
        dot[0] = 0;
1246
        if(next[0] == '$') {
1247
            if(strcmp(next+1,sobj->Label.getValue())!=0)
1248
                continue;
1249
        } else if(strcmp(next,sobj->getNameInDocument())!=0) {
1250
            continue;
1251
        }
1252
        auto it = nameMap.find(sobj->getExportName(true));
1253
        if(it == nameMap.end())
1254
            continue;
1255
        auto imported = doc->getObject(it->second.c_str());
1256
        if(!imported)
1257
            FC_THROWM(RuntimeError, "Failed to find imported object " << it->second);
1258
        ss.write(sub,next-sub);
1259
        if(next[0] == '$')
1260
            ss << '$' << imported->Label.getStrValue() << '.';
1261
        else
1262
            ss << it->second << '.';
1263
        sub = dot+1;
1264
    }
1265
    if(sub!=subname.c_str())
1266
        return ss.str();
1267
    return {};
1268
}
1269

1270
#define ATTR_SHADOWED "shadowed"
1271
#define ATTR_SHADOW "shadow"
1272
#define ATTR_MAPPED "mapped"
1273

1274
// We do not have topo naming yet, ignore shadow sub for now
1275
#define IGNORE_SHADOW true
1276

1277
void PropertyLinkSub::Save (Base::Writer &writer) const
1278
{
1279
    assert(_cSubList.size() == _ShadowSubList.size());
1280

1281
    std::string internal_name;
1282
    // it can happen that the object is still alive but is not part of the document anymore and thus
1283
    // returns 0
1284
    if (_pcLinkSub && _pcLinkSub->isAttachedToDocument())
1285
        internal_name = _pcLinkSub->getExportName();
1286
    writer.Stream() << writer.ind() << "<LinkSub value=\""
1287
        <<  internal_name <<"\" count=\"" <<  _cSubList.size();
1288
    writer.Stream() << "\">" << std::endl;
1289
    writer.incInd();
1290
    auto owner = dynamic_cast<DocumentObject*>(getContainer());
1291
    bool exporting = owner && owner->isExporting();
1292
    for(unsigned int i = 0;i<_cSubList.size(); i++) {
1293
        const auto &shadow = _ShadowSubList[i];
1294
        // shadow.second stores the old style element name. For backward
1295
        // compatibility reason, we shall store the old name into attribute
1296
        // 'value' whenever possible.
1297
        const auto &sub = shadow.second.empty()?_cSubList[i]:shadow.second;
1298
        writer.Stream() << writer.ind() << "<Sub value=\"";
1299
        if(exporting) {
1300
            std::string exportName;
1301
            writer.Stream() << encodeAttribute(exportSubName(exportName,_pcLinkSub,sub.c_str()));
1302
            if(!shadow.second.empty() && shadow.first == _cSubList[i])
1303
                writer.Stream() << "\" " ATTR_MAPPED "=\"1";
1304
        } else {
1305
            writer.Stream() << encodeAttribute(sub);
1306
            if(!_cSubList[i].empty()) {
1307
                if(sub!=_cSubList[i]) {
1308
                    // Stores the actual value that is shadowed. For new version FC,
1309
                    // we will restore this shadowed value instead.
1310
                    writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_cSubList[i]);
1311
                }else if(!shadow.first.empty()){
1312
                    // Here means the user set value is old style element name.
1313
                    // We shall then store the shadow somewhere else.
1314
                    writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first);
1315
                }
1316
            }
1317
        }
1318
        writer.Stream()<<"\"/>" << endl;
1319
    }
1320
    writer.decInd();
1321
    writer.Stream() << writer.ind() << "</LinkSub>" << endl ;
1322
}
1323

1324
void PropertyLinkSub::Restore(Base::XMLReader &reader)
1325
{
1326
    // read my element
1327
    reader.readElement("LinkSub");
1328
    // get the values of my attributes
1329
    std::string name = reader.getName(reader.getAttribute("value"));
1330
    int count = reader.getAttributeAsInteger("count");
1331

1332
    // Property not in a DocumentObject!
1333
    assert(getContainer()->isDerivedFrom<App::DocumentObject>() );
1334
    App::Document* document = static_cast<DocumentObject*>(getContainer())->getDocument();
1335

1336
    DocumentObject *pcObject = nullptr;
1337
    if (!name.empty()) {
1338
        pcObject = document ? document->getObject(name.c_str()) : nullptr;
1339
        if (!pcObject) {
1340
            if (reader.isVerbose()) {
1341
                FC_WARN("Lost link to " << name
1342
                        << " while loading, maybe an object was not loaded correctly");
1343
            }
1344
        }
1345
    }
1346

1347
    std::vector<int> mapped;
1348
    std::vector<std::string> values(count);
1349
    std::vector<ShadowSub> shadows(count);
1350
    bool restoreLabel=false;
1351
    // Sub may store '.' separated object names, so be aware of the possible mapping when import
1352
    for (int i = 0; i < count; i++) {
1353
        reader.readElement("Sub");
1354
        shadows[i].second = importSubName(reader,reader.getAttribute("value"),restoreLabel);
1355
        if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) {
1356
            values[i] = shadows[i].first =
1357
                importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel);
1358
        } else {
1359
            values[i] = shadows[i].second;
1360
            if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW)
1361
                shadows[i].first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel);
1362
        }
1363
        if(reader.hasAttribute(ATTR_MAPPED))
1364
            mapped.push_back(i);
1365
    }
1366
    setFlag(LinkRestoreLabel,restoreLabel);
1367

1368
    reader.readEndElement("LinkSub");
1369

1370
    if(pcObject) {
1371
        setValue(pcObject,std::move(values),std::move(shadows));
1372
        _mapped = std::move(mapped);
1373
    }
1374
    else {
1375
       setValue(nullptr);
1376
    }
1377
}
1378

1379
template<class Func, class... Args >
1380
std::vector<std::string> updateLinkSubs(const App::DocumentObject *obj,
1381
        const std::vector<std::string> &subs, Func *f, Args&&... args )
1382
{
1383
    if(!obj || !obj->isAttachedToDocument())
1384
        return {};
1385

1386
    std::vector<std::string> res;
1387
    for(auto it=subs.begin();it!=subs.end();++it) {
1388
        const auto &sub = *it;
1389
        auto new_sub = (*f)(obj,sub.c_str(),std::forward<Args>(args)...);
1390
        if(new_sub.size()) {
1391
            if(res.empty()) {
1392
                res.reserve(subs.size());
1393
                res.insert(res.end(),subs.begin(),it);
1394
            }
1395
            res.push_back(std::move(new_sub));
1396
        }else if(!res.empty())
1397
            res.push_back(sub);
1398
    }
1399
    return res;
1400
}
1401

1402
Property *PropertyLinkSub::CopyOnImportExternal(
1403
        const std::map<std::string,std::string> &nameMap) const
1404
{
1405
    auto owner = dynamic_cast<const DocumentObject*>(getContainer());
1406
    if(!owner || !owner->getDocument())
1407
        return nullptr;
1408
    if(!_pcLinkSub || !_pcLinkSub->isAttachedToDocument())
1409
        return nullptr;
1410

1411
    auto subs = updateLinkSubs(_pcLinkSub,_cSubList,
1412
            &tryImportSubName,owner->getDocument(),nameMap);
1413
    auto linked = tryImport(owner->getDocument(),_pcLinkSub,nameMap);
1414
    if(subs.empty() && linked==_pcLinkSub)
1415
        return nullptr;
1416

1417
    PropertyLinkSub *p= new PropertyLinkSub();
1418
    p->_pcLinkSub = linked;
1419
    if(subs.empty())
1420
        p->_cSubList = _cSubList;
1421
    else
1422
        p->_cSubList = std::move(subs);
1423
    return p;
1424
}
1425

1426
Property *PropertyLinkSub::CopyOnLabelChange(App::DocumentObject *obj,
1427
        const std::string &ref, const char *newLabel) const
1428
{
1429
    auto owner = dynamic_cast<const DocumentObject*>(getContainer());
1430
    if(!owner || !owner->getDocument())
1431
        return nullptr;
1432
    if(!_pcLinkSub || !_pcLinkSub->isAttachedToDocument())
1433
        return nullptr;
1434

1435
    auto subs = updateLinkSubs(_pcLinkSub,_cSubList,&updateLabelReference,obj,ref,newLabel);
1436
    if(subs.empty())
1437
        return nullptr;
1438

1439
    PropertyLinkSub *p= new PropertyLinkSub();
1440
    p->_pcLinkSub = _pcLinkSub;
1441
    p->_cSubList = std::move(subs);
1442
    return p;
1443
}
1444

1445
Property *PropertyLinkSub::CopyOnLinkReplace(const App::DocumentObject *parent,
1446
        App::DocumentObject *oldObj, App::DocumentObject *newObj) const
1447
{
1448
    auto res = tryReplaceLinkSubs(getContainer(),_pcLinkSub,parent,oldObj,newObj,_cSubList);
1449
    if(res.first) {
1450
        PropertyLinkSub *p= new PropertyLinkSub();
1451
        p->_pcLinkSub = res.first;
1452
        p->_cSubList = std::move(res.second);
1453
        return p;
1454
    }
1455
    return nullptr;
1456
}
1457

1458
Property *PropertyLinkSub::Copy() const
1459
{
1460
    PropertyLinkSub *p= new PropertyLinkSub();
1461
    p->_pcLinkSub = _pcLinkSub;
1462
    p->_cSubList = _cSubList;
1463
    return p;
1464
}
1465

1466
void PropertyLinkSub::Paste(const Property &from)
1467
{
1468
    if(!from.isDerivedFrom(PropertyLinkSub::getClassTypeId()))
1469
        throw Base::TypeError("Incompatible property to paste to");
1470
    auto &link = static_cast<const PropertyLinkSub&>(from);
1471
    setValue(link._pcLinkSub, link._cSubList);
1472
}
1473

1474
void PropertyLinkSub::getLinks(std::vector<App::DocumentObject *> &objs,
1475
        bool all, std::vector<std::string> *subs, bool newStyle) const
1476
{
1477
    if(all||_pcScope!=LinkScope::Hidden) {
1478
        if(_pcLinkSub && _pcLinkSub->isAttachedToDocument()) {
1479
            objs.push_back(_pcLinkSub);
1480
            if(subs)
1481
                *subs = getSubValues(newStyle);
1482
        }
1483
    }
1484
}
1485

1486
void PropertyLinkSub::breakLink(App::DocumentObject *obj, bool clear) {
1487
    if(obj == _pcLinkSub || (clear && getContainer()==obj))
1488
        setValue(nullptr);
1489
}
1490

1491
static App::DocumentObject *adjustLinkSubs(App::PropertyLinkBase *prop,
1492
        const std::set<App::DocumentObject*> &inList,
1493
        App::DocumentObject *link, std::vector<std::string> &subs,
1494
        std::map<App::DocumentObject *, std::vector<std::string> > *links=nullptr)
1495
{
1496
    App::DocumentObject *newLink = nullptr;
1497
    for(auto &sub : subs) {
1498
        size_t pos = sub.find('.');
1499
        for(;pos!=std::string::npos;pos=sub.find('.',pos+1)) {
1500
            auto sobj = link->getSubObject(sub.substr(0,pos+1).c_str());
1501
            if(!sobj ||
1502
               (!prop->testFlag(PropertyLinkBase::LinkAllowExternal) &&
1503
                sobj->getDocument()!=link->getDocument()))
1504
            {
1505
                pos = std::string::npos;
1506
                break;
1507
            }
1508
            if(!newLink) {
1509
                if(inList.count(sobj))
1510
                    continue;
1511
                newLink = sobj;
1512
                if(links)
1513
                    (*links)[sobj].push_back(sub.substr(pos+1));
1514
                else
1515
                    sub = sub.substr(pos+1);
1516
            }else if(links)
1517
                (*links)[sobj].push_back(sub.substr(pos+1));
1518
            else if(sobj == newLink)
1519
                sub = sub.substr(pos+1);
1520
            break;
1521
        }
1522
        if(pos == std::string::npos)
1523
            return nullptr;
1524
    }
1525
    return newLink;
1526
}
1527

1528
bool PropertyLinkSub::adjustLink(const std::set<App::DocumentObject*> &inList) {
1529
    if (_pcScope==LinkScope::Hidden)
1530
        return false;
1531
    if(!_pcLinkSub || !_pcLinkSub->isAttachedToDocument() || !inList.count(_pcLinkSub))
1532
        return false;
1533
    auto subs = _cSubList;
1534
    auto link = adjustLinkSubs(this,inList,_pcLinkSub,subs);
1535
    if(link) {
1536
        setValue(link,std::move(subs));
1537
        return true;
1538
    }
1539
    return false;
1540
}
1541

1542
//**************************************************************************
1543
// PropertyLinkSubList
1544
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1545

1546
TYPESYSTEM_SOURCE(App::PropertyLinkSubList, App::PropertyLinkBase)
1547
TYPESYSTEM_SOURCE(App::PropertyLinkSubListChild, App::PropertyLinkSubList)
1548
TYPESYSTEM_SOURCE(App::PropertyLinkSubListGlobal, App::PropertyLinkSubList)
1549
TYPESYSTEM_SOURCE(App::PropertyLinkSubListHidden, App::PropertyLinkSubList)
1550

1551
//**************************************************************************
1552
// Construction/Destruction
1553

1554

1555
PropertyLinkSubList::PropertyLinkSubList() = default;
1556

1557
PropertyLinkSubList::~PropertyLinkSubList()
1558
{
1559
    //in case this property is dynamically removed
1560
#ifndef USE_OLD_DAG
1561
    //maintain backlinks
1562
    if (!_lValueList.empty() && getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
1563
        App::DocumentObject* parent = static_cast<DocumentObject*>(getContainer());
1564
        // before accessing internals make sure the object is not about to be destroyed
1565
        // otherwise the backlink contains dangling pointers
1566
        if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
1567
            for(auto *obj : _lValueList) {
1568
                if (obj)
1569
                    obj->_removeBackLink(parent);
1570
            }
1571
        }
1572
    }
1573
#endif
1574
}
1575

1576
void PropertyLinkSubList::setSyncSubObject(bool enable)
1577
{
1578
    _Flags.set((std::size_t)LinkSyncSubObject, enable);
1579
}
1580

1581
void PropertyLinkSubList::verifyObject(App::DocumentObject* obj, App::DocumentObject* parent)
1582
{
1583
    if (obj) {
1584
        if (!obj->isAttachedToDocument())
1585
            throw Base::ValueError("PropertyLinkSubList: invalid document object");
1586
        if (!testFlag(LinkAllowExternal) && parent && parent->getDocument() != obj->getDocument())
1587
            throw Base::ValueError("PropertyLinkSubList does not support external object");
1588
    }
1589
}
1590

1591
void PropertyLinkSubList::setSize(int newSize)
1592
{
1593
    _lValueList.resize(newSize);
1594
    _lSubList  .resize(newSize);
1595
    _ShadowSubList.resize(newSize);
1596
}
1597

1598
int PropertyLinkSubList::getSize() const
1599
{
1600
    return static_cast<int>(_lValueList.size());
1601
}
1602

1603
void PropertyLinkSubList::setValue(DocumentObject* lValue,const char* SubName)
1604
{
1605
    auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
1606
    verifyObject(lValue, parent);
1607

1608
#ifndef USE_OLD_DAG
1609
    //maintain backlinks
1610
    if(parent) {
1611
        // before accessing internals make sure the object is not about to be destroyed
1612
        // otherwise the backlink contains dangling pointers
1613
        if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
1614
            for(auto *obj : _lValueList) {
1615
                if (obj)
1616
                    obj->_removeBackLink(parent);
1617
            }
1618
            if (lValue)
1619
                lValue->_addBackLink(parent);
1620
        }
1621
    }
1622
#endif
1623

1624
    if (lValue) {
1625
        aboutToSetValue();
1626
        _lValueList.resize(1);
1627
        _lValueList[0]=lValue;
1628
        _lSubList.resize(1);
1629
        _lSubList[0]=SubName;
1630
    }
1631
    else {
1632
        aboutToSetValue();
1633
        _lValueList.clear();
1634
        _lSubList.clear();
1635
    }
1636
    updateElementReference(nullptr);
1637
    checkLabelReferences(_lSubList);
1638
    hasSetValue();
1639
}
1640

1641
void PropertyLinkSubList::setValues(const std::vector<DocumentObject*>& lValue,const std::vector<const char*>& lSubNames)
1642
{
1643
    auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
1644
    for(auto obj : lValue) {
1645
        verifyObject(obj, parent);
1646
    }
1647

1648
    if (lValue.size() != lSubNames.size())
1649
        throw Base::ValueError("PropertyLinkSubList::setValues: size of subelements list != size of objects list");
1650

1651
#ifndef USE_OLD_DAG
1652
    //maintain backlinks.
1653
    if(parent) {
1654
        // before accessing internals make sure the object is not about to be destroyed
1655
        // otherwise the backlink contains dangling pointers
1656
        if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
1657
            //_lValueList can contain items multiple times, but we trust the document
1658
            //object to ensure that this works
1659
            for(auto *obj : _lValueList) {
1660
                if (obj)
1661
                    obj->_removeBackLink(parent);
1662
            }
1663

1664
            //maintain backlinks. lValue can contain items multiple times, but we trust the document
1665
            //object to ensure that the backlink is only added once
1666
            for(auto *obj : lValue) {
1667
                if (obj)
1668
                    obj->_addBackLink(parent);
1669
            }
1670
        }
1671
    }
1672
#endif
1673

1674
    aboutToSetValue();
1675
    _lValueList = lValue;
1676
    _lSubList.resize(lSubNames.size());
1677
    int i = 0;
1678
    for (std::vector<const char*>::const_iterator it = lSubNames.begin();it!=lSubNames.end();++it,++i) {
1679
        if (*it)
1680
            _lSubList[i] = *it;
1681
    }
1682
    updateElementReference(nullptr);
1683
    checkLabelReferences(_lSubList);
1684
    hasSetValue();
1685
}
1686

1687
void PropertyLinkSubList::setValues(const std::vector<DocumentObject*>& lValue,
1688
        const std::vector<std::string>& lSubNames, std::vector<ShadowSub> &&ShadowSubList)
1689
{
1690
    setValues(std::vector<DocumentObject*>(lValue),
1691
            std::vector<std::string>(lSubNames),std::move(ShadowSubList));
1692
}
1693

1694
void PropertyLinkSubList::setValues(std::vector<DocumentObject*>&& lValue,
1695
        std::vector<std::string>&& lSubNames, std::vector<ShadowSub> &&ShadowSubList)
1696
{
1697
    auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
1698
    for(auto obj : lValue) {
1699
        verifyObject(obj, parent);
1700
    }
1701
    if (lValue.size() != lSubNames.size())
1702
        throw Base::ValueError("PropertyLinkSubList::setValues: size of subelements list != size of objects list");
1703

1704
#ifndef USE_OLD_DAG
1705
    //maintain backlinks.
1706
    if(parent) {
1707
        // before accessing internals make sure the object is not about to be destroyed
1708
        // otherwise the backlink contains dangling pointers
1709
        if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
1710
            //_lValueList can contain items multiple times, but we trust the document
1711
            //object to ensure that this works
1712
            for(auto *obj : _lValueList) {
1713
                if (obj)
1714
                    obj->_removeBackLink(parent);
1715
            }
1716

1717
            //maintain backlinks. lValue can contain items multiple times, but we trust the document
1718
            //object to ensure that the backlink is only added once
1719
            for(auto *obj : lValue) {
1720
                if (obj)
1721
                    obj->_addBackLink(parent);
1722
            }
1723
        }
1724
    }
1725
#endif
1726

1727
    aboutToSetValue();
1728
    _lValueList = std::move(lValue);
1729
    _lSubList = std::move(lSubNames);
1730
    if(ShadowSubList.size()==_lSubList.size())
1731
        _ShadowSubList = std::move(ShadowSubList);
1732
    else
1733
        updateElementReference(nullptr);
1734
    checkLabelReferences(_lSubList);
1735
    hasSetValue();
1736
}
1737

1738
void PropertyLinkSubList::setValue(DocumentObject* lValue, const std::vector<std::string> &SubList)
1739
{
1740
    auto parent = dynamic_cast<App::DocumentObject*>(getContainer());
1741
    verifyObject(lValue, parent);
1742

1743
#ifndef USE_OLD_DAG
1744
    //maintain backlinks.
1745
    if(parent) {
1746
        // before accessing internals make sure the object is not about to be destroyed
1747
        // otherwise the backlink contains dangling pointers
1748
        if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
1749
            //_lValueList can contain items multiple times, but we trust the document
1750
            //object to ensure that this works
1751
            for(auto *obj : _lValueList) {
1752
                if (obj)
1753
                    obj->_removeBackLink(parent);
1754
            }
1755

1756
            //maintain backlinks. lValue can contain items multiple times, but we trust the document
1757
            //object to ensure that the backlink is only added once
1758
            if (lValue)
1759
                lValue->_addBackLink(parent);
1760
        }
1761
    }
1762
#endif
1763

1764
    aboutToSetValue();
1765
    std::size_t size = SubList.size();
1766
    this->_lValueList.clear();
1767
    this->_lSubList.clear();
1768
    if (size == 0) {
1769
        if (lValue) {
1770
            this->_lValueList.push_back(lValue);
1771
            this->_lSubList.emplace_back();
1772
        }
1773
    }
1774
    else {
1775
        this->_lSubList = SubList;
1776
        this->_lValueList.insert(this->_lValueList.begin(), size, lValue);
1777
    }
1778
    updateElementReference(nullptr);
1779
    checkLabelReferences(_lSubList);
1780
    hasSetValue();
1781
}
1782

1783
void PropertyLinkSubList::addValue(App::DocumentObject *obj, const std::vector<std::string> &subs, bool reset)
1784
{
1785
    auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
1786
    verifyObject(obj, parent);
1787

1788
#ifndef USE_OLD_DAG
1789
    //maintain backlinks.
1790
    if (parent) {
1791
        // before accessing internals make sure the object is not about to be destroyed
1792
        // otherwise the backlink contains dangling pointers
1793
        if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) {
1794
            //_lValueList can contain items multiple times, but we trust the document
1795
            //object to ensure that this works
1796
            if (reset) {
1797
                for(auto* value : _lValueList) {
1798
                    if (value && value == obj)
1799
                        value->_removeBackLink(parent);
1800
                }
1801
            }
1802

1803
            //maintain backlinks. lValue can contain items multiple times, but we trust the document
1804
            //object to ensure that the backlink is only added once
1805
            if (obj)
1806
                obj->_addBackLink(parent);
1807
        }
1808
    }
1809
#endif
1810

1811
    std::vector<DocumentObject*> valueList;
1812
    std::vector<std::string>     subList;
1813

1814
    if (reset) {
1815
        for (std::size_t i=0; i<_lValueList.size(); i++) {
1816
            if (_lValueList[i] != obj) {
1817
                valueList.push_back(_lValueList[i]);
1818
                subList.push_back(_lSubList[i]);
1819
            }
1820
        }
1821
    }
1822
    else {
1823
        valueList = _lValueList;
1824
        subList = _lSubList;
1825
    }
1826

1827
    std::size_t size = subs.size();
1828
    if (size == 0) {
1829
        if (obj) {
1830
            valueList.push_back(obj);
1831
            subList.emplace_back();
1832
        }
1833
    }
1834
    else if (obj) {
1835
        subList.insert(subList.end(), subs.begin(), subs.end());
1836
        valueList.insert(valueList.end(), size, obj);
1837
    }
1838

1839
    aboutToSetValue();
1840
    _lValueList = valueList;
1841
    _lSubList = subList;
1842
    updateElementReference(nullptr);
1843
    checkLabelReferences(_lSubList);
1844
    hasSetValue();
1845
}
1846

1847
const string PropertyLinkSubList::getPyReprString() const
1848
{
1849
    assert(this->_lValueList.size() == this->_lSubList.size());
1850

1851
    if (this->_lValueList.empty())
1852
        return std::string("None");
1853

1854
    std::stringstream strm;
1855
    strm << "[";
1856
    for (std::size_t i = 0; i < this->_lSubList.size(); i++) {
1857
        if (i>0)
1858
            strm << ",(";
1859
        else
1860
            strm << "(";
1861
        App::DocumentObject* obj = this->_lValueList[i];
1862
        if (obj) {
1863
            strm << "App.getDocument('" << obj->getDocument()->getName()
1864
                 << "').getObject('" << obj->getNameInDocument() << "')";
1865
        } else {
1866
            strm << "None";
1867
        }
1868
        strm << ",";
1869
        strm << "'" << this->_lSubList[i] << "'";
1870
        strm << ")";
1871
    }
1872
    strm << "]";
1873
    return strm.str();
1874
}
1875

1876
DocumentObject *PropertyLinkSubList::getValue() const
1877
{
1878
    App::DocumentObject* ret = nullptr;
1879
    //FIXME: cache this to avoid iterating each time, to improve speed
1880
    for (auto i : this->_lValueList) {
1881
        if (!ret)
1882
            ret = i;
1883
        if (ret != i)
1884
            return nullptr;
1885
    }
1886
    return ret;
1887
}
1888

1889
int PropertyLinkSubList::removeValue(App::DocumentObject *lValue)
1890
{
1891
    assert(this->_lValueList.size() == this->_lSubList.size());
1892

1893
    std::size_t num = std::count(this->_lValueList.begin(), this->_lValueList.end(), lValue);
1894
    if (num == 0)
1895
        return 0;
1896

1897
    std::vector<DocumentObject*> links;
1898
    std::vector<std::string> subs;
1899
    links.reserve(this->_lValueList.size() - num);
1900
    subs.reserve(this->_lSubList.size() - num);
1901

1902
    for (std::size_t i=0; i<this->_lValueList.size(); ++i) {
1903
        if (this->_lValueList[i] != lValue) {
1904
            links.push_back(this->_lValueList[i]);
1905
            subs.push_back(this->_lSubList[i]);
1906
        }
1907
    }
1908

1909
    setValues(links, subs);
1910
    return static_cast<int>(num);
1911
}
1912

1913
void PropertyLinkSubList::setSubListValues(const std::vector<PropertyLinkSubList::SubSet>& values)
1914
{
1915
    std::vector<DocumentObject*> links;
1916
    std::vector<std::string> subs;
1917

1918
    for (const auto & value : values) {
1919
        for (const auto& jt : value.second) {
1920
            links.push_back(value.first);
1921
            subs.push_back(jt);
1922
        }
1923
    }
1924

1925
    setValues(links, subs);
1926
}
1927

1928
std::vector<PropertyLinkSubList::SubSet> PropertyLinkSubList::getSubListValues(bool newStyle) const
1929
{
1930
    std::vector<PropertyLinkSubList::SubSet> values;
1931
    if (_lValueList.size() != _lSubList.size())
1932
        throw Base::ValueError("PropertyLinkSubList::getSubListValues: size of subelements list != size of objects list");
1933

1934
    assert(_ShadowSubList.size() == _lSubList.size());
1935

1936
    for (std::size_t i = 0; i < _lValueList.size(); i++) {
1937
        App::DocumentObject* link = _lValueList[i];
1938
        std::string sub;
1939
        if(newStyle && !_ShadowSubList[i].first.empty())
1940
            sub = _ShadowSubList[i].first;
1941
        else if(!newStyle && !_ShadowSubList[i].second.empty())
1942
            sub = _ShadowSubList[i].second;
1943
        else
1944
            sub = _lSubList[i];
1945
        if (values.empty() || values.back().first != link){
1946
            //new object started, start a new subset.
1947
            values.emplace_back(link, std::vector<std::string>());
1948
        }
1949
        values.back().second.push_back(sub);
1950
    }
1951
    return values;
1952
}
1953

1954
PyObject *PropertyLinkSubList::getPyObject()
1955
{
1956
    std::vector<SubSet> subLists = getSubListValues();
1957
    std::size_t count = subLists.size();
1958
#if 0//FIXME: Should switch to tuple
1959
    Py::Tuple sequence(count);
1960
#else
1961
    Py::List sequence(count);
1962
#endif
1963
    for (std::size_t i = 0; i<count; i++) {
1964
        Py::Tuple tup(2);
1965
        tup[0] = Py::asObject(subLists[i].first->getPyObject());
1966

1967
        const std::vector<std::string>& sub = subLists[i].second;
1968
        Py::Tuple items(sub.size());
1969
        for (std::size_t j = 0; j < sub.size(); j++) {
1970
            items[j] = Py::String(sub[j]);
1971
        }
1972

1973
        tup[1] = items;
1974
        sequence[i] = tup;
1975
    }
1976

1977
    return Py::new_reference_to(sequence);
1978
}
1979

1980
void PropertyLinkSubList::setPyObject(PyObject *value)
1981
{
1982
    try { //try PropertyLinkSub syntax
1983
        PropertyLinkSub dummy;
1984
        dummy.setPyObject(value);
1985
        this->setValue(dummy.getValue(), dummy.getSubValues());
1986
        return;
1987
    }
1988
    catch (...) {}
1989
    try {
1990
        // try PropertyLinkList syntax
1991
        PropertyLinkList dummy;
1992
        dummy.setPyObject(value);
1993
        const auto &values = dummy.getValues();
1994
        std::vector<std::string> subs(values.size());
1995
        this->setValues(values,subs);
1996
        return;
1997
    }catch(...) {}
1998

1999
    static const char *errMsg =
2000
        "Expects sequence of items of type DocObj, (DocObj,SubName), or (DocObj, (SubName,...))";
2001

2002
    if (!PyTuple_Check(value) && !PyList_Check(value))
2003
        throw Base::TypeError(errMsg);
2004

2005
    Py::Sequence list(value);
2006
    Py::Sequence::size_type size = list.size();
2007

2008
    std::vector<DocumentObject*> values;
2009
    values.reserve(size);
2010
    std::vector<std::string>     SubNames;
2011
    SubNames.reserve(size);
2012
    for (Py::Sequence::size_type i=0; i<size; i++) {
2013
        Py::Object item = list[i];
2014
        if ((item.isTuple() || item.isSequence()) && PySequence_Size(*item)==2) {
2015
            Py::Sequence seq(item);
2016
            if (PyObject_TypeCheck(seq[0].ptr(), &(DocumentObjectPy::Type))){
2017
                auto obj = static_cast<DocumentObjectPy*>(seq[0].ptr())->getDocumentObjectPtr();
2018
                PropertyString propString;
2019
                if (seq[1].isString()) {
2020
                    values.push_back(obj);
2021
                    propString.setPyObject(seq[1].ptr());
2022
                    SubNames.emplace_back(propString.getValue());
2023
                } else if (seq[1].isSequence()) {
2024
                    Py::Sequence list(seq[1]);
2025
                    for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
2026
                        if(!(*it).isString())
2027
                            throw Base::TypeError(errMsg);
2028
                        values.push_back(obj);
2029
                        propString.setPyObject((*it).ptr());
2030
                        SubNames.emplace_back(propString.getValue());
2031
                    }
2032
                } else
2033
                    throw Base::TypeError(errMsg);
2034
            }
2035
        } else if (PyObject_TypeCheck(*item, &(DocumentObjectPy::Type))) {
2036
            DocumentObjectPy *pcObj;
2037
            pcObj = static_cast<DocumentObjectPy*>(*item);
2038
            values.push_back(pcObj->getDocumentObjectPtr());
2039
            SubNames.emplace_back();
2040
        } else
2041
            throw Base::TypeError(errMsg);
2042
    }
2043
    setValues(values,SubNames);
2044
}
2045

2046
void PropertyLinkSubList::afterRestore() {
2047
    assert(_lSubList.size() == _ShadowSubList.size());
2048
    if(!testFlag(LinkRestoreLabel))
2049
        return;
2050
    setFlag(LinkRestoreLabel,false);
2051
    for(size_t i=0;i<_lSubList.size();++i)
2052
        restoreLabelReference(_lValueList[i],_lSubList[i],&_ShadowSubList[i]);
2053
}
2054

2055
void PropertyLinkSubList::onContainerRestored() {
2056
    unregisterElementReference();
2057
    for(size_t i=0;i<_lSubList.size();++i)
2058
        _registerElementReference(_lValueList[i],_lSubList[i],_ShadowSubList[i]);
2059
}
2060

2061
void PropertyLinkSubList::updateElementReference(DocumentObject *feature, bool reverse, bool notify) {
2062
    if(!feature) {
2063
        _ShadowSubList.clear();
2064
        unregisterElementReference();
2065
    }
2066
    _ShadowSubList.resize(_lSubList.size());
2067
    auto owner = freecad_dynamic_cast<DocumentObject>(getContainer());
2068
    if(owner && owner->isRestoring())
2069
        return;
2070
    int i=0;
2071
    bool touched = false;
2072
    for(auto &sub : _lSubList) {
2073
        auto obj = _lValueList[i];
2074
        if(_updateElementReference(feature,obj,sub,_ShadowSubList[i++],reverse,notify&&!touched))
2075
            touched = true;
2076
    }
2077
    if(!touched)
2078
        return;
2079

2080
    std::vector<int> mapped;
2081
    mapped.reserve(_mapped.size());
2082
    for(int idx : _mapped) {
2083
        if(idx<(int)_lSubList.size()) {
2084
            if(!_ShadowSubList[idx].first.empty())
2085
                _lSubList[idx] = _ShadowSubList[idx].first;
2086
            else
2087
                mapped.push_back(idx);
2088
        }
2089
    }
2090
    _mapped.swap(mapped);
2091
    if(owner && feature)
2092
        owner->onUpdateElementReference(this);
2093
    if(notify)
2094
        hasSetValue();
2095
}
2096

2097
bool PropertyLinkSubList::referenceChanged() const{
2098
    return !_mapped.empty();
2099
}
2100

2101
void PropertyLinkSubList::Save (Base::Writer &writer) const
2102
{
2103
    assert(_lSubList.size() == _ShadowSubList.size());
2104

2105
    int count = 0;
2106
    for(auto obj : _lValueList) {
2107
        if(obj && obj->isAttachedToDocument())
2108
            ++count;
2109
    }
2110
    writer.Stream() << writer.ind() << "<LinkSubList count=\"" << count <<"\">" << endl;
2111
    writer.incInd();
2112
    auto owner = dynamic_cast<DocumentObject*>(getContainer());
2113
    bool exporting = owner && owner->isExporting();
2114
    for (int i = 0; i < getSize(); i++) {
2115
        auto obj = _lValueList[i];
2116
        if(!obj || !obj->isAttachedToDocument())
2117
            continue;
2118
        const auto &shadow = _ShadowSubList[i];
2119
        // shadow.second stores the old style element name. For backward
2120
        // compatibility reason, we shall store the old name into attribute
2121
        // 'value' whenever possible.
2122
        const auto &sub = shadow.second.empty()?_lSubList[i]:shadow.second;
2123

2124
        writer.Stream() << writer.ind() << "<Link obj=\"" << obj->getExportName() << "\" sub=\"";
2125
        if(exporting) {
2126
            std::string exportName;
2127
            writer.Stream() << encodeAttribute(exportSubName(exportName,obj,sub.c_str()));
2128
            if(!shadow.second.empty() && _lSubList[i]==shadow.first)
2129
                writer.Stream() << "\" " ATTR_MAPPED "=\"1";
2130
        } else {
2131
            writer.Stream() << encodeAttribute(sub);
2132
            if(!_lSubList[i].empty()) {
2133
                if(sub!=_lSubList[i]) {
2134
                    // Stores the actual value that is shadowed. For new version FC,
2135
                    // we will restore this shadowed value instead.
2136
                    writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_lSubList[i]);
2137
                }else if(!shadow.first.empty()) {
2138
                    // Here means the user set value is old style element name.
2139
                    // We shall then store the shadow somewhere else.
2140
                    writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first);
2141
                }
2142
            }
2143
        }
2144
        writer.Stream() << "\"/>" << endl;
2145
    }
2146

2147
    writer.decInd();
2148
    writer.Stream() << writer.ind() << "</LinkSubList>" << endl ;
2149
}
2150

2151
void PropertyLinkSubList::Restore(Base::XMLReader &reader)
2152
{
2153
    // read my element
2154
    reader.readElement("LinkSubList");
2155
    // get the value of my attribute
2156
    int count = reader.getAttributeAsInteger("count");
2157

2158
    std::vector<DocumentObject*> values;
2159
    values.reserve(count);
2160
    std::vector<std::string> SubNames;
2161
    SubNames.reserve(count);
2162
    std::vector<ShadowSub> shadows;
2163
    shadows.reserve(count);
2164
    DocumentObject* father = dynamic_cast<DocumentObject*>(getContainer());
2165
    App::Document* document = father ? father->getDocument() : nullptr;
2166
    std::vector<int> mapped;
2167
    bool restoreLabel=false;
2168
    for (int i = 0; i < count; i++) {
2169
        reader.readElement("Link");
2170
        std::string name = reader.getName(reader.getAttribute("obj"));
2171
        // In order to do copy/paste it must be allowed to have defined some
2172
        // referenced objects in XML which do not exist anymore in the new
2173
        // document. Thus, we should silently ignore this.
2174
        // Property not in an object!
2175
        DocumentObject* child = document ? document->getObject(name.c_str()) : nullptr;
2176
        if (child) {
2177
            values.push_back(child);
2178
            shadows.emplace_back();
2179
            auto &shadow = shadows.back();
2180
            shadow.second = importSubName(reader,reader.getAttribute("sub"),restoreLabel);
2181
            if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) {
2182
                shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel);
2183
                SubNames.push_back(shadow.first);
2184
            }else{
2185
                SubNames.push_back(shadow.second);
2186
                if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW)
2187
                    shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel);
2188
            }
2189
            if(reader.hasAttribute(ATTR_MAPPED))
2190
                mapped.push_back(i);
2191
        } else if (reader.isVerbose())
2192
            Base::Console().Warning("Lost link to '%s' while loading, maybe "
2193
                                    "an object was not loaded correctly\n",name.c_str());
2194
    }
2195
    setFlag(LinkRestoreLabel,restoreLabel);
2196

2197
    reader.readEndElement("LinkSubList");
2198

2199
    // assignment
2200
    setValues(values,SubNames,std::move(shadows));
2201
    _mapped.swap(mapped);
2202
}
2203

2204
bool PropertyLinkSubList::upgrade(Base::XMLReader &reader, const char *typeName)
2205
{
2206
    Base::Type type = Base::Type::fromName(typeName);
2207
    if (type.isDerivedFrom(PropertyLink::getClassTypeId())) {
2208
        PropertyLink prop;
2209
        prop.setContainer(getContainer());
2210
        prop.Restore(reader);
2211
        setValue(prop.getValue());
2212
        return true;
2213
    }
2214
    else if (type.isDerivedFrom(PropertyLinkList::getClassTypeId())) {
2215
        PropertyLinkList prop;
2216
        prop.setContainer(getContainer());
2217
        prop.Restore(reader);
2218
        std::vector<std::string> subnames;
2219
        subnames.resize(prop.getSize());
2220
        setValues(prop.getValues(), subnames);
2221
        return true;
2222
    }
2223
    else if (type.isDerivedFrom(PropertyLinkSub::getClassTypeId())) {
2224
        PropertyLinkSub prop;
2225
        prop.setContainer(getContainer());
2226
        prop.Restore(reader);
2227
        setValue(prop.getValue(), prop.getSubValues());
2228
        return true;
2229
    }
2230

2231
    return false;
2232
}
2233

2234
Property *PropertyLinkSubList::CopyOnImportExternal(
2235
        const std::map<std::string,std::string> &nameMap) const
2236
{
2237
    auto owner = dynamic_cast<const DocumentObject*>(getContainer());
2238
    if(!owner || !owner->getDocument() || _lValueList.size()!=_lSubList.size())
2239
        return nullptr;
2240
    std::vector<App::DocumentObject *> values;
2241
    std::vector<std::string> subs;
2242
    auto itSub = _lSubList.begin();
2243
    for(auto itValue=_lValueList.begin();itValue!=_lValueList.end();++itValue,++itSub) {
2244
        auto value = *itValue;
2245
        const auto &sub = *itSub;
2246
        if(!value || !value->isAttachedToDocument()) {
2247
            if(!values.empty()) {
2248
                values.push_back(value);
2249
                subs.push_back(sub);
2250
            }
2251
            continue;
2252
        }
2253
        auto linked = tryImport(owner->getDocument(),value,nameMap);
2254
        auto new_sub = tryImportSubName(value,sub.c_str(),owner->getDocument(),nameMap);
2255
        if(linked!=value || !new_sub.empty()) {
2256
            if(values.empty()) {
2257
                values.reserve(_lValueList.size());
2258
                values.insert(values.end(),_lValueList.begin(),itValue);
2259
                subs.reserve(_lSubList.size());
2260
                subs.insert(subs.end(),_lSubList.begin(),itSub);
2261
            }
2262
            values.push_back(linked);
2263
            subs.push_back(std::move(new_sub));
2264
        }else if(!values.empty()) {
2265
            values.push_back(linked);
2266
            subs.push_back(sub);
2267
        }
2268
    }
2269
    if(values.empty())
2270
        return nullptr;
2271
    std::unique_ptr<PropertyLinkSubList> p(new PropertyLinkSubList);
2272
    p->_lValueList = std::move(values);
2273
    p->_lSubList = std::move(subs);
2274
    return p.release();
2275
}
2276

2277
Property *PropertyLinkSubList::CopyOnLabelChange(App::DocumentObject *obj,
2278
        const std::string &ref, const char *newLabel) const
2279
{
2280
    auto owner = dynamic_cast<const DocumentObject*>(getContainer());
2281
    if(!owner || !owner->getDocument())
2282
        return nullptr;
2283
    std::vector<App::DocumentObject *> values;
2284
    std::vector<std::string> subs;
2285
    auto itSub = _lSubList.begin();
2286
    for(auto itValue=_lValueList.begin();itValue!=_lValueList.end();++itValue,++itSub) {
2287
        auto value = *itValue;
2288
        const auto &sub = *itSub;
2289
        if(!value || !value->isAttachedToDocument()) {
2290
            if(!values.empty()) {
2291
                values.push_back(value);
2292
                subs.push_back(sub);
2293
            }
2294
            continue;
2295
        }
2296
        auto new_sub = updateLabelReference(value,sub.c_str(),obj,ref,newLabel);
2297
        if(!new_sub.empty()) {
2298
            if(values.empty()) {
2299
                values.reserve(_lValueList.size());
2300
                values.insert(values.end(),_lValueList.begin(),itValue);
2301
                subs.reserve(_lSubList.size());
2302
                subs.insert(subs.end(),_lSubList.begin(),itSub);
2303
            }
2304
            values.push_back(value);
2305
            subs.push_back(std::move(new_sub));
2306
        }else if(!values.empty()) {
2307
            values.push_back(value);
2308
            subs.push_back(sub);
2309
        }
2310
    }
2311
    if(values.empty())
2312
        return nullptr;
2313
    std::unique_ptr<PropertyLinkSubList> p(new PropertyLinkSubList);
2314
    p->_lValueList = std::move(values);
2315
    p->_lSubList = std::move(subs);
2316
    return p.release();
2317
}
2318

2319
Property *PropertyLinkSubList::CopyOnLinkReplace(const App::DocumentObject *parent,
2320
        App::DocumentObject *oldObj, App::DocumentObject *newObj) const
2321
{
2322
    std::vector<App::DocumentObject *> values;
2323
    std::vector<std::string> subs;
2324
    auto itSub = _lSubList.begin();
2325
    std::vector<size_t> positions;
2326
    for(auto itValue=_lValueList.begin();itValue!=_lValueList.end();++itValue,++itSub) {
2327
        auto value = *itValue;
2328
        const auto &sub = *itSub;
2329
        if(!value || !value->isAttachedToDocument()) {
2330
            if(!values.empty()) {
2331
                values.push_back(value);
2332
                subs.push_back(sub);
2333
            }
2334
            continue;
2335
        }
2336
        auto res = tryReplaceLink(getContainer(),value,parent,oldObj,newObj,sub.c_str());
2337
        if(res.first) {
2338
            if(values.empty()) {
2339
                values.reserve(_lValueList.size());
2340
                values.insert(values.end(),_lValueList.begin(),itValue);
2341
                subs.reserve(_lSubList.size());
2342
                subs.insert(subs.end(),_lSubList.begin(),itSub);
2343
            }
2344
            if(res.first == newObj) {
2345
                // check for duplication
2346
                auto itS = subs.begin();
2347
                for(auto itV=values.begin();itV!=values.end();) {
2348
                    if(*itV == res.first && *itS == res.second) {
2349
                        itV = values.erase(itV);
2350
                        itS = subs.erase(itS);
2351
                    } else {
2352
                        ++itV;
2353
                        ++itS;
2354
                    }
2355
                }
2356
                positions.push_back(values.size());
2357
            }
2358
            values.push_back(res.first);
2359
            subs.push_back(std::move(res.second));
2360
        }else if(!values.empty()) {
2361
            bool duplicate = false;
2362
            if(value == newObj) {
2363
                for(auto pos : positions) {
2364
                    if(sub == subs[pos]) {
2365
                        duplicate = true;
2366
                        break;
2367
                    }
2368
                }
2369
            }
2370
            if(!duplicate) {
2371
                values.push_back(value);
2372
                subs.push_back(sub);
2373
            }
2374
        }
2375
    }
2376
    if(values.empty())
2377
        return nullptr;
2378
    std::unique_ptr<PropertyLinkSubList> p(new PropertyLinkSubList);
2379
    p->_lValueList = std::move(values);
2380
    p->_lSubList = std::move(subs);
2381
    return p.release();
2382
}
2383

2384
Property *PropertyLinkSubList::Copy() const
2385
{
2386
    PropertyLinkSubList *p = new PropertyLinkSubList();
2387
    p->_lValueList = _lValueList;
2388
    p->_lSubList   = _lSubList;
2389
    return p;
2390
}
2391

2392
void PropertyLinkSubList::Paste(const Property &from)
2393
{
2394
    if(!from.isDerivedFrom(PropertyLinkSubList::getClassTypeId()))
2395
        throw Base::TypeError("Incompatible property to paste to");
2396
    auto &link = static_cast<const PropertyLinkSubList&>(from);
2397
    setValues(link._lValueList, link._lSubList);
2398
}
2399

2400
unsigned int PropertyLinkSubList::getMemSize () const
2401
{
2402
   unsigned int size = static_cast<unsigned int>(_lValueList.size() * sizeof(App::DocumentObject *));
2403
   for(int i = 0;i<getSize(); i++)
2404
       size += _lSubList[i].size();
2405
   return size;
2406
}
2407

2408
std::vector<std::string> PropertyLinkSubList::getSubValues(bool newStyle) const {
2409
    assert(_lSubList.size() == _ShadowSubList.size());
2410
    std::vector<std::string> ret;
2411
    ret.reserve(_ShadowSubList.size());
2412
    for(size_t i=0;i<_ShadowSubList.size();++i)
2413
        ret.push_back(getSubNameWithStyle(_lSubList[i],_ShadowSubList[i],newStyle));
2414
    return ret;
2415
}
2416

2417
void PropertyLinkSubList::getLinks(std::vector<App::DocumentObject *> &objs,
2418
        bool all, std::vector<std::string> *subs, bool newStyle) const
2419
{
2420
    if(all||_pcScope!=LinkScope::Hidden) {
2421
        objs.reserve(objs.size()+_lValueList.size());
2422
        for(auto obj : _lValueList) {
2423
            if(obj && obj->isAttachedToDocument())
2424
                objs.push_back(obj);
2425
        }
2426
        if(subs) {
2427
            auto _subs = getSubValues(newStyle);
2428
            subs->reserve(subs->size()+_subs.size());
2429
            std::move(_subs.begin(),_subs.end(),std::back_inserter(*subs));
2430
        }
2431
    }
2432
}
2433

2434
void PropertyLinkSubList::breakLink(App::DocumentObject *obj, bool clear) {
2435
    std::vector<DocumentObject*> values;
2436
    std::vector<std::string> subs;
2437

2438
    if(clear && getContainer()==obj) {
2439
        setValues(values,subs);
2440
        return;
2441
    }
2442
    assert(_lValueList.size()==_lSubList.size());
2443

2444
    values.reserve(_lValueList.size());
2445
    subs.reserve(_lSubList.size());
2446

2447
    int i=-1;
2448
    for(auto o : _lValueList) {
2449
        ++i;
2450
        if(o==obj)
2451
            continue;
2452
        values.push_back(o);
2453
        subs.push_back(_lSubList[i]);
2454
    }
2455
    if(values.size()!=_lValueList.size())
2456
        setValues(values,subs);
2457
}
2458

2459
bool PropertyLinkSubList::adjustLink(const std::set<App::DocumentObject*> &inList) {
2460
    if (_pcScope==LinkScope::Hidden)
2461
        return false;
2462
    auto subs = _lSubList;
2463
    auto links = _lValueList;
2464
    int idx = -1;
2465
    bool touched = false;
2466
    for(std::string &sub : subs) {
2467
        ++idx;
2468
        auto &link = links[idx];
2469
        if(!link || !link->isAttachedToDocument() || !inList.count(link))
2470
            continue;
2471
        touched = true;
2472
        size_t pos = sub.find('.');
2473
        for(;pos!=std::string::npos;pos=sub.find('.',pos+1)) {
2474
            auto sobj = link->getSubObject(sub.substr(0,pos+1).c_str());
2475
            if(!sobj || sobj->getDocument()!=link->getDocument()) {
2476
                pos = std::string::npos;
2477
                break;
2478
            }
2479
            if(!inList.count(sobj)) {
2480
                link = sobj;
2481
                sub = sub.substr(pos+1);
2482
                break;
2483
            }
2484
        }
2485
        if(pos == std::string::npos)
2486
            return false;
2487
    }
2488
    if(touched)
2489
        setValues(links,subs);
2490
    return touched;
2491
}
2492

2493
//**************************************************************************
2494
// DocInfo
2495
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2496

2497
// Key on absolute path.
2498
// Because of possible symbolic links, multiple entry may refer to the same
2499
// file. We used to rely on QFileInfo::canonicalFilePath to resolve it, but
2500
// has now been changed to simply use the absoluteFilePath(), and rely on user
2501
// to be aware of possible duplicated file location. The reason being that
2502
// some user (especially Linux user) use symlink to organize file tree.
2503
using DocInfoMap = std::map<QString, DocInfoPtr>;
2504
DocInfoMap _DocInfoMap;
2505

2506
class App::DocInfo :
2507
    public std::enable_shared_from_this<App::DocInfo>
2508
{
2509
public:
2510
    using Connection = boost::signals2::scoped_connection;
2511
    Connection connFinishRestoreDocument;
2512
    Connection connPendingReloadDocument;
2513
    Connection connDeleteDocument;
2514
    Connection connSaveDocument;
2515
    Connection connDeletedObject;
2516

2517
    DocInfoMap::iterator myPos;
2518
    std::string myPath;
2519
    App::Document *pcDoc{nullptr};
2520
    std::set<PropertyXLink*> links;
2521

2522
    static std::string getDocPath(
2523
            const char *filename, App::Document *pDoc, bool relative, QString *fullPath = nullptr)
2524
    {
2525
        bool absolute;
2526
        // The path could be an URI, in that case
2527
        // TODO: build a far much more resilient approach to test for an URI
2528
        QString path = QString::fromUtf8(filename);
2529
        if (path.startsWith(QLatin1String("https://"))) {
2530
            // We do have an URI
2531
            if (fullPath)
2532
                *fullPath = path;
2533
               return std::string(filename);
2534
        }
2535

2536
        // make sure the filename is absolute path
2537
        path = QDir::cleanPath(path);
2538
        if((absolute = QFileInfo(path).isAbsolute())) {
2539
            if(fullPath)
2540
                *fullPath = path;
2541
            if(!relative)
2542
                return std::string(path.toUtf8().constData());
2543
        }
2544

2545
        const char *docPath = pDoc->getFileName();
2546
        if(!docPath || *docPath==0)
2547
            throw Base::RuntimeError("Owner document not saved");
2548

2549
        QDir docDir(QFileInfo(QString::fromUtf8(docPath)).absoluteDir());
2550
        if(!absolute) {
2551
            path = QDir::cleanPath(docDir.absoluteFilePath(path));
2552
            if(fullPath)
2553
                *fullPath = path;
2554
        }
2555

2556
        if(relative)
2557
            return std::string(docDir.relativeFilePath(path).toUtf8().constData());
2558
        else
2559
            return std::string(path.toUtf8().constData());
2560
    }
2561

2562
    static DocInfoPtr get(const char *filename,
2563
            App::Document *pDoc,PropertyXLink *l, const char *objName)
2564
    {
2565
        QString path;
2566
        l->filePath = getDocPath(filename,pDoc,true,&path);
2567

2568
        FC_LOG("finding doc " << filename);
2569

2570
        auto it = _DocInfoMap.find(path);
2571
        DocInfoPtr info;
2572
        if(it != _DocInfoMap.end()) {
2573
            info = it->second;
2574
            if(!info->pcDoc) {
2575
                QString fullpath(info->getFullPath());
2576
                if(fullpath.size() &&
2577
                   App::GetApplication().addPendingDocument(
2578
                       fullpath.toUtf8().constData(),objName,
2579
                       l->testFlag(PropertyLinkBase::LinkAllowPartial))==0)
2580
                {
2581
                    for(App::Document *doc : App::GetApplication().getDocuments()) {
2582
                        if(getFullPath(doc->getFileName()) == fullpath) {
2583
                            info->attach(doc);
2584
                            break;
2585
                        }
2586
                    }
2587
                }
2588
            }
2589
        } else {
2590
            info = std::make_shared<DocInfo>();
2591
            auto ret = _DocInfoMap.insert(std::make_pair(path,info));
2592
            info->init(ret.first,objName,l);
2593
        }
2594

2595
        if(info->pcDoc) {
2596
            // make sure to attach only external object
2597
            auto owner = Base::freecad_dynamic_cast<DocumentObject>(l->getContainer());
2598
            if(owner && owner->getDocument() == info->pcDoc)
2599
                return info;
2600
        }
2601

2602
        info->links.insert(l);
2603
        return info;
2604
    }
2605

2606
    static QString getFullPath(const char *p) {
2607
        QString path = QString::fromUtf8(p);
2608
        if (path.isEmpty())
2609
            return path;
2610

2611
        if (path.startsWith(QLatin1String("https://")))
2612
            return path;
2613
        else {
2614
            return QFileInfo(path).absoluteFilePath();
2615
        }
2616
    }
2617

2618
    QString getFullPath() const {
2619
        QString path = myPos->first;
2620
        if (path.startsWith(QLatin1String("https://")))
2621
            return path;
2622
        else {
2623
            return QFileInfo(myPos->first).absoluteFilePath();
2624
        }
2625
    }
2626

2627
    const char *filePath() const {
2628
        return myPath.c_str();
2629
    }
2630

2631
    void deinit() {
2632
        FC_LOG("deinit " << (pcDoc?pcDoc->getName():filePath()));
2633
        assert(links.empty());
2634
        connFinishRestoreDocument.disconnect();
2635
        connPendingReloadDocument.disconnect();
2636
        connDeleteDocument.disconnect();
2637
        connSaveDocument.disconnect();
2638
        connDeletedObject.disconnect();
2639

2640
        auto me = shared_from_this();
2641
        _DocInfoMap.erase(myPos);
2642
        myPos = _DocInfoMap.end();
2643
        myPath.clear();
2644
        pcDoc = nullptr;
2645
    }
2646

2647
    void init(DocInfoMap::iterator pos, const char *objName, PropertyXLink *l) {
2648
        myPos = pos;
2649
        myPath = myPos->first.toUtf8().constData();
2650
        App::Application &app = App::GetApplication();
2651
        //NOLINTBEGIN
2652
        connFinishRestoreDocument = app.signalFinishRestoreDocument.connect(
2653
            std::bind(&DocInfo::slotFinishRestoreDocument,this,sp::_1));
2654
        connPendingReloadDocument = app.signalPendingReloadDocument.connect(
2655
            std::bind(&DocInfo::slotFinishRestoreDocument,this,sp::_1));
2656
        connDeleteDocument = app.signalDeleteDocument.connect(
2657
            std::bind(&DocInfo::slotDeleteDocument,this,sp::_1));
2658
        connSaveDocument = app.signalSaveDocument.connect(
2659
            std::bind(&DocInfo::slotSaveDocument,this,sp::_1));
2660
        //NOLINTEND
2661

2662
        QString fullpath(getFullPath());
2663
        if(fullpath.isEmpty())
2664
            FC_ERR("document not found " << filePath());
2665
        else{
2666
            for(App::Document *doc : App::GetApplication().getDocuments()) {
2667
                if(getFullPath(doc->getFileName()) == fullpath) {
2668
                    if(doc->testStatus(App::Document::PartialDoc) && !doc->getObject(objName))
2669
                        break;
2670
                    attach(doc);
2671
                    return;
2672
                }
2673
            }
2674
            FC_LOG("document pending " << filePath());
2675
            app.addPendingDocument(fullpath.toUtf8().constData(),objName,
2676
                    l->testFlag(PropertyLinkBase::LinkAllowPartial));
2677
        }
2678
    }
2679

2680
    void attach(Document *doc) {
2681
        assert(!pcDoc);
2682
        pcDoc = doc;
2683
        FC_LOG("attaching " << doc->getName() << ", " << doc->getFileName());
2684
        std::map<App::PropertyLinkBase*,std::vector<App::PropertyXLink*> > parentLinks;
2685
        for(auto it=links.begin(),itNext=it;it!=links.end();it=itNext) {
2686
            ++itNext;
2687
            auto link = *it;
2688
            if(link->_pcLink)
2689
                continue;
2690
            if(link->parentProp) {
2691
                parentLinks[link->parentProp].push_back(link);
2692
                continue;
2693
            }
2694
            auto obj = doc->getObject(link->objectName.c_str());
2695
            if(obj)
2696
                link->restoreLink(obj);
2697
            else if (doc->testStatus(App::Document::PartialDoc)) {
2698
                App::GetApplication().addPendingDocument(
2699
                        doc->FileName.getValue(),
2700
                        link->objectName.c_str(),
2701
                        false);
2702
                FC_WARN("reloading partial document '" << doc->FileName.getValue()
2703
                        << "' due to object " << link->objectName);
2704
            } else
2705
                FC_WARN("object '" << link->objectName << "' not found in document '"
2706
                        << doc->getName() << "'");
2707
        }
2708
        for(auto &v : parentLinks) {
2709
            v.first->setFlag(PropertyLinkBase::LinkRestoring);
2710
            v.first->aboutToSetValue();
2711
            for(auto link : v.second) {
2712
                auto obj = doc->getObject(link->objectName.c_str());
2713
                if(obj)
2714
                    link->restoreLink(obj);
2715
                else if (doc->testStatus(App::Document::PartialDoc)) {
2716
                    App::GetApplication().addPendingDocument(
2717
                            doc->FileName.getValue(),
2718
                            link->objectName.c_str(),
2719
                            false);
2720
                    FC_WARN("reloading partial document '" << doc->FileName.getValue()
2721
                            << "' due to object " << link->objectName);
2722
                } else
2723
                    FC_WARN("object '" << link->objectName << "' not found in document '"
2724
                            << doc->getName() << "'");
2725
            }
2726
            v.first->hasSetValue();
2727
            v.first->setFlag(PropertyLinkBase::LinkRestoring,false);
2728
        }
2729
    }
2730

2731
    void remove(PropertyXLink *l) {
2732
        auto it = links.find(l);
2733
        if(it != links.end()) {
2734
            links.erase(it);
2735
            if(links.empty())
2736
                deinit();
2737
        }
2738
    }
2739

2740
    static void restoreDocument(const App::Document &doc) {
2741
        auto it = _DocInfoMap.find(getFullPath(doc.FileName.getValue()));
2742
        if(it==_DocInfoMap.end())
2743
            return;
2744
        it->second->slotFinishRestoreDocument(doc);
2745
    }
2746

2747
    void slotFinishRestoreDocument(const App::Document &doc) {
2748
        if(pcDoc)
2749
            return;
2750
        QString fullpath(getFullPath());
2751
        if(!fullpath.isEmpty() && getFullPath(doc.getFileName())==fullpath)
2752
            attach(const_cast<App::Document*>(&doc));
2753
    }
2754

2755
    void slotSaveDocument(const App::Document &doc) {
2756
        if(!pcDoc) {
2757
            slotFinishRestoreDocument(doc);
2758
            return;
2759
        }
2760
        if(&doc!=pcDoc)
2761
            return;
2762

2763
        QFileInfo info(myPos->first);
2764
        QString path(info.absoluteFilePath());
2765
        const char *filename = doc.getFileName();
2766
        QString docPath(getFullPath(filename));
2767

2768
        if(path.isEmpty() || path!=docPath) {
2769
            FC_LOG("document '" << doc.getName() << "' path changed");
2770
            auto me = shared_from_this();
2771
            auto ret = _DocInfoMap.insert(std::make_pair(docPath,me));
2772
            if(!ret.second) {
2773
                // is that even possible?
2774
                FC_WARN("document '" << doc.getName() << "' path exists, detach");
2775
                slotDeleteDocument(doc);
2776
                return;
2777
            }
2778
            _DocInfoMap.erase(myPos);
2779
            myPos = ret.first;
2780

2781
            std::set<PropertyXLink *> tmp;
2782
            tmp.swap(links);
2783
            for(auto link : tmp) {
2784
                auto owner = static_cast<DocumentObject*>(link->getContainer());
2785
                // adjust file path for each PropertyXLink
2786
                DocInfo::get(filename,owner->getDocument(),link,link->objectName.c_str());
2787
            }
2788
        }
2789

2790
        // time stamp changed, touch the linking document.
2791
        std::set<Document*> docs;
2792
        for(auto link : links) {
2793
            auto linkdoc = static_cast<DocumentObject*>(link->getContainer())->getDocument();
2794
            auto ret = docs.insert(linkdoc);
2795
            if(ret.second) {
2796
                // This will signal the Gui::Document to call setModified();
2797
                FC_LOG("touch document " << linkdoc->getName() 
2798
                        << " on time stamp change of " << link->getFullName());
2799
                linkdoc->Comment.touch();
2800
            }
2801
        }
2802
    }
2803

2804
    void slotDeleteDocument(const App::Document &doc) {
2805
        for(auto it=links.begin(),itNext=it;it!=links.end();it=itNext) {
2806
            ++itNext;
2807
            auto link = *it;
2808
            auto obj = dynamic_cast<DocumentObject*>(link->getContainer());
2809
            if(obj && obj->getDocument() == &doc) {
2810
                links.erase(it);
2811
                // must call unlink here, so that PropertyLink::resetLink can
2812
                // remove back link before the owner object is marked as being
2813
                // destroyed
2814
                link->unlink();
2815
            }
2816
        }
2817
        if(links.empty()) {
2818
            deinit();
2819
            return;
2820
        }
2821
        if(pcDoc!=&doc)
2822
            return;
2823
        std::map<App::PropertyLinkBase*,std::vector<App::PropertyXLink*> > parentLinks;
2824
        for(auto link : links) {
2825
            link->setFlag(PropertyLinkBase::LinkDetached);
2826
            if(link->parentProp)
2827
                parentLinks[link->parentProp].push_back(link);
2828
            else
2829
                parentLinks[nullptr].push_back(link);
2830
        }
2831
        for(auto &v : parentLinks) {
2832
            if(v.first) {
2833
                v.first->setFlag(PropertyLinkBase::LinkDetached);
2834
                v.first->aboutToSetValue();
2835
            }
2836
            for(auto l : v.second)
2837
                l->detach();
2838
            if(v.first) {
2839
                v.first->hasSetValue();
2840
                v.first->setFlag(PropertyLinkBase::LinkDetached,false);
2841
            }
2842
        }
2843
        pcDoc = nullptr;
2844
    }
2845

2846
    bool hasXLink(const App::Document *doc) const{
2847
        for(auto link : links) {
2848
            auto obj = dynamic_cast<DocumentObject*>(link->getContainer());
2849
            if(obj && obj->getDocument() == doc)
2850
                return true;
2851
        }
2852
        return false;
2853
    }
2854

2855
    static void breakLinks(App::DocumentObject *obj, bool clear) {
2856
        auto doc = obj->getDocument();
2857
        for(auto itD=_DocInfoMap.begin(),itDNext=itD;itD!=_DocInfoMap.end();itD=itDNext) {
2858
            ++itDNext;
2859
            auto docInfo = itD->second;
2860
            if(docInfo->pcDoc != doc)
2861
                continue;
2862
            auto &links = docInfo->links;
2863
            std::set<PropertyLinkBase*> parentLinks;
2864
            for(auto it=links.begin(),itNext=it;it!=links.end();it=itNext) {
2865
                ++itNext;
2866
                auto link = *it;
2867
                if(link->_pcLink!=obj && !(clear && link->getContainer()==obj))
2868
                    continue;
2869
                if(link->parentProp)
2870
                    parentLinks.insert(link->parentProp);
2871
                else
2872
                    link->breakLink(obj,clear);
2873
            }
2874
            for(auto link : parentLinks)
2875
                link->breakLink(obj,clear);
2876
        }
2877
    }
2878
};
2879

2880
void PropertyLinkBase::breakLinks(App::DocumentObject *link,
2881
        const std::vector<App::DocumentObject*> &objs, bool clear)
2882
{
2883
    std::vector<Property*> props;
2884
    for(auto obj : objs) {
2885
        props.clear();
2886
        obj->getPropertyList(props);
2887
        for(auto prop : props) {
2888
            auto linkProp = dynamic_cast<PropertyLinkBase*>(prop);
2889
            if(linkProp)
2890
                linkProp->breakLink(link,clear);
2891
        }
2892
    }
2893
    DocInfo::breakLinks(link,clear);
2894
}
2895

2896
//**************************************************************************
2897
// PropertyXLink
2898
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2899

2900
TYPESYSTEM_SOURCE(App::PropertyXLink , App::PropertyLink)
2901

2902
PropertyXLink::PropertyXLink(bool _allowPartial, PropertyLinkBase *parent)
2903
    :parentProp(parent)
2904
{
2905
    setAllowPartial(_allowPartial);
2906
    setAllowExternal(true);
2907
    setSyncSubObject(true);
2908
    if(parent)
2909
        setContainer(parent->getContainer());
2910
}
2911

2912
PropertyXLink::~PropertyXLink() {
2913
    unlink();
2914
}
2915

2916
void PropertyXLink::setSyncSubObject(bool enable)
2917
{
2918
    _Flags.set((std::size_t)LinkSyncSubObject, enable);
2919
}
2920

2921
void PropertyXLink::unlink() {
2922
    if(docInfo) {
2923
        docInfo->remove(this);
2924
        docInfo.reset();
2925
    }
2926
    objectName.clear();
2927
    resetLink();
2928
}
2929

2930
void PropertyXLink::detach() {
2931
    if(docInfo && _pcLink) {
2932
        aboutToSetValue();
2933
        resetLink();
2934
        updateElementReference(nullptr);
2935
        hasSetValue();
2936
    }
2937
}
2938

2939
void PropertyXLink::aboutToSetValue() {
2940
    if(parentProp)
2941
        parentProp->aboutToSetChildValue(*this);
2942
    else
2943
        PropertyLinkBase::aboutToSetValue();
2944
}
2945

2946
void PropertyXLink::hasSetValue() {
2947
    if(parentProp)
2948
        parentProp->hasSetChildValue(*this);
2949
    else
2950
        PropertyLinkBase::hasSetValue();
2951
}
2952

2953
void PropertyXLink::setSubName(const char *subname)
2954
{
2955
    std::vector<std::string> subs;
2956
    if(subname && subname[0])
2957
        subs.emplace_back(subname);
2958
    aboutToSetValue();
2959
    setSubValues(std::move(subs));
2960
    hasSetValue();
2961
}
2962

2963
void PropertyXLink::setSubValues(std::vector<std::string> &&subs,
2964
        std::vector<ShadowSub> &&shadows)
2965
{
2966
    _SubList = std::move(subs);
2967
    _ShadowSubList.clear();
2968
    if(shadows.size() == _SubList.size())
2969
        _ShadowSubList = std::move(shadows);
2970
    else
2971
        updateElementReference(nullptr);
2972
    checkLabelReferences(_SubList);
2973
}
2974

2975
void PropertyXLink::setValue(App::DocumentObject * lValue) {
2976
    setValue(lValue,nullptr);
2977
}
2978

2979
void PropertyXLink::setValue(App::DocumentObject * lValue, const char *subname)
2980
{
2981
    std::vector<std::string> subs;
2982
    if(subname && subname[0])
2983
        subs.emplace_back(subname);
2984
    setValue(lValue,std::move(subs));
2985
}
2986

2987
void PropertyXLink::restoreLink(App::DocumentObject *lValue) {
2988
    assert(!_pcLink && lValue && docInfo);
2989

2990
    auto owner = dynamic_cast<DocumentObject*>(getContainer());
2991
    if(!owner || !owner->isAttachedToDocument())
2992
        throw Base::RuntimeError("invalid container");
2993

2994
    bool touched = owner->isTouched();
2995
    setFlag(LinkDetached,false);
2996
    setFlag(LinkRestoring);
2997
    aboutToSetValue();
2998
#ifndef USE_OLD_DAG
2999
    if (!owner->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden)
3000
        lValue->_addBackLink(owner);
3001
#endif
3002
    _pcLink=lValue;
3003
    updateElementReference(nullptr);
3004
    hasSetValue();
3005
    setFlag(LinkRestoring,false);
3006

3007
    if(!touched &&
3008
        owner->isTouched() &&
3009
        docInfo &&
3010
        docInfo->pcDoc &&
3011
        stamp==docInfo->pcDoc->LastModifiedDate.getValue())
3012
    {
3013
        owner->purgeTouched();
3014
    }
3015
}
3016

3017
void PropertyXLink::setValue(App::DocumentObject *lValue,
3018
        std::vector<std::string> &&subs, std::vector<ShadowSub> &&shadows)
3019
{
3020
    if(_pcLink==lValue && _SubList==subs)
3021
        return;
3022

3023
    if(lValue && (!lValue->isAttachedToDocument() || !lValue->getDocument())) {
3024
        throw Base::ValueError("Invalid object");
3025
        return;
3026
    }
3027

3028
    auto owner = dynamic_cast<DocumentObject*>(getContainer());
3029
    if(!owner || !owner->isAttachedToDocument())
3030
        throw Base::RuntimeError("invalid container");
3031

3032
    if(lValue == owner)
3033
        throw Base::ValueError("self linking");
3034

3035
    aboutToSetValue();
3036

3037
    DocInfoPtr info;
3038
    const char *name = "";
3039
    if(lValue) {
3040
        name = lValue->getNameInDocument();
3041
        if(lValue->getDocument() != owner->getDocument()) {
3042
            if(!docInfo || lValue->getDocument()!=docInfo->pcDoc)
3043
            {
3044
                const char *filename = lValue->getDocument()->getFileName();
3045
                if(!filename || *filename==0)
3046
                    throw Base::RuntimeError("Linked document not saved");
3047
                FC_LOG("xlink set to new document " << lValue->getDocument()->getName());
3048
                info = DocInfo::get(filename,owner->getDocument(),this,name);
3049
                assert(info && info->pcDoc == lValue->getDocument());
3050
            }else
3051
                info = docInfo;
3052
        }
3053
    }
3054

3055
    setFlag(LinkDetached,false);
3056
#ifndef USE_OLD_DAG
3057
    if (!owner->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) {
3058
        if(_pcLink)
3059
            _pcLink->_removeBackLink(owner);
3060
        if(lValue)
3061
            lValue->_addBackLink(owner);
3062
    }
3063
#endif
3064
    if(docInfo!=info) {
3065
        unlink();
3066
        docInfo = info;
3067
    }
3068
    if(!docInfo)
3069
        filePath.clear();
3070
    _pcLink=lValue;
3071
    if(docInfo && docInfo->pcDoc)
3072
        stamp=docInfo->pcDoc->LastModifiedDate.getValue();
3073
    objectName = name;
3074
    setSubValues(std::move(subs),std::move(shadows));
3075
    hasSetValue();
3076
}
3077

3078
void PropertyXLink::setValue(std::string &&filename, std::string &&name,
3079
        std::vector<std::string> &&subs, std::vector<ShadowSub> &&shadows)
3080
{
3081
    if(name.empty()) {
3082
        setValue(nullptr,std::move(subs),std::move(shadows));
3083
        return;
3084
    }
3085
    auto owner = dynamic_cast<DocumentObject*>(getContainer());
3086
    if(!owner || !owner->isAttachedToDocument())
3087
        throw Base::RuntimeError("invalid container");
3088

3089
    DocumentObject *pObject=nullptr;
3090
    DocInfoPtr info;
3091
    if(!filename.empty()) {
3092
        owner->getDocument()->signalLinkXsetValue(filename);
3093
        info = DocInfo::get(filename.c_str(),owner->getDocument(),this,name.c_str());
3094
        if(info->pcDoc)
3095
            pObject = info->pcDoc->getObject(name.c_str());
3096
    }else
3097
        pObject = owner->getDocument()->getObject(name.c_str());
3098

3099
    if(pObject) {
3100
        setValue(pObject,std::move(subs),std::move(shadows));
3101
        return;
3102
    }
3103
    setFlag(LinkDetached,false);
3104
    aboutToSetValue();
3105
#ifndef USE_OLD_DAG
3106
    if (_pcLink && !owner->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden)
3107
        _pcLink->_removeBackLink(owner);
3108
#endif
3109
    _pcLink = nullptr;
3110
    if(docInfo!=info) {
3111
        unlink();
3112
        docInfo = info;
3113
    }
3114
    if(!docInfo)
3115
        filePath.clear();
3116
    if(docInfo && docInfo->pcDoc)
3117
        stamp=docInfo->pcDoc->LastModifiedDate.getValue();
3118
    objectName = std::move(name);
3119
    setSubValues(std::move(subs),std::move(shadows));
3120
    hasSetValue();
3121
}
3122

3123
void PropertyXLink::setValue(App::DocumentObject *link,
3124
        const std::vector<std::string> &subs, std::vector<ShadowSub> &&shadows)
3125
{
3126
    setValue(link,std::vector<std::string>(subs),std::move(shadows));
3127
}
3128

3129
App::Document *PropertyXLink::getDocument() const {
3130
    return docInfo?docInfo->pcDoc:nullptr;
3131
}
3132

3133
const char *PropertyXLink::getDocumentPath() const {
3134
    return docInfo?docInfo->filePath():filePath.c_str();
3135
}
3136

3137
const char *PropertyXLink::getObjectName() const {
3138
    return objectName.c_str();
3139
}
3140

3141
bool PropertyXLink::upgrade(Base::XMLReader &reader, const char *typeName) {
3142
    if(strcmp(typeName,App::PropertyLinkGlobal::getClassTypeId().getName())==0 ||
3143
       strcmp(typeName,App::PropertyLink::getClassTypeId().getName())==0 ||
3144
       strcmp(typeName,App::PropertyLinkChild::getClassTypeId().getName())==0)
3145
    {
3146
        PropertyLink::Restore(reader);
3147
        return true;
3148
    }
3149
    FC_ERR("Cannot upgrade from " << typeName);
3150
    return false;
3151
}
3152

3153
int PropertyXLink::checkRestore(std::string *msg) const {
3154
    if(!docInfo) {
3155
        if(!_pcLink && !objectName.empty()) {
3156
            // this condition means linked object not found
3157
            if(msg) {
3158
                std::ostringstream ss;
3159
                ss << "Link not restored" << std::endl;
3160
                ss << "Object: " << objectName;
3161
                if(!filePath.empty())
3162
                    ss << std::endl << "File: " << filePath;
3163
                *msg = ss.str();
3164
            }
3165
            return 2;
3166
        }
3167
        return 0;
3168
    }
3169
    if(!_pcLink) {
3170
        if(testFlag(LinkAllowPartial) &&
3171
           (!docInfo->pcDoc ||
3172
            docInfo->pcDoc->testStatus(App::Document::PartialDoc)))
3173
        {
3174
            return 0;
3175
        }
3176
        if(msg) {
3177
            std::ostringstream ss;
3178
            ss << "Link not restored" << std::endl;
3179
            ss << "Linked object: " << objectName;
3180
            if(docInfo->pcDoc)
3181
                ss << std::endl << "Linked document: " << docInfo->pcDoc->Label.getValue();
3182
            else if(!filePath.empty())
3183
                ss << std::endl << "Linked file: " << filePath;
3184
            *msg = ss.str();
3185
        }
3186
        return 2;
3187
    }
3188
    if(!docInfo->pcDoc || stamp==docInfo->pcDoc->LastModifiedDate.getValue())
3189
        return 0;
3190

3191
    if(msg) {
3192
        std::ostringstream ss;
3193
        ss << "Time stamp changed on link "
3194
            << _pcLink->getFullName();
3195
        *msg = ss.str();
3196
    }
3197
    return 1;
3198
}
3199

3200
void PropertyXLink::afterRestore() {
3201
    assert(_SubList.size() == _ShadowSubList.size());
3202
    if(!testFlag(LinkRestoreLabel) || !_pcLink || !_pcLink->isAttachedToDocument())
3203
        return;
3204
    setFlag(LinkRestoreLabel,false);
3205
    for(size_t i=0;i<_SubList.size();++i)
3206
        restoreLabelReference(_pcLink,_SubList[i],&_ShadowSubList[i]);
3207
}
3208

3209
void PropertyXLink::onContainerRestored() {
3210
    if(!_pcLink || !_pcLink->isAttachedToDocument())
3211
        return;
3212
    for(size_t i=0;i<_SubList.size();++i)
3213
        _registerElementReference(_pcLink,_SubList[i],_ShadowSubList[i]);
3214
}
3215

3216
void PropertyXLink::updateElementReference(DocumentObject *feature,bool reverse,bool notify) {
3217
    if(!updateLinkReference(this,feature,reverse,notify,_pcLink,_SubList,_mapped,_ShadowSubList))
3218
        return;
3219
    if(notify)
3220
        hasSetValue();
3221
}
3222

3223
bool PropertyXLink::referenceChanged() const{
3224
    return !_mapped.empty();
3225
}
3226

3227
void PropertyXLink::Save (Base::Writer &writer) const {
3228
    auto owner = dynamic_cast<const DocumentObject *>(getContainer());
3229
    if(!owner || !owner->getDocument())
3230
        return;
3231

3232
    assert(_SubList.size() == _ShadowSubList.size());
3233

3234
    auto exporting = owner->isExporting();
3235
    if(_pcLink && exporting && _pcLink->isExporting()) {
3236
        // this means, we are exporting the owner and the linked object together.
3237
        // Lets save the export name
3238
        writer.Stream() << writer.ind() << "<XLink name=\"" << _pcLink->getExportName();
3239
    }else {
3240
        const char *path = filePath.c_str();
3241
        std::string _path;
3242
        if(exporting) {
3243
            // Here means we are exporting the owner but not exporting the
3244
            // linked object.  Try to use absolute file path for easy transition
3245
            // into document at different directory
3246
            if(docInfo)
3247
                _path = docInfo->filePath();
3248
            else {
3249
                auto pDoc = owner->getDocument();
3250
                const char *docPath = pDoc->getFileName();
3251
                if(docPath && docPath[0]) {
3252
                    if(!filePath.empty())
3253
                        _path = DocInfo::getDocPath(filePath.c_str(),pDoc,false);
3254
                    else
3255
                        _path = docPath;
3256
                }else
3257
                    FC_WARN("PropertyXLink export without saving the document");
3258
            }
3259
            if(!_path.empty())
3260
                path = _path.c_str();
3261
        }
3262
        writer.Stream() << writer.ind()
3263
            << "<XLink file=\"" << encodeAttribute(path)
3264
            << "\" stamp=\"" << (docInfo&&docInfo->pcDoc?docInfo->pcDoc->LastModifiedDate.getValue():"")
3265
            << "\" name=\"" << objectName;
3266
    }
3267

3268
    if(testFlag(LinkAllowPartial))
3269
        writer.Stream() << "\" partial=\"1";
3270

3271
    if(_SubList.empty()) {
3272
        writer.Stream() << "\"/>" << std::endl;
3273
    } else if(_SubList.size() == 1) {
3274
        const auto &subName = _SubList[0];
3275
        const auto &shadowSub = _ShadowSubList[0];
3276
        const auto &sub = shadowSub.second.empty()?subName:shadowSub.second;
3277
        if(exporting) {
3278
            std::string exportName;
3279
            writer.Stream() << "\" sub=\"" << 
3280
                encodeAttribute(exportSubName(exportName,_pcLink,sub.c_str()));
3281
            if(!shadowSub.second.empty() && shadowSub.first==subName)
3282
                writer.Stream() << "\" " ATTR_MAPPED "=\"1";
3283
        }else{
3284
            writer.Stream() << "\" sub=\"" << encodeAttribute(sub);
3285
            if(!sub.empty()) {
3286
                if(sub!=subName)
3287
                    writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(subName);
3288
                else if(!shadowSub.first.empty())
3289
                    writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadowSub.first);
3290
            }
3291
        }
3292
        writer.Stream() << "\"/>" << std::endl;
3293
    }else {
3294
        writer.Stream() <<"\" count=\"" << _SubList.size() << "\">" << std::endl;
3295
        writer.incInd();
3296
        for(unsigned int i = 0;i<_SubList.size(); i++) {
3297
            const auto &shadow = _ShadowSubList[i];
3298
            // shadow.second stores the old style element name. For backward
3299
            // compatibility reason, we shall store the old name into attribute
3300
            // 'value' whenever possible.
3301
            const auto &sub = shadow.second.empty()?_SubList[i]:shadow.second;
3302
            writer.Stream() << writer.ind() << "<Sub value=\"";
3303
            if(exporting) {
3304
                std::string exportName;
3305
                writer.Stream() << encodeAttribute(exportSubName(exportName,_pcLink,sub.c_str()));
3306
                if(!shadow.second.empty() && shadow.first == _SubList[i])
3307
                    writer.Stream() << "\" " ATTR_MAPPED "=\"1";
3308
            } else {
3309
                writer.Stream() << encodeAttribute(sub);
3310
                if(!_SubList[i].empty()) {
3311
                    if(sub!=_SubList[i])
3312
                        writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_SubList[i]);
3313
                    else if(!shadow.first.empty())
3314
                        writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first);
3315
                }
3316
            }
3317
            writer.Stream()<<"\"/>" << endl;
3318
        }
3319
        writer.decInd();
3320
        writer.Stream() << writer.ind() << "</XLink>" << endl ;
3321
    }
3322
}
3323

3324
void PropertyXLink::Restore(Base::XMLReader &reader)
3325
{
3326
    // read my element
3327
    reader.readElement("XLink");
3328
    std::string stampAttr,file;
3329
    if(reader.hasAttribute("stamp"))
3330
        stampAttr = reader.getAttribute("stamp");
3331
    if(reader.hasAttribute("file"))
3332
        file = reader.getAttribute("file");
3333
    setFlag(LinkAllowPartial,
3334
            reader.hasAttribute("partial") &&
3335
            reader.getAttributeAsInteger("partial"));
3336
    std::string name;
3337
    if(file.empty())
3338
        name = reader.getName(reader.getAttribute("name"));
3339
    else
3340
        name = reader.getAttribute("name");
3341

3342
    assert(getContainer()->isDerivedFrom<App::DocumentObject>());
3343
    DocumentObject *object = nullptr;
3344
    if(!name.empty() && file.empty()) {
3345
        DocumentObject* parent = static_cast<DocumentObject*>(getContainer());
3346
        Document *document = parent->getDocument();
3347
        object = document ? document->getObject(name.c_str()) : nullptr;
3348
        if(!object) {
3349
            if(reader.isVerbose()) {
3350
                FC_WARN("Lost link to '" << name << "' while loading, maybe "
3351
                        "an object was not loaded correctly");
3352
            }
3353
        }
3354
    }
3355

3356
    std::vector<std::string> subs;
3357
    std::vector<ShadowSub> shadows;
3358
    std::vector<int> mapped;
3359
    bool restoreLabel = false;
3360
    if(reader.hasAttribute("sub")) {
3361
        if(reader.hasAttribute(ATTR_MAPPED))
3362
            mapped.push_back(0);
3363
        subs.emplace_back();
3364
        auto &subname = subs.back();
3365
        shadows.emplace_back();
3366
        auto &shadow = shadows.back();
3367
        shadow.second = importSubName(reader,reader.getAttribute("sub"),restoreLabel);
3368
        if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW)
3369
            subname = shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel);
3370
        else {
3371
            subname = shadow.second;
3372
            if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW)
3373
                shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel);
3374
        }
3375
    }else if(reader.hasAttribute("count")) {
3376
        int count = reader.getAttributeAsInteger("count");
3377
        subs.resize(count);
3378
        shadows.resize(count);
3379
        for (int i = 0; i < count; i++) {
3380
            reader.readElement("Sub");
3381
            shadows[i].second = importSubName(reader,reader.getAttribute("value"),restoreLabel);
3382
            if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW)
3383
                subs[i] = shadows[i].first =
3384
                    importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel);
3385
            else {
3386
                subs[i] = shadows[i].second;
3387
                if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW)
3388
                    shadows[i].first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel);
3389
            }
3390
            if(reader.hasAttribute(ATTR_MAPPED))
3391
                mapped.push_back(i);
3392
        }
3393
        reader.readEndElement("XLink");
3394
    }
3395
    setFlag(LinkRestoreLabel,restoreLabel);
3396

3397
    if (name.empty()) {
3398
        setValue(nullptr);
3399
        return;
3400
    }
3401

3402
    if(!file.empty() || (!object && !name.empty())) {
3403
        this->stamp = stampAttr;
3404
        setValue(std::move(file),std::move(name),std::move(subs),std::move(shadows));
3405
    }else
3406
        setValue(object,std::move(subs),std::move(shadows));
3407
    _mapped = std::move(mapped);
3408
}
3409

3410
Property *PropertyXLink::CopyOnImportExternal(
3411
        const std::map<std::string,std::string> &nameMap) const
3412
{
3413
    auto owner = Base::freecad_dynamic_cast<const DocumentObject>(getContainer());
3414
    if(!owner || !owner->getDocument() || !_pcLink || !_pcLink->isAttachedToDocument())
3415
        return nullptr;
3416

3417
    auto subs = updateLinkSubs(_pcLink,_SubList,
3418
                    &tryImportSubName,owner->getDocument(),nameMap);
3419
    auto linked = tryImport(owner->getDocument(),_pcLink,nameMap);
3420
    if(subs.empty() && linked==_pcLink)
3421
        return nullptr;
3422

3423
    std::unique_ptr<PropertyXLink> p(new PropertyXLink);
3424
    copyTo(*p,linked,&subs);
3425
    return p.release();
3426
}
3427

3428
Property *PropertyXLink::CopyOnLinkReplace(const App::DocumentObject *parent,
3429
        App::DocumentObject *oldObj, App::DocumentObject *newObj) const
3430
{
3431
    auto res = tryReplaceLinkSubs(getContainer(),_pcLink,parent,oldObj,newObj,_SubList);
3432
    if(!res.first)
3433
        return nullptr;
3434
    std::unique_ptr<PropertyXLink> p(new PropertyXLink);
3435
    copyTo(*p,res.first,&res.second);
3436
    return p.release();
3437
}
3438

3439
Property *PropertyXLink::CopyOnLabelChange(App::DocumentObject *obj,
3440
        const std::string &ref, const char *newLabel) const
3441
{
3442
    auto owner = dynamic_cast<const DocumentObject*>(getContainer());
3443
    if(!owner || !owner->getDocument() || !_pcLink || !_pcLink->isAttachedToDocument())
3444
        return nullptr;
3445
    auto subs = updateLinkSubs(_pcLink,_SubList,&updateLabelReference,obj,ref,newLabel);
3446
    if(subs.empty())
3447
        return nullptr;
3448
    std::unique_ptr<PropertyXLink> p(new PropertyXLink);
3449
    copyTo(*p,_pcLink,&subs);
3450
    return p.release();
3451
}
3452

3453
void PropertyXLink::copyTo(PropertyXLink &other,
3454
        DocumentObject *linked, std::vector<std::string> *subs) const
3455
{
3456
    if(!linked)
3457
        linked = _pcLink;
3458
    if(linked && linked->isAttachedToDocument()) {
3459
        other.docName = linked->getDocument()->getName();
3460
        other.objectName = linked->getNameInDocument();
3461
        other.docInfo.reset();
3462
        other.filePath.clear();
3463
    }else{
3464
        other.objectName = objectName;
3465
        other.docName.clear();
3466
        other.docInfo = docInfo;
3467
        other.filePath = filePath;
3468
    }
3469
    if(subs)
3470
        other._SubList = std::move(*subs);
3471
    else
3472
        other._SubList = _SubList;
3473
    other._Flags = _Flags;
3474
}
3475

3476
Property *PropertyXLink::Copy() const
3477
{
3478
    std::unique_ptr<PropertyXLink> p(new PropertyXLink);
3479
    copyTo(*p);
3480
    return p.release();
3481
}
3482

3483
void PropertyXLink::Paste(const Property &from)
3484
{
3485
    if(!from.isDerivedFrom(PropertyXLink::getClassTypeId()))
3486
        throw Base::TypeError("Incompatible property to paste to");
3487

3488
    const auto &other = static_cast<const PropertyXLink&>(from);
3489
    if(!other.docName.empty()) {
3490
        auto doc = GetApplication().getDocument(other.docName.c_str());
3491
        if(!doc) {
3492
            FC_WARN("Document '" << other.docName << "' not found");
3493
            return;
3494
        }
3495
        auto obj = doc->getObject(other.objectName.c_str());
3496
        if(!obj) {
3497
            FC_WARN("Object '" << other.docName << '#' << other.objectName << "' not found");
3498
            return;
3499
        }
3500
        setValue(obj,std::vector<std::string>(other._SubList));
3501
    } else
3502
        setValue(std::string(other.filePath),std::string(other.objectName),
3503
                std::vector<std::string>(other._SubList));
3504
    setFlag(LinkAllowPartial,other.testFlag(LinkAllowPartial));
3505
}
3506

3507
bool PropertyXLink::supportXLink(const App::Property *prop) {
3508
    return prop->isDerivedFrom(PropertyXLink::getClassTypeId()) ||
3509
        prop->isDerivedFrom(PropertyXLinkSubList::getClassTypeId()) ||
3510
        prop->isDerivedFrom(PropertyXLinkContainer::getClassTypeId());
3511
}
3512

3513
bool PropertyXLink::hasXLink(const App::Document *doc) {
3514
    for(auto &v : _DocInfoMap) {
3515
        if(v.second->hasXLink(doc))
3516
            return true;
3517
    }
3518
    return false;
3519
}
3520

3521
bool PropertyXLink::hasXLink(
3522
        const std::vector<App::DocumentObject*> &objs, std::vector<App::Document*> *unsaved)
3523
{
3524
    std::set<App::Document*> docs;
3525
    bool ret = false;
3526
    for(auto o : objs) {
3527
        if(o && o->isAttachedToDocument() && docs.insert(o->getDocument()).second) {
3528
            if(!hasXLink(o->getDocument()))
3529
                continue;
3530
            if(!unsaved)
3531
                return true;
3532
            ret = true;
3533
            if(!o->getDocument()->isSaved())
3534
                unsaved->push_back(o->getDocument());
3535
        }
3536
    }
3537
    return ret;
3538
}
3539

3540
void PropertyXLink::restoreDocument(const App::Document &doc) {
3541
    DocInfo::restoreDocument(doc);
3542
}
3543

3544
std::map<App::Document*,std::set<App::Document*> >
3545
PropertyXLink::getDocumentOutList(App::Document *doc) {
3546
    std::map<App::Document*,std::set<App::Document*> > ret;
3547
    for(auto &v : _DocInfoMap) {
3548
        for(auto link : v.second->links) {
3549
            if(!v.second->pcDoc
3550
                    || link->getScope() == LinkScope::Hidden
3551
                    || link->testStatus(Property::PropTransient)
3552
                    || link->testStatus(Property::Transient)
3553
                    || link->testStatus(Property::PropNoPersist))
3554
                continue;
3555
            auto obj = dynamic_cast<App::DocumentObject*>(link->getContainer());
3556
            if(!obj || !obj->isAttachedToDocument() || !obj->getDocument())
3557
                continue;
3558
            if(doc && obj->getDocument()!=doc)
3559
                continue;
3560
            ret[obj->getDocument()].insert(v.second->pcDoc);
3561
        }
3562
    }
3563
    return ret;
3564
}
3565

3566
std::map<App::Document*,std::set<App::Document*> >
3567
PropertyXLink::getDocumentInList(App::Document *doc) {
3568
    std::map<App::Document*,std::set<App::Document*> > ret;
3569
    for(auto &v : _DocInfoMap) {
3570
        if(!v.second->pcDoc || (doc && doc!=v.second->pcDoc))
3571
            continue;
3572
        auto &docs = ret[v.second->pcDoc];
3573
        for(auto link : v.second->links) {
3574
            if(link->getScope() == LinkScope::Hidden
3575
                    || link->testStatus(Property::PropTransient)
3576
                    || link->testStatus(Property::Transient)
3577
                    || link->testStatus(Property::PropNoPersist))
3578
                continue;
3579
            auto obj = dynamic_cast<App::DocumentObject*>(link->getContainer());
3580
            if(obj && obj->isAttachedToDocument() && obj->getDocument())
3581
                docs.insert(obj->getDocument());
3582
        }
3583
    }
3584
    return ret;
3585
}
3586

3587
PyObject *PropertyXLink::getPyObject()
3588
{
3589
    if(!_pcLink)
3590
        Py_Return;
3591
    const auto &subs = getSubValues(false);
3592
    if(subs.empty())
3593
        return _pcLink->getPyObject();
3594
    Py::Tuple ret(2);
3595
    ret.setItem(0,Py::Object(_pcLink->getPyObject(),true));
3596
    PropertyString propString;
3597
    if (subs.size() == 1) {
3598
        propString.setValue(subs.front());
3599
        ret.setItem(1,Py::asObject(propString.getPyObject()));
3600
    } else {
3601
        Py::List list(subs.size());
3602
        int i = 0;
3603
        for (auto &sub : subs) {
3604
            propString.setValue(sub);
3605
            list[i++] = Py::asObject(propString.getPyObject());
3606
        }
3607
        ret.setItem(1, list);
3608
    }
3609
    return Py::new_reference_to(ret);
3610
}
3611

3612
void PropertyXLink::setPyObject(PyObject *value) {
3613
    if(PySequence_Check(value)) {
3614
        Py::Sequence seq(value);
3615
        if(seq.size()!=2)
3616
            throw Base::ValueError("Expect input sequence of size 2");
3617
        std::vector<std::string> subs;
3618
        Py::Object pyObj(seq[0].ptr());
3619
        Py::Object pySub(seq[1].ptr());
3620
        if(pyObj.isNone()) {
3621
            setValue(nullptr);
3622
            return;
3623
        } else if(!PyObject_TypeCheck(pyObj.ptr(), &DocumentObjectPy::Type))
3624
            throw Base::TypeError("Expect the first element to be of 'DocumentObject'");
3625
        PropertyString propString;
3626
        if(pySub.isString()) {
3627
            propString.setPyObject(pySub.ptr());
3628
            subs.push_back(propString.getStrValue());
3629
        } else if (pySub.isSequence()) {
3630
            Py::Sequence seq(pySub);
3631
            subs.reserve(seq.size());
3632
            for(Py_ssize_t i=0;i<seq.size();++i) {
3633
                Py::Object sub(seq[i]);
3634
                if(!sub.isString())
3635
                    throw Base::TypeError("Expect only string inside second argument");
3636
                propString.setPyObject(sub.ptr());
3637
                subs.push_back(propString.getStrValue());
3638
            }
3639
        }else
3640
            throw Base::TypeError("Expect the second element to be a string or sequence of string");
3641
        setValue(static_cast<DocumentObjectPy*>(pyObj.ptr())->getDocumentObjectPtr(), std::move(subs));
3642
    } else if(PyObject_TypeCheck(value, &(DocumentObjectPy::Type))) {
3643
        setValue(static_cast<DocumentObjectPy*>(value)->getDocumentObjectPtr());
3644
    } else if (Py_None == value) {
3645
        setValue(nullptr);
3646
    } else {
3647
        throw Base::TypeError("type must be 'DocumentObject', 'None', or '(DocumentObject, SubName)' or "
3648
                "'DocumentObject, [SubName..])");
3649
    }
3650
}
3651

3652
const char *PropertyXLink::getSubName(bool newStyle) const {
3653
    if(_SubList.empty() || _ShadowSubList.empty())
3654
        return "";
3655
    return getSubNameWithStyle(_SubList[0],_ShadowSubList[0],newStyle).c_str();
3656
}
3657

3658
void PropertyXLink::getLinks(std::vector<App::DocumentObject *> &objs,
3659
        bool all, std::vector<std::string> *subs, bool newStyle) const
3660
{
3661
    if((all||_pcScope!=LinkScope::Hidden) && _pcLink && _pcLink->isAttachedToDocument()) {
3662
        objs.push_back(_pcLink);
3663
        if(subs && _SubList.size()==_ShadowSubList.size())
3664
            *subs = getSubValues(newStyle);
3665
    }
3666
}
3667

3668
bool PropertyXLink::adjustLink(const std::set<App::DocumentObject*> &inList) {
3669
    if (_pcScope==LinkScope::Hidden)
3670
        return false;
3671
    if(!_pcLink || !_pcLink->isAttachedToDocument() || !inList.count(_pcLink))
3672
        return false;
3673
    auto subs = _SubList;
3674
    auto link = adjustLinkSubs(this,inList,_pcLink,subs);
3675
    if(link) {
3676
        setValue(link,std::move(subs));
3677
        return true;
3678
    }
3679
    return false;
3680
}
3681

3682
std::vector<std::string> PropertyXLink::getSubValues(bool newStyle) const {
3683
    assert(_SubList.size() == _ShadowSubList.size());
3684
    std::vector<std::string> ret;
3685
    ret.reserve(_SubList.size());
3686
    for(size_t i=0;i<_ShadowSubList.size();++i)
3687
        ret.push_back(getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle));
3688
    return ret;
3689
}
3690

3691
std::vector<std::string> PropertyXLink::getSubValuesStartsWith(const char* starter, bool newStyle) const
3692
{
3693
    (void)newStyle;
3694

3695
    std::vector<std::string> temp;
3696
    for(const auto & it : _SubList) {
3697
        if(strncmp(starter, it.c_str(), strlen(starter)) == 0) {
3698
            temp.push_back(it);
3699
        }
3700
    }
3701
    return temp;
3702
}
3703

3704
void PropertyXLink::setAllowPartial(bool enable) {
3705
    setFlag(LinkAllowPartial,enable);
3706
    if(enable)
3707
        return;
3708
    auto owner = dynamic_cast<const DocumentObject*>(getContainer());
3709
    if(!owner)
3710
        return;
3711
    if(!App::GetApplication().isRestoring() &&
3712
       !owner->getDocument()->isPerformingTransaction() &&
3713
       !_pcLink && docInfo && !filePath.empty() && !objectName.empty() &&
3714
       (!docInfo->pcDoc || docInfo->pcDoc->testStatus(Document::PartialDoc)))
3715
    {
3716
        auto path = docInfo->getDocPath(filePath.c_str(),owner->getDocument(),false);
3717
        if(!path.empty())
3718
            App::GetApplication().openDocument(path.c_str());
3719
    }
3720
}
3721

3722
//**************************************************************************
3723
// PropertyXLinkSub
3724
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3725

3726
TYPESYSTEM_SOURCE(App::PropertyXLinkSub , App::PropertyXLink)
3727

3728
PropertyXLinkSub::PropertyXLinkSub(bool allowPartial, PropertyLinkBase *parent)
3729
    :PropertyXLink(allowPartial,parent)
3730
{
3731

3732
}
3733

3734
PropertyXLinkSub::~PropertyXLinkSub() = default;
3735

3736
bool PropertyXLinkSub::upgrade(Base::XMLReader &reader, const char *typeName) {
3737
    if(strcmp(typeName, PropertyLinkSubGlobal::getClassTypeId().getName())==0 ||
3738
       strcmp(typeName, PropertyLinkSub::getClassTypeId().getName())==0 ||
3739
       strcmp(typeName, PropertyLinkSubChild::getClassTypeId().getName())==0)
3740
    {
3741
        App::PropertyLinkSub linkProp;
3742
        linkProp.setContainer(getContainer());
3743
        linkProp.Restore(reader);
3744
        setValue(linkProp.getValue(),linkProp.getSubValues());
3745
        return true;
3746
    }
3747
    return PropertyXLink::upgrade(reader,typeName);
3748
}
3749

3750
PyObject *PropertyXLinkSub::getPyObject()
3751
{
3752
    if(!_pcLink)
3753
        Py_Return;
3754
    Py::Tuple ret(2);
3755
    ret.setItem(0,Py::Object(_pcLink->getPyObject(),true));
3756
    const auto &subs = getSubValues(false);
3757
    Py::List list(subs.size());
3758
    int i = 0;
3759
    PropertyString propString;
3760
    for (auto &sub : subs) {
3761
        propString.setValue(sub);
3762
        list[i++] = Py::asObject(propString.getPyObject());
3763
    }
3764
    ret.setItem(1, list);
3765
    return Py::new_reference_to(ret);
3766
}
3767

3768
//**************************************************************************
3769
// PropertyXLinkSubList
3770
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3771

3772
TYPESYSTEM_SOURCE(App::PropertyXLinkSubList , App::PropertyLinkBase)
3773

3774
//**************************************************************************
3775
// Construction/Destruction
3776

3777

3778
PropertyXLinkSubList::PropertyXLinkSubList()
3779
{
3780
    _pcScope = LinkScope::Global;
3781
    setSyncSubObject(true);
3782
}
3783

3784
PropertyXLinkSubList::~PropertyXLinkSubList() = default;
3785

3786
void PropertyXLinkSubList::setSyncSubObject(bool enable)
3787
{
3788
    _Flags.set((std::size_t)LinkSyncSubObject, enable);
3789
}
3790

3791
int PropertyXLinkSubList::getSize() const
3792
{
3793
    return static_cast<int>(_Links.size());
3794
}
3795

3796
void PropertyXLinkSubList::setValue(DocumentObject* lValue,const char* SubName)
3797
{
3798
    std::map<DocumentObject*,std::vector<std::string> > values;
3799
    if(lValue) {
3800
        auto &subs = values[lValue];
3801
        if(SubName)
3802
            subs.emplace_back(SubName);
3803
    }
3804
    setValues(std::move(values));
3805
}
3806

3807
void PropertyXLinkSubList::setValues(
3808
        const std::vector<DocumentObject*>& lValue,
3809
        const std::vector<const char*>& lSubNames)
3810
{
3811
#define CHECK_SUB_SIZE(_l,_r) do{\
3812
        if(_l.size()!=_r.size())\
3813
            FC_THROWM(Base::ValueError, "object and subname size mismatch");\
3814
    }while(0)
3815
    CHECK_SUB_SIZE(lValue,lSubNames);
3816
    std::map<DocumentObject*,std::vector<std::string> > values;
3817
    int i=0;
3818
    for(auto &obj : lValue) {
3819
        const char *sub = lSubNames[i++];
3820
        if(sub)
3821
            values[obj].emplace_back(sub);
3822
    }
3823
    setValues(std::move(values));
3824
}
3825

3826
void PropertyXLinkSubList::setValues(const std::vector<DocumentObject*>& lValue,
3827
                                     const std::vector<std::string>& lSubNames)
3828
{
3829
    CHECK_SUB_SIZE(lValue,lSubNames);
3830
    std::map<DocumentObject*,std::vector<std::string> > values;
3831
    int i=0;
3832
    for(auto &obj : lValue)
3833
        values[obj].push_back(lSubNames[i++]);
3834
    setValues(std::move(values));
3835
}
3836

3837
void PropertyXLinkSubList::setSubListValues(const std::vector<PropertyLinkSubList::SubSet> &svalues) {
3838
    std::map<DocumentObject*,std::vector<std::string> > values;
3839
    for(auto &v : svalues) {
3840
        auto &s = values[v.first];
3841
        s.reserve(s.size()+v.second.size());
3842
        s.insert(s.end(),v.second.begin(),v.second.end());
3843
    }
3844
    setValues(std::move(values));
3845
}
3846

3847
void PropertyXLinkSubList::setValues(
3848
        const std::map<App::DocumentObject*,std::vector<std::string> > &values)
3849
{
3850
    setValues(std::map<App::DocumentObject*,std::vector<std::string> >(values));
3851
}
3852

3853
void PropertyXLinkSubList::setValues(
3854
        std::map<App::DocumentObject*,std::vector<std::string> > &&values)
3855
{
3856
    for(auto &v : values) {
3857
        if(!v.first || !v.first->isAttachedToDocument())
3858
            FC_THROWM(Base::ValueError,"invalid document object");
3859
    }
3860

3861
    atomic_change guard(*this);
3862

3863
    for(auto it=_Links.begin(),itNext=it;it!=_Links.end();it=itNext) {
3864
        ++itNext;
3865
        auto iter = values.find(it->getValue());
3866
        if(iter == values.end()) {
3867
            _Links.erase(it);
3868
            continue;
3869
        }
3870
        it->setSubValues(std::move(iter->second));
3871
        values.erase(iter);
3872
    }
3873

3874
    for(auto &v : values) {
3875
        _Links.emplace_back(testFlag(LinkAllowPartial),this);
3876
        _Links.back().setValue(v.first,std::move(v.second));
3877
    }
3878
    guard.tryInvoke();
3879
}
3880

3881
void PropertyXLinkSubList::addValue(App::DocumentObject *obj,
3882
        const std::vector<std::string> &subs, bool reset)
3883
{
3884
    addValue(obj,std::vector<std::string>(subs),reset);
3885
}
3886

3887
void PropertyXLinkSubList::addValue(App::DocumentObject *obj,
3888
        std::vector<std::string> &&subs, bool reset) {
3889

3890
    if(!obj || !obj->isAttachedToDocument())
3891
        FC_THROWM(Base::ValueError,"invalid document object");
3892

3893
    for(auto &l : _Links) {
3894
        if(l.getValue() == obj) {
3895
            auto s = l.getSubValues();
3896
            if(s.empty() || reset)
3897
                l.setSubValues(std::move(subs));
3898
            else {
3899
                s.reserve(s.size()+subs.size());
3900
                std::move(subs.begin(),subs.end(),std::back_inserter(s));
3901
                l.setSubValues(std::move(s));
3902
            }
3903
            return;
3904
        }
3905
    }
3906
    atomic_change guard(*this);
3907
    _Links.emplace_back(testFlag(LinkAllowPartial),this);
3908
    _Links.back().setValue(obj,std::move(subs));
3909
    guard.tryInvoke();
3910
}
3911

3912
void PropertyXLinkSubList::setValue(DocumentObject *lValue, const std::vector<std::string> &SubList)
3913
{
3914
    std::map<DocumentObject *, std::vector<std::string> > values;
3915
    if(lValue)
3916
        values[lValue] = SubList;
3917
    setValues(std::move(values));
3918
}
3919

3920
void PropertyXLinkSubList::setValues(const std::vector<DocumentObject*> &values) {
3921
    atomic_change guard(*this);
3922
    _Links.clear();
3923
    for(auto obj : values) {
3924
        _Links.emplace_back(testFlag(LinkAllowPartial),this);
3925
        _Links.back().setValue(obj);
3926
    }
3927
    guard.tryInvoke();
3928
}
3929

3930
void PropertyXLinkSubList::set1Value(int idx,
3931
                                     DocumentObject *value,
3932
                                     const std::vector<std::string> &SubList)
3933
{
3934
    if(idx < -1 || idx > getSize())
3935
        throw Base::RuntimeError("index out of bound");
3936

3937
    if(idx < 0 || idx+1 == getSize()) {
3938
        if(SubList.empty()) {
3939
            addValue(value,SubList);
3940
            return;
3941
        }
3942
        atomic_change guard(*this);
3943
        _Links.emplace_back(testFlag(LinkAllowPartial),this);
3944
        _Links.back().setValue(value);
3945
        guard.tryInvoke();
3946
        return;
3947
    }
3948

3949
    auto it = _Links.begin();
3950
    for(;idx;--idx)
3951
        ++it;
3952
    it->setValue(value,SubList);
3953
}
3954

3955
const string PropertyXLinkSubList::getPyReprString() const
3956
{
3957
    if (_Links.empty())
3958
        return std::string("None");
3959
    std::ostringstream ss;
3960
    ss << '[';
3961
    for(auto &link : _Links) {
3962
        auto obj = link.getValue();
3963
        if(!obj || !obj->isAttachedToDocument())
3964
            continue;
3965
        ss << "(App.getDocument('" << obj->getDocument()->getName()
3966
           << "').getObject('" << obj->getNameInDocument() << "'),  (";
3967
        const auto &subs = link.getSubValues();
3968
        if(subs.empty())
3969
            ss << "''";
3970
        else{
3971
            for(auto &sub : subs)
3972
                ss << "'" << sub << "',";
3973
        }
3974
        ss << ")), ";
3975
    }
3976
    ss << ']';
3977
    return ss.str();
3978
}
3979

3980
DocumentObject *PropertyXLinkSubList::getValue() const
3981
{
3982
    if(!_Links.empty())
3983
        return _Links.begin()->getValue();
3984
    return nullptr;
3985
}
3986

3987
int PropertyXLinkSubList::removeValue(App::DocumentObject *lValue)
3988
{
3989
    atomic_change guard(*this,false);
3990
    int ret = 0;
3991
    for(auto it=_Links.begin();it!=_Links.end();) {
3992
        if(it->getValue() != lValue)
3993
            ++it;
3994
        else {
3995
            guard.aboutToChange();
3996
            it = _Links.erase(it);
3997
            ++ret;
3998
        }
3999
    }
4000
    guard.tryInvoke();
4001
    return ret;
4002
}
4003

4004
PyObject *PropertyXLinkSubList::getPyObject()
4005
{
4006
    Py::List list;
4007
    for(auto &link : _Links) {
4008
        auto obj = link.getValue();
4009
        if(!obj || !obj->isAttachedToDocument())
4010
            continue;
4011

4012
        Py::Tuple tup(2);
4013
        tup[0] = Py::asObject(obj->getPyObject());
4014

4015
        const auto &subs = link.getSubValues();
4016
        Py::Tuple items(subs.size());
4017
        for (std::size_t j = 0; j < subs.size(); j++) {
4018
            items[j] = Py::String(subs[j]);
4019
        }
4020
        tup[1] = items;
4021
        list.append(tup);
4022
    }
4023
    return Py::new_reference_to(list);
4024
}
4025

4026
void PropertyXLinkSubList::setPyObject(PyObject *value)
4027
{
4028
    try { //try PropertyLinkSub syntax
4029
        PropertyLinkSub dummy;
4030
        dummy.setAllowExternal(true);
4031
        dummy.setPyObject(value);
4032
        this->setValue(dummy.getValue(), dummy.getSubValues());
4033
        return;
4034
    }
4035
    catch (Base::Exception&) {}
4036

4037
    if (!PyTuple_Check(value) && !PyList_Check(value))
4038
        throw Base::TypeError("Invalid type. Accepts (DocumentObject, (subname...)) or sequence of such type.");
4039
    Py::Sequence seq(value);
4040
    std::map<DocumentObject*, std::vector<std::string> > values;
4041
    try {
4042
        for(Py_ssize_t i=0;i<seq.size();++i) {
4043
            PropertyLinkSub link;
4044
            link.setAllowExternal(true);
4045
            link.setPyObject(seq[i].ptr());
4046
            const auto &subs = link.getSubValues();
4047
            auto &s = values[link.getValue()];
4048
            s.reserve(s.size()+subs.size());
4049
            s.insert(s.end(),subs.begin(),subs.end());
4050
        }
4051
    }
4052
    catch(Base::Exception&){
4053
        throw Base::TypeError("Invalid type inside sequence. Must be type of (DocumentObject, (subname...))");
4054
    }
4055
    setValues(std::move(values));
4056
}
4057

4058
void PropertyXLinkSubList::afterRestore() {
4059
    for(auto &l : _Links)
4060
        l.afterRestore();
4061
}
4062

4063
void PropertyXLinkSubList::onContainerRestored() {
4064
    for(auto &l : _Links)
4065
        l.onContainerRestored();
4066
}
4067

4068
void PropertyXLinkSubList::updateElementReference(DocumentObject *feature, bool reverse,bool notify) {
4069
    for(auto &l : _Links)
4070
        l.updateElementReference(feature,reverse,notify);
4071
}
4072

4073
bool PropertyXLinkSubList::referenceChanged() const{
4074
    for(auto &l : _Links) {
4075
        if(l.referenceChanged())
4076
            return true;
4077
    }
4078
    return false;
4079
}
4080

4081
void PropertyXLinkSubList::Save (Base::Writer &writer) const
4082
{
4083
    writer.Stream() << writer.ind() << "<XLinkSubList count=\"" << _Links.size();
4084
    if(testFlag(LinkAllowPartial))
4085
        writer.Stream() << "\" partial=\"1";
4086
    writer.Stream() <<"\">" << endl;
4087
    writer.incInd();
4088
    for(auto &l : _Links)
4089
        l.Save(writer);
4090
    writer.decInd();
4091
    writer.Stream() << writer.ind() << "</XLinkSubList>" << endl ;
4092
}
4093

4094
void PropertyXLinkSubList::Restore(Base::XMLReader &reader)
4095
{
4096
    reader.readElement("XLinkSubList");
4097
    setFlag(LinkAllowPartial,
4098
                reader.hasAttribute("partial") &&
4099
                reader.getAttributeAsInteger("partial"));
4100
    int count = reader.getAttributeAsInteger("count");
4101
    atomic_change guard(*this,false);
4102
    _Links.clear();
4103
    for(int i=0;i<count;++i) {
4104
        _Links.emplace_back(false,this);
4105
        _Links.back().Restore(reader);
4106
    }
4107
    reader.readEndElement("XLinkSubList");
4108
    guard.tryInvoke();
4109
}
4110

4111
Property *PropertyXLinkSubList::CopyOnImportExternal(
4112
        const std::map<std::string,std::string> &nameMap) const
4113
{
4114
    std::unique_ptr<Property> copy;
4115
    auto it = _Links.begin();
4116
    for(;it!=_Links.end();++it) {
4117
        copy.reset(it->CopyOnImportExternal(nameMap));
4118
        if(copy) break;
4119
    }
4120
    if(!copy)
4121
        return nullptr;
4122
    std::unique_ptr<PropertyXLinkSubList> p(new PropertyXLinkSubList);
4123
    for(auto iter=_Links.begin();iter!=it;++iter) {
4124
        p->_Links.emplace_back();
4125
        iter->copyTo(p->_Links.back());
4126
    }
4127
    p->_Links.emplace_back();
4128
    static_cast<PropertyXLinkSub&>(*copy).copyTo(p->_Links.back());
4129
    for(++it;it!=_Links.end();++it) {
4130
        p->_Links.emplace_back();
4131
        copy.reset(it->CopyOnImportExternal(nameMap));
4132
        if(copy)
4133
            static_cast<PropertyXLinkSub&>(*copy).copyTo(p->_Links.back());
4134
        else
4135
            it->copyTo(p->_Links.back());
4136
    }
4137
    return p.release();
4138
}
4139

4140
Property *PropertyXLinkSubList::CopyOnLabelChange(App::DocumentObject *obj,
4141
        const std::string &ref, const char *newLabel) const
4142
{
4143
    std::unique_ptr<Property> copy;
4144
    auto it = _Links.begin();
4145
    for(;it!=_Links.end();++it) {
4146
        copy.reset(it->CopyOnLabelChange(obj,ref,newLabel));
4147
        if(copy) break;
4148
    }
4149
    if(!copy)
4150
        return nullptr;
4151
    std::unique_ptr<PropertyXLinkSubList> p(new PropertyXLinkSubList);
4152
    for(auto iter=_Links.begin();iter!=it;++iter) {
4153
        p->_Links.emplace_back();
4154
        iter->copyTo(p->_Links.back());
4155
    }
4156
    p->_Links.emplace_back();
4157
    static_cast<PropertyXLinkSub&>(*copy).copyTo(p->_Links.back());
4158
    for(++it;it!=_Links.end();++it) {
4159
        p->_Links.emplace_back();
4160
        copy.reset(it->CopyOnLabelChange(obj,ref,newLabel));
4161
        if(copy)
4162
            static_cast<PropertyXLinkSub&>(*copy).copyTo(p->_Links.back());
4163
        else
4164
            it->copyTo(p->_Links.back());
4165
    }
4166
    return p.release();
4167
}
4168

4169
Property *PropertyXLinkSubList::CopyOnLinkReplace(const App::DocumentObject *parent,
4170
        App::DocumentObject *oldObj, App::DocumentObject *newObj) const
4171
{
4172
    std::unique_ptr<Property> copy;
4173
    PropertyXLinkSub *copied = nullptr;
4174
    std::set<std::string> subs;
4175
    auto it = _Links.begin();
4176
    for(;it!=_Links.end();++it) {
4177
        copy.reset(it->CopyOnLinkReplace(parent,oldObj,newObj));
4178
        if(copy) {
4179
            copied = static_cast<PropertyXLinkSub*>(copy.get());
4180
            if(copied->getValue() == newObj) {
4181
                for(auto &sub : copied->getSubValues())
4182
                    subs.insert(sub);
4183
            }
4184
            break;
4185
        }
4186
    }
4187
    if(!copy)
4188
        return nullptr;
4189
    std::unique_ptr<PropertyXLinkSubList> p(new PropertyXLinkSubList);
4190
    for(auto iter=_Links.begin();iter!=it;++iter) {
4191
        if(iter->getValue()==newObj && copied->getValue()==newObj) {
4192
            // merge subnames in case new object already exists
4193
            for(auto &sub : iter->getSubValues()) {
4194
                if(subs.insert(sub).second)
4195
                    copied->_SubList.push_back(sub);
4196
            }
4197
        } else {
4198
            p->_Links.emplace_back();
4199
            iter->copyTo(p->_Links.back());
4200
        }
4201
    }
4202
    p->_Links.emplace_back();
4203
    copied->copyTo(p->_Links.back());
4204
    copied = &p->_Links.back();
4205
    for(++it;it!=_Links.end();++it) {
4206
        if((it->getValue()==newObj||it->getValue()==oldObj)
4207
                && copied->getValue()==newObj)
4208
        {
4209
            // merge subnames in case new object already exists
4210
            for(auto &sub : it->getSubValues()) {
4211
                if(subs.insert(sub).second)
4212
                    copied->_SubList.push_back(sub);
4213
            }
4214
            continue;
4215
        }
4216
        p->_Links.emplace_back();
4217
        copy.reset(it->CopyOnLinkReplace(parent,oldObj,newObj));
4218
        if(copy)
4219
            static_cast<PropertyXLinkSub&>(*copy).copyTo(p->_Links.back());
4220
        else
4221
            it->copyTo(p->_Links.back());
4222
    }
4223
    return p.release();
4224
}
4225

4226
Property *PropertyXLinkSubList::Copy() const
4227
{
4228
    PropertyXLinkSubList *p = new PropertyXLinkSubList();
4229
    for(auto &l : _Links) {
4230
        p->_Links.emplace_back(testFlag(LinkAllowPartial),p);
4231
        l.copyTo(p->_Links.back());
4232
    }
4233
    return p;
4234
}
4235

4236
void PropertyXLinkSubList::Paste(const Property &from)
4237
{
4238
    if(!from.isDerivedFrom(PropertyXLinkSubList::getClassTypeId()))
4239
        throw Base::TypeError("Incompatible property to paste to");
4240

4241
    aboutToSetValue();
4242
    _Links.clear();
4243
    for(auto &l : static_cast<const PropertyXLinkSubList&>(from)._Links) {
4244
        _Links.emplace_back(testFlag(LinkAllowPartial),this);
4245
        _Links.back().Paste(l);
4246
    }
4247
    hasSetValue();
4248
}
4249

4250
unsigned int PropertyXLinkSubList::getMemSize () const
4251
{
4252
    unsigned int size=0;
4253
    for(auto &l : _Links)
4254
        size += l.getMemSize();
4255
   return size;
4256
}
4257

4258
const std::vector<std::string> &PropertyXLinkSubList::getSubValues(App::DocumentObject *obj) const {
4259
    for(auto &l : _Links) {
4260
        if(l.getValue() == obj)
4261
            return l.getSubValues();
4262
    }
4263
    FC_THROWM(Base::RuntimeError, "object not found");
4264
}
4265

4266
std::vector<std::string> PropertyXLinkSubList::getSubValues(App::DocumentObject *obj, bool newStyle) const {
4267
    for(auto &l : _Links) {
4268
        if(l.getValue() == obj)
4269
            return l.getSubValues(newStyle);
4270
    }
4271
    return {};
4272
}
4273

4274
void PropertyXLinkSubList::getLinks(std::vector<App::DocumentObject *> &objs,
4275
        bool all, std::vector<std::string> *subs, bool newStyle) const
4276
{
4277
    if(all||_pcScope!=LinkScope::Hidden) {
4278
        if(!subs) {
4279
            objs.reserve(objs.size()+_Links.size());
4280
            for(auto &l : _Links) {
4281
                auto obj = l.getValue();
4282
                if(obj && obj->isAttachedToDocument())
4283
                    objs.push_back(obj);
4284
            }
4285
            return;
4286
        }
4287
        size_t count=0;
4288
        for(auto &l : _Links) {
4289
            auto obj = l.getValue();
4290
            if(obj && obj->isAttachedToDocument())
4291
                count += std::max((int)l.getSubValues().size(), 1);
4292
        }
4293
        if(!count) {
4294
            objs.reserve(objs.size()+_Links.size());
4295
            for(auto &l : _Links) {
4296
                auto obj = l.getValue();
4297
                if(obj && obj->isAttachedToDocument())
4298
                    objs.push_back(obj);
4299
            }
4300
            return;
4301
        }
4302

4303
        objs.reserve(objs.size()+count);
4304
        subs->reserve(subs->size()+count);
4305
        for(auto &l : _Links) {
4306
            auto obj = l.getValue();
4307
            if(obj && obj->isAttachedToDocument()) {
4308
                auto subnames = l.getSubValues(newStyle);
4309
                if (subnames.empty())
4310
                    subnames.emplace_back("");
4311
                for(auto &sub : subnames) {
4312
                    objs.push_back(obj);
4313
                    subs->push_back(std::move(sub));
4314
                }
4315
            }
4316
        }
4317
    }
4318
}
4319

4320
void PropertyXLinkSubList::breakLink(App::DocumentObject *obj, bool clear) {
4321
    if(clear && getContainer()==obj) {
4322
        setValue(nullptr);
4323
        return;
4324
    }
4325
    atomic_change guard(*this,false);
4326
    for(auto &l : _Links) {
4327
        if(l.getValue() == obj) {
4328
            guard.aboutToChange();
4329
            l.setValue(nullptr);
4330
        }
4331
    }
4332
    guard.tryInvoke();
4333
}
4334

4335
bool PropertyXLinkSubList::adjustLink(const std::set<App::DocumentObject*> &inList) {
4336
    if (_pcScope==LinkScope::Hidden)
4337
        return false;
4338
    std::map<App::DocumentObject*,std::vector<std::string> > values;
4339
    bool touched = false;
4340
    int count=0;
4341
    for(auto &l : _Links) {
4342
        auto obj = l.getValue();
4343
        if(!obj || !obj->isAttachedToDocument()) {
4344
            ++count;
4345
            continue;
4346
        }
4347
        if(inList.count(obj) && adjustLinkSubs(this,inList,obj,l._SubList,&values))
4348
            touched = true;
4349
    }
4350
    if(touched) {
4351
        decltype(_Links) tmp;
4352
        if(count) {
4353
            // XLink allows detached state, i.e. with closed external document. So
4354
            // we need to preserve empty link
4355
            for(auto it=_Links.begin(),itNext=it;it!=_Links.end();it=itNext) {
4356
                ++itNext;
4357
                if(!it->getValue())
4358
                    tmp.splice(tmp.end(),_Links,it);
4359
            }
4360
        }
4361
        setValues(std::move(values));
4362
        _Links.splice(_Links.end(),tmp);
4363
    }
4364
    return touched;
4365
}
4366

4367
int PropertyXLinkSubList::checkRestore(std::string *msg) const {
4368
    for(auto &l : _Links) {
4369
        int res;
4370
        if((res = l.checkRestore(msg)))
4371
            return res;
4372
    }
4373
    return 0;
4374
}
4375

4376
bool PropertyXLinkSubList::upgrade(Base::XMLReader &reader, const char *typeName) {
4377
    if(strcmp(typeName, PropertyLinkListGlobal::getClassTypeId().getName())==0 ||
4378
       strcmp(typeName, PropertyLinkList::getClassTypeId().getName())==0 ||
4379
       strcmp(typeName, PropertyLinkListChild::getClassTypeId().getName())==0)
4380
    {
4381
        PropertyLinkList linkProp;
4382
        linkProp.setContainer(getContainer());
4383
        linkProp.Restore(reader);
4384
        setValues(linkProp.getValues());
4385
        return true;
4386
    } else if (strcmp(typeName, PropertyLinkSubListGlobal::getClassTypeId().getName())==0 ||
4387
               strcmp(typeName, PropertyLinkSubList::getClassTypeId().getName())==0 ||
4388
               strcmp(typeName, PropertyLinkSubListChild::getClassTypeId().getName())==0)
4389
    {
4390
        PropertyLinkSubList linkProp;
4391
        linkProp.setContainer(getContainer());
4392
        linkProp.Restore(reader);
4393
        std::map<DocumentObject *, std::vector<std::string> > values;
4394
        const auto &objs = linkProp.getValues();
4395
        const auto &subs = linkProp.getSubValues();
4396
        assert(objs.size() == subs.size());
4397
        for(size_t i=0;i<objs.size();++i)
4398
            values[objs[i]].push_back(subs[i]);
4399
        setValues(std::move(values));
4400
        return true;
4401
    }
4402
    _Links.clear();
4403
    _Links.emplace_back(testFlag(LinkAllowPartial),this);
4404
    if(!_Links.back().upgrade(reader,typeName)) {
4405
        _Links.clear();
4406
        return false;
4407
    }
4408
    return true;
4409
}
4410

4411
void PropertyXLinkSubList::setAllowPartial(bool enable) {
4412
    setFlag(LinkAllowPartial,enable);
4413
    for(auto &l : _Links)
4414
        l.setAllowPartial(enable);
4415
}
4416

4417
void PropertyXLinkSubList::hasSetChildValue(Property &) {
4418
    if(!signalCounter)
4419
        hasSetValue();
4420
}
4421

4422
void PropertyXLinkSubList::aboutToSetChildValue(Property &) {
4423
    if(!signalCounter || !hasChanged) {
4424
        aboutToSetValue();
4425
        if(signalCounter)
4426
            hasChanged = true;
4427
    }
4428
}
4429

4430
std::vector<App::DocumentObject*> PropertyXLinkSubList::getValues() const
4431
{
4432
    std::vector<DocumentObject*> xLinks;
4433
    getLinks(xLinks);
4434
    return(xLinks);
4435
}
4436

4437
//**************************************************************************
4438
// PropertyXLinkList
4439
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4440

4441
TYPESYSTEM_SOURCE(App::PropertyXLinkList , App::PropertyXLinkSubList)
4442

4443
//**************************************************************************
4444
// Construction/Destruction
4445

4446
PropertyXLinkList::PropertyXLinkList() = default;
4447

4448
PropertyXLinkList::~PropertyXLinkList() = default;
4449

4450
PyObject *PropertyXLinkList::getPyObject()
4451
{
4452
    for(auto &link : _Links) {
4453
        auto obj = link.getValue();
4454
        if(!obj || !obj->isAttachedToDocument())
4455
            continue;
4456
        if(link.hasSubName())
4457
            return PropertyXLinkSubList::getPyObject();
4458
    }
4459

4460
    Py::List list;
4461
    for(auto &link : _Links) {
4462
        auto obj = link.getValue();
4463
        if(!obj || !obj->isAttachedToDocument())
4464
            continue;
4465
        list.append(Py::asObject(obj->getPyObject()));
4466
    }
4467
    return Py::new_reference_to(list);
4468
}
4469

4470
void PropertyXLinkList::setPyObject(PyObject *value)
4471
{
4472
    try { //try PropertyLinkList syntax
4473
        PropertyLinkList dummy;
4474
        dummy.setAllowExternal(true);
4475
        dummy.setPyObject(value);
4476
        this->setValues(dummy.getValues());
4477
        return;
4478
    }
4479
    catch (Base::Exception&) {}
4480

4481
    PropertyXLinkSubList::setPyObject(value);
4482
}
4483

4484
//**************************************************************************
4485
// PropertyXLinkContainer
4486
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4487
TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyXLinkContainer , App::PropertyLinkBase)
4488

4489
PropertyXLinkContainer::PropertyXLinkContainer() {
4490
    _pcScope = LinkScope::Global;
4491
    _LinkRestored = false;
4492
}
4493

4494
PropertyXLinkContainer::~PropertyXLinkContainer() = default;
4495

4496
void PropertyXLinkContainer::afterRestore() {
4497
    _DocMap.clear();
4498
    if(!_XLinkRestores)
4499
        return;
4500
    _Deps.clear();
4501
    for(auto &info : *_XLinkRestores) {
4502
        auto obj = info.xlink->getValue();
4503
        if(!obj)
4504
            continue;
4505
        if(!info.docName.empty()) {
4506
            if(info.docName != obj->getDocument()->getName())
4507
                _DocMap[info.docName] = obj->getDocument()->getName();
4508
            if(info.docLabel != obj->getDocument()->Label.getValue())
4509
                _DocMap[App::quote(info.docLabel)] = obj->getDocument()->Label.getValue();
4510
        }
4511
        if(_Deps.insert(std::make_pair(obj,info.xlink->getScope()==LinkScope::Hidden)).second)
4512
            _XLinks[obj->getFullName()] = std::move(info.xlink);
4513
    }
4514
    _XLinkRestores.reset();
4515
}
4516

4517
void PropertyXLinkContainer::breakLink(App::DocumentObject *obj, bool clear) {
4518
    if(!obj || !obj->isAttachedToDocument())
4519
        return;
4520
    auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
4521
    if(!owner || !owner->isAttachedToDocument())
4522
        return;
4523
    if(!clear || obj!=owner) {
4524
        auto it = _Deps.find(obj);
4525
        if(it == _Deps.end())
4526
            return;
4527
        aboutToSetValue();
4528
        onBreakLink(obj);
4529
        if (obj->getDocument() != owner->getDocument())
4530
            _XLinks.erase(obj->getFullName());
4531
        else if (!it->second)
4532
            obj->_removeBackLink(owner);
4533
        _Deps.erase(it);
4534
        hasSetValue();
4535
        return;
4536
    }
4537
    if(obj!=owner)
4538
        return;
4539
    for(auto &v : _Deps) {
4540
        auto key = v.first;
4541
        if(!key || !key->isAttachedToDocument())
4542
            continue;
4543
        onBreakLink(key);
4544
        if(!v.second && key->getDocument()==owner->getDocument())
4545
            key->_removeBackLink(owner);
4546
    }
4547
    _XLinks.clear();
4548
    _Deps.clear();
4549
}
4550

4551
int PropertyXLinkContainer::checkRestore(std::string *msg) const {
4552
    if(_LinkRestored) {
4553
        for(auto &v : _XLinks) {
4554
            int res = v.second->checkRestore(msg);
4555
            if(res)
4556
                return res;
4557
        }
4558
    }
4559
    return 0;
4560
}
4561

4562
void PropertyXLinkContainer::Save (Base::Writer &writer) const {
4563

4564
    writer.Stream() << writer.ind() << "<XLinks count=\"" << _XLinks.size();
4565

4566
    std::map<App::Document*,int> docSet;
4567
    auto owner = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
4568
    if(owner && !owner->isExporting()) {
4569
        // Document name and label can change on restore, we shall record the
4570
        // current document name and label and pair it with the associated
4571
        // xlink, so that we can restore them correctly.
4572
        int i=-1;
4573
        for(auto &v : _XLinks) {
4574
            ++i;
4575
            auto obj = v.second->getValue();
4576
            if(obj && obj->getDocument())
4577
                docSet.insert(std::make_pair(obj->getDocument(),i));
4578
        }
4579

4580
        if(!docSet.empty())
4581
            writer.Stream() << "\" docs=\"" << docSet.size();
4582
    }
4583

4584
    std::ostringstream ss;
4585
    int hidden = 0;
4586
    int i=-1;
4587
    for(auto &v : _XLinks) {
4588
        ++i;
4589
        if(v.second->getScope() == LinkScope::Hidden) {
4590
            ss << i << ' ';
4591
            ++hidden;
4592
        }
4593
    }
4594
    if(hidden)
4595
        writer.Stream() << "\" hidden=\"" << ss.str();
4596

4597
    writer.Stream() << "\">" << std::endl;
4598
    writer.incInd();
4599

4600
    for(auto &v : docSet) {
4601
        writer.Stream() << writer.ind() << "<DocMap "
4602
            << "name=\"" << v.first->getName()
4603
            << "\" label=\"" << encodeAttribute(v.first->Label.getValue())
4604
            << "\" index=\"" << v.second << "\"/>" << std::endl;
4605
    }
4606

4607
    for(auto &v : _XLinks)
4608
        v.second->Save(writer);
4609
    writer.decInd();
4610

4611
    writer.Stream() << writer.ind() << "</XLinks>" << std::endl;
4612
}
4613

4614
void PropertyXLinkContainer::Restore(Base::XMLReader &reader) {
4615
    reader.readElement("XLinks");
4616
    auto count = reader.getAttributeAsUnsigned("count");
4617
    _XLinkRestores = std::make_unique<std::vector<RestoreInfo>>(count);
4618

4619
    if(reader.hasAttribute("hidden")) {
4620
        std::istringstream iss(reader.getAttribute("hidden"));
4621
        int index;
4622
        while(iss >> index) {
4623
            if(index>=0 && index<static_cast<int>(count))
4624
                _XLinkRestores->at(index).hidden = true;
4625
        }
4626
    }
4627

4628
    if(reader.hasAttribute("docs")) {
4629
        auto docCount = reader.getAttributeAsUnsigned("docs");
4630
        _DocMap.clear();
4631
        for(unsigned i=0;i<docCount;++i) {
4632
            reader.readElement("DocMap");
4633
            auto index = reader.getAttributeAsUnsigned("index");
4634
            if(index>=count) {
4635
                FC_ERR(propertyName(this) << " invalid document map entry");
4636
                continue;
4637
            }
4638
            auto &info = _XLinkRestores->at(index);
4639
            info.docName = reader.getAttribute("name");
4640
            info.docLabel = reader.getAttribute("label");
4641
        }
4642
    }
4643

4644
    for(auto &info : *_XLinkRestores) {
4645
        info.xlink.reset(createXLink());
4646
        if(info.hidden)
4647
            info.xlink->setScope(LinkScope::Hidden);
4648
        info.xlink->Restore(reader);
4649
    }
4650
    reader.readEndElement("XLinks");
4651
}
4652

4653
void PropertyXLinkContainer::aboutToSetChildValue(App::Property &prop) {
4654
    auto xlink = dynamic_cast<App::PropertyXLink*>(&prop);
4655
    if(xlink && xlink->testFlag(LinkDetached)) {
4656
        if(_Deps.erase(const_cast<App::DocumentObject*>(xlink->getValue())))
4657
            onBreakLink(xlink->getValue());
4658
    }
4659
}
4660

4661
void PropertyXLinkContainer::onBreakLink(DocumentObject *) {
4662
}
4663

4664
PropertyXLink *PropertyXLinkContainer::createXLink() {
4665
    return new PropertyXLink(false,this);
4666
}
4667

4668
bool PropertyXLinkContainer::isLinkedToDocument(const App::Document &doc) const {
4669
    auto iter = _XLinks.lower_bound(doc.getName());
4670
    if(iter != _XLinks.end()) {
4671
        size_t len = strlen(doc.getName());
4672
        return iter->first.size()>len
4673
            && iter->first[len] == '#'
4674
            && boost::starts_with(iter->first,doc.getName());
4675
    }
4676
    return false;
4677
}
4678

4679
void PropertyXLinkContainer::updateDeps(std::map<DocumentObject*,bool> &&newDeps) {
4680
    auto owner = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
4681
    if(!owner || !owner->isAttachedToDocument())
4682
        return;
4683
    newDeps.erase(owner);
4684

4685
    for(auto &v : newDeps) {
4686
        auto obj = v.first;
4687
        if(obj && obj->isAttachedToDocument()) {
4688
            auto it = _Deps.find(obj);
4689
            if(it != _Deps.end()) {
4690
                if(v.second != it->second) {
4691
                    if(v.second)
4692
                        obj->_removeBackLink(owner);
4693
                    else
4694
                        obj->_addBackLink(owner);
4695
                }
4696
                _Deps.erase(it);
4697
                continue;
4698
            }
4699
            if(owner->getDocument()!=obj->getDocument()) {
4700
                auto &xlink = _XLinks[obj->getFullName()];
4701
                if(!xlink) {
4702
                    xlink.reset(createXLink());
4703
                    xlink->setValue(obj);
4704
                }
4705
                xlink->setScope(v.second?LinkScope::Hidden:LinkScope::Global);
4706
            }
4707
            else if(!v.second)
4708
                obj->_addBackLink(owner);
4709

4710
            onAddDep(obj);
4711
        }
4712
    }
4713
    for(auto &v : _Deps) {
4714
        auto obj = v.first;
4715
        if(!obj || !obj->isAttachedToDocument())
4716
            continue;
4717
        if(obj->getDocument()==owner->getDocument()) {
4718
            if(!v.second)
4719
                obj->_removeBackLink(owner);
4720
        }else
4721
            _XLinks.erase(obj->getFullName());
4722
        onRemoveDep(obj);
4723
    }
4724
    _Deps = std::move(newDeps);
4725

4726
    _LinkRestored = testFlag(LinkRestoring);
4727

4728
    if(!_LinkRestored && !testFlag(LinkDetached)) {
4729
        for(auto it=_XLinks.begin(),itNext=it;it!=_XLinks.end();it=itNext) {
4730
            ++itNext;
4731
            if(!it->second->getValue())
4732
                _XLinks.erase(it);
4733
        }
4734
    }
4735
}
4736

4737
void PropertyXLinkContainer::clearDeps() {
4738
    auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
4739
    if(!owner || !owner->isAttachedToDocument())
4740
        return;
4741
#ifndef USE_OLD_DAG
4742
    if (!owner->testStatus(ObjectStatus::Destroy)) {
4743
        for(auto &v : _Deps) {
4744
            auto obj = v.first;
4745
            if(!v.second && obj && obj->isAttachedToDocument() && obj->getDocument()==owner->getDocument())
4746
                obj->_removeBackLink(owner);
4747
        }
4748
    }
4749
#endif
4750
    _Deps.clear();
4751
    _XLinks.clear();
4752
    _LinkRestored = false;
4753
}
4754

4755
void PropertyXLinkContainer::getLinks(std::vector<App::DocumentObject *> &objs, 
4756
        bool all, std::vector<std::string> * /*subs*/, bool /*newStyle*/) const
4757
{
4758
    for(auto &v : _Deps) {
4759
        if(all || !v.second)
4760
            objs.push_back(v.first);
4761
    }
4762
}
4763

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.