FreeCAD

Форк
0
/
Tree.cpp 
5822 строки · 205.2 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2004 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
#ifndef _PreComp_
27
# include <QAction>
28
# include <QActionGroup>
29
# include <QApplication>
30
# include <QContextMenuEvent>
31
# include <QCursor>
32
# include <QHeaderView>
33
# include <QMenu>
34
# include <QMessageBox>
35
# include <QPainter>
36
# include <QPixmap>
37
# include <QThread>
38
# include <QTimer>
39
# include <QToolTip>
40
# include <QVBoxLayout>
41
#endif
42

43
#include <Base/Console.h>
44
#include <Base/Reader.h>
45
#include <Base/Sequencer.h>
46
#include <Base/Tools.h>
47
#include <Base/Writer.h>
48

49
#include <App/Color.h>
50
#include <App/Document.h>
51
#include <App/DocumentObjectGroup.h>
52
#include <App/AutoTransaction.h>
53
#include <App/GeoFeatureGroupExtension.h>
54
#include <App/Link.h>
55

56
#include "Tree.h"
57
#include "BitmapFactory.h"
58
#include "Command.h"
59
#include "Document.h"
60
#include "ExpressionCompleter.h"
61
#include "Macro.h"
62
#include "MainWindow.h"
63
#include "MenuManager.h"
64
#include "TreeParams.h"
65
#include "View3DInventor.h"
66
#include "ViewProviderDocumentObject.h"
67
#include "Widgets.h"
68
#include "Workbench.h"
69

70

71
FC_LOG_LEVEL_INIT("Tree", false, true, true)
72

73
#define _TREE_PRINT(_level,_func,_msg) \
74
    _FC_PRINT(FC_LOG_INSTANCE,_level,_func, '['<<getTreeName()<<"] " << _msg)
75
#define TREE_MSG(_msg) _TREE_PRINT(FC_LOGLEVEL_MSG,Notify<Base::LogStyle::Message>,_msg)
76
#define TREE_WARN(_msg) _TREE_PRINT(FC_LOGLEVEL_WARN,Notify<Base::LogStyle::Warning>,_msg)
77
#define TREE_ERR(_msg) _TREE_PRINT(FC_LOGLEVEL_ERR,Notify<Base::LogStyle::Error>,_msg)
78
#define TREE_LOG(_msg) _TREE_PRINT(FC_LOGLEVEL_LOG,Notify<Base::LogStyle::Log>,_msg)
79
#define TREE_TRACE(_msg) _TREE_PRINT(FC_LOGLEVEL_TRACE,Notify<Base::LogStyle::Log>,_msg)
80

81
using namespace Gui;
82
namespace sp = std::placeholders;
83

84
/////////////////////////////////////////////////////////////////////////////////
85

86
std::unique_ptr<QPixmap>  TreeWidget::documentPixmap;
87
std::unique_ptr<QPixmap>  TreeWidget::documentPartialPixmap;
88
static QBrush _TreeItemBackground;
89
std::set<TreeWidget*> TreeWidget::Instances;
90
static TreeWidget* _LastSelectedTreeWidget;
91
const int TreeWidget::DocumentType = 1000;
92
const int TreeWidget::ObjectType = 1001;
93
static bool _DraggingActive;
94
static bool _DragEventFilter;
95

96
static bool isVisibilityIconEnabled() {
97
    return TreeParams::getVisibilityIcon();
98
}
99

100
static bool isOnlyNameColumnDisplayed() {
101
    return TreeParams::getHideInternalNames() 
102
        && TreeParams::getHideColumn();
103
}
104

105
static bool isSelectionCheckBoxesEnabled() {
106
    return TreeParams::getCheckBoxesSelection();
107
}
108

109
void TreeParams::onItemBackgroundChanged()
110
{
111
    if (getItemBackground()) {
112
        App::Color color;
113
        color.setPackedValue(getItemBackground());
114
        QColor col;
115
        col.setRedF(color.r);
116
        col.setGreenF(color.g);
117
        col.setBlueF(color.b);
118
        col.setAlphaF(color.a);
119
        _TreeItemBackground = QBrush(col);
120
    } else
121
        _TreeItemBackground = QBrush();
122
    refreshTreeViews();
123
}
124

125
//////////////////////////////////////////////////////////////////////////////////////
126
struct Stats {
127
#define DEFINE_STATS \
128
    DEFINE_STAT(testStatus1) \
129
    DEFINE_STAT(testStatus2) \
130
    DEFINE_STAT(testStatus3) \
131
    DEFINE_STAT(getIcon) \
132
    DEFINE_STAT(setIcon) \
133

134
#define DEFINE_STAT(_name) \
135
    FC_DURATION_DECLARE(_name);\
136
    int _name##_count;
137

138
    DEFINE_STATS
139

140
        void init() {
141
#undef DEFINE_STAT
142
#define DEFINE_STAT(_name) \
143
        FC_DURATION_INIT(_name);\
144
        _name##_count = 0;
145

146
        DEFINE_STATS
147
    }
148

149
    void print() {
150
#undef DEFINE_STAT
151
#define DEFINE_STAT(_name) FC_DURATION_MSG(_name, #_name " count: " << _name##_count);
152
        DEFINE_STATS
153
    }
154

155
#undef DEFINE_STAT
156
#define DEFINE_STAT(_name) \
157
    void time_##_name(FC_TIME_POINT &t) {\
158
        ++_name##_count;\
159
        FC_DURATION_PLUS(_name,t);\
160
    }
161

162
    DEFINE_STATS
163
};
164

165
//static Stats _Stats;
166

167
struct TimingInfo {
168
    bool timed = false;
169
    FC_TIME_POINT t;
170
    FC_DURATION& d;
171
    explicit TimingInfo(FC_DURATION& d)
172
        :d(d)
173
    {
174
        _FC_TIME_INIT(t);
175
    }
176
    ~TimingInfo() {
177
        stop();
178
    }
179
    void stop() {
180
        if (!timed) {
181
            timed = true;
182
            FC_DURATION_PLUS(d, t);
183
        }
184
    }
185
    void reset() {
186
        stop();
187
        _FC_TIME_INIT(t);
188
    }
189
};
190

191
// #define DO_TIMING
192
#ifdef DO_TIMING
193
#define _Timing(_idx,_name) ++_Stats._name##_count; TimingInfo _tt##_idx(_Stats._name)
194
#define Timing(_name) _Timing(0,_name)
195
#define _TimingStop(_idx,_name) _tt##_idx.stop();
196
#define TimingStop(_name) _TimingStop(0,_name);
197
#define TimingInit() _Stats.init();
198
#define TimingPrint() _Stats.print();
199
#else
200
#define _Timing(...) do{}while(0)
201
#define Timing(...) do{}while(0)
202
#define TimingInit() do{}while(0)
203
#define TimingPrint() do{}while(0)
204
#define _TimingStop(...) do{}while(0);
205
#define TimingStop(...) do{}while(0);
206
#endif
207

208
// ---------------------------------------------------------------------------
209

210
using DocumentObjectItems = std::set<DocumentObjectItem*>;
211

212
class Gui::DocumentObjectData {
213
public:
214
    bool dirtyFlag {};
215
    DocumentItem* docItem;
216
    DocumentObjectItems items;
217
    ViewProviderDocumentObject* viewObject;
218
    DocumentObjectItem* rootItem{nullptr};
219
    std::vector<App::DocumentObject*> children;
220
    std::set<App::DocumentObject*> childSet;
221
    bool removeChildrenFromRoot;
222
    bool itemHidden;
223
    std::string label;
224
    std::string label2;
225
    std::string internalName;
226

227
    using Connection = boost::signals2::scoped_connection;
228

229
    Connection connectIcon;
230
    Connection connectTool;
231
    Connection connectStat;
232
    Connection connectHl;
233

234
    DocumentObjectData(DocumentItem* docItem, ViewProviderDocumentObject* vpd)
235
        : docItem(docItem)
236
        , viewObject(vpd)
237
    {
238
        //NOLINTBEGIN
239
        // Setup connections
240
        connectIcon = viewObject->signalChangeIcon.connect(
241
            std::bind(&DocumentObjectData::slotChangeIcon, this));
242
        connectTool = viewObject->signalChangeToolTip.connect(
243
            std::bind(&DocumentObjectData::slotChangeToolTip, this, sp::_1));
244
        connectStat = viewObject->signalChangeStatusTip.connect(
245
            std::bind(&DocumentObjectData::slotChangeStatusTip, this, sp::_1));
246
        connectHl = viewObject->signalChangeHighlight.connect(
247
            std::bind(&DocumentObjectData::slotChangeHighlight, this, sp::_1, sp::_2));
248
        //NOLINTEND
249

250
        removeChildrenFromRoot = viewObject->canRemoveChildrenFromRoot();
251
        itemHidden = !viewObject->showInTree();
252
        label = viewObject->getObject()->Label.getValue();
253
        label2 = viewObject->getObject()->Label2.getValue();
254
        internalName = viewObject->getObject()->getNameInDocument();
255
    }
256

257
    void insertItem(DocumentObjectItem* item)
258
    {
259
        items.insert(item);
260
        dirtyFlag = true;
261
    }
262

263
    void removeItem(DocumentObjectItem* item)
264
    {
265
        auto it = items.find(item);
266
        if (it == items.end()) {
267
            assert(0);
268
        }
269
        else {
270
            items.erase(it);
271
            dirtyFlag = true;
272
        }
273
    }
274

275
    const char* getTreeName() const {
276
        return docItem->getTreeName();
277
    }
278

279
    void updateChildren(DocumentObjectDataPtr other) {
280
        children = other->children;
281
        childSet = other->childSet;
282
    }
283

284
    bool updateChildren(bool checkVisibility) {
285
        auto newChildren = viewObject->claimChildren();
286
        auto obj = viewObject->getObject();
287
        std::set<App::DocumentObject*> newSet;
288
        bool updated = false;
289
        for (auto child : newChildren) {
290
            auto childVp = docItem->getViewProvider(child);
291
            if (!childVp)
292
                continue;
293
            if (child && child->isAttachedToDocument()) {
294
                if (!newSet.insert(child).second) {
295
                    TREE_WARN("duplicate child item " << obj->getFullName()
296
                        << '.' << child->getNameInDocument());
297
                }
298
                else if (!childSet.erase(child)) {
299
                    // this means new child detected
300
                    updated = true;
301
                    if (child->getDocument() == obj->getDocument() &&
302
                        child->getDocument() == docItem->document()->getDocument())
303
                    {
304
                        auto& parents = docItem->_ParentMap[child];
305
                        if (parents.insert(obj).second && child->Visibility.getValue()) {
306
                            bool showable = false;
307
                            for (auto parent : parents) {
308
                                if (!parent->hasChildElement()
309
                                    && parent->getLinkedObject(false) == parent)
310
                                {
311
                                    showable = true;
312
                                    break;
313
                                }
314
                            }
315
                            childVp->setShowable(showable);
316
                        }
317
                    }
318
                }
319
            }
320
        }
321
        for (auto child : childSet) {
322
            if (newSet.find(child) == newSet.end()) {
323
                // this means old child removed
324
                updated = true;
325
                auto mapIt = docItem->_ParentMap.find(child);
326

327
                // If 'child' is not part of the map then it has already been deleted
328
                // in _slotDeleteObject.
329
                if (mapIt != docItem->_ParentMap.end()) {
330
                    docItem->_ParentMap[child].erase(obj);
331

332
                    auto childVp = docItem->getViewProvider(child);
333
                    if (childVp && child->getDocument() == obj->getDocument())
334
                        childVp->setShowable(docItem->isObjectShowable(child));
335
                }
336
            }
337
        }
338
        // We still need to check the order of the children
339
        updated = updated || children != newChildren;
340
        children.swap(newChildren);
341
        childSet.swap(newSet);
342

343
        if (updated && checkVisibility) {
344
            for (auto child : children) {
345
                auto childVp = docItem->getViewProvider(child);
346
                if (childVp && child->getDocument() == obj->getDocument())
347
                    childVp->setShowable(docItem->isObjectShowable(child));
348
            }
349
        }
350
        return updated;
351
    }
352

353
    void testStatus(bool resetStatus = false) {
354
        QIcon icon, icon2;
355
        for (auto item : items)
356
            item->testStatus(resetStatus, icon, icon2);
357
    }
358

359
    void slotChangeIcon() {
360
        testStatus(true);
361
    }
362

363
    void slotChangeToolTip(const QString& tip) {
364
        for (auto item : items)
365
            item->setToolTip(0, tip);
366
    }
367

368
    void slotChangeStatusTip(const QString& tip) {
369
        for (auto item : items)
370
            item->setStatusTip(0, tip);
371
    }
372

373
    void slotChangeHighlight(bool set, Gui::HighlightMode mode) {
374
        for (auto item : items)
375
            item->setHighlight(set, mode);
376
    }
377
};
378

379
// ---------------------------------------------------------------------------
380

381
class DocumentItem::ExpandInfo :
382
    public std::unordered_map<std::string, DocumentItem::ExpandInfoPtr>
383
{
384
public:
385
    void restore(Base::XMLReader& reader) {
386
        int level = reader.level();
387
        int count = reader.getAttributeAsInteger("count");
388
        for (int i = 0; i < count; ++i) {
389
            reader.readElement("Expand");
390
            auto& entry = (*this)[reader.getAttribute("name")];
391
            if (!reader.hasAttribute("count"))
392
                continue;
393
            entry.reset(new ExpandInfo);
394
            entry->restore(reader);
395
        }
396
        reader.readEndElement("Expand", level - 1);
397
    }
398
};
399

400
// ---------------------------------------------------------------------------
401

402
namespace Gui {
403
/**
404
 * TreeWidget item delegate for editing
405
 */
406
class TreeWidgetItemDelegate: public QStyledItemDelegate {
407
    typedef QStyledItemDelegate inherited;
408

409
    // Beware, big scary hack incoming!
410
    //
411
    // This is artificial QTreeWidget that is not rendered and its sole goal is to be the source
412
    // of style information that can be manipulated using QSS. From Qt6.5 tree branches also
413
    // have rendered background using ::item sub-control. Whole row also gets background from
414
    // the same sub-control. Only way to prevent this is to disable background of ::item,
415
    // this however limits our ability to style tree items. As solution we create this widget
416
    // that will be for painter to read information and draw proper backgrounds only when asked.
417
    //
418
    // More information: https://github.com/FreeCAD/FreeCAD/pull/13807
419
    QTreeView *artificial;
420

421
    QRect calculateItemRect(const QStyleOptionViewItem &option) const;
422

423
public:
424
    explicit TreeWidgetItemDelegate(QObject* parent=nullptr);
425

426
    virtual QWidget* createEditor(QWidget *parent,
427
            const QStyleOptionViewItem &, const QModelIndex &index) const;
428

429
    virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
430

431
    virtual void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const;
432

433
    virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
434
};
435

436
} // namespace Gui
437

438
TreeWidgetItemDelegate::TreeWidgetItemDelegate(QObject* parent)
439
    : QStyledItemDelegate(parent)
440
{
441
    artificial = new QTreeView(qobject_cast<QWidget*>(parent));
442
    artificial->setObjectName(QString::fromLatin1("DocumentTreeItems"));
443
    artificial->setFixedSize(0, 0); // ensure that it does not render
444
}
445

446

447
QRect TreeWidgetItemDelegate::calculateItemRect(const QStyleOptionViewItem &option) const
448
{
449
    auto tree = static_cast<TreeWidget*>(parent());
450
    auto style = tree->style();
451

452
    QRect rect = option.rect;
453

454
    const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, artificial) + 1;
455

456
    // 2 margin for text, 2 margin for decoration (icon) = 4 times margin
457
    int width = 4 * margin
458
        + option.fontMetrics.boundingRect(option.text).width()
459
        + option.decorationSize.width()
460
        + TreeParams::getItemBackgroundPadding()
461
    ;
462

463
    if (TreeParams::getCheckBoxesSelection()) {
464
        // another 2 margin for checkbox
465
        width += 2 * margin
466
            + style->pixelMetric(QStyle::PM_IndicatorWidth)
467
            + style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
468
    }
469

470
    if (width < rect.width()) {
471
        rect.setWidth(width);
472
    }
473

474
    return rect;
475
}
476

477
void TreeWidgetItemDelegate::paint(QPainter *painter,
478
                const QStyleOptionViewItem &option, const QModelIndex &index) const
479
{
480
    QStyleOptionViewItem opt = option;
481
    initStyleOption(&opt, index);
482

483
    auto tree = static_cast<TreeWidget*>(parent());
484
    auto style = tree->style();
485

486
    // If only the first column is shown, we'll trim the color background when
487
    // rendering as transparent overlay.
488
    bool trimColumnSize = isOnlyNameColumnDisplayed(); 
489

490
    if (index.column() == 0) {
491
        if (tree->testAttribute(Qt::WA_NoSystemBackground)
492
                && (trimColumnSize || (opt.backgroundBrush.style() == Qt::NoBrush
493
                                && _TreeItemBackground.style() != Qt::NoBrush)))
494
        {
495
            QRect rect = calculateItemRect(option);
496

497
            if (trimColumnSize && opt.backgroundBrush.style() == Qt::NoBrush) {
498
                painter->fillRect(rect, _TreeItemBackground);
499
            } else if (!opt.state.testFlag(QStyle::State_Selected)) {
500
                painter->fillRect(rect, _TreeItemBackground);
501
            }
502
        }
503
    }
504
    style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, artificial);
505
}
506

507
void TreeWidgetItemDelegate::initStyleOption(QStyleOptionViewItem *option,
508
                                             const QModelIndex &index) const
509
{
510
    inherited::initStyleOption(option, index);
511

512
    auto tree = static_cast<TreeWidget*>(parent());
513
    auto item = tree->itemFromIndex(index);
514

515
    if (!item) {
516
        return;
517
    }
518

519
    auto mousePos = option->widget->mapFromGlobal(QCursor::pos());
520
    auto isHovered = option->rect.contains(mousePos);
521
    if (!isHovered) {
522
        option->state &= ~QStyle::State_MouseOver;
523
    }
524

525
    QSize size = option->icon.actualSize(QSize(0xffff, 0xffff));
526

527
    if (size.height() > 0) {
528
        option->decorationSize = QSize(
529
            size.width() * TreeWidget::iconSize() / size.height(),
530
            TreeWidget::iconSize()
531
        );
532
    }
533

534
    if (isOnlyNameColumnDisplayed()) {
535
        option->rect = calculateItemRect(*option);
536

537
        // we need to extend this shape a bit, 3px on each side
538
        // this value was obtained experimentally
539
        option->rect.setWidth(option->rect.width() + 3 * 2);
540
    }
541
}
542

543
QWidget* TreeWidgetItemDelegate::createEditor(
544
        QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
545
{
546
    auto ti = static_cast<QTreeWidgetItem*>(index.internalPointer());
547
    if (ti->type() != TreeWidget::ObjectType || index.column() > 1)
548
        return nullptr;
549
    auto item = static_cast<DocumentObjectItem*>(ti);
550
    App::DocumentObject* obj = item->object()->getObject();
551
    auto& prop = index.column() ? obj->Label2 : obj->Label;
552

553
    std::ostringstream str;
554
    str << "Change " << obj->getNameInDocument() << '.' << prop.getName();
555
    App::GetApplication().setActiveTransaction(str.str().c_str());
556
    FC_LOG("create editor transaction " << App::GetApplication().getActiveTransaction());
557

558
    QLineEdit *editor;
559
    if(TreeParams::getLabelExpression()) {
560
        ExpLineEdit *le = new ExpLineEdit(parent);
561
        le->setAutoApply(true);
562
        le->setFrame(false);
563
        le->bind(App::ObjectIdentifier(prop));
564
        editor = le;
565
    } else {
566
        editor = new QLineEdit(parent);
567
    }
568
    editor->setReadOnly(prop.isReadOnly());
569
    return editor;
570
}
571

572
QSize TreeWidgetItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
573
{
574
    QSize size = QStyledItemDelegate::sizeHint(option, index);
575
    int spacing = std::max(0, static_cast<int>(TreeParams::getItemSpacing()));
576
    size.setHeight(size.height() + spacing);
577
    return size;
578
}
579
// ---------------------------------------------------------------------------
580

581
TreeWidget::TreeWidget(const char* name, QWidget* parent)
582
    : QTreeWidget(parent), SelectionObserver(true, ResolveMode::NoResolve)
583
    , contextItem(nullptr)
584
    , searchObject(nullptr)
585
    , searchDoc(nullptr)
586
    , searchContextDoc(nullptr)
587
    , editingItem(nullptr)
588
    , currentDocItem(nullptr)
589
    , myName(name)
590
{
591
    Instances.insert(this);
592
    if (!_LastSelectedTreeWidget)
593
        _LastSelectedTreeWidget = this;
594

595
    this->setDragEnabled(true);
596
    this->setAcceptDrops(true);
597
    this->setDragDropMode(QTreeWidget::InternalMove);
598
    this->setColumnCount(3);
599
    this->setItemDelegate(new TreeWidgetItemDelegate(this));
600
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
601

602
    this->showHiddenAction = new QAction(this);
603
    this->showHiddenAction->setCheckable(true);
604
    connect(this->showHiddenAction, &QAction::triggered,
605
            this, &TreeWidget::onShowHidden);
606

607
    this->toggleVisibilityInTreeAction = new QAction(this);
608
    connect(this->toggleVisibilityInTreeAction, &QAction::triggered,
609
            this, &TreeWidget::onToggleVisibilityInTree);
610

611
    this->createGroupAction = new QAction(this);
612
    connect(this->createGroupAction, &QAction::triggered,
613
            this, &TreeWidget::onCreateGroup);
614

615
    this->relabelObjectAction = new QAction(this);
616
#ifndef Q_OS_MAC
617
    this->relabelObjectAction->setShortcut(Qt::Key_F2);
618
#endif
619
    connect(this->relabelObjectAction, &QAction::triggered,
620
            this, &TreeWidget::onRelabelObject);
621

622
    this->finishEditingAction = new QAction(this);
623
    connect(this->finishEditingAction, &QAction::triggered,
624
            this, &TreeWidget::onFinishEditing);
625

626
    this->selectDependentsAction = new QAction(this);
627
    connect(this->selectDependentsAction, &QAction::triggered,
628
            this, &TreeWidget::onSelectDependents);
629

630
    this->closeDocAction = new QAction(this);
631
    connect(this->closeDocAction, &QAction::triggered,
632
            this, &TreeWidget::onCloseDoc);
633

634
    this->reloadDocAction = new QAction(this);
635
    connect(this->reloadDocAction, &QAction::triggered,
636
            this, &TreeWidget::onReloadDoc);
637

638
    this->skipRecomputeAction = new QAction(this);
639
    this->skipRecomputeAction->setCheckable(true);
640
    connect(this->skipRecomputeAction, &QAction::toggled,
641
            this, &TreeWidget::onSkipRecompute);
642

643
    this->allowPartialRecomputeAction = new QAction(this);
644
    this->allowPartialRecomputeAction->setCheckable(true);
645
    connect(this->allowPartialRecomputeAction, &QAction::toggled,
646
            this, &TreeWidget::onAllowPartialRecompute);
647

648
    this->markRecomputeAction = new QAction(this);
649
    connect(this->markRecomputeAction, &QAction::triggered,
650
            this, &TreeWidget::onMarkRecompute);
651

652
    this->recomputeObjectAction = new QAction(this);
653
    connect(this->recomputeObjectAction, &QAction::triggered,
654
            this, &TreeWidget::onRecomputeObject);
655
    this->searchObjectsAction = new QAction(this);
656
    this->searchObjectsAction->setText(tr("Search..."));
657
    this->searchObjectsAction->setStatusTip(tr("Search for objects"));
658
    connect(this->searchObjectsAction, &QAction::triggered,
659
            this, &TreeWidget::onSearchObjects);
660

661
    //NOLINTBEGIN
662
    // Setup connections
663
    connectNewDocument = Application::Instance->signalNewDocument.connect(std::bind(&TreeWidget::slotNewDocument, this, sp::_1, sp::_2));
664
    connectDelDocument = Application::Instance->signalDeleteDocument.connect(std::bind(&TreeWidget::slotDeleteDocument, this, sp::_1));
665
    connectRenDocument = Application::Instance->signalRenameDocument.connect(std::bind(&TreeWidget::slotRenameDocument, this, sp::_1));
666
    connectActDocument = Application::Instance->signalActiveDocument.connect(std::bind(&TreeWidget::slotActiveDocument, this, sp::_1));
667
    connectRelDocument = Application::Instance->signalRelabelDocument.connect(std::bind(&TreeWidget::slotRelabelDocument, this, sp::_1));
668
    connectShowHidden = Application::Instance->signalShowHidden.connect(std::bind(&TreeWidget::slotShowHidden, this, sp::_1));
669

670
    // Gui::Document::signalChangedObject informs the App::Document property
671
    // change, not view provider's own property, which is what the signal below
672
    // for
673
    connectChangedViewObj = Application::Instance->signalChangedObject.connect(
674
        std::bind(&TreeWidget::slotChangedViewObject, this, sp::_1, sp::_2));
675
    //NOLINTEND
676

677
    setupResizableColumn(this);
678
    this->header()->setStretchLastSection(true);
679
    QObject::connect(this->header(), &QHeaderView::sectionResized, [](int idx, int, int newSize) {
680
        if (idx == 1)
681
            TreeParams::setColumnSize2(newSize);
682
        else if (idx == 2)
683
                TreeParams::setColumnSize3(newSize);
684
            else
685
                TreeParams::setColumnSize1(newSize);
686
    });
687

688
    // Add the first main label
689
    this->rootItem = invisibleRootItem();
690
    this->expandItem(this->rootItem);
691
    this->setSelectionMode(QAbstractItemView::ExtendedSelection);
692

693
    this->setMouseTracking(true); // needed for itemEntered() to work
694

695

696
    this->preselectTimer = new QTimer(this);
697
    this->preselectTimer->setSingleShot(true);
698

699
    this->statusTimer = new QTimer(this);
700
    this->statusTimer->setSingleShot(false);
701

702
    this->selectTimer = new QTimer(this);
703
    this->selectTimer->setSingleShot(true);
704

705
    connect(this->statusTimer, &QTimer::timeout, this, &TreeWidget::onUpdateStatus);
706
    connect(this, &QTreeWidget::itemEntered, this, &TreeWidget::onItemEntered);
707
    connect(this, &QTreeWidget::itemCollapsed, this, &TreeWidget::onItemCollapsed);
708
    connect(this, &QTreeWidget::itemExpanded, this, &TreeWidget::onItemExpanded);
709
    connect(this, &QTreeWidget::itemSelectionChanged,
710
            this, &TreeWidget::onItemSelectionChanged);
711
    connect(this, &QTreeWidget::itemChanged, this, &TreeWidget::onItemChanged);
712
    connect(this->preselectTimer, &QTimer::timeout, this, &TreeWidget::onPreSelectTimer);
713
    connect(this->selectTimer, &QTimer::timeout, this, &TreeWidget::onSelectTimer);
714
    preselectTime.start();
715

716
    setupText();
717
    if (!documentPixmap) {
718
        documentPixmap = std::make_unique<QPixmap>(Gui::BitmapFactory().pixmap("Document"));
719
        QIcon icon(*documentPixmap);
720
        documentPartialPixmap = std::make_unique<QPixmap>(icon.pixmap(documentPixmap->size(), QIcon::Disabled));
721
    }
722
    setColumnHidden(1, TreeParams::getHideColumn());
723
    setColumnHidden(2, TreeParams::getHideInternalNames());
724
    header()->setVisible(!TreeParams::getHideColumn() || !TreeParams::getHideInternalNames());
725
}
726

727
TreeWidget::~TreeWidget()
728
{
729
    connectNewDocument.disconnect();
730
    connectDelDocument.disconnect();
731
    connectRenDocument.disconnect();
732
    connectActDocument.disconnect();
733
    connectRelDocument.disconnect();
734
    connectShowHidden.disconnect();
735
    connectChangedViewObj.disconnect();
736
    Instances.erase(this);
737
    if (_LastSelectedTreeWidget == this)
738
        _LastSelectedTreeWidget = nullptr;
739
}
740

741
const char* TreeWidget::getTreeName() const {
742
    return myName.c_str();
743
}
744

745
// reimpelement to select only objects in the active document
746
void TreeWidget::selectAll() {
747
    auto gdoc = Application::Instance->getDocument(
748
        App::GetApplication().getActiveDocument());
749
    if (!gdoc)
750
        return;
751
    auto itDoc = DocumentMap.find(gdoc);
752
    if (itDoc == DocumentMap.end())
753
        return;
754
    if (TreeParams::getRecordSelection())
755
        Gui::Selection().selStackPush();
756
    Gui::Selection().clearSelection();
757
    Gui::Selection().setSelection(gdoc->getDocument()->getName(), gdoc->getDocument()->getObjects());
758
}
759

760
bool TreeWidget::isObjectShowable(App::DocumentObject* obj) {
761
    if (!obj || !obj->isAttachedToDocument())
762
        return true;
763
    Gui::Document* doc = Application::Instance->getDocument(obj->getDocument());
764
    if (!doc)
765
        return true;
766
    if (Instances.empty())
767
        return true;
768
    auto tree = *Instances.begin();
769
    auto it = tree->DocumentMap.find(doc);
770
    if (it != tree->DocumentMap.end())
771
        return it->second->isObjectShowable(obj);
772
    return true;
773
}
774

775
static bool _DisableCheckTopParent;
776

777
void TreeWidget::checkTopParent(App::DocumentObject*& obj, std::string& subname) {
778
    if (_DisableCheckTopParent)
779
        return;
780
    if (!Instances.empty() && obj && obj->isAttachedToDocument()) {
781
        auto tree = *Instances.begin();
782
        auto it = tree->DocumentMap.find(Application::Instance->getDocument(obj->getDocument()));
783
        if (it != tree->DocumentMap.end()) {
784
            if (tree->statusTimer->isActive()) {
785
                bool locked = tree->blockSelection(true);
786
                tree->_updateStatus(false);
787
                tree->blockSelection(locked);
788
            }
789
            auto parent = it->second->getTopParent(obj, subname);
790
            if (parent)
791
                obj = parent;
792
        }
793
    }
794
}
795

796
void TreeWidget::resetItemSearch() {
797
    if (!searchObject)
798
        return;
799
    auto it = ObjectTable.find(searchObject);
800
    if (it != ObjectTable.end()) {
801
        for (auto& data : it->second) {
802
            if (!data)
803
                continue;
804
            for (auto item : data->items)
805
                static_cast<DocumentObjectItem*>(item)->restoreBackground();
806
        }
807
    }
808
    searchObject = nullptr;
809
}
810

811
void TreeWidget::startItemSearch(QLineEdit* edit) {
812
    resetItemSearch();
813
    searchDoc = nullptr;
814
    searchContextDoc = nullptr;
815
    auto sels = selectedItems();
816
    if (sels.size() == 1) {
817
        if (sels.front()->type() == DocumentType) {
818
            searchDoc = static_cast<DocumentItem*>(sels.front())->document();
819
        }
820
        else if (sels.front()->type() == ObjectType) {
821
            auto item = static_cast<DocumentObjectItem*>(sels.front());
822
            searchDoc = item->object()->getDocument();
823
            searchContextDoc = item->getOwnerDocument()->document();
824
        }
825
    }
826
    else
827
        searchDoc = Application::Instance->activeDocument();
828

829
    App::DocumentObject* obj = nullptr;
830
    if (searchContextDoc && !searchContextDoc->getDocument()->getObjects().empty())
831
        obj = searchContextDoc->getDocument()->getObjects().front();
832
    else if (searchDoc && !searchDoc->getDocument()->getObjects().empty())
833
        obj = searchDoc->getDocument()->getObjects().front();
834

835
    if (obj)
836
        static_cast<ExpressionLineEdit*>(edit)->setDocumentObject(obj);
837
}
838

839
void TreeWidget::itemSearch(const QString& text, bool select) {
840
    resetItemSearch();
841

842
    auto docItem = getDocumentItem(searchDoc);
843
    if (!docItem) {
844
        docItem = getDocumentItem(Application::Instance->activeDocument());
845
        if (!docItem) {
846
            FC_TRACE("item search no document");
847
            resetItemSearch();
848
            return;
849
        }
850
    }
851

852
    auto doc = docItem->document()->getDocument();
853
    const auto& objs = doc->getObjects();
854
    if (objs.empty()) {
855
        FC_TRACE("item search no objects");
856
        return;
857
    }
858
    std::string txt(text.toUtf8().constData());
859
    try {
860
        if (txt.empty())
861
            return;
862
        if (txt.find("<<") == std::string::npos) {
863
            auto pos = txt.find('.');
864
            if (pos == std::string::npos)
865
                txt += '.';
866
            else if (pos != txt.size() - 1) {
867
                txt.insert(pos + 1, "<<");
868
                if (txt.back() != '.')
869
                    txt += '.';
870
                txt += ">>.";
871
            }
872
        }
873
        else if (txt.back() != '.')
874
            txt += '.';
875
        txt += "_self";
876
        auto path = App::ObjectIdentifier::parse(objs.front(), txt);
877
        if (path.getPropertyName() != "_self") {
878
            FC_TRACE("Object " << txt << " not found in " << doc->getName());
879
            return;
880
        }
881
        auto obj = path.getDocumentObject();
882
        if (!obj) {
883
            FC_TRACE("Object " << txt << " not found in " << doc->getName());
884
            return;
885
        }
886
        std::string subname = path.getSubObjectName();
887
        App::DocumentObject* parent = nullptr;
888
        if (searchContextDoc) {
889
            auto it = DocumentMap.find(searchContextDoc);
890
            if (it != DocumentMap.end()) {
891
                parent = it->second->getTopParent(obj, subname);
892
                if (parent) {
893
                    obj = parent;
894
                    docItem = it->second;
895
                    doc = docItem->document()->getDocument();
896
                }
897
            }
898
        }
899
        if (!parent) {
900
            parent = docItem->getTopParent(obj, subname);
901
            while (!parent) {
902
                if (docItem->document()->getDocument() == obj->getDocument()) {
903
                    // this shouldn't happen
904
                    FC_LOG("Object " << txt << " not found in " << doc->getName());
905
                    return;
906
                }
907
                auto it = DocumentMap.find(Application::Instance->getDocument(obj->getDocument()));
908
                if (it == DocumentMap.end())
909
                    return;
910
                docItem = it->second;
911
                parent = docItem->getTopParent(obj, subname);
912
            }
913
            obj = parent;
914
        }
915
        auto item = docItem->findItemByObject(true, obj, subname.c_str());
916
        if (!item) {
917
            FC_TRACE("item " << txt << " not found in " << doc->getName());
918
            return;
919
        }
920
        scrollToItem(item);
921
        Selection().setPreselect(obj->getDocument()->getName(),
922
            obj->getNameInDocument(), subname.c_str(), 0, 0, 0,
923
            SelectionChanges::MsgSource::TreeView);
924
        if (select) {
925
            Gui::Selection().selStackPush();
926
            Gui::Selection().clearSelection();
927
            Gui::Selection().addSelection(obj->getDocument()->getName(),
928
                obj->getNameInDocument(), subname.c_str());
929
            Gui::Selection().selStackPush();
930
        }
931
        else {
932
            searchObject = item->object()->getObject();
933
            item->setBackground(0, QColor(255, 255, 0, 100));
934
        }
935
        FC_TRACE("found item " << txt);
936
    }
937
    catch (...)
938
    {
939
        FC_TRACE("item " << txt << " search exception in " << doc->getName());
940
    }
941
}
942

943
Gui::Document* TreeWidget::selectedDocument() {
944
    for (auto tree : Instances) {
945
        if (!tree->isVisible())
946
            continue;
947
        auto sels = tree->selectedItems();
948
        if (sels.size() == 1 && sels[0]->type() == DocumentType)
949
            return static_cast<DocumentItem*>(sels[0])->document();
950
    }
951
    return nullptr;
952
}
953

954
void TreeWidget::updateStatus(bool delay) {
955
    for (auto tree : Instances)
956
        tree->_updateStatus(delay);
957
}
958

959
void TreeWidget::_updateStatus(bool delay) {
960
    // When running from a different thread Qt will raise a warning
961
    // when trying to start the QTimer
962
    if (Q_UNLIKELY(thread() != QThread::currentThread())) {
963
        return;
964
    }
965

966
    if (!delay) {
967
        if (!ChangedObjects.empty() || !NewObjects.empty())
968
            onUpdateStatus();
969
        return;
970
    }
971
    int timeout = TreeParams::getStatusTimeout();
972
    if (timeout < 0)
973
        timeout = 1;
974
    statusTimer->start(timeout);
975
}
976

977
void TreeWidget::contextMenuEvent(QContextMenuEvent* e)
978
{
979
    // ask workbenches and view provider, ...
980
    MenuItem view;
981
    Gui::Application::Instance->setupContextMenu("Tree", &view);
982

983
    view << "Std_Properties" << "Separator" << "Std_Expressions";
984
    Workbench::createLinkMenu(&view);
985

986
    QMenu contextMenu;
987

988
    QMenu subMenu;
989
    QMenu editMenu;
990
    QActionGroup subMenuGroup(&subMenu);
991
    subMenuGroup.setExclusive(true);
992
    connect(&subMenuGroup, &QActionGroup::triggered,
993
            this, &TreeWidget::onActivateDocument);
994
    MenuManager::getInstance()->setupContextMenu(&view, contextMenu);
995

996
    // get the current item
997
    this->contextItem = itemAt(e->pos());
998

999
    if (this->contextItem && this->contextItem->type() == DocumentType) {
1000
        auto docitem = static_cast<DocumentItem*>(this->contextItem);
1001
        App::Document* doc = docitem->document()->getDocument();
1002

1003
        // It's better to let user decide whether and how to activate
1004
        // the current document, such as by double-clicking.
1005
        // App::GetApplication().setActiveDocument(doc);
1006

1007
        showHiddenAction->setChecked(docitem->showHidden());
1008
        contextMenu.addAction(this->showHiddenAction);
1009
        contextMenu.addAction(this->searchObjectsAction);
1010
        contextMenu.addAction(this->closeDocAction);
1011
        if (doc->testStatus(App::Document::PartialDoc))
1012
            contextMenu.addAction(this->reloadDocAction);
1013
        else {
1014
            for (auto d : doc->getDependentDocuments()) {
1015
                if (d->testStatus(App::Document::PartialDoc)) {
1016
                    contextMenu.addAction(this->reloadDocAction);
1017
                    break;
1018
                }
1019
            }
1020
            contextMenu.addAction(this->selectDependentsAction);
1021
            this->skipRecomputeAction->setChecked(doc->testStatus(App::Document::SkipRecompute));
1022
            contextMenu.addAction(this->skipRecomputeAction);
1023
            this->allowPartialRecomputeAction->setChecked(doc->testStatus(App::Document::AllowPartialRecompute));
1024
            if (doc->testStatus(App::Document::SkipRecompute))
1025
                contextMenu.addAction(this->allowPartialRecomputeAction);
1026
            contextMenu.addAction(this->markRecomputeAction);
1027
            contextMenu.addAction(this->createGroupAction);
1028
        }
1029
        contextMenu.addSeparator();
1030
    }
1031
    else if (this->contextItem && this->contextItem->type() == ObjectType) {
1032
        auto objitem = static_cast<DocumentObjectItem*>
1033
            (this->contextItem);
1034

1035
        // check that the selection is not across several documents
1036
        bool acrossDocuments = false;
1037
        auto SelectedObjectsList = Selection().getCompleteSelection();
1038
        // get the object's document as reference
1039
        App::Document* doc = objitem->object()->getObject()->getDocument();
1040
        for (auto it = SelectedObjectsList.begin(); it != SelectedObjectsList.end(); ++it) {
1041
            if ((*it).pDoc != doc) {
1042
                acrossDocuments = true;
1043
                break;
1044
            }
1045
        }
1046

1047
        showHiddenAction->setChecked(doc->ShowHidden.getValue());
1048
        contextMenu.addAction(this->showHiddenAction);
1049
        contextMenu.addAction(this->toggleVisibilityInTreeAction);
1050

1051
        if (!acrossDocuments) { // is only sensible for selections within one document
1052
            if (objitem->object()->getObject()->isDerivedFrom(App::DocumentObjectGroup::getClassTypeId()))
1053
                contextMenu.addAction(this->createGroupAction);
1054
            // if there are dependent objects in the selection, add context menu to add them to selection
1055
            if (CheckForDependents())
1056
                contextMenu.addAction(this->selectDependentsAction);
1057
        }
1058

1059
        contextMenu.addSeparator();
1060
        contextMenu.addAction(this->markRecomputeAction);
1061
        contextMenu.addAction(this->recomputeObjectAction);
1062
        contextMenu.addSeparator();
1063

1064
        // relabeling is only possible for a single selected document
1065
        if (SelectedObjectsList.size() == 1)
1066
            contextMenu.addAction(this->relabelObjectAction);
1067

1068
        auto selItems = this->selectedItems();
1069
        // if only one item is selected, setup the edit menu
1070
        if (selItems.size() == 1) {
1071
            objitem->object()->setupContextMenu(&editMenu, this, SLOT(onStartEditing()));
1072
            QList<QAction*> editAct = editMenu.actions();
1073
            if (!editAct.isEmpty()) {
1074
                QAction* topact = contextMenu.actions().constFirst();
1075
                for (QList<QAction*>::iterator it = editAct.begin(); it != editAct.end(); ++it)
1076
                    contextMenu.insertAction(topact, *it);
1077
                QAction* first = editAct.front();
1078
                contextMenu.setDefaultAction(first);
1079
                if (objitem->object()->isEditing())
1080
                    contextMenu.insertAction(topact, this->finishEditingAction);
1081
                contextMenu.insertSeparator(topact);
1082
            }
1083
        }
1084
    }
1085

1086

1087
    // add a submenu to active a document if two or more exist
1088
    std::vector<App::Document*> docs = App::GetApplication().getDocuments();
1089
    if (docs.size() >= 2) {
1090
        contextMenu.addSeparator();
1091
        App::Document* activeDoc = App::GetApplication().getActiveDocument();
1092
        subMenu.setTitle(tr("Activate document"));
1093
        contextMenu.addMenu(&subMenu);
1094
        QAction* active = nullptr;
1095
        for (auto it = docs.begin(); it != docs.end(); ++it) {
1096
            QString label = QString::fromUtf8((*it)->Label.getValue());
1097
            QAction* action = subMenuGroup.addAction(label);
1098
            action->setCheckable(true);
1099
            action->setStatusTip(tr("Activate document %1").arg(label));
1100
            action->setData(QByteArray((*it)->getName()));
1101
            if (*it == activeDoc) active = action;
1102
        }
1103

1104
        if (active)
1105
            active->setChecked(true);
1106
        subMenu.addActions(subMenuGroup.actions());
1107
    }
1108

1109
    // add a submenu to present the settings of the tree.
1110
    QMenu settingsMenu;
1111
    settingsMenu.setTitle(tr("Tree settings"));
1112
    contextMenu.addSeparator();
1113
    contextMenu.addMenu(&settingsMenu);
1114

1115
    QAction* action = new QAction(tr("Show description"), this);
1116
    QAction* internalNameAction = new QAction(tr("Show internal name"), this);
1117
    action->setStatusTip(tr("Show a description column for items. An item's description can be set by pressing F2 (or your OS's edit button) or by editing the 'label2' property."));
1118
    action->setCheckable(true);
1119

1120
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
1121
    action->setChecked(!hGrp->GetBool("HideColumn", true));
1122

1123
    settingsMenu.addAction(action);
1124
    QObject::connect(action, &QAction::triggered, this, [this, action, internalNameAction, hGrp]() {
1125
        bool show = action->isChecked();
1126
        hGrp->SetBool("HideColumn", !show);
1127
        setColumnHidden(1, !show);
1128
        header()->setVisible(action->isChecked()||internalNameAction->isChecked());
1129
    });
1130

1131

1132
    internalNameAction->setStatusTip(tr("Show an internal name column for items."));
1133
    internalNameAction->setCheckable(true);
1134

1135
    internalNameAction->setChecked(!hGrp->GetBool("HideInternalNames", true));
1136

1137
    settingsMenu.addAction(internalNameAction);
1138

1139
    QObject::connect(internalNameAction, &QAction::triggered, this, [this, action, internalNameAction, hGrp]() {
1140
        bool show = internalNameAction->isChecked();
1141
        hGrp->SetBool("HideInternalNames", !show);
1142
        setColumnHidden(2, !show);
1143
        header()->setVisible(action->isChecked()||internalNameAction->isChecked());
1144
    });
1145

1146
    if (contextMenu.actions().count() > 0) {
1147
        try {
1148
            contextMenu.exec(QCursor::pos());
1149
        }
1150
        catch (Base::Exception& e) {
1151
            e.ReportException();
1152
        }
1153
        catch (std::exception& e) {
1154
            FC_ERR("C++ exception: " << e.what());
1155
        }
1156
        catch (...) {
1157
            FC_ERR("Unknown exception");
1158
        }
1159
        contextItem = nullptr;
1160
    }
1161
}
1162

1163
void TreeWidget::hideEvent(QHideEvent* ev) {
1164
    QTreeWidget::hideEvent(ev);
1165
}
1166

1167
void TreeWidget::showEvent(QShowEvent* ev) {
1168
    QTreeWidget::showEvent(ev);
1169
}
1170

1171
void TreeWidget::onCreateGroup()
1172
{
1173
    QString name = tr("Group");
1174
    App::AutoTransaction trans("Create group");
1175
    if (this->contextItem->type() == DocumentType) {
1176
        auto docitem = static_cast<DocumentItem*>(this->contextItem);
1177
        App::Document* doc = docitem->document()->getDocument();
1178
        QString cmd = QString::fromLatin1("App.getDocument(\"%1\").addObject"
1179
            "(\"App::DocumentObjectGroup\",\"Group\").Label=\"%2\"")
1180
            .arg(QString::fromLatin1(doc->getName()), name);
1181
        Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8());
1182
    }
1183
    else if (this->contextItem->type() == ObjectType) {
1184
        auto objitem = static_cast<DocumentObjectItem*>
1185
            (this->contextItem);
1186
        App::DocumentObject* obj = objitem->object()->getObject();
1187
        App::Document* doc = obj->getDocument();
1188
        QString cmd = QString::fromLatin1("App.getDocument(\"%1\").getObject(\"%2\")"
1189
            ".newObject(\"App::DocumentObjectGroup\",\"Group\").Label=\"%3\"")
1190
            .arg(QString::fromLatin1(doc->getName()),
1191
                QString::fromLatin1(obj->getNameInDocument()),
1192
                name);
1193
        Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8());
1194
    }
1195
}
1196

1197
void TreeWidget::onRelabelObject()
1198
{
1199
    QTreeWidgetItem* item = currentItem();
1200
    if (item)
1201
        editItem(item);
1202
}
1203

1204
void TreeWidget::onStartEditing()
1205
{
1206
    auto action = qobject_cast<QAction*>(sender());
1207
    if (action) {
1208
        if (this->contextItem && this->contextItem->type() == ObjectType) {
1209
            auto objitem = static_cast<DocumentObjectItem*>
1210
                (this->contextItem);
1211
            int edit = action->data().toInt();
1212

1213
            App::DocumentObject* obj = objitem->object()->getObject();
1214
            if (!obj || !obj->isAttachedToDocument())
1215
                return;
1216
            auto doc = const_cast<Document*>(objitem->getOwnerDocument()->document());
1217
            MDIView* view = doc->getActiveView();
1218
            if (view) getMainWindow()->setActiveWindow(view);
1219

1220
            editingItem = objitem;
1221
            if (!doc->setEdit(objitem->object(), edit))
1222
                editingItem = nullptr;
1223
        }
1224
    }
1225
}
1226

1227
void TreeWidget::onFinishEditing()
1228
{
1229
    if (this->contextItem && this->contextItem->type() == ObjectType) {
1230
        auto objitem = static_cast<DocumentObjectItem*>
1231
            (this->contextItem);
1232
        App::DocumentObject* obj = objitem->object()->getObject();
1233
        if (!obj)
1234
            return;
1235
        Gui::Document* doc = Gui::Application::Instance->getDocument(obj->getDocument());
1236
        doc->commitCommand();
1237
        doc->resetEdit();
1238
        doc->getDocument()->recompute();
1239
    }
1240
}
1241

1242
// check if selection has dependent objects
1243
bool TreeWidget::CheckForDependents()
1244
{
1245
    // if the selected object is a document
1246
    if (this->contextItem && this->contextItem->type() == DocumentType) {
1247
        return true;
1248
    }
1249
    // it can be an object
1250
    else {
1251
        QList<QTreeWidgetItem*> items = this->selectedItems();
1252
        for (QList<QTreeWidgetItem*>::iterator it = items.begin(); it != items.end(); ++it) {
1253
            if ((*it)->type() == ObjectType) {
1254
                auto objitem = static_cast<DocumentObjectItem*>(*it);
1255
                App::DocumentObject* obj = objitem->object()->getObject();
1256
                // get dependents
1257
                auto subObjectList = obj->getOutList();
1258
                if (!subObjectList.empty())
1259
                    return true;
1260
            }
1261
        }
1262
    }
1263

1264
    return false;
1265
}
1266

1267
// adds an App::DocumentObject* and its dependent objects to the selection
1268
void TreeWidget::addDependentToSelection(App::Document* doc, App::DocumentObject* docObject)
1269
{
1270
    // add the docObject to the selection
1271
    Selection().addSelection(doc->getName(), docObject->getNameInDocument());
1272
    // get the dependent
1273
    auto subObjectList = docObject->getOutList();
1274
    // the dependent can in turn have dependents, thus add them recursively
1275
    for (auto itDepend = subObjectList.begin(); itDepend != subObjectList.end(); ++itDepend)
1276
        addDependentToSelection(doc, (*itDepend));
1277
}
1278

1279
// add dependents of the selected tree object to selection
1280
void TreeWidget::onSelectDependents()
1281
{
1282
    // We only have this context menu entry if the selection is within one document but it
1283
    // might be not the active document. Therefore get the document not here but later by casting.
1284
    App::Document* doc;
1285

1286
    // if the selected object is a document
1287
    if (this->contextItem && this->contextItem->type() == DocumentType) {
1288
        auto docitem = static_cast<DocumentItem*>(this->contextItem);
1289
        doc = docitem->document()->getDocument();
1290
        std::vector<App::DocumentObject*> obj = doc->getObjects();
1291
        for (auto it = obj.begin(); it != obj.end(); ++it)
1292
            Selection().addSelection(doc->getName(), (*it)->getNameInDocument());
1293
    }
1294
    // it can be an object
1295
    else {
1296
        QList<QTreeWidgetItem*> items = this->selectedItems();
1297
        for (QList<QTreeWidgetItem*>::iterator it = items.begin(); it != items.end(); ++it) {
1298
            if ((*it)->type() == ObjectType) {
1299
                auto objitem = static_cast<DocumentObjectItem*>(*it);
1300
                doc = objitem->object()->getObject()->getDocument();
1301
                App::DocumentObject* obj = objitem->object()->getObject();
1302
                // the dependents can also have dependents, thus add them recursively via a separate void
1303
                addDependentToSelection(doc, obj);
1304
            }
1305
        }
1306
    }
1307
}
1308

1309
void TreeWidget::onSkipRecompute(bool on)
1310
{
1311
    // if a document item is selected then touch all objects
1312
    if (this->contextItem && this->contextItem->type() == DocumentType) {
1313
        auto docitem = static_cast<DocumentItem*>(this->contextItem);
1314
        App::Document* doc = docitem->document()->getDocument();
1315
        doc->setStatus(App::Document::SkipRecompute, on);
1316
    }
1317
}
1318

1319
void TreeWidget::onAllowPartialRecompute(bool on)
1320
{
1321
    // if a document item is selected then touch all objects
1322
    if (this->contextItem && this->contextItem->type() == DocumentType) {
1323
        auto docitem = static_cast<DocumentItem*>(this->contextItem);
1324
        App::Document* doc = docitem->document()->getDocument();
1325
        doc->setStatus(App::Document::AllowPartialRecompute, on);
1326
    }
1327
}
1328

1329
void TreeWidget::onMarkRecompute()
1330
{
1331
    // if a document item is selected then touch all objects
1332
    if (this->contextItem && this->contextItem->type() == DocumentType) {
1333
        auto docitem = static_cast<DocumentItem*>(this->contextItem);
1334
        App::Document* doc = docitem->document()->getDocument();
1335
        std::vector<App::DocumentObject*> obj = doc->getObjects();
1336
        for (auto it = obj.begin(); it != obj.end(); ++it)
1337
            (*it)->enforceRecompute();
1338
    }
1339
    // mark all selected objects
1340
    else {
1341
        QList<QTreeWidgetItem*> items = this->selectedItems();
1342
        for (QList<QTreeWidgetItem*>::iterator it = items.begin(); it != items.end(); ++it) {
1343
            if ((*it)->type() == ObjectType) {
1344
                auto objitem = static_cast<DocumentObjectItem*>(*it);
1345
                App::DocumentObject* obj = objitem->object()->getObject();
1346
                obj->enforceRecompute();
1347
            }
1348
        }
1349
    }
1350
}
1351

1352
void TreeWidget::onRecomputeObject() {
1353
    std::vector<App::DocumentObject*> objs;
1354
    const auto items = selectedItems();
1355
    for (auto ti : items) {
1356
        if (ti->type() == ObjectType) {
1357
            auto objitem = static_cast<DocumentObjectItem*>(ti);
1358
            objs.push_back(objitem->object()->getObject());
1359
            objs.back()->enforceRecompute();
1360
        }
1361
    }
1362
    if (objs.empty())
1363
        return;
1364
    App::AutoTransaction committer("Recompute object");
1365
    objs.front()->getDocument()->recompute(objs, true);
1366
}
1367

1368

1369
DocumentItem* TreeWidget::getDocumentItem(const Gui::Document* doc) const {
1370
    auto it = DocumentMap.find(doc);
1371
    if (it != DocumentMap.end())
1372
        return it->second;
1373
    return nullptr;
1374
}
1375

1376
void TreeWidget::selectAllInstances(const ViewProviderDocumentObject& vpd) {
1377
    if (!isSelectionAttached())
1378
        return;
1379

1380
    if (selectTimer->isActive())
1381
        onSelectTimer();
1382
    else
1383
        _updateStatus(false);
1384

1385
    for (const auto& v : DocumentMap)
1386
        v.second->selectAllInstances(vpd);
1387
}
1388

1389
static int &treeIconSize()
1390
{
1391
    static int _treeIconSize = -1;
1392

1393
    if (_treeIconSize < 0)
1394
        _treeIconSize = TreeParams::getIconSize();
1395
    return _treeIconSize;
1396
}
1397

1398
int TreeWidget::iconHeight() const
1399
{
1400
    return treeIconSize();
1401
}
1402

1403
void TreeWidget::setIconHeight(int height)
1404
{
1405
    if (treeIconSize() == height)
1406
        return;
1407

1408
    treeIconSize() = height;
1409
    if (treeIconSize() <= 0)
1410
        treeIconSize() = std::max(10, iconSize());
1411

1412
    for(auto tree : Instances)
1413
        tree->setIconSize(QSize(treeIconSize(), treeIconSize()));
1414
}
1415

1416
int TreeWidget::iconSize() {
1417
    static int defaultSize;
1418
    if (defaultSize == 0) {
1419
        auto tree = instance();
1420
        if(tree) {
1421
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1422
            defaultSize = tree->viewOptions().decorationSize.width();
1423
#else
1424
            QStyleOptionViewItem opt;
1425
            tree->initViewItemOption(&opt);
1426
            defaultSize = opt.decorationSize.width();
1427
#endif
1428
        }
1429
        else {
1430
            defaultSize = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
1431
        }
1432
    }
1433
    if (treeIconSize() > 0)
1434
        return std::max(10, treeIconSize());
1435
    return defaultSize;
1436
}
1437

1438
TreeWidget* TreeWidget::instance() {
1439
    auto res = _LastSelectedTreeWidget;
1440
    if (res && res->isVisible())
1441
        return res;
1442
    for (auto inst : Instances) {
1443
        if (!res) res = inst;
1444
        if (inst->isVisible())
1445
            return inst;
1446
    }
1447
    return res;
1448
}
1449

1450
void TreeWidget::setupResizableColumn(TreeWidget *tree) {
1451
    auto mode = TreeParams::getResizableColumn()?
1452
        QHeaderView::Interactive : QHeaderView::ResizeToContents;
1453
    for(auto inst : Instances) {
1454
        if(!tree || tree==inst) {
1455
            inst->header()->setSectionResizeMode(0, mode);
1456
            inst->header()->setSectionResizeMode(1, mode);
1457
            inst->header()->setSectionResizeMode(2, mode);
1458
            if (TreeParams::getResizableColumn()) {
1459
                QSignalBlocker blocker(inst);
1460
                if (TreeParams::getColumnSize1() > 0)
1461
                    inst->header()->resizeSection(0, TreeParams::getColumnSize1());
1462
                if (TreeParams::getColumnSize2() > 0)
1463
                    inst->header()->resizeSection(1, TreeParams::getColumnSize2());
1464
               if (TreeParams::getColumnSize3() > 0)
1465
                    inst->header()->resizeSection(2, TreeParams::getColumnSize3());
1466
            }
1467
        }
1468
    }
1469
}
1470

1471
std::vector<TreeWidget::SelInfo> TreeWidget::getSelection(App::Document* doc)
1472
{
1473
    std::vector<SelInfo> ret;
1474

1475
    TreeWidget* tree = instance();
1476
    if (!tree || !tree->isSelectionAttached()) {
1477
        for (auto pTree : Instances)
1478
            if (pTree->isSelectionAttached()) {
1479
                tree = pTree;
1480
                break;
1481
            }
1482
    }
1483
    if (!tree)
1484
        return ret;
1485

1486
    if (tree->selectTimer->isActive())
1487
        tree->onSelectTimer();
1488
    else
1489
        tree->_updateStatus(false);
1490

1491
    const auto items = tree->selectedItems();
1492
    for (auto ti : items) {
1493
        if (ti->type() != ObjectType)
1494
            continue;
1495
        auto item = static_cast<DocumentObjectItem*>(ti);
1496
        auto vp = item->object();
1497
        auto obj = vp->getObject();
1498
        if (!obj || !obj->isAttachedToDocument()) {
1499
            FC_WARN("skip invalid object");
1500
            continue;
1501
        }
1502
        if (doc && obj->getDocument() != doc) {
1503
            FC_LOG("skip objects not from current document");
1504
            continue;
1505
        }
1506
        ViewProviderDocumentObject* parentVp = nullptr;
1507
        auto parent = item->getParentItem();
1508
        if (parent) {
1509
            parentVp = parent->object();
1510
            if (!parentVp->getObject()->isAttachedToDocument()) {
1511
                FC_WARN("skip '" << obj->getFullName() << "' with invalid parent");
1512
                continue;
1513
            }
1514
        }
1515
        ret.emplace_back();
1516
        auto& sel = ret.back();
1517
        sel.topParent = nullptr;
1518
        std::ostringstream ss;
1519
        item->getSubName(ss, sel.topParent);
1520
        if (!sel.topParent)
1521
            sel.topParent = obj;
1522
        else
1523
            ss << obj->getNameInDocument() << '.';
1524
        sel.subname = ss.str();
1525
        sel.parentVp = parentVp;
1526
        sel.vp = vp;
1527
    }
1528
    return ret;
1529
}
1530

1531
void TreeWidget::selectAllLinks(App::DocumentObject* obj) {
1532
    if (!isSelectionAttached())
1533
        return;
1534

1535
    if (!obj || !obj->isAttachedToDocument()) {
1536
        TREE_ERR("invalid object");
1537
        return;
1538
    }
1539

1540
    if (selectTimer->isActive())
1541
        onSelectTimer();
1542
    else
1543
        _updateStatus(false);
1544

1545
    for (auto link : App::GetApplication().getLinksTo(obj, App::GetLinkRecursive))
1546
    {
1547
        if (!link || !link->isAttachedToDocument()) {
1548
            TREE_ERR("invalid linked object");
1549
            continue;
1550
        }
1551
        auto vp = dynamic_cast<ViewProviderDocumentObject*>(
1552
            Application::Instance->getViewProvider(link));
1553
        if (!vp) {
1554
            TREE_ERR("invalid view provider of the linked object");
1555
            continue;
1556
        }
1557
        for (auto& v : DocumentMap)
1558
            v.second->selectAllInstances(*vp);
1559
    }
1560
}
1561

1562
void TreeWidget::onSearchObjects()
1563
{
1564
    Q_EMIT emitSearchObjects();
1565
}
1566

1567
void TreeWidget::onActivateDocument(QAction* active)
1568
{
1569
    // activate the specified document
1570
    QByteArray docname = active->data().toByteArray();
1571
    Gui::Document* doc = Application::Instance->getDocument((const char*)docname);
1572
    if (doc && !doc->setActiveView())
1573
        doc->setActiveView(nullptr, View3DInventor::getClassTypeId());
1574
}
1575

1576
Qt::DropActions TreeWidget::supportedDropActions() const
1577
{
1578
    return Qt::LinkAction | Qt::CopyAction | Qt::MoveAction;
1579
}
1580

1581
bool TreeWidget::event(QEvent* e)
1582
{
1583
    return QTreeWidget::event(e);
1584
}
1585

1586
bool TreeWidget::eventFilter(QObject*, QEvent* ev) {
1587
    switch (ev->type()) {
1588
    case QEvent::KeyPress:
1589
    case QEvent::KeyRelease: {
1590
        auto ke = static_cast<QKeyEvent*>(ev);
1591
        if (ke->key() != Qt::Key_Escape) {
1592
            // Qt 5 only recheck key modifier on mouse move, so generate a fake
1593
            // event to trigger drag cursor change
1594
            auto mouseEvent = new QMouseEvent(QEvent::MouseMove,
1595
                mapFromGlobal(QCursor::pos()), QCursor::pos(), Qt::NoButton,
1596
                QApplication::mouseButtons(), QApplication::queryKeyboardModifiers());
1597
            QApplication::postEvent(this, mouseEvent);
1598
        }
1599
        break;
1600
    }
1601
    default:
1602
        break;
1603
    }
1604
    return false;
1605
}
1606

1607
namespace Gui {
1608

1609
bool isTreeViewDragging()
1610
{
1611
    return _DraggingActive;
1612
}
1613

1614
} // namespace Gui
1615

1616
void TreeWidget::keyPressEvent(QKeyEvent* event)
1617
{
1618
    if (event->matches(QKeySequence::Find)) {
1619
        event->accept();
1620
        onSearchObjects();
1621
        return;
1622
    }
1623
    else if (event->modifiers() == Qt::AltModifier) {
1624
        if (event->key() == Qt::Key_Left) {
1625
            for (auto& item : selectedItems()) {
1626
                item->setExpanded(false);
1627
            }
1628
            event->accept();
1629
            return;
1630
        }
1631
        else if (event->key() == Qt::Key_Right) {
1632
            for (auto& item : selectedItems()) {
1633
                item->setExpanded(true);
1634
            }
1635
            event->accept();
1636
            return;
1637
        }
1638
        else if (event->key() == Qt::Key_Up) {
1639
            for (auto& item : selectedItems()) {
1640
                item->setExpanded(true);
1641
                for (auto& child : childrenOfItem(*item)) {
1642
                    child->setExpanded(false);
1643
                }
1644
            }
1645
            event->accept();
1646
            return;
1647
        }
1648
        else if (event->key() == Qt::Key_Down) {
1649
            for (auto& item : selectedItems()) {
1650
                item->setExpanded(true);
1651
                for (auto& child : childrenOfItem(*item)) {
1652
                    child->setExpanded(true);
1653
                }
1654
            }
1655
            event->accept();
1656
            return;
1657
        }
1658
    }
1659
    else if (event->key() == Qt::Key_Left) {
1660
        auto index = currentIndex();
1661
        if (index.column() == 1) {
1662
            setCurrentIndex(model()->index(index.row(), 0, index.parent()));
1663
            event->accept();
1664
            return;
1665
        }
1666
    }
1667
    else if (event->key() == Qt::Key_Right) {
1668
        auto index = currentIndex();
1669
        if (index.column() == 0) {
1670
            setCurrentIndex(model()->index(index.row(), 1, index.parent()));
1671
            event->accept();
1672
            return;
1673
        }
1674
    }
1675
    QTreeWidget::keyPressEvent(event);
1676
}
1677

1678
void TreeWidget::mousePressEvent(QMouseEvent* event)
1679
{
1680
    if (isVisibilityIconEnabled()) {
1681
        QTreeWidgetItem* item = itemAt(event->pos());
1682
        if (item && item->type() == TreeWidget::ObjectType && event->button() == Qt::LeftButton) {
1683
            auto objitem = static_cast<DocumentObjectItem*>(item);
1684

1685
            // Mouse position relative to viewport
1686
            auto mousePos = event->pos();
1687

1688
            // Rect occupied by the item relative to viewport
1689
            auto iconRect = visualItemRect(objitem);
1690

1691
            auto style = this->style();
1692

1693
            // If the checkboxes are visible, these are displayed before the icon
1694
            // and we have to compensate for its width.
1695
            if (isSelectionCheckBoxesEnabled()) {
1696
                int checkboxWidth = style->pixelMetric(QStyle::PM_IndicatorWidth)
1697
                                    + style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
1698
                iconRect.adjust(checkboxWidth, 0, 0, 0);
1699
            }
1700

1701
            int const margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
1702
            iconRect.adjust(margin, 0, 0, 0);
1703

1704
            // We are interested in the first icon (visibility icon)
1705
            iconRect.setWidth(iconSize());
1706

1707
            // If the visibility icon was clicked, toggle the DocumentObject visibility
1708
            if (iconRect.contains(mousePos)) {
1709
                auto obj = objitem->object()->getObject();
1710
                char const* objname = obj->getNameInDocument();
1711

1712
                App::DocumentObject* parent = nullptr;
1713
                std::ostringstream subName;
1714
                objitem->getSubName(subName, parent);
1715

1716
                // Try the ElementVisible API, if that is not supported toggle the Visibility property
1717
                int visible = -1;
1718
                if (parent) {
1719
                    visible = parent->isElementVisible(objname);
1720
                }
1721
                if (parent && visible >= 0) {
1722
                    parent->setElementVisible(objname, !visible);
1723
                } else {
1724
                    visible = obj->Visibility.getValue();
1725
                    obj->Visibility.setValue(!visible);
1726
                }
1727
            }
1728
        }
1729
    }
1730

1731
    QTreeWidget::mousePressEvent(event);
1732
}
1733

1734
void TreeWidget::mouseDoubleClickEvent(QMouseEvent* event)
1735
{
1736
    QTreeWidgetItem* item = itemAt(event->pos());
1737
    if (!item)
1738
        return;
1739

1740
    try {
1741
        if (item->type() == TreeWidget::DocumentType) {
1742
            Gui::Document* doc = static_cast<DocumentItem*>(item)->document();
1743
            if (!doc)
1744
                return;
1745
            if (doc->getDocument()->testStatus(App::Document::PartialDoc)) {
1746
                contextItem = item;
1747
                onReloadDoc();
1748
                return;
1749
            }
1750
            if (!doc->setActiveView())
1751
                doc->setActiveView(nullptr, View3DInventor::getClassTypeId());
1752
        }
1753
        else if (item->type() == TreeWidget::ObjectType) {
1754
            auto objitem = static_cast<DocumentObjectItem*>(item);
1755
            ViewProviderDocumentObject* vp = objitem->object();
1756

1757
            objitem->getOwnerDocument()->document()->setActiveView(vp);
1758
            auto manager = Application::Instance->macroManager();
1759
            auto lines = manager->getLines();
1760

1761
            std::ostringstream ss;
1762
            ss << Command::getObjectCmd(vp->getObject())
1763
                << ".ViewObject.doubleClicked()";
1764

1765
            const char* commandText = vp->getTransactionText();
1766
            if (commandText) {
1767
                auto editDoc = Application::Instance->editDocument();
1768
                App::AutoTransaction committer(commandText, true);
1769

1770
                if (!vp->doubleClicked())
1771
                    QTreeWidget::mouseDoubleClickEvent(event);
1772
                else if (lines == manager->getLines())
1773
                    manager->addLine(MacroManager::Gui, ss.str().c_str());
1774

1775
                // If the double click starts an editing, let the transaction persist
1776
                if (!editDoc && Application::Instance->editDocument())
1777
                    committer.setEnable(false);
1778
            }
1779
            else {
1780
                if (!vp->doubleClicked())
1781
                    QTreeWidget::mouseDoubleClickEvent(event);
1782
                else if (lines == manager->getLines())
1783
                    manager->addLine(MacroManager::Gui, ss.str().c_str());
1784
            }
1785
        }
1786
    }
1787
    catch (Base::Exception& e) {
1788
        e.ReportException();
1789
    }
1790
    catch (std::exception& e) {
1791
        FC_ERR("C++ exception: " << e.what());
1792
    }
1793
    catch (...) {
1794
        FC_ERR("Unknown exception");
1795
    }
1796
}
1797

1798
void TreeWidget::startDragging() {
1799
    if (state() != NoState)
1800
        return;
1801
    if (selectedItems().empty())
1802
        return;
1803

1804
    setState(DraggingState);
1805
    startDrag(model()->supportedDragActions());
1806
    setState(NoState);
1807
    stopAutoScroll();
1808
}
1809

1810
void TreeWidget::startDrag(Qt::DropActions supportedActions)
1811
{
1812
    Base::StateLocker guard(_DraggingActive);
1813
    QTreeWidget::startDrag(supportedActions);
1814
    if (_DragEventFilter) {
1815
        _DragEventFilter = false;
1816
        qApp->removeEventFilter(this);
1817
    }
1818
}
1819

1820
bool TreeWidget::dropMimeData(QTreeWidgetItem* parent, int index,
1821
    const QMimeData* data, Qt::DropAction action)
1822
{
1823
    return QTreeWidget::dropMimeData(parent, index, data, action);
1824
}
1825

1826
void TreeWidget::dragEnterEvent(QDragEnterEvent* event)
1827
{
1828
    QTreeWidget::dragEnterEvent(event);
1829
}
1830

1831
void TreeWidget::dragLeaveEvent(QDragLeaveEvent* event)
1832
{
1833
    QTreeWidget::dragLeaveEvent(event);
1834
}
1835

1836

1837
struct ItemInfo {
1838
    std::string doc;
1839
    std::string obj;
1840
    std::string parentDoc;
1841
    std::string parent;
1842
    std::string ownerDoc;
1843
    std::string owner;
1844
    std::string subname;
1845
    std::string topDoc;
1846
    std::string topObj;
1847
    std::string topSubname;
1848
    std::vector<std::string> subs;
1849
    bool dragging = false;
1850
};
1851

1852
struct ItemInfo2 {
1853
    std::string doc;
1854
    std::string obj;
1855
    std::string parentDoc;
1856
    std::string parent;
1857
    std::string topDoc;
1858
    std::string topObj;
1859
    std::string topSubname;
1860
};
1861

1862
namespace {
1863
    class DropHandler
1864
    {
1865
    public:
1866
        static std::vector<std::pair<DocumentObjectItem*, std::vector<std::string> > > filterItems(const QList<QTreeWidgetItem*>& sels, QTreeWidgetItem* targetItem)
1867
        {
1868
            std::vector<std::pair<DocumentObjectItem*, std::vector<std::string> > > items;
1869
            items.reserve(sels.size());
1870
            for (auto ti : sels) {
1871
                if (ti->type() != TreeWidget::ObjectType)
1872
                    continue;
1873
                // ignore child elements if the parent is selected
1874
                if (sels.contains(ti->parent()))
1875
                    continue;
1876
                if (ti == targetItem)
1877
                    continue;
1878
                auto item = static_cast<DocumentObjectItem*>(ti);
1879
                items.emplace_back();
1880
                auto& info = items.back();
1881
                info.first = item;
1882
                const auto& subnames = item->getSubNames();
1883
                info.second.insert(info.second.end(), subnames.begin(), subnames.end());
1884
            }
1885

1886
            return items;
1887
        }
1888
        static App::PropertyPlacement* getPlacement(const ItemInfo& info, const App::DocumentObject* obj, Base::Matrix4D& mat)
1889
        {
1890
            App::PropertyPlacement* propPlacement = nullptr;
1891
            if (!info.topObj.empty()) {
1892
                auto doc = App::GetApplication().getDocument(info.topDoc.c_str());
1893
                if (doc) {
1894
                    auto topObj = doc->getObject(info.topObj.c_str());
1895
                    if (topObj) {
1896
                        auto sobj = topObj->getSubObject(info.topSubname.c_str(), nullptr, &mat);
1897
                        if (sobj == obj) {
1898
                            propPlacement = Base::freecad_dynamic_cast<App::PropertyPlacement>(
1899
                                obj->getPropertyByName("Placement"));
1900
                        }
1901
                    }
1902
                }
1903
            }
1904
            else {
1905
                propPlacement = Base::freecad_dynamic_cast<App::PropertyPlacement>(
1906
                    obj->getPropertyByName("Placement"));
1907
                if (propPlacement)
1908
                    mat = propPlacement->getValue().toMatrix();
1909
            }
1910

1911
            return propPlacement;
1912
        }
1913
    };
1914

1915
    QPoint getPos(QEvent* event) {
1916
        if (auto* dragMoveEvent = dynamic_cast<QDragMoveEvent*>(event)) {
1917
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1918
            return dragMoveEvent->pos();
1919
#else
1920
            return dragMoveEvent->position().toPoint();
1921
#endif
1922
        }
1923

1924
        else if (auto* dropEvent = dynamic_cast<QDropEvent*>(event)) {
1925
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1926
            return dropEvent->pos();
1927
#else
1928
            return dropEvent->position().toPoint();
1929
#endif
1930
        }
1931

1932
        // For unsupported event types or if casting fails
1933
        return QPoint(-1, -1);
1934
    }
1935

1936
    Qt::DropAction getDropAction(int size, const int type)
1937
    {
1938
        if (QApplication::keyboardModifiers() == Qt::ControlModifier) {
1939
            return Qt::CopyAction;
1940
        }
1941
        else if (QApplication::keyboardModifiers() == Qt::AltModifier
1942
            && (size == 1 || type == TreeWidget::DocumentType)) {
1943
            return Qt::LinkAction;
1944
        }
1945
        else {
1946
            return Qt::MoveAction;
1947
        }
1948
    }
1949
}
1950

1951
void TreeWidget::dragMoveEvent(QDragMoveEvent* event)
1952
{
1953
    // Qt5 does not change drag cursor in response to modifier key press,
1954
    // because QDrag installs a event filter that eats up key event. We install
1955
    // a filter after Qt and generate fake mouse move event in response to key
1956
    // press event, which triggers QDrag to update its cursor
1957
    if (!_DragEventFilter) {
1958
        _DragEventFilter = true;
1959
        qApp->installEventFilter(this);
1960
    }
1961

1962
    QTreeWidget::dragMoveEvent(event);
1963
    if (!event->isAccepted()) {
1964
        //return;
1965
        // QTreeWidget::dragMoveEvent is rejecting the event when in between items
1966
        // at DocumentItem root level. Which is preventing reordering. To work around
1967
        // we accept for now, then reject below if targetItem not found.
1968
        event->accept();
1969
    }
1970

1971

1972
    TargetItemInfo targetInfo = getTargetInfo(event);
1973
    QTreeWidgetItem* targetItem = targetInfo.targetItem;
1974
    if (!targetItem) {
1975
        event->ignore();
1976
        return;
1977
    }
1978

1979
    auto items = selectedItems();
1980

1981
    auto da = getDropAction(items.size(), targetItem->type());
1982
    event->setDropAction(da);
1983

1984
    if (targetItem->type() == TreeWidget::DocumentType) {
1985
        leaveEvent(nullptr);
1986
    }
1987
    else if (targetItem->type() == TreeWidget::ObjectType) {
1988
        onItemEntered(targetItem);
1989

1990
        auto targetItemObj = static_cast<DocumentObjectItem*>(targetItem);
1991
        Gui::ViewProviderDocumentObject* vp = targetItemObj->object();
1992

1993
        // if we are in between or if target doesn't accept drops then the target is the parent
1994
        if (da == Qt::MoveAction && (targetInfo.inThresholdZone || !vp->canDropObjects())) {
1995
            targetInfo.targetItem = targetInfo.targetItem->parent();
1996
            if (targetInfo.targetItem->type() == TreeWidget::DocumentType) {
1997
                leaveEvent(nullptr);
1998
                return;
1999
            }
2000

2001
            targetItemObj = static_cast<DocumentObjectItem*>(targetInfo.targetItem);
2002
            vp = targetItemObj->object();
2003

2004
            if (!vp) {
2005
                TREE_TRACE("cannot drop");
2006
                return;
2007
            }
2008
        }
2009
        try {
2010
            if (da != Qt::LinkAction && !vp->canDropObjects()) {
2011
                if (!(event->possibleActions() & Qt::LinkAction) || items.size() != 1) {
2012
                    TREE_TRACE("Cannot drop here");
2013
                    event->ignore();
2014
                    return;
2015
                }
2016
            }
2017
            for (auto ti : items) {
2018
                if (ti->type() != TreeWidget::ObjectType) {
2019
                    TREE_TRACE("cannot drop");
2020
                    event->ignore();
2021
                    return;
2022
                }
2023
                auto item = static_cast<DocumentObjectItem*>(ti);
2024

2025
                auto obj = item->object()->getObject();
2026

2027
                if (da == Qt::MoveAction) {
2028
                    // Check if item can be dragged from his parent
2029
                    auto parentItem = item->getParentItem();
2030
                    if (parentItem && !(parentItem->object()->canDragObjects() && parentItem->object()->canDragObject(obj)))
2031
                    {
2032
                        TREE_TRACE("Cannot drag object");
2033
                        event->ignore();
2034
                        return;
2035
                    }
2036
                }
2037

2038
                std::ostringstream str;
2039
                auto owner = item->getRelativeParent(str, targetItemObj);
2040
                auto subname = str.str();
2041

2042
                // let the view provider decide to accept the object or ignore it
2043
                if (da != Qt::LinkAction && !vp->canDropObjectEx(obj, owner, subname.c_str(), item->mySubs)) {
2044
                    TREE_TRACE("cannot drop " << obj->getFullName() << ' ' << (owner ? owner->getFullName() : "<No Owner>") << '.' << subname);
2045
                    event->ignore();
2046
                    return;
2047
                }
2048
            }
2049
        }
2050
        catch (Base::Exception& e) {
2051
            e.ReportException();
2052
            event->ignore();
2053
        }
2054
        catch (std::exception& e) {
2055
            FC_ERR("C++ exception: " << e.what());
2056
            event->ignore();
2057
        }
2058
        catch (...) {
2059
            FC_ERR("Unknown exception");
2060
            event->ignore();
2061
        }
2062
    }
2063
    else {
2064
        leaveEvent(nullptr);
2065
        event->ignore();
2066
    }
2067
}
2068

2069
TreeWidget::TargetItemInfo TreeWidget::getTargetInfo(QEvent* ev)
2070
{
2071
    TargetItemInfo targetInfo;
2072

2073
    QPoint pos = getPos(ev);
2074
    if (pos == QPoint(-1, -1)) {
2075
        return {}; // Return an empty struct
2076
    }
2077

2078
    targetInfo.targetItem = itemAt(pos);
2079
    // not dropped onto an item or one of the source items is also the destination item
2080
    if (!targetInfo.targetItem || targetInfo.targetItem->isSelected()) {
2081
        return {};
2082
    }
2083
    targetInfo.underMouseItem = targetInfo.targetItem;
2084

2085
    if (targetInfo.targetItem->type() == TreeWidget::ObjectType) {
2086
        auto targetItemObj = static_cast<DocumentObjectItem*>(targetInfo.targetItem);
2087
        targetInfo.targetDoc = targetItemObj->getOwnerDocument()->document()->getDocument();
2088
    }
2089
    else if (targetInfo.targetItem->type() == TreeWidget::DocumentType) {
2090
        auto targetDocItem = static_cast<DocumentItem*>(targetInfo.targetItem);
2091
        targetInfo.targetDoc = targetDocItem->document()->getDocument();
2092
    }
2093
    else {
2094
        return {};
2095
    }
2096

2097
    // Calculate the position of the mouse relative to the item's rectangle
2098
    QRect itemRect = visualItemRect(targetInfo.targetItem);
2099
    int mouseY = pos.y();
2100
    int itemMidPoint = itemRect.top() + itemRect.height() / 2;
2101
    int threshold = itemRect.height() * 0.20; // 20% of the item's height as threshold
2102

2103
    targetInfo.inBottomHalf = mouseY > itemMidPoint;
2104
    targetInfo.inThresholdZone = ((mouseY < itemRect.top() + threshold) && !targetInfo.targetItem->isExpanded())
2105
        || (mouseY > itemRect.top() + itemRect.height() - threshold);
2106
    return targetInfo;
2107
}
2108

2109
bool TreeWidget::dropInDocument(QDropEvent* event, TargetItemInfo& targetInfo,
2110
                                std::vector<TreeWidget::ObjectItemSubname> items)
2111
{
2112
    std::string errMsg;
2113
    auto da = event->dropAction();
2114
    bool touched = false;
2115

2116
    std::vector<ItemInfo2> infos;
2117
    infos.reserve(items.size());
2118
    bool syncPlacement = TreeParams::getSyncPlacement();
2119

2120
    App::AutoTransaction committer(
2121
        da == Qt::LinkAction ? "Link object" :
2122
        da == Qt::CopyAction ? "Copy object" : "Move object");
2123

2124
    // check if items can be dragged
2125
    for (auto& v : items) {
2126
        auto item = v.first;
2127
        auto obj = item->object()->getObject();
2128
        auto parentItem = item->getParentItem();
2129
        if (parentItem) {
2130
            bool allParentsOK = canDragFromParents(parentItem, obj, nullptr);
2131

2132
            if (!allParentsOK || !parentItem->object()->canDragObjects() || !parentItem->object()->canDragObject(obj)) {
2133
                committer.close(true);
2134
                TREE_ERR("'" << obj->getFullName() << "' cannot be dragged out of '" << parentItem->object()->getObject()->getFullName() << "'");
2135
                return false;
2136
            }
2137
        }
2138
        else if (da != Qt::MoveAction || item->myOwner != targetInfo.targetItem) {
2139
            // We will not drag item out of parent if either, 1) modifier
2140
            // key is held, or 2) the dragging item is not inside the
2141
            // dropping document tree.
2142
            parentItem = nullptr;
2143
        }
2144
        infos.emplace_back();
2145
        auto& info = infos.back();
2146
        info.doc = obj->getDocument()->getName();
2147
        info.obj = obj->getNameInDocument();
2148
        if (parentItem) {
2149
            auto parent = parentItem->object()->getObject();
2150
            info.parentDoc = parent->getDocument()->getName();
2151
            info.parent = parent->getNameInDocument();
2152
        }
2153
        if (syncPlacement) {
2154
            std::ostringstream ss;
2155
            App::DocumentObject* topParent = nullptr;
2156
            item->getSubName(ss, topParent);
2157
            if (topParent) {
2158
                info.topDoc = topParent->getDocument()->getName();
2159
                info.topObj = topParent->getNameInDocument();
2160
                ss << obj->getNameInDocument() << '.';
2161
                info.topSubname = ss.str();
2162
            }
2163
        }
2164
    }
2165
    // Because the existence of subname, we must de-select the drag the
2166
    // object manually. Just do a complete clear here for simplicity
2167
    Selection().selStackPush();
2168
    Selection().clearCompleteSelection();
2169

2170
    // Open command
2171
    auto manager = Application::Instance->macroManager();
2172
    try {
2173
        std::vector<App::DocumentObject*> droppedObjs;
2174
        for (auto& info : infos) {
2175
            auto doc = App::GetApplication().getDocument(info.doc.c_str());
2176
            if (!doc) continue;
2177
            auto obj = doc->getObject(info.obj.c_str());
2178
            auto vpc = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(obj));
2179
            if (!vpc) {
2180
                FC_WARN("Cannot find dragging object " << info.obj);
2181
                continue;
2182
            }
2183

2184
            Base::Matrix4D mat;
2185
            App::PropertyPlacement* propPlacement = nullptr;
2186
            if (syncPlacement) {
2187
                if (!info.topObj.empty()) {
2188
                    auto doc = App::GetApplication().getDocument(info.topDoc.c_str());
2189
                    if (doc) {
2190
                        auto topObj = doc->getObject(info.topObj.c_str());
2191
                        if (topObj) {
2192
                            auto sobj = topObj->getSubObject(info.topSubname.c_str(), nullptr, &mat);
2193
                            if (sobj == obj) {
2194
                                propPlacement = dynamic_cast<App::PropertyPlacement*>(obj->getPropertyByName("Placement"));
2195
                            }
2196
                        }
2197
                    }
2198
                }
2199
                else {
2200
                    propPlacement = dynamic_cast<App::PropertyPlacement*>(obj->getPropertyByName("Placement"));
2201
                    if (propPlacement) {
2202
                        mat = propPlacement->getValue().toMatrix();
2203
                    }
2204
                }
2205
            }
2206

2207
            if (da == Qt::LinkAction) {
2208
                std::string name = targetInfo.targetDoc->getUniqueObjectName("Link");
2209
                FCMD_DOC_CMD(targetInfo.targetDoc, "addObject('App::Link','" << name << "').setLink("
2210
                    << Command::getObjectCmd(obj) << ")");
2211
                auto link = targetInfo.targetDoc->getObject(name.c_str());
2212
                if (!link)
2213
                    continue;
2214
                FCMD_OBJ_CMD(link, "Label='" << obj->getLinkedObject(true)->Label.getValue() << "'");
2215
                propPlacement = dynamic_cast<App::PropertyPlacement*>(link->getPropertyByName("Placement"));
2216
                if (propPlacement)
2217
                    propPlacement->setValueIfChanged(Base::Placement(mat));
2218
                droppedObjs.push_back(link);
2219
            }
2220
            else if (!info.parent.empty()) {
2221
                auto parentDoc = App::GetApplication().getDocument(info.parentDoc.c_str());
2222
                if (!parentDoc) {
2223
                    FC_WARN("Canont find document " << info.parentDoc);
2224
                    continue;
2225
                }
2226
                auto parent = parentDoc->getObject(info.parent.c_str());
2227
                auto vpp = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(parent));
2228
                if (!vpp) {
2229
                    FC_WARN("Cannot find dragging object's parent " << info.parent);
2230
                    continue;
2231
                }
2232

2233
                std::ostringstream ss;
2234
                ss << Command::getObjectCmd(vpp->getObject()) << ".ViewObject.dragObject(" << Command::getObjectCmd(obj) << ')';
2235
                auto lines = manager->getLines();
2236
                vpp->dragObject(obj);
2237
                if (manager->getLines() == lines) {
2238
                    manager->addLine(MacroManager::Gui, ss.str().c_str());
2239
                }
2240

2241
                //make sure it is not part of a geofeaturegroup anymore.
2242
                //When this has happen we need to handle all removed
2243
                //objects
2244
                auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(obj);
2245
                if (grp) {
2246
                    FCMD_OBJ_CMD(grp, "removeObject(" << Command::getObjectCmd(obj) << ")");
2247
                }
2248

2249
                // check if the object has been deleted
2250
                obj = doc->getObject(info.obj.c_str());
2251
                if (!obj || !obj->isAttachedToDocument()) {
2252
                    continue;
2253
                }
2254
                droppedObjs.push_back(obj);
2255
                if (propPlacement) {
2256
                    propPlacement->setValueIfChanged(Base::Placement(mat));
2257
                }
2258
            }
2259
            else {
2260
                std::ostringstream ss;
2261
                ss << "App.getDocument('" << targetInfo.targetDoc->getName() << "')."
2262
                    << (da == Qt::CopyAction ? "copyObject(" : "moveObject(")
2263
                    << Command::getObjectCmd(obj) << ", True)";
2264
                App::DocumentObject* res = nullptr;
2265
                if (da == Qt::CopyAction) {
2266
                    auto copied = targetInfo.targetDoc->copyObject({ obj }, true);
2267
                    if (!copied.empty()) {
2268
                        res = copied.back();
2269
                    }
2270
                }
2271
                else if (da == Qt::MoveAction && obj->getDocument() == targetInfo.targetDoc) {
2272
                    // Moving a object within the document root.
2273
                    // Do not set 'res' as changing the placement is not desired: #13690
2274
                    droppedObjs.push_back(obj);
2275
                }
2276
                else {
2277
                    // Moving a object from another document.
2278
                    res = targetInfo.targetDoc->moveObject(obj, true);
2279
                }
2280
                if (res) {
2281
                    propPlacement = dynamic_cast<App::PropertyPlacement*>( res->getPropertyByName("Placement"));
2282
                    if (propPlacement) {
2283
                        propPlacement->setValueIfChanged(Base::Placement(mat));
2284
                    }
2285
                    droppedObjs.push_back(res);
2286
                }
2287
                manager->addLine(MacroManager::App, ss.str().c_str());
2288
            }
2289
        }
2290
        touched = true;
2291
        Base::FlagToggler<> guard(_DisableCheckTopParent);
2292
        Selection().setSelection(targetInfo.targetDoc->getName(), droppedObjs);
2293

2294
        // If moved, then we sort objects properly.
2295
        if (da == Qt::MoveAction) {
2296
            sortDroppedObjects(targetInfo, droppedObjs);
2297
        }
2298
    }
2299
    catch (const Base::Exception& e) {
2300
        e.ReportException();
2301
        errMsg = e.what();
2302
    }
2303
    catch (std::exception& e) {
2304
        FC_ERR("C++ exception: " << e.what());
2305
        errMsg = e.what();
2306
    }
2307
    catch (...) {
2308
        FC_ERR("Unknown exception");
2309
        errMsg = "Unknown exception";
2310
    }
2311
    if (!errMsg.empty()) {
2312
        committer.close(true);
2313
        QMessageBox::critical(getMainWindow(), QObject::tr("Drag & drop failed"), QString::fromUtf8(errMsg.c_str()));
2314
        return false;
2315
    }
2316
    return touched;
2317
}
2318

2319
bool TreeWidget::dropInObject(QDropEvent* event, TargetItemInfo& targetInfo,
2320
                              std::vector<TreeWidget::ObjectItemSubname> items)
2321
{
2322
    std::string errMsg;
2323
    auto da = event->dropAction();
2324
    bool touched = false;
2325

2326
    // add object to group
2327
    auto targetItemObj = static_cast<DocumentObjectItem*>(targetInfo.targetItem);
2328
    Gui::ViewProviderDocumentObject* vp = targetItemObj->object();
2329

2330
    if (!vp || !vp->getObject() || !vp->getObject()->isAttachedToDocument()) {
2331
        TREE_TRACE("invalid object");
2332
        return false;
2333
    }
2334

2335
    // if we are in between or if target doesn't accept drops then the target is the parent
2336
    if (da == Qt::MoveAction && (targetInfo.inThresholdZone || !vp->canDropObjects())) {
2337
        targetInfo.targetItem = targetInfo.targetItem->parent();
2338
        if (targetInfo.targetItem->type() == TreeWidget::DocumentType) {
2339
            return dropInDocument(event, targetInfo, items);
2340
        }
2341

2342
        targetItemObj = static_cast<DocumentObjectItem*>(targetInfo.targetItem);
2343
        vp = targetItemObj->object();
2344

2345
        if (!vp || !vp->getObject() || !vp->getObject()->isAttachedToDocument()) {
2346
            TREE_TRACE("invalid object");
2347
            return false;
2348
        }
2349
    }
2350

2351
    if (da != Qt::LinkAction && !vp->canDropObjects()) {
2352
        if (!(event->possibleActions() & Qt::LinkAction) || items.size() != 1) {
2353
            TREE_TRACE("Cannot drop objects");
2354
            return false; // no group like object
2355
        }
2356
    }
2357

2358
    App::DocumentObject* targetObj = targetItemObj->object()->getObject();
2359
    std::ostringstream targetSubname;
2360
    App::DocumentObject* targetParent = nullptr;
2361
    targetItemObj->getSubName(targetSubname, targetParent);
2362
    Selection().selStackPush();
2363
    Selection().clearCompleteSelection();
2364
    if (targetParent) {
2365
        targetSubname << vp->getObject()->getNameInDocument() << '.';
2366
        Selection().addSelection(targetParent->getDocument()->getName(), targetParent->getNameInDocument(), targetSubname.str().c_str());
2367
    }
2368
    else {
2369
        targetParent = targetObj;
2370
        Selection().addSelection(targetParent->getDocument()->getName(), targetParent->getNameInDocument());
2371
    }
2372

2373
    // Open command
2374
    App::AutoTransaction committer("Drop object");
2375

2376
    bool syncPlacement = TreeParams::getSyncPlacement() && targetItemObj->isGroup();
2377
    bool setSelection = true;
2378
    std::vector<App::DocumentObject*> draggedObjects;
2379
    std::vector<std::pair<App::DocumentObject*, std::string> > droppedObjects;
2380
    std::vector<ItemInfo> infos;
2381
    // Only keep text names here, because you never know when doing drag
2382
    // and drop some object may delete other objects.
2383
    infos.reserve(items.size());
2384
    for (auto& v : items) {
2385
        infos.emplace_back();
2386
        auto& info = infos.back();
2387
        auto item = v.first;
2388
        App::DocumentObject* obj = item->object()->getObject();
2389

2390
        std::ostringstream str;
2391
        App::DocumentObject* topParent = nullptr;
2392
        auto owner = item->getRelativeParent(str, targetItemObj, &topParent, &info.topSubname);
2393
        if (syncPlacement && topParent) {
2394
            info.topDoc = topParent->getDocument()->getName();
2395
            info.topObj = topParent->getNameInDocument();
2396
        }
2397
        info.subname = str.str();
2398
        info.doc = obj->getDocument()->getName();
2399
        info.obj = obj->getNameInDocument();
2400
        if (owner) {
2401
            info.ownerDoc = owner->getDocument()->getName();
2402
            info.owner = owner->getNameInDocument();
2403
        }
2404

2405
        info.subs.swap(v.second);
2406

2407
        // check if items can be dragged
2408
        if (da == Qt::MoveAction && item->myOwner == targetItemObj->myOwner && vp->canDragAndDropObject(obj)) {
2409
            auto parentItem = item->getParentItem();
2410
            if (!parentItem) {
2411
                info.dragging = true;
2412
            }
2413
            else {
2414

2415
                bool allParentsOK = canDragFromParents(parentItem, obj, targetObj);
2416

2417
                if (allParentsOK) {
2418
                    auto vpp = parentItem->object();
2419
                    info.dragging = true;
2420
                    info.parent = vpp->getObject()->getNameInDocument();
2421
                    info.parentDoc = vpp->getObject()->getDocument()->getName();
2422
                }
2423
                else {
2424
                    committer.close(true);
2425
                    return false;
2426
                }
2427
            }
2428
        }
2429

2430
        if (da != Qt::LinkAction
2431
            && !vp->canDropObjectEx(obj, owner, info.subname.c_str(), item->mySubs))
2432
        {
2433
            if (event->possibleActions() & Qt::LinkAction) {
2434
                if (items.size() > 1) {
2435
                    committer.close(true);
2436
                    TREE_TRACE("Cannot replace with more than one object");
2437
                    return false;
2438
                }
2439
                auto ext = vp->getObject()->getExtensionByType<App::LinkBaseExtension>(true);
2440
                if ((!ext || !ext->getLinkedObjectProperty()) && !targetItemObj->getParentItem()) {
2441
                    committer.close(true);
2442
                    TREE_TRACE("Cannot replace without parent");
2443
                    return false;
2444
                }
2445
                da = Qt::LinkAction;
2446
            }
2447
        }
2448
    }
2449

2450
    try {
2451
        std::set<App::DocumentObject*> inList;
2452
        auto parentObj = targetObj;
2453
        if (da == Qt::LinkAction && targetItemObj->getParentItem()) {
2454
            parentObj = targetItemObj->getParentItem()->object()->getObject();
2455
        }
2456
        inList = parentObj->getInListEx(true);
2457
        inList.insert(parentObj);
2458

2459
        std::string target = targetObj->getNameInDocument();
2460
        auto targetDoc = targetObj->getDocument();
2461
        for (auto& info : infos) {
2462
            auto& subname = info.subname;
2463
            targetObj = targetDoc->getObject(target.c_str());
2464
            vp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>( Application::Instance->getViewProvider(targetObj));
2465
            if (!vp) {
2466
                FC_ERR("Cannot find drop target object " << target);
2467
                break;
2468
            }
2469

2470
            auto doc = App::GetApplication().getDocument(info.doc.c_str());
2471
            if (!doc) {
2472
                FC_WARN("Cannot find document " << info.doc);
2473
                continue;
2474
            }
2475
            auto obj = doc->getObject(info.obj.c_str());
2476
            auto vpc = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(obj));
2477
            if (!vpc) {
2478
                FC_WARN("Cannot find dragging object " << info.obj);
2479
                continue;
2480
            }
2481

2482
            ViewProviderDocumentObject* vpp = nullptr;
2483
            if (da != Qt::LinkAction && !info.parentDoc.empty()) {
2484
                auto parentDoc = App::GetApplication().getDocument(info.parentDoc.c_str());
2485
                if (parentDoc) {
2486
                    auto parent = parentDoc->getObject(info.parent.c_str());
2487
                    vpp = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(parent));
2488
                }
2489
                if (!vpp) {
2490
                    FC_WARN("Cannot find dragging object's parent " << info.parent);
2491
                    continue;
2492
                }
2493
            }
2494

2495
            App::DocumentObject* owner = nullptr;
2496
            if (!info.ownerDoc.empty()) {
2497
                auto ownerDoc = App::GetApplication().getDocument(info.ownerDoc.c_str());
2498
                if (ownerDoc)
2499
                    owner = ownerDoc->getObject(info.owner.c_str());
2500
                if (!owner) {
2501
                    FC_WARN("Cannot find dragging object's top parent " << info.owner);
2502
                    continue;
2503
                }
2504
            }
2505

2506
            Base::Matrix4D mat;
2507
            App::PropertyPlacement* propPlacement = nullptr;
2508
            if (syncPlacement) {
2509
                propPlacement = DropHandler::getPlacement(info, obj, mat);
2510
            }
2511

2512
            auto dropParent = targetParent;
2513

2514
            auto manager = Application::Instance->macroManager();
2515
            std::ostringstream ss;
2516
            if (vpp) {
2517
                auto lines = manager->getLines();
2518
                ss << Command::getObjectCmd(vpp->getObject())
2519
                    << ".ViewObject.dragObject(" << Command::getObjectCmd(obj) << ')';
2520
                vpp->dragObject(obj);
2521
                if (manager->getLines() == lines)
2522
                    manager->addLine(MacroManager::Gui, ss.str().c_str());
2523
                owner = nullptr;
2524
                subname.clear();
2525
                ss.str("");
2526

2527
                obj = doc->getObject(info.obj.c_str());
2528
                if (!obj || !obj->isAttachedToDocument()) {
2529
                    FC_WARN("Dropping object deleted: " << info.doc << '#' << info.obj);
2530
                    continue;
2531
                }
2532
            }
2533

2534
            if (da == Qt::MoveAction) {
2535
                // Try to adjust relative links to avoid cyclic dependency, may
2536
                // throw exception if failed
2537
                ss.str("");
2538
                ss << Command::getObjectCmd(obj) << ".adjustRelativeLinks("
2539
                    << Command::getObjectCmd(targetObj) << ")";
2540
                manager->addLine(MacroManager::Gui, ss.str().c_str());
2541

2542
                std::set<App::DocumentObject*> visited;
2543
                if (obj->adjustRelativeLinks(inList, &visited)) {
2544
                    inList = parentObj->getInListEx(true);
2545
                    inList.insert(parentObj);
2546

2547
                    // TODO: link adjustment and placement adjustment does
2548
                    // not work together at the moment.
2549
                    propPlacement = nullptr;
2550
                }
2551
            }
2552

2553
            if (inList.count(obj)) {
2554
                FC_THROWM(Base::RuntimeError, "Dependency loop detected for " << obj->getFullName());
2555
            }
2556

2557

2558
            std::string dropName;
2559
            ss.str("");
2560
            if (da == Qt::LinkAction) {
2561
                auto parentItem = targetItemObj->getParentItem();
2562
                if (parentItem) {
2563
                    ss << Command::getObjectCmd(
2564
                        parentItem->object()->getObject(), nullptr, ".replaceObject(", true)
2565
                        << Command::getObjectCmd(targetObj) << ","
2566
                        << Command::getObjectCmd(obj) << ")";
2567

2568
                    std::ostringstream ss;
2569

2570
                    dropParent = nullptr;
2571
                    parentItem->getSubName(ss, dropParent);
2572
                    if (dropParent)
2573
                        ss << parentItem->object()->getObject()->getNameInDocument() << '.';
2574
                    else
2575
                        dropParent = parentItem->object()->getObject();
2576
                    ss << obj->getNameInDocument() << '.';
2577
                    dropName = ss.str();
2578
                }
2579
                else {
2580
                    TREE_WARN("ignore replace operation without parent");
2581
                    continue;
2582
                }
2583

2584
                Gui::Command::runCommand(Gui::Command::App, ss.str().c_str());
2585

2586
            }
2587
            else {
2588
                ss << Command::getObjectCmd(vp->getObject())
2589
                    << ".ViewObject.dropObject(" << Command::getObjectCmd(obj);
2590
                if (owner) {
2591
                    ss << "," << Command::getObjectCmd(owner)
2592
                        << ",'" << subname << "',[";
2593
                }
2594
                else
2595
                    ss << ",None,'',[";
2596
                for (auto& sub : info.subs)
2597
                    ss << "'" << sub << "',";
2598
                ss << "])";
2599
                auto lines = manager->getLines();
2600
                dropName = vp->dropObjectEx(obj, owner, subname.c_str(), info.subs);
2601
                if (manager->getLines() == lines)
2602
                    manager->addLine(MacroManager::Gui, ss.str().c_str());
2603
                if (!dropName.empty())
2604
                    dropName = targetSubname.str() + dropName;
2605
            }
2606

2607
            touched = true;
2608

2609
            // Construct the subname pointing to the dropped object
2610
            if (dropName.empty()) {
2611
                auto pos = targetSubname.tellp();
2612
                targetSubname << obj->getNameInDocument() << '.' << std::ends;
2613
                dropName = targetSubname.str();
2614
                targetSubname.seekp(pos);
2615
            }
2616

2617
            Base::Matrix4D newMat;
2618
            auto sobj = dropParent->getSubObject(dropName.c_str(), nullptr, &newMat);
2619
            if (!sobj) {
2620
                FC_LOG("failed to find dropped object "
2621
                    << dropParent->getFullName() << '.' << dropName);
2622
                setSelection = false;
2623
                continue;
2624
            }
2625

2626
            if (da != Qt::CopyAction && propPlacement) {
2627
                // try to adjust placement
2628
                if ((info.dragging && sobj == obj) ||
2629
                    (!info.dragging && sobj->getLinkedObject(false) == obj))
2630
                {
2631
                    if (!info.dragging)
2632
                        propPlacement = Base::freecad_dynamic_cast<App::PropertyPlacement>(
2633
                            sobj->getPropertyByName("Placement"));
2634
                    if (propPlacement) {
2635
                        newMat *= propPlacement->getValue().inverse().toMatrix();
2636
                        newMat.inverseGauss();
2637
                        Base::Placement pla(newMat * mat);
2638
                        propPlacement->setValueIfChanged(pla);
2639
                    }
2640
                }
2641
            }
2642
            droppedObjects.emplace_back(dropParent, dropName);
2643
            draggedObjects.push_back(obj);
2644
        }
2645
        Base::FlagToggler<> guard(_DisableCheckTopParent);
2646
        if (setSelection && !droppedObjects.empty()) {
2647
            Selection().selStackPush();
2648
            Selection().clearCompleteSelection();
2649
            for (auto& v : droppedObjects) {
2650
                Selection().addSelection(v.first->getDocument()->getName(),
2651
                    v.first->getNameInDocument(), v.second.c_str());
2652
            }
2653
            Selection().selStackPush();
2654
        }
2655

2656
        // If moved, then we sort objects properly.
2657
        if (da == Qt::MoveAction && vp->acceptReorderingObjects()) {
2658
            sortDroppedObjects(targetInfo, draggedObjects);
2659
        }
2660
    }
2661
    catch (const Base::Exception& e) {
2662
        e.ReportException();
2663
        errMsg = e.what();
2664
    }
2665
    catch (std::exception& e) {
2666
        FC_ERR("C++ exception: " << e.what());
2667
        errMsg = e.what();
2668
    }
2669
    catch (...) {
2670
        FC_ERR("Unknown exception");
2671
        errMsg = "Unknown exception";
2672
    }
2673
    if (!errMsg.empty()) {
2674
        committer.close(true);
2675
        QMessageBox::critical(getMainWindow(), QObject::tr("Drag & drop failed"),
2676
            QString::fromUtf8(errMsg.c_str()));
2677
        return false;
2678
    }
2679
    return touched;
2680
}
2681

2682
bool TreeWidget::canDragFromParents(DocumentObjectItem* parentItem, App::DocumentObject* obj, App::DocumentObject* target)
2683
{
2684
    // We query all the parents recursively. (for cases like assembly/group/part)
2685
    bool allParentsOK = true;
2686
    while (parentItem) {
2687
        if (!parentItem->object()->canDragObjectToTarget(obj, target)) {
2688
            allParentsOK = false;
2689
            break;
2690
        }
2691
        parentItem = parentItem->getParentItem();
2692
    }
2693

2694
    return allParentsOK;
2695
}
2696

2697
void TreeWidget::dropEvent(QDropEvent* event)
2698
{
2699
    //FIXME: This should actually be done inside dropMimeData
2700

2701
    TargetItemInfo targetInfo = getTargetInfo(event);
2702
    if (!targetInfo.targetItem) {
2703
        return;
2704
    }
2705

2706
    // filter out the selected items we cannot handle
2707
    std::vector<ObjectItemSubname> items;
2708
    items = DropHandler::filterItems(selectedItems(), targetInfo.targetItem);
2709
    if (items.empty()) {
2710
        return; // nothing needs to be done
2711
    }
2712

2713
    event->setDropAction(getDropAction(items.size(), targetInfo.targetItem->type()));
2714

2715
    bool touched = false;
2716
    if (targetInfo.targetItem->type() == TreeWidget::ObjectType) {
2717
        touched = dropInObject(event, targetInfo, items);
2718
    }
2719
    else if (targetInfo.targetItem->type() == TreeWidget::DocumentType) {
2720
        touched = dropInDocument(event, targetInfo, items);
2721
    }
2722

2723
    if (touched && TreeParams::getRecomputeOnDrop()) {
2724
        targetInfo.targetDoc->recompute();
2725
    }
2726
    if (touched && TreeParams::getSyncView()) {
2727
        auto gdoc = Application::Instance->getDocument(targetInfo.targetDoc);
2728
        if (gdoc)
2729
            gdoc->setActiveView();
2730
    }
2731
}
2732

2733
void TreeWidget::sortDroppedObjects(TargetItemInfo& targetInfo, std::vector<App::DocumentObject*> draggedObjects)
2734
{
2735
    if (targetInfo.targetItem == targetInfo.underMouseItem) {
2736
        return;
2737
    }
2738
    auto underMouseItemObj = static_cast<DocumentObjectItem*>(targetInfo.underMouseItem);
2739
    auto underMouseObj = underMouseItemObj->object()->getObject();
2740
    std::vector<App::DocumentObject*> sortedObjList;
2741
    std::vector<App::DocumentObject*> objList;
2742

2743
    auto sortIntoList = [&sortedObjList, &draggedObjects, underMouseObj, &targetInfo](const std::vector<App::DocumentObject*>& objects) {
2744
        for (auto* obj : objects) {
2745
            if (obj == underMouseObj) {
2746
                if (targetInfo.inBottomHalf) {
2747
                    sortedObjList.push_back(obj);
2748
                }
2749

2750
                for (auto* draggedObj : draggedObjects) {
2751
                    sortedObjList.push_back(draggedObj);
2752
                }
2753

2754
                if (!targetInfo.inBottomHalf) {
2755
                    sortedObjList.push_back(obj);
2756
                }
2757
            }
2758
            else {
2759
                if (std::find(draggedObjects.begin(), draggedObjects.end(), obj) == draggedObjects.end()) {
2760
                    sortedObjList.push_back(obj);
2761
                }
2762
            }
2763
        }
2764
    };
2765

2766
    if (targetInfo.targetItem->type() == TreeWidget::ObjectType) {
2767
        // To update the order of items of groups such as App::Part, we just need to change the order in the Group property
2768
        auto targetItemObj = static_cast<DocumentObjectItem*>(targetInfo.targetItem);
2769
        App::DocumentObject* targetObj = targetItemObj->object()->getObject();
2770

2771
        auto propGroup = Base::freecad_dynamic_cast<App::PropertyLinkList>(targetObj->getPropertyByName("Group"));
2772
        if (!propGroup) {
2773
            return;
2774
        }
2775

2776
        objList = propGroup->getValue();
2777
        sortIntoList(objList); // Move dropped objects to correct position
2778
        propGroup->setValue(sortedObjList);
2779
    }
2780
    else if (targetInfo.targetItem->type() == TreeWidget::DocumentType) {
2781
        Gui::Document* guiDoc = Gui::Application::Instance->getDocument(targetInfo.targetDoc->getName());
2782
        objList = guiDoc->getTreeRootObjects();
2783
        // First we need to sort objList by treeRank.
2784
        std::sort(objList.begin(), objList.end(),
2785
            [](App::DocumentObject* a, App::DocumentObject* b) {
2786
            auto vpA = dynamic_cast<Gui::ViewProviderDocumentObject*>(Gui::Application::Instance->getViewProvider(a));
2787
            auto vpB = dynamic_cast<Gui::ViewProviderDocumentObject*>(Gui::Application::Instance->getViewProvider(b));
2788
            if (vpA && vpB) {
2789
                return vpA->getTreeRank() < vpB->getTreeRank();
2790
            }
2791
            return false; // Keep the original order if either vpA or vpB is nullptr
2792
        });
2793

2794
        // Then we move dropped objects to their correct position
2795
        sortIntoList(objList);
2796

2797
        // Then we set the tree rank
2798
        for (size_t i = 0; i < sortedObjList.size(); ++i) {
2799
            auto vp = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(sortedObjList[i]));
2800
            vp->setTreeRank(i);
2801
        }
2802

2803
        // Lastly we refresh the tree
2804
        static_cast<DocumentItem*>(targetInfo.targetItem)->sortObjectItems();
2805
    }
2806
}
2807

2808
void TreeWidget::drawRow(QPainter* painter, const QStyleOptionViewItem& options, const QModelIndex& index) const
2809
{
2810
    QTreeWidget::drawRow(painter, options, index);
2811
}
2812

2813
void TreeWidget::slotNewDocument(const Gui::Document& Doc, bool isMainDoc)
2814
{
2815
    if (Doc.getDocument()->testStatus(App::Document::TempDoc))
2816
        return;
2817
    auto item = new DocumentItem(&Doc, this->rootItem);
2818
    if (isMainDoc)
2819
        this->expandItem(item);
2820
    item->setIcon(0, *documentPixmap);
2821
    item->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue()));
2822
    DocumentMap[&Doc] = item;
2823
}
2824

2825

2826
void TreeWidget::onReloadDoc() {
2827
    if (!this->contextItem || this->contextItem->type() != DocumentType)
2828
        return;
2829
    auto docitem = static_cast<DocumentItem*>(this->contextItem);
2830
    App::Document* doc = docitem->document()->getDocument();
2831
    std::string name = doc->FileName.getValue();
2832
    Application::Instance->reopen(doc);
2833
    for (auto& v : DocumentMap) {
2834
        if (name == v.first->getDocument()->FileName.getValue()) {
2835
            scrollToItem(v.second);
2836
            App::GetApplication().setActiveDocument(v.first->getDocument());
2837
            break;
2838
        }
2839
    }
2840
}
2841

2842
void TreeWidget::onCloseDoc()
2843
{
2844
    if (!this->contextItem || this->contextItem->type() != DocumentType)
2845
        return;
2846
    try {
2847
        auto docitem = static_cast<DocumentItem*>(this->contextItem);
2848
        Gui::Document* gui = docitem->document();
2849
        App::Document* doc = gui->getDocument();
2850
        if (gui->canClose(true, true))
2851
            Command::doCommand(Command::Doc, "App.closeDocument(\"%s\")", doc->getName());
2852
    }
2853
    catch (const Base::Exception& e) {
2854
        e.ReportException();
2855
    }
2856
    catch (std::exception& e) {
2857
        FC_ERR("C++ exception: " << e.what());
2858
    }
2859
    catch (...) {
2860
        FC_ERR("Unknown exception");
2861
    }
2862
}
2863

2864
void TreeWidget::slotRenameDocument(const Gui::Document& Doc)
2865
{
2866
    // do nothing here
2867
    Q_UNUSED(Doc);
2868
}
2869

2870
void TreeWidget::slotChangedViewObject(const Gui::ViewProvider& vp, const App::Property& prop)
2871
{
2872
    if (!App::GetApplication().isRestoring()
2873
        && vp.isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()))
2874
    {
2875
        const auto& vpd = static_cast<const ViewProviderDocumentObject&>(vp);
2876
        if (&prop == &vpd.ShowInTree) {
2877
            ChangedObjects.emplace(vpd.getObject(), 0);
2878
            _updateStatus();
2879
        }
2880
    }
2881
}
2882

2883
void TreeWidget::slotTouchedObject(const App::DocumentObject& obj) {
2884
    ChangedObjects.emplace(const_cast<App::DocumentObject*>(&obj), 0);
2885
    _updateStatus();
2886
}
2887

2888
void TreeWidget::slotShowHidden(const Gui::Document& Doc)
2889
{
2890
    auto it = DocumentMap.find(&Doc);
2891
    if (it != DocumentMap.end())
2892
        it->second->updateItemsVisibility(it->second, it->second->showHidden());
2893
}
2894

2895
void TreeWidget::slotRelabelDocument(const Gui::Document& Doc)
2896
{
2897
    auto it = DocumentMap.find(&Doc);
2898
    if (it != DocumentMap.end()) {
2899
        it->second->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue()));
2900
    }
2901
}
2902

2903
void TreeWidget::slotActiveDocument(const Gui::Document& Doc)
2904
{
2905
    auto jt = DocumentMap.find(&Doc);
2906
    if (jt == DocumentMap.end())
2907
        return; // signal is emitted before the item gets created
2908
    int displayMode = TreeParams::getDocumentMode();
2909
    for (auto it = DocumentMap.begin();
2910
        it != DocumentMap.end(); ++it)
2911
    {
2912
        QFont f = it->second->font(0);
2913
        f.setBold(it == jt);
2914
        it->second->setHidden(0 == displayMode && it != jt);
2915
        if (2 == displayMode) {
2916
            it->second->setExpanded(it == jt);
2917
        }
2918
        // this must be done as last step
2919
        it->second->setFont(0, f);
2920
    }
2921
}
2922

2923
struct UpdateDisabler {
2924
    QWidget& widget;
2925
    int& blocked;
2926
    bool visible{false};
2927
    bool focus{false};
2928

2929
    // Note! DO NOT block signal here, or else
2930
    // QTreeWidgetItem::setChildIndicatorPolicy() does not work
2931
    UpdateDisabler(QWidget& w, int& blocked)
2932
        : widget(w), blocked(blocked)
2933
    {
2934
        if (++blocked > 1)
2935
            return;
2936
        focus = widget.hasFocus();
2937
        visible = widget.isVisible();
2938
        if (visible) {
2939
            // setUpdatesEnabled(false) does not seem to speed up anything.
2940
            // setVisible(false) on the other hand makes QTreeWidget::setData
2941
            // (i.e. any change to QTreeWidgetItem) faster by 10+ times.
2942
            //
2943
            // widget.setUpdatesEnabled(false);
2944

2945
            widget.setVisible(false);
2946
        }
2947
    }
2948
    ~UpdateDisabler() {
2949
        if (blocked <= 0 || --blocked != 0)
2950
            return;
2951

2952
        if (visible) {
2953
            widget.setVisible(true);
2954
            if (focus)
2955
                widget.setFocus();
2956
        }
2957
    }
2958
};
2959

2960
void TreeWidget::onUpdateStatus()
2961
{
2962
    if (this->state() == DraggingState || App::GetApplication().isRestoring()) {
2963
        _updateStatus();
2964
        return;
2965
    }
2966

2967
    for (auto& v : DocumentMap) {
2968
        if (v.first->isPerformingTransaction()) {
2969
            // We have to delay item creation until undo/redo is done, because the
2970
            // object re-creation while in transaction may break tree view item
2971
            // update logic. For example, a parent object re-created before its
2972
            // children, but the parent's link property already contains all the
2973
            // (detached) children.
2974
            _updateStatus();
2975
            return;
2976
        }
2977
    }
2978

2979
    FC_LOG("begin update status");
2980

2981
    UpdateDisabler disabler(*this, updateBlocked);
2982

2983
    std::vector<App::DocumentObject*> errors;
2984

2985
    // Checking for new objects
2986
    for (auto& v : NewObjects) {
2987
        auto doc = App::GetApplication().getDocument(v.first.c_str());
2988
        if (!doc)
2989
            continue;
2990
        auto gdoc = Application::Instance->getDocument(doc);
2991
        if (!gdoc)
2992
            continue;
2993
        auto docItem = getDocumentItem(gdoc);
2994
        if (!docItem)
2995
            continue;
2996
        for (auto id : v.second) {
2997
            auto obj = doc->getObjectByID(id);
2998
            if (!obj)
2999
                continue;
3000
            if (obj->isError())
3001
                errors.push_back(obj);
3002
            if (docItem->ObjectMap.count(obj))
3003
                continue;
3004
            auto vpd = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(gdoc->getViewProvider(obj));
3005
            if (vpd)
3006
                docItem->createNewItem(*vpd);
3007
        }
3008
    }
3009
    NewObjects.clear();
3010

3011
    // Update children of changed objects
3012
    for (auto& v : ChangedObjects) {
3013
        auto obj = v.first;
3014

3015
        auto iter = ObjectTable.find(obj);
3016
        if (iter == ObjectTable.end())
3017
            continue;
3018

3019
        if (v.second.test(CS_Error) && obj->isError())
3020
            errors.push_back(obj);
3021

3022
        if (!iter->second.empty()) {
3023
            auto data = *iter->second.begin();
3024
            bool itemHidden = !data->viewObject->showInTree();
3025
            if (data->itemHidden != itemHidden) {
3026
                for (auto& data : iter->second) {
3027
                    data->itemHidden = itemHidden;
3028
                    if (data->docItem->showHidden())
3029
                        continue;
3030
                    for (auto item : data->items)
3031
                        item->setHidden(itemHidden);
3032
                }
3033
            }
3034
        }
3035

3036
        updateChildren(iter->first, iter->second, v.second.test(CS_Output), false);
3037
    }
3038
    ChangedObjects.clear();
3039

3040
    FC_LOG("update item status");
3041
    TimingInit();
3042
    for (auto pos = DocumentMap.begin(); pos != DocumentMap.end(); ++pos) {
3043
        pos->second->testStatus();
3044
    }
3045
    TimingPrint();
3046

3047
    // Checking for just restored documents
3048
    for (auto& v : DocumentMap) {
3049
        auto docItem = v.second;
3050

3051
        for (auto obj : docItem->PopulateObjects)
3052
            docItem->populateObject(obj);
3053
        docItem->PopulateObjects.clear();
3054

3055
        auto doc = v.first->getDocument();
3056

3057
        if (!docItem->connectChgObject.connected()) {
3058
            //NOLINTBEGIN
3059
            docItem->connectChgObject = docItem->document()->signalChangedObject.connect(
3060
                std::bind(&TreeWidget::slotChangeObject, this, sp::_1, sp::_2));
3061
            docItem->connectTouchedObject = doc->signalTouchedObject.connect(
3062
                std::bind(&TreeWidget::slotTouchedObject, this, sp::_1));
3063
            //NOLINTEND
3064
        }
3065

3066
        if (doc->testStatus(App::Document::PartialDoc))
3067
            docItem->setIcon(0, *documentPartialPixmap);
3068
        else if (docItem->_ExpandInfo) {
3069
            for (auto& entry : *docItem->_ExpandInfo) {
3070
                const char* name = entry.first.c_str();
3071
                bool legacy = name[0] == '*';
3072
                if (legacy)
3073
                    ++name;
3074
                auto obj = doc->getObject(name);
3075
                if (!obj)
3076
                    continue;
3077
                auto iter = docItem->ObjectMap.find(obj);
3078
                if (iter == docItem->ObjectMap.end())
3079
                    continue;
3080
                if (iter->second->rootItem)
3081
                    docItem->restoreItemExpansion(entry.second, iter->second->rootItem);
3082
                else if (legacy && !iter->second->items.empty()) {
3083
                    auto item = *iter->second->items.begin();
3084
                    item->setExpanded(true);
3085
                }
3086
            }
3087
        }
3088
        docItem->_ExpandInfo.reset();
3089
    }
3090

3091
    if (Selection().hasSelection() && !selectTimer->isActive() && !this->isSelectionBlocked()) {
3092
        this->blockSelection(true);
3093
        currentDocItem = nullptr;
3094
        for (auto& v : DocumentMap) {
3095
            v.second->setSelected(false);
3096
            v.second->selectItems();
3097
        }
3098
        this->blockSelection(false);
3099
    }
3100

3101
    auto activeDocItem = getDocumentItem(Application::Instance->activeDocument());
3102

3103
    QTreeWidgetItem* errItem = nullptr;
3104
    for (auto obj : errors) {
3105
        DocumentObjectDataPtr data;
3106
        if (activeDocItem) {
3107
            auto it = activeDocItem->ObjectMap.find(obj);
3108
            if (it != activeDocItem->ObjectMap.end())
3109
                data = it->second;
3110
        }
3111
        if (!data) {
3112
            auto docItem = getDocumentItem(
3113
                Application::Instance->getDocument(obj->getDocument()));
3114
            if (docItem) {
3115
                auto it = docItem->ObjectMap.find(obj);
3116
                if (it != docItem->ObjectMap.end())
3117
                    data = it->second;
3118
            }
3119
        }
3120
        if (data) {
3121
            auto item = data->rootItem;
3122
            if (!item && !data->items.empty()) {
3123
                item = *data->items.begin();
3124
                data->docItem->showItem(item, false, true);
3125
            }
3126
            if (!errItem)
3127
                errItem = item;
3128
        }
3129
    }
3130
    if (errItem)
3131
        scrollToItem(errItem);
3132

3133
    updateGeometries();
3134
    statusTimer->stop();
3135

3136
    FC_LOG("done update status");
3137
}
3138

3139
void TreeWidget::onItemEntered(QTreeWidgetItem* item)
3140
{
3141
    // object item selected
3142
    if (item && item->type() == TreeWidget::ObjectType) {
3143
        auto objItem = static_cast<DocumentObjectItem*>(item);
3144
        objItem->displayStatusInfo();
3145

3146
        if (TreeParams::getPreSelection()) {
3147
            int timeout = TreeParams::getPreSelectionDelay();
3148
            if (timeout < 0)
3149
                timeout = 1;
3150
            if (preselectTime.elapsed() < timeout)
3151
                onPreSelectTimer();
3152
            else {
3153
                timeout = TreeParams::getPreSelectionTimeout();
3154
                if (timeout < 0)
3155
                    timeout = 1;
3156
                preselectTimer->start(timeout);
3157
                Selection().rmvPreselect();
3158
            }
3159
        }
3160
    }
3161
    else if (TreeParams::getPreSelection())
3162
        Selection().rmvPreselect();
3163
}
3164

3165
void TreeWidget::leaveEvent(QEvent* event)
3166
{
3167
    Q_UNUSED(event)
3168
    if (!updateBlocked && TreeParams::getPreSelection()) {
3169
        preselectTimer->stop();
3170
        Selection().rmvPreselect();
3171
    }
3172
}
3173

3174
void TreeWidget::onPreSelectTimer() {
3175
    if (!TreeParams::getPreSelection())
3176
        return;
3177
    auto item = itemAt(viewport()->mapFromGlobal(QCursor::pos()));
3178
    if (!item || item->type() != TreeWidget::ObjectType)
3179
        return;
3180

3181
    preselectTime.restart();
3182
    auto objItem = static_cast<DocumentObjectItem*>(item);
3183
    auto vp = objItem->object();
3184
    auto obj = vp->getObject();
3185
    std::ostringstream ss;
3186
    App::DocumentObject* parent = nullptr;
3187
    objItem->getSubName(ss, parent);
3188
    if (!parent)
3189
        parent = obj;
3190
    else if (!obj->redirectSubName(ss, parent, nullptr))
3191
        ss << obj->getNameInDocument() << '.';
3192
    Selection().setPreselect(parent->getDocument()->getName(), parent->getNameInDocument(),
3193
        ss.str().c_str(), 0, 0, 0, SelectionChanges::MsgSource::TreeView);
3194
}
3195

3196
void TreeWidget::onItemCollapsed(QTreeWidgetItem* item)
3197
{
3198
    // object item collapsed
3199
    if (item && item->type() == TreeWidget::ObjectType) {
3200
        static_cast<DocumentObjectItem*>(item)->setExpandedStatus(false);
3201
    }
3202
}
3203

3204
void TreeWidget::onItemExpanded(QTreeWidgetItem* item)
3205
{
3206
    // object item expanded
3207
    if (item && item->type() == TreeWidget::ObjectType) {
3208
        auto objItem = static_cast<DocumentObjectItem*>(item);
3209
        objItem->setExpandedStatus(true);
3210
        objItem->getOwnerDocument()->populateItem(objItem, false, false);
3211
    }
3212
}
3213

3214
void TreeWidget::scrollItemToTop()
3215
{
3216
    auto doc = Application::Instance->activeDocument();
3217
    for (auto tree : Instances) {
3218
        if (!tree->isSelectionAttached() || tree->isSelectionBlocked())
3219
            continue;
3220

3221
        tree->_updateStatus(false);
3222

3223
        if (doc && Gui::Selection().hasSelection(doc->getDocument()->getName(), ResolveMode::NoResolve)) {
3224
            auto it = tree->DocumentMap.find(doc);
3225
            if (it != tree->DocumentMap.end()) {
3226
                bool lock = tree->blockSelection(true);
3227
                it->second->selectItems(DocumentItem::SR_FORCE_EXPAND);
3228
                tree->blockSelection(lock);
3229
            }
3230
        }
3231
        else {
3232
            tree->blockSelection(true);
3233
            for (int i = 0; i < tree->rootItem->childCount(); i++) {
3234
                auto docItem = dynamic_cast<DocumentItem*>(tree->rootItem->child(i));
3235
                if (!docItem)
3236
                    continue;
3237
                auto doc = docItem->document()->getDocument();
3238
                if (Gui::Selection().hasSelection(doc->getName())) {
3239
                    tree->currentDocItem = docItem;
3240
                    docItem->selectItems(DocumentItem::SR_FORCE_EXPAND);
3241
                    tree->currentDocItem = nullptr;
3242
                    break;
3243
                }
3244
            }
3245
            tree->blockSelection(false);
3246
        }
3247
        tree->selectTimer->stop();
3248
        tree->_updateStatus(false);
3249
    }
3250
}
3251

3252
void TreeWidget::expandSelectedItems(TreeItemMode mode)
3253
{
3254
    if (!isSelectionAttached())
3255
        return;
3256

3257
    const auto items = selectedItems();
3258
    for (auto item : items) {
3259
        switch (mode) {
3260
        case TreeItemMode::ExpandPath: {
3261
            QTreeWidgetItem* parentItem = item->parent();
3262
            while (parentItem) {
3263
                parentItem->setExpanded(true);
3264
                parentItem = parentItem->parent();
3265
            }
3266
            item->setExpanded(true);
3267
            break;
3268
        }
3269
        case TreeItemMode::ExpandItem:
3270
            item->setExpanded(true);
3271
            break;
3272
        case TreeItemMode::CollapseItem:
3273
            item->setExpanded(false);
3274
            break;
3275
        case TreeItemMode::ToggleItem:
3276
            if (item->isExpanded())
3277
                item->setExpanded(false);
3278
            else
3279
                item->setExpanded(true);
3280
            break;
3281
        }
3282
    }
3283
}
3284

3285
void TreeWidget::setupText()
3286
{
3287
    this->headerItem()->setText(0, tr("Labels & Attributes"));
3288
    this->headerItem()->setText(1, tr("Description"));
3289
    this->headerItem()->setText(2, tr("Internal name"));
3290

3291
    this->showHiddenAction->setText(tr("Show items hidden in tree view"));
3292
    this->showHiddenAction->setStatusTip(tr("Show items that are marked as 'hidden' in the tree view"));
3293

3294
    this->toggleVisibilityInTreeAction->setText(tr("Toggle visibility in tree view"));
3295
    this->toggleVisibilityInTreeAction->setStatusTip(tr("Toggles the visibility of selected items in the tree view"));
3296

3297
    this->createGroupAction->setText(tr("Create group"));
3298
    this->createGroupAction->setStatusTip(tr("Create a group"));
3299

3300
    this->relabelObjectAction->setText(tr("Rename"));
3301
    this->relabelObjectAction->setStatusTip(tr("Rename object"));
3302

3303
    this->finishEditingAction->setText(tr("Finish editing"));
3304
    this->finishEditingAction->setStatusTip(tr("Finish editing object"));
3305

3306
    this->selectDependentsAction->setText(tr("Add dependent objects to selection"));
3307
    this->selectDependentsAction->setStatusTip(tr("Adds all dependent objects to the selection"));
3308

3309
    this->closeDocAction->setText(tr("Close document"));
3310
    this->closeDocAction->setStatusTip(tr("Close the document"));
3311

3312
    this->reloadDocAction->setText(tr("Reload document"));
3313
    this->reloadDocAction->setStatusTip(tr("Reload a partially loaded document"));
3314

3315
    this->skipRecomputeAction->setText(tr("Skip recomputes"));
3316
    this->skipRecomputeAction->setStatusTip(tr("Enable or disable recomputations of document"));
3317

3318
    this->allowPartialRecomputeAction->setText(tr("Allow partial recomputes"));
3319
    this->allowPartialRecomputeAction->setStatusTip(
3320
        tr("Enable or disable recomputating editing object when 'skip recomputation' is enabled"));
3321

3322
    this->markRecomputeAction->setText(tr("Mark to recompute"));
3323
    this->markRecomputeAction->setStatusTip(tr("Mark this object to be recomputed"));
3324
    this->markRecomputeAction->setIcon(BitmapFactory().iconFromTheme("Std_MarkToRecompute"));
3325

3326
    this->recomputeObjectAction->setText(tr("Recompute object"));
3327
    this->recomputeObjectAction->setStatusTip(tr("Recompute the selected object"));
3328
    this->recomputeObjectAction->setIcon(BitmapFactory().iconFromTheme("view-refresh"));
3329
}
3330

3331
void TreeWidget::syncView(ViewProviderDocumentObject* vp)
3332
{
3333
    if (currentDocItem && TreeParams::getSyncView()) {
3334
        bool focus = hasFocus();
3335
        currentDocItem->document()->setActiveView(vp);
3336
        if (focus)
3337
            setFocus();
3338
    }
3339
}
3340

3341
void TreeWidget::onShowHidden()
3342
{
3343
    if (!this->contextItem)
3344
        return;
3345
    DocumentItem* docItem = nullptr;
3346
    if (this->contextItem->type() == DocumentType)
3347
        docItem = static_cast<DocumentItem*>(contextItem);
3348
    else if (this->contextItem->type() == ObjectType)
3349
        docItem = static_cast<DocumentObjectItem*>(contextItem)->getOwnerDocument();
3350
    if (docItem)
3351
        docItem->setShowHidden(showHiddenAction->isChecked());
3352
}
3353

3354
void TreeWidget::onToggleVisibilityInTree()
3355
{
3356
    const auto items = selectedItems();
3357
    for (auto item : items) {
3358
        if (item->type() == ObjectType) {
3359
            auto objectItem = static_cast<DocumentObjectItem*>(item);
3360
            auto object = objectItem->object();
3361

3362
            // toggle value
3363
            bool showInTree = !object->showInTree();
3364

3365
            // update object
3366
            object->ShowInTree.setValue(showInTree);
3367

3368
            // update GUI
3369
            auto ownerDocument = objectItem->getOwnerDocument();
3370
            bool hidden = !ownerDocument->showHidden() && !showInTree;
3371
            objectItem->setHidden(hidden);
3372
            if (hidden) {
3373
                objectItem->setSelected(false);
3374
            }
3375
        }
3376
    }
3377
}
3378

3379
void TreeWidget::changeEvent(QEvent* e)
3380
{
3381
    if (e->type() == QEvent::LanguageChange)
3382
        setupText();
3383

3384
    QTreeWidget::changeEvent(e);
3385
}
3386

3387
void TreeWidget::onItemSelectionChanged()
3388
{
3389
    if (!this->isSelectionAttached()
3390
        || this->isSelectionBlocked()
3391
        || updateBlocked)
3392
        return;
3393

3394
    _LastSelectedTreeWidget = this;
3395

3396
    // block tmp. the connection to avoid to notify us ourself
3397
    bool lock = this->blockSelection(true);
3398

3399
    if (selectTimer->isActive())
3400
        onSelectTimer();
3401
    else
3402
        _updateStatus(false);
3403

3404
    auto selItems = selectedItems();
3405

3406
    // do not allow document item multi-selection
3407
    if (!selItems.empty()) {
3408
        auto firstType = selItems.back()->type();
3409
        for (auto it = selItems.begin(); it != selItems.end();) {
3410
            auto item = *it;
3411
            if ((firstType == ObjectType && item->type() != ObjectType)
3412
                || (firstType == DocumentType && item != selItems.back()))
3413
            {
3414
                item->setSelected(false);
3415
                it = selItems.erase(it);
3416
            }
3417
            else
3418
                ++it;
3419
        }
3420
    }
3421

3422
    if (selItems.size() <= 1) {
3423
        if (TreeParams::getRecordSelection())
3424
            Gui::Selection().selStackPush();
3425

3426
        // This special handling to deal with possible discrepancy of
3427
        // Gui.Selection and Tree view selection because of newly added
3428
        // DocumentObject::redirectSubName()
3429
        Selection().clearCompleteSelection();
3430
        DocumentObjectItem* item = nullptr;
3431
        if (!selItems.empty()) {
3432
            if (selItems.front()->type() == ObjectType)
3433
                item = static_cast<DocumentObjectItem*>(selItems.front());
3434
            else if (selItems.front()->type() == DocumentType) {
3435
                auto ditem = static_cast<DocumentItem*>(selItems.front());
3436
                if (TreeParams::getSyncView()) {
3437
                    bool focus = hasFocus();
3438
                    ditem->document()->setActiveView();
3439
                    if (focus)
3440
                        setFocus();
3441
                }
3442
                // For triggering property editor refresh
3443
                Gui::Selection().signalSelectionChanged(SelectionChanges());
3444
            }
3445
        }
3446
        for (auto& v : DocumentMap) {
3447
            currentDocItem = v.second;
3448
            v.second->clearSelection(item);
3449
            currentDocItem = nullptr;
3450
        }
3451
        if (TreeParams::getRecordSelection())
3452
            Gui::Selection().selStackPush();
3453
    }
3454
    else {
3455
        for (auto pos = DocumentMap.begin(); pos != DocumentMap.end(); ++pos) {
3456
            currentDocItem = pos->second;
3457
            pos->second->updateSelection(pos->second);
3458
            currentDocItem = nullptr;
3459
        }
3460
        if (TreeParams::getRecordSelection())
3461
            Gui::Selection().selStackPush(true, true);
3462
    }
3463

3464
    this->blockSelection(lock);
3465
}
3466

3467
void TreeWidget::synchronizeSelectionCheckBoxes() {
3468
    const bool useCheckBoxes = isSelectionCheckBoxesEnabled();
3469
    for (auto tree : TreeWidget::Instances) {
3470
        QSignalBlocker blocker(tree);
3471
        for (QTreeWidgetItemIterator it(tree); *it; ++it) {
3472
            auto item = *it;
3473
            if (item->type() == ObjectType) {
3474
                if (useCheckBoxes)
3475
                    item->setCheckState(0, item->isSelected() ? Qt::Checked : Qt::Unchecked);
3476
                else
3477
                    item->setData(0, Qt::CheckStateRole, QVariant());
3478
            }
3479
        }
3480
        tree->resizeColumnToContents(0);
3481
    }
3482
}
3483

3484
void TreeWidget::updateVisibilityIcons() {
3485
    for (auto tree : TreeWidget::Instances) {
3486
        QSignalBlocker blocker(tree);
3487
        for (QTreeWidgetItemIterator it(tree); *it; ++it) {
3488
            auto item = *it;
3489
            if (item->type() == ObjectType) {
3490
                auto objitem = static_cast<DocumentObjectItem*>(item);
3491
                objitem->testStatus(true);
3492
            }
3493
        }
3494
        tree->resizeColumnToContents(0);
3495
    }
3496
}
3497

3498
QList<QTreeWidgetItem*> TreeWidget::childrenOfItem(const QTreeWidgetItem& item) const {
3499
    QList children = QList<QTreeWidgetItem*>();
3500

3501
    // check item is in this tree
3502
    if (!this->indexFromItem(&item).isValid())
3503
        return children;
3504

3505
    for (int i = 0; i < item.childCount(); i++) {
3506
        children.append(item.child(i));
3507
    }
3508
    return children;
3509
}
3510

3511
void TreeWidget::onItemChanged(QTreeWidgetItem* item, int column) {
3512
    if (column == 0 && isSelectionCheckBoxesEnabled()) {
3513
        bool selected = item->isSelected();
3514
        bool checked = item->checkState(0) == Qt::Checked;
3515
        if (checked != selected) {
3516
            item->setSelected(checked);
3517
        }
3518
    }
3519
}
3520

3521
void TreeWidget::onSelectTimer() {
3522

3523
    _updateStatus(false);
3524

3525
    bool syncSelect = TreeParams::getSyncSelection();
3526
    bool locked = this->blockSelection(true);
3527
    if (Selection().hasSelection()) {
3528
        for (auto& v : DocumentMap) {
3529
            v.second->setSelected(false);
3530
            currentDocItem = v.second;
3531
            v.second->selectItems(syncSelect ? DocumentItem::SR_EXPAND : DocumentItem::SR_SELECT);
3532
            currentDocItem = nullptr;
3533
        }
3534
    }
3535
    else {
3536
        for (auto& v : DocumentMap)
3537
            v.second->clearSelection();
3538
    }
3539
    this->blockSelection(locked);
3540
    selectTimer->stop();
3541
    return;
3542
}
3543

3544
void TreeWidget::onSelectionChanged(const SelectionChanges& msg)
3545
{
3546
    // When running from a different thread Qt will raise a warning
3547
    // when trying to start the QTimer
3548
    if (Q_UNLIKELY(thread() != QThread::currentThread())) {
3549
        return;
3550
    }
3551

3552
    switch (msg.Type)
3553
    {
3554
    case SelectionChanges::AddSelection:
3555
    case SelectionChanges::RmvSelection:
3556
    case SelectionChanges::SetSelection:
3557
    case SelectionChanges::ClrSelection: {
3558
        int timeout = TreeParams::getSelectionTimeout();
3559
        if (timeout <= 0)
3560
            timeout = 1;
3561
        selectTimer->start(timeout);
3562
        break;
3563
    }
3564
    default:
3565
        break;
3566
    }
3567
}
3568

3569
// ----------------------------------------------------------------------------
3570

3571
/* TRANSLATOR Gui::TreePanel */
3572

3573
TreePanel::TreePanel(const char* name, QWidget* parent)
3574
    : QWidget(parent)
3575
{
3576
    this->treeWidget = new TreeWidget(name, this);
3577
    int indent = TreeParams::getIndentation();
3578
    if (indent)
3579
        this->treeWidget->setIndentation(indent);
3580

3581
    auto pLayout = new QVBoxLayout(this);
3582
    pLayout->setSpacing(0);
3583
    pLayout->setContentsMargins(0, 0, 0, 0);
3584
    pLayout->addWidget(this->treeWidget);
3585
    connect(this->treeWidget, &TreeWidget::emitSearchObjects,
3586
            this, &TreePanel::showEditor);
3587

3588
    this->searchBox = new Gui::ExpressionLineEdit(this, true);
3589
    static_cast<ExpressionLineEdit*>(this->searchBox)->setExactMatch(Gui::ExpressionParameter::instance()->isExactMatch());
3590
    pLayout->addWidget(this->searchBox);
3591
    this->searchBox->hide();
3592
    this->searchBox->installEventFilter(this);
3593
    this->searchBox->setPlaceholderText(tr("Search"));
3594
    connect(this->searchBox, &QLineEdit::returnPressed,
3595
            this, &TreePanel::accept);
3596
    connect(this->searchBox, &QLineEdit::textChanged,
3597
            this, &TreePanel::itemSearch);
3598
}
3599

3600
TreePanel::~TreePanel() = default;
3601

3602
void TreePanel::accept()
3603
{
3604
    QString text = this->searchBox->text();
3605
    hideEditor();
3606
    this->treeWidget->setFocus();
3607
    this->treeWidget->itemSearch(text, true);
3608
}
3609

3610
bool TreePanel::eventFilter(QObject* obj, QEvent* ev)
3611
{
3612
    if (obj != this->searchBox)
3613
        return false;
3614

3615
    if (ev->type() == QEvent::KeyPress) {
3616
        bool consumed = false;
3617
        int key = static_cast<QKeyEvent*>(ev)->key();
3618
        switch (key) {
3619
        case Qt::Key_Escape:
3620
            hideEditor();
3621
            consumed = true;
3622
            treeWidget->setFocus();
3623
            break;
3624

3625
        default:
3626
            break;
3627
        }
3628

3629
        return consumed;
3630
    }
3631

3632
    return false;
3633
}
3634

3635
void TreePanel::showEditor()
3636
{
3637
    this->searchBox->show();
3638
    this->searchBox->setFocus();
3639
    this->treeWidget->startItemSearch(searchBox);
3640
}
3641

3642
void TreePanel::hideEditor()
3643
{
3644
    static_cast<ExpressionLineEdit*>(this->searchBox)->setDocumentObject(nullptr);
3645
    this->searchBox->clear();
3646
    this->searchBox->hide();
3647
    this->treeWidget->resetItemSearch();
3648
    auto sels = this->treeWidget->selectedItems();
3649
    if (!sels.empty())
3650
        this->treeWidget->scrollToItem(sels.front());
3651
}
3652

3653
void TreePanel::itemSearch(const QString& text)
3654
{
3655
    this->treeWidget->itemSearch(text, false);
3656
}
3657

3658
// ----------------------------------------------------------------------------
3659

3660
/* TRANSLATOR Gui::TreeDockWidget */
3661

3662
TreeDockWidget::TreeDockWidget(Gui::Document* pcDocument, QWidget* parent)
3663
    : DockWindow(pcDocument, parent)
3664
{
3665
    setWindowTitle(tr("Tree view"));
3666
    auto panel = new TreePanel("TreeView", this);
3667
    auto pLayout = new QGridLayout(this);
3668
    pLayout->setSpacing(0);
3669
    pLayout->setContentsMargins(0, 0, 0, 0);
3670
    pLayout->addWidget(panel, 0, 0);
3671
}
3672

3673
TreeDockWidget::~TreeDockWidget() = default;
3674

3675
void TreeWidget::selectLinkedObject(App::DocumentObject* linked) {
3676
    if (!isSelectionAttached() || isSelectionBlocked())
3677
        return;
3678

3679
    auto linkedVp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
3680
        Application::Instance->getViewProvider(linked));
3681
    if (!linkedVp) {
3682
        TREE_ERR("invalid linked view provider");
3683
        return;
3684
    }
3685
    auto linkedDoc = getDocumentItem(linkedVp->getDocument());
3686
    if (!linkedDoc) {
3687
        TREE_ERR("cannot find document of linked object");
3688
        return;
3689
    }
3690

3691
    if (selectTimer->isActive())
3692
        onSelectTimer();
3693
    else
3694
        _updateStatus(false);
3695

3696
    auto it = linkedDoc->ObjectMap.find(linked);
3697
    if (it == linkedDoc->ObjectMap.end()) {
3698
        TREE_ERR("cannot find tree item of linked object");
3699
        return;
3700
    }
3701
    auto linkedItem = it->second->rootItem;
3702
    if (!linkedItem)
3703
        linkedItem = *it->second->items.begin();
3704

3705
    if (linkedDoc->showItem(linkedItem, true))
3706
        scrollToItem(linkedItem);
3707

3708
    if (linkedDoc->document()->getDocument() != App::GetApplication().getActiveDocument()) {
3709
        bool focus = hasFocus();
3710
        linkedDoc->document()->setActiveView(linkedItem->object());
3711
        if (focus)
3712
            setFocus();
3713
    }
3714
}
3715

3716
// ----------------------------------------------------------------------------
3717

3718
DocumentItem::DocumentItem(const Gui::Document* doc, QTreeWidgetItem* parent)
3719
    : QTreeWidgetItem(parent, TreeWidget::DocumentType), pDocument(const_cast<Gui::Document*>(doc))
3720
{
3721
    //NOLINTBEGIN
3722
    // Setup connections
3723
    connectNewObject = doc->signalNewObject.connect(std::bind(&DocumentItem::slotNewObject, this, sp::_1));
3724
    connectDelObject = doc->signalDeletedObject.connect(
3725
        std::bind(&TreeWidget::slotDeleteObject, getTree(), sp::_1));
3726
    if (!App::GetApplication().isRestoring()) {
3727
        connectChgObject = doc->signalChangedObject.connect(
3728
            std::bind(&TreeWidget::slotChangeObject, getTree(), sp::_1, sp::_2));
3729
        connectTouchedObject = doc->getDocument()->signalTouchedObject.connect(
3730
            std::bind(&TreeWidget::slotTouchedObject, getTree(), sp::_1));
3731
    }
3732
    connectEdtObject = doc->signalInEdit.connect(std::bind(&DocumentItem::slotInEdit, this, sp::_1));
3733
    connectResObject = doc->signalResetEdit.connect(std::bind(&DocumentItem::slotResetEdit, this, sp::_1));
3734
    connectHltObject = doc->signalHighlightObject.connect(
3735
        std::bind(&DocumentItem::slotHighlightObject, this, sp::_1, sp::_2, sp::_3, sp::_4, sp::_5));
3736
    connectExpObject = doc->signalExpandObject.connect(
3737
        std::bind(&DocumentItem::slotExpandObject, this, sp::_1, sp::_2, sp::_3, sp::_4));
3738
    connectScrObject = doc->signalScrollToObject.connect(std::bind(&DocumentItem::slotScrollToObject, this, sp::_1));
3739
    auto adoc = doc->getDocument();
3740
    connectRecomputed = adoc->signalRecomputed.connect(std::bind(&DocumentItem::slotRecomputed, this, sp::_1, sp::_2));
3741
    connectRecomputedObj = adoc->signalRecomputedObject.connect(
3742
        std::bind(&DocumentItem::slotRecomputedObject, this, sp::_1));
3743
    //NOLINTEND
3744

3745
    setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable/*|Qt::ItemIsEditable*/);
3746

3747
    treeName = getTree()->getTreeName();
3748
}
3749

3750
DocumentItem::~DocumentItem()
3751
{
3752
    connectNewObject.disconnect();
3753
    connectDelObject.disconnect();
3754
    connectChgObject.disconnect();
3755
    connectTouchedObject.disconnect();
3756
    connectEdtObject.disconnect();
3757
    connectResObject.disconnect();
3758
    connectHltObject.disconnect();
3759
    connectExpObject.disconnect();
3760
    connectScrObject.disconnect();
3761
    connectRecomputed.disconnect();
3762
    connectRecomputedObj.disconnect();
3763
}
3764

3765
TreeWidget* DocumentItem::getTree() const {
3766
    return static_cast<TreeWidget*>(treeWidget());
3767
}
3768

3769
const char* DocumentItem::getTreeName() const {
3770
    return treeName;
3771
}
3772

3773
#define FOREACH_ITEM(_item, _obj) \
3774
    auto _it = ObjectMap.end();\
3775
    if(_obj.getObject() && _obj.getObject()->isAttachedToDocument())\
3776
        _it = ObjectMap.find(_obj.getObject());\
3777
    if(_it != ObjectMap.end()) {\
3778
        for(auto _item : _it->second->items) {
3779

3780
#define FOREACH_ITEM_ALL(_item) \
3781
    for(const auto& _v : ObjectMap) {\
3782
        for(auto _item : _v.second->items) {
3783

3784
#define END_FOREACH_ITEM }}
3785

3786

3787
void DocumentItem::slotInEdit(const Gui::ViewProviderDocumentObject& v)
3788
{
3789
    (void)v;
3790

3791
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
3792
        "User parameter:BaseApp/Preferences/TreeView");
3793
    unsigned long col = hGrp->GetUnsigned("TreeEditColor", 563609599);
3794
    QColor color(App::Color::fromPackedRGB<QColor>(col));
3795

3796
    if (!getTree()->editingItem) {
3797
        auto doc = Application::Instance->editDocument();
3798
        if (!doc)
3799
            return;
3800
        ViewProviderDocumentObject* parentVp = nullptr;
3801
        std::string subname;
3802
        auto vp = doc->getInEdit(&parentVp, &subname);
3803
        if (!parentVp)
3804
            parentVp = dynamic_cast<ViewProviderDocumentObject*>(vp);
3805
        if (parentVp)
3806
            getTree()->editingItem = findItemByObject(true, parentVp->getObject(), subname.c_str());
3807
    }
3808

3809
    if (getTree()->editingItem)
3810
        getTree()->editingItem->setBackground(0, color);
3811
    else {
3812
        FOREACH_ITEM(item, v)
3813
            item->setBackground(0, color);
3814
        END_FOREACH_ITEM
3815
    }
3816
}
3817

3818
void DocumentItem::slotResetEdit(const Gui::ViewProviderDocumentObject& v)
3819
{
3820
    auto tree = getTree();
3821
    FOREACH_ITEM_ALL(item)
3822
        if (tree->editingItem) {
3823
            if (item == tree->editingItem) {
3824
                item->setData(0, Qt::BackgroundRole, QVariant());
3825
                break;
3826
            }
3827
        }
3828
        else if (item->object() == &v)
3829
            item->setData(0, Qt::BackgroundRole, QVariant());
3830
    END_FOREACH_ITEM
3831
        tree->editingItem = nullptr;
3832
}
3833

3834
void DocumentItem::slotNewObject(const Gui::ViewProviderDocumentObject& obj) {
3835
    if (!obj.getObject() || !obj.getObject()->isAttachedToDocument()) {
3836
        FC_ERR("view provider not attached");
3837
        return;
3838
    }
3839
    getTree()->NewObjects[pDocument->getDocument()->getName()].push_back(obj.getObject()->getID());
3840
    getTree()->_updateStatus();
3841
}
3842

3843
bool DocumentItem::createNewItem(const Gui::ViewProviderDocumentObject& obj,
3844
    QTreeWidgetItem* parent, int index, DocumentObjectDataPtr data)
3845
{
3846
    if (!obj.getObject() ||
3847
        !obj.getObject()->isAttachedToDocument() ||
3848
        obj.getObject()->testStatus(App::PartialObject))
3849
        return false;
3850

3851
    if (!data) {
3852
        auto& pdata = ObjectMap[obj.getObject()];
3853
        if (!pdata) {
3854
            pdata = std::make_shared<DocumentObjectData>(
3855
                this, const_cast<ViewProviderDocumentObject*>(&obj));
3856
            auto& entry = getTree()->ObjectTable[obj.getObject()];
3857
            if (!entry.empty())
3858
                pdata->updateChildren(*entry.begin());
3859
            else
3860
                pdata->updateChildren(true);
3861
            entry.insert(pdata);
3862
        }
3863
        else if (pdata->rootItem && !parent) {
3864
            Base::Console().Warning("DocumentItem::slotNewObject: Cannot add view provider twice.\n");
3865
            return false;
3866
        }
3867
        data = pdata;
3868
    }
3869

3870
    auto item = new DocumentObjectItem(this, data);
3871
    if (!parent || parent == this) {
3872
        parent = this;
3873
        data->rootItem = item;
3874
        if (index < 0)
3875
            index = findRootIndex(obj.getObject());
3876
    }
3877
    if (index < 0)
3878
        parent->addChild(item);
3879
    else
3880
        parent->insertChild(index, item);
3881
    assert(item->parent() == parent);
3882
    item->setText(0, QString::fromUtf8(data->label.c_str()));
3883
    if (!data->label2.empty())
3884
        item->setText(1, QString::fromUtf8(data->label2.c_str()));
3885
    item->setText(2, QString::fromUtf8(data->internalName.c_str()));
3886
    if (!obj.showInTree() && !showHidden())
3887
        item->setHidden(true);
3888
    item->testStatus(true);
3889

3890
    populateItem(item);
3891
    return true;
3892
}
3893

3894
ViewProviderDocumentObject* DocumentItem::getViewProvider(App::DocumentObject* obj) {
3895
    return Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
3896
            Application::Instance->getViewProvider(obj));
3897
}
3898

3899
void TreeWidget::slotDeleteDocument(const Gui::Document& Doc)
3900
{
3901
    NewObjects.erase(Doc.getDocument()->getName());
3902
    auto it = DocumentMap.find(&Doc);
3903
    if (it != DocumentMap.end()) {
3904
        UpdateDisabler disabler(*this, updateBlocked);
3905
        auto docItem = it->second;
3906
        for (auto& v : docItem->ObjectMap) {
3907
            for (auto item : v.second->items)
3908
                item->myOwner = nullptr;
3909
            auto obj = v.second->viewObject->getObject();
3910
            if (obj->getDocument() == Doc.getDocument()) {
3911
                _slotDeleteObject(*v.second->viewObject, docItem);
3912
                continue;
3913
            }
3914
            auto it = ObjectTable.find(obj);
3915
            assert(it != ObjectTable.end());
3916
            assert(it->second.size() > 1);
3917
            it->second.erase(v.second);
3918
        }
3919
        this->rootItem->takeChild(this->rootItem->indexOfChild(docItem));
3920
        delete docItem;
3921
        DocumentMap.erase(it);
3922
    }
3923
}
3924

3925
void TreeWidget::slotDeleteObject(const Gui::ViewProviderDocumentObject& view) {
3926
    _slotDeleteObject(view, nullptr);
3927
}
3928

3929
void TreeWidget::_slotDeleteObject(const Gui::ViewProviderDocumentObject& view, DocumentItem* deletingDoc)
3930
{
3931
    auto obj = view.getObject();
3932
    auto itEntry = ObjectTable.find(obj);
3933
    if (itEntry == ObjectTable.end())
3934
        return;
3935

3936
    if (itEntry->second.empty()) {
3937
        ObjectTable.erase(itEntry);
3938
        return;
3939
    }
3940

3941
    TREE_LOG("delete object " << obj->getFullName());
3942

3943
    bool needUpdate = false;
3944

3945
    for (const auto& data : itEntry->second) {
3946
        DocumentItem* docItem = data->docItem;
3947
        if (docItem == deletingDoc)
3948
            continue;
3949

3950
        auto doc = docItem->document()->getDocument();
3951
        auto& items = data->items;
3952

3953
        if (obj->getDocument() == doc)
3954
            docItem->_ParentMap.erase(obj);
3955

3956
        bool lock = blockSelection(true);
3957
        for (auto cit = items.begin(), citNext = cit; cit != items.end(); cit = citNext) {
3958
            ++citNext;
3959
            (*cit)->myOwner = nullptr;
3960
            delete* cit;
3961
        }
3962
        blockSelection(lock);
3963

3964
        // Check for any child of the deleted object that is not in the tree, and put it
3965
        // under document item.
3966
        for (auto child : data->children) {
3967
            auto childVp = docItem->getViewProvider(child);
3968
            if (!childVp || child->getDocument() != doc)
3969
                continue;
3970
            docItem->_ParentMap[child].erase(obj);
3971
            auto cit = docItem->ObjectMap.find(child);
3972
            if (cit == docItem->ObjectMap.end() || cit->second->items.empty()) {
3973
                if (docItem->createNewItem(*childVp))
3974
                    needUpdate = true;
3975
            }
3976
            else {
3977
                auto childItem = *cit->second->items.begin();
3978
                if (childItem->requiredAtRoot(false)) {
3979
                    if (docItem->createNewItem(*childItem->object(), docItem, -1, childItem->myData))
3980
                        needUpdate = true;
3981
                }
3982
            }
3983
            childVp->setShowable(docItem->isObjectShowable(child));
3984
        }
3985
        docItem->ObjectMap.erase(obj);
3986
    }
3987
    ObjectTable.erase(itEntry);
3988

3989
    if (needUpdate)
3990
        _updateStatus();
3991
}
3992

3993
bool DocumentItem::populateObject(App::DocumentObject* obj) {
3994
    // make sure at least one of the item corresponding to obj is populated
3995
    auto it = ObjectMap.find(obj);
3996
    if (it == ObjectMap.end())
3997
        return false;
3998
    auto& items = it->second->items;
3999
    if (items.empty())
4000
        return false;
4001
    for (auto item : items) {
4002
        if (item->populated)
4003
            return true;
4004
    }
4005
    TREE_LOG("force populate object " << obj->getFullName());
4006
    auto item = *items.begin();
4007
    item->populated = true;
4008
    populateItem(item, true);
4009
    return true;
4010
}
4011

4012
void DocumentItem::populateItem(DocumentObjectItem* item, bool refresh, bool delay)
4013
{
4014
    (void)delay;
4015

4016
    if (item->populated && !refresh)
4017
        return;
4018

4019
    // Lazy loading policy: We will create an item for each children object if
4020
    // a) the item is expanded, or b) there is at least one free child, i.e.
4021
    // child originally located at root.
4022

4023
    item->setChildIndicatorPolicy(item->myData->children.empty() ?
4024
        QTreeWidgetItem::DontShowIndicator : QTreeWidgetItem::ShowIndicator);
4025

4026
    if (!item->populated && !item->isExpanded()) {
4027
        bool doPopulate = false;
4028

4029
        bool external = item->object()->getDocument() != item->getOwnerDocument()->document();
4030
        if (external)
4031
            return;
4032
        auto obj = item->object()->getObject();
4033
        auto linked = obj->getLinkedObject(true);
4034
        if (linked && linked->getDocument() != obj->getDocument())
4035
            return;
4036
        for (auto child : item->myData->children) {
4037
            auto it = ObjectMap.find(child);
4038
            if (it == ObjectMap.end() || it->second->items.empty()) {
4039
                auto vp = getViewProvider(child);
4040
                if (!vp) continue;
4041
                doPopulate = true;
4042
                break;
4043
            }
4044
            if (item->myData->removeChildrenFromRoot) {
4045
                if (it->second->rootItem) {
4046
                    doPopulate = true;
4047
                    break;
4048
                }
4049
            }
4050
        }
4051

4052
        if (!doPopulate)
4053
            return;
4054
    }
4055

4056
    item->populated = true;
4057
    bool checkHidden = !showHidden();
4058
    bool updated = false;
4059

4060
    int i = -1;
4061
    // iterate through the claimed children, and try to synchronize them with the
4062
    // children tree item with the same order of appearance.
4063
    int childCount = item->childCount();
4064
    for (auto child : item->myData->children) {
4065

4066
        ++i; // the current index of the claimed child
4067

4068
        bool found = false;
4069
        for (int j = i; j < childCount; ++j) {
4070
            QTreeWidgetItem* ci = item->child(j);
4071
            if (ci->type() != TreeWidget::ObjectType)
4072
                continue;
4073

4074
            auto childItem = static_cast<DocumentObjectItem*>(ci);
4075
            if (childItem->object()->getObject() != child)
4076
                continue;
4077

4078
            found = true;
4079
            if (j != i) { // fix index if it is changed
4080
                childItem->setHighlight(false);
4081
                item->removeChild(ci);
4082
                item->insertChild(i, ci);
4083
                assert(ci->parent() == item);
4084
                if (checkHidden)
4085
                    updateItemsVisibility(ci, false);
4086
            }
4087

4088
            // Check if the item just changed its policy of whether to remove
4089
            // children item from the root.
4090
            if (item->myData->removeChildrenFromRoot) {
4091
                if (childItem->myData->rootItem) {
4092
                    assert(childItem != childItem->myData->rootItem);
4093
                    bool lock = getTree()->blockSelection(true);
4094
                    delete childItem->myData->rootItem;
4095
                    getTree()->blockSelection(lock);
4096
                }
4097
            }
4098
            else if (childItem->requiredAtRoot()) {
4099
                createNewItem(*childItem->object(), this, -1, childItem->myData);
4100
                updated = true;
4101
            }
4102
            break;
4103
        }
4104

4105
        if (found)
4106
            continue;
4107

4108
        // This algo will be recursively applied to newly created child items
4109
        // through slotNewObject -> populateItem
4110

4111
        auto it = ObjectMap.find(child);
4112
        if (it == ObjectMap.end() || it->second->items.empty()) {
4113
            auto vp = getViewProvider(child);
4114
            if (!vp || !createNewItem(*vp, item, i, it == ObjectMap.end() ? DocumentObjectDataPtr() : it->second))
4115
                --i;
4116
            else
4117
                updated = true;
4118
            continue;
4119
        }
4120

4121
        if (!item->myData->removeChildrenFromRoot || !it->second->rootItem) {
4122
            DocumentObjectItem* childItem = *it->second->items.begin();
4123
            if (!createNewItem(*childItem->object(), item, i, it->second))
4124
                --i;
4125
            else
4126
                updated = true;
4127
        }
4128
        else {
4129
            DocumentObjectItem* childItem = it->second->rootItem;
4130
            if (item == childItem || item->isChildOfItem(childItem)) {
4131
                TREE_ERR("Cyclic dependency in "
4132
                    << item->object()->getObject()->getFullName()
4133
                    << '.' << childItem->object()->getObject()->getFullName());
4134
                --i;
4135
                continue;
4136
            }
4137
            it->second->rootItem = nullptr;
4138
            childItem->setHighlight(false);
4139
            this->removeChild(childItem);
4140
            item->insertChild(i, childItem);
4141
            assert(childItem->parent() == item);
4142
            if (checkHidden)
4143
                updateItemsVisibility(childItem, false);
4144
        }
4145
    }
4146

4147
    for (++i; item->childCount() > i;) {
4148
        QTreeWidgetItem* ci = item->child(i);
4149
        if (ci->type() == TreeWidget::ObjectType) {
4150
            auto childItem = static_cast<DocumentObjectItem*>(ci);
4151
            if (childItem->requiredAtRoot()) {
4152
                item->removeChild(childItem);
4153
                auto index = findRootIndex(childItem->object()->getObject());
4154
                if (index >= 0)
4155
                    this->insertChild(index, childItem);
4156
                else
4157
                    this->addChild(childItem);
4158
                assert(childItem->parent() == this);
4159
                if (checkHidden)
4160
                    updateItemsVisibility(childItem, false);
4161
                childItem->myData->rootItem = childItem;
4162
                continue;
4163
            }
4164
        }
4165

4166
        bool lock = getTree()->blockSelection(true);
4167
        delete ci;
4168
        getTree()->blockSelection(lock);
4169
    }
4170
    if (updated)
4171
        getTree()->_updateStatus();
4172
}
4173

4174
int DocumentItem::findRootIndex(App::DocumentObject* childObj) {
4175
    if (!TreeParams::getKeepRootOrder() || !childObj || !childObj->isAttachedToDocument())
4176
        return -1;
4177

4178
    // Use view provider's tree rank to find correct place at the root level.
4179

4180
    int count = this->childCount();
4181
    if (!count)
4182
        return -1;
4183

4184
    int first, last;
4185

4186
    auto getTreeRank = [](Gui::ViewProviderDocumentObject* vp) -> int {
4187
        if (vp->getTreeRank() == -1) {
4188
            vp->setTreeRank(vp->getObject()->getID());
4189
        }
4190
        return vp->getTreeRank();
4191
    };
4192

4193
    auto vpc = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(childObj));
4194
    int childTreeRank = getTreeRank(vpc);
4195

4196
    // find the last item
4197
    for (last = count - 1; last >= 0; --last) {
4198
        auto citem = this->child(last);
4199
        if (citem->type() == TreeWidget::ObjectType) {
4200
            auto vp = static_cast<DocumentObjectItem*>(citem)->object();
4201
            if (getTreeRank(vp) <= childTreeRank) {
4202
                return last + 1;
4203
            }
4204
            break;
4205
        }
4206
    }
4207

4208
    // find the first item
4209
    for (first = 0; first < count; ++first) {
4210
        auto citem = this->child(first);
4211
        if (citem->type() == TreeWidget::ObjectType) {
4212
            auto vp = static_cast<DocumentObjectItem*>(citem)->object();
4213
            if (getTreeRank(vp) > childTreeRank) {
4214
                return first;
4215
            }
4216
            break;
4217
        }
4218
    }
4219

4220
    // now do a binary search to find the lower bound, assuming the root level
4221
    // object is already in order
4222
    count = last - first;
4223
    int pos;
4224
    while (count > 0) {
4225
        int step = count / 2;
4226
        pos = first + step;
4227
        for (; pos <= last; ++pos) {
4228
            auto citem = this->child(pos);
4229
            if (citem->type() != TreeWidget::ObjectType)
4230
                continue;
4231
            auto vp = static_cast<DocumentObjectItem*>(citem)->object();
4232
            if (vp->getTreeRank() < childTreeRank) {
4233
                first = ++pos;
4234
                count -= step + 1;
4235
            }
4236
            else
4237
                count = step;
4238
            break;
4239
        }
4240
        if (pos > last)
4241
            return -1;
4242
    }
4243
    if (first > last)
4244
        return -1;
4245
    return first;
4246
}
4247

4248
void DocumentItem::sortObjectItems()
4249
{
4250
    QSignalBlocker guard(getTree());
4251

4252
    std::vector<DocumentObjectItem*> sortedItems;
4253
    sortedItems.reserve(this->childCount());
4254

4255
    for (int i = 0; i < this->childCount(); ++i) {
4256
        QTreeWidgetItem* treeItem = this->child(i);
4257
        if (treeItem->type() == TreeWidget::ObjectType) {
4258
            sortedItems.push_back(static_cast<DocumentObjectItem*>(treeItem));
4259
        }
4260
    }
4261

4262
    std::stable_sort(sortedItems.begin(), sortedItems.end(),
4263
        [](DocumentObjectItem* a, DocumentObjectItem* b) {
4264
        return a->object()->getTreeRank() < b->object()->getTreeRank();
4265
    });
4266

4267
    int sortedIndex = 0;
4268
    std::vector<bool> expansion;
4269
    for (int i = 0; i < this->childCount(); ++i) {
4270
        QTreeWidgetItem* treeItem = this->child(i);
4271
        if (treeItem->type() != TreeWidget::ObjectType) {
4272
            continue;
4273
        }
4274

4275
        DocumentObjectItem* sortedItem = sortedItems[sortedIndex++];
4276
        if (sortedItem == treeItem) {
4277
            continue;
4278
        }
4279

4280
        expansion.clear();
4281
        sortedItem->getExpandedSnapshot(expansion);
4282

4283
        this->removeChild(sortedItem);
4284
        this->insertChild(i, sortedItem);
4285
        if (!showHidden()) {
4286
            updateItemsVisibility(sortedItem, false);
4287
        }
4288

4289
        std::vector<bool>::const_iterator expFrom = expansion.cbegin();
4290
        sortedItem->applyExpandedSnapshot(expansion, expFrom);
4291
    }
4292
}
4293

4294
void TreeWidget::slotChangeObject(
4295
    const Gui::ViewProviderDocumentObject& view, const App::Property& prop) {
4296

4297
    auto obj = view.getObject();
4298
    if (!obj || !obj->isAttachedToDocument())
4299
        return;
4300

4301
    auto itEntry = ObjectTable.find(obj);
4302
    if (itEntry == ObjectTable.end() || itEntry->second.empty())
4303
        return;
4304

4305
    _updateStatus();
4306

4307
    // Let's not waste time on the newly added Visibility property in
4308
    // DocumentObject.
4309
    if (&prop == &obj->Visibility)
4310
        return;
4311

4312
    if (&prop == &obj->Label) {
4313
        const char* label = obj->Label.getValue();
4314
        auto firstData = *itEntry->second.begin();
4315
        if (firstData->label != label) {
4316
            for (const auto& data : itEntry->second) {
4317
                data->label = label;
4318
                auto displayName = QString::fromUtf8(label);
4319
                for (auto item : data->items)
4320
                    item->setText(0, displayName);
4321
            }
4322
        }
4323
        return;
4324
    }
4325

4326
    if (&prop == &obj->Label2) {
4327
        const char* label = obj->Label2.getValue();
4328
        auto firstData = *itEntry->second.begin();
4329
        if (firstData->label2 != label) {
4330
            for (const auto& data : itEntry->second) {
4331
                data->label2 = label;
4332
                auto displayName = QString::fromUtf8(label);
4333
                for (auto item : data->items)
4334
                    item->setText(1, displayName);
4335
            }
4336
        }
4337
        return;
4338
    }
4339

4340
    auto& s = ChangedObjects[obj];
4341
    if (prop.testStatus(App::Property::Output)
4342
        || prop.testStatus(App::Property::NoRecompute))
4343
    {
4344
        s.set(CS_Output);
4345
    }
4346
}
4347

4348
void TreeWidget::updateChildren(App::DocumentObject* obj,
4349
    const std::set<DocumentObjectDataPtr>& dataSet, bool propOutput, bool force)
4350
{
4351
    bool childrenChanged = false;
4352
    std::vector<App::DocumentObject*> children;
4353
    bool removeChildrenFromRoot = true;
4354

4355
    DocumentObjectDataPtr found;
4356
    for (auto data : dataSet) {
4357
        if (!found) {
4358
            found = data;
4359
            childrenChanged = found->updateChildren(force);
4360
            removeChildrenFromRoot = found->viewObject->canRemoveChildrenFromRoot();
4361
            if (!childrenChanged && found->removeChildrenFromRoot == removeChildrenFromRoot)
4362
                return;
4363
        }
4364
        else if (childrenChanged)
4365
            data->updateChildren(found);
4366
        data->removeChildrenFromRoot = removeChildrenFromRoot;
4367
        DocumentItem* docItem = data->docItem;
4368
        for (auto item : data->items)
4369
            docItem->populateItem(item, true);
4370
    }
4371

4372
    if (force)
4373
        return;
4374

4375
    if (childrenChanged && propOutput) {
4376
        // When a property is marked as output, it will not touch its object,
4377
        // and thus, its property change will not be propagated through
4378
        // recomputation. So we have to manually check for each links here.
4379
        for (auto link : App::GetApplication().getLinksTo(obj, App::GetLinkRecursive)) {
4380
            if (ChangedObjects.count(link))
4381
                continue;
4382
            std::vector<App::DocumentObject*> linkedChildren;
4383
            DocumentObjectDataPtr found;
4384
            auto it = ObjectTable.find(link);
4385
            if (it == ObjectTable.end())
4386
                continue;
4387
            for (auto data : it->second) {
4388
                if (!found) {
4389
                    found = data;
4390
                    if (!found->updateChildren(false))
4391
                        break;
4392
                }
4393
                data->updateChildren(found);
4394
                DocumentItem* docItem = data->docItem;
4395
                for (auto item : data->items)
4396
                    docItem->populateItem(item, true);
4397
            }
4398
        }
4399
    }
4400

4401
    if (childrenChanged) {
4402
        if (!selectTimer->isActive())
4403
            onSelectionChanged(SelectionChanges());
4404

4405
        //if the item is in a GeoFeatureGroup we may need to update that too, as the claim children
4406
        //of the geofeaturegroup depends on what the childs claim
4407
        auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(obj);
4408
        if (grp && !ChangedObjects.count(grp)) {
4409
            auto iter = ObjectTable.find(grp);
4410
            if (iter != ObjectTable.end())
4411
                updateChildren(grp, iter->second, true, false);
4412
        }
4413
    }
4414
}
4415

4416
void DocumentItem::slotHighlightObject(const Gui::ViewProviderDocumentObject& obj,
4417
    const Gui::HighlightMode& high, bool set, const App::DocumentObject* parent, const char* subname)
4418
{
4419
    getTree()->_updateStatus(false);
4420
    if (parent && parent->getDocument() != document()->getDocument()) {
4421
        auto it = getTree()->DocumentMap.find(Application::Instance->getDocument(parent->getDocument()));
4422
        if (it != getTree()->DocumentMap.end())
4423
            it->second->slotHighlightObject(obj, high, set, parent, subname);
4424
        return;
4425
    }
4426
    FOREACH_ITEM(item, obj)
4427
        if (parent) {
4428
            App::DocumentObject* topParent = nullptr;
4429
            std::ostringstream ss;
4430
            item->getSubName(ss, topParent);
4431
            if (!topParent) {
4432
                if (parent != obj.getObject())
4433
                    continue;
4434
            }
4435
        }
4436
    item->setHighlight(set, high);
4437
    if (parent)
4438
        return;
4439
    END_FOREACH_ITEM
4440
}
4441

4442
static unsigned int countExpandedItem(const QTreeWidgetItem* item) {
4443
    unsigned int size = 0;
4444
    for (int i = 0, count = item->childCount(); i < count; ++i) {
4445
        auto citem = item->child(i);
4446
        if (citem->type() != TreeWidget::ObjectType || !citem->isExpanded())
4447
            continue;
4448
        auto obj = static_cast<const DocumentObjectItem*>(citem)->object()->getObject();
4449
        if (obj->isAttachedToDocument())
4450
            size += strlen(obj->getNameInDocument()) + countExpandedItem(citem);
4451
    }
4452
    return size;
4453
}
4454

4455
unsigned int DocumentItem::getMemSize() const {
4456
    return countExpandedItem(this);
4457
}
4458

4459
static void saveExpandedItem(Base::Writer& writer, const QTreeWidgetItem* item) {
4460
    int itemCount = 0;
4461
    for (int i = 0, count = item->childCount(); i < count; ++i) {
4462
        auto citem = item->child(i);
4463
        if (citem->type() != TreeWidget::ObjectType || !citem->isExpanded())
4464
            continue;
4465
        auto obj = static_cast<const DocumentObjectItem*>(citem)->object()->getObject();
4466
        if (obj->isAttachedToDocument())
4467
            ++itemCount;
4468
    }
4469

4470
    if (!itemCount) {
4471
        writer.Stream() << "/>" << std::endl;
4472
        return;
4473
    }
4474

4475
    writer.Stream() << " count=\"" << itemCount << "\">" << std::endl;
4476
    writer.incInd();
4477
    for (int i = 0, count = item->childCount(); i < count; ++i) {
4478
        auto citem = item->child(i);
4479
        if (citem->type() != TreeWidget::ObjectType || !citem->isExpanded())
4480
            continue;
4481
        auto obj = static_cast<const DocumentObjectItem*>(citem)->object()->getObject();
4482
        if (obj->isAttachedToDocument()) {
4483
            writer.Stream() << writer.ind() << "<Expand name=\""
4484
                << obj->getNameInDocument() << "\"";
4485
            saveExpandedItem(writer, static_cast<const DocumentObjectItem*>(citem));
4486
        }
4487
    }
4488
    writer.decInd();
4489
    writer.Stream() << writer.ind() << "</Expand>" << std::endl;
4490
}
4491

4492
void DocumentItem::Save(Base::Writer& writer) const {
4493
    writer.Stream() << writer.ind() << "<Expand ";
4494
    saveExpandedItem(writer, this);
4495
}
4496

4497
void DocumentItem::Restore(Base::XMLReader& reader) {
4498
    reader.readElement("Expand");
4499
    if (!reader.hasAttribute("count"))
4500
        return;
4501
    _ExpandInfo.reset(new ExpandInfo);
4502
    _ExpandInfo->restore(reader);
4503
    for (auto inst : TreeWidget::Instances) {
4504
        if (inst != getTree()) {
4505
            auto docItem = inst->getDocumentItem(document());
4506
            if (docItem)
4507
                docItem->_ExpandInfo = _ExpandInfo;
4508
        }
4509
    }
4510
}
4511

4512
void DocumentItem::restoreItemExpansion(const ExpandInfoPtr& info, DocumentObjectItem* item) {
4513
    item->setExpanded(true);
4514
    if (!info)
4515
        return;
4516
    for (int i = 0, count = item->childCount(); i < count; ++i) {
4517
        auto citem = item->child(i);
4518
        if (citem->type() != TreeWidget::ObjectType)
4519
            continue;
4520
        auto obj = static_cast<DocumentObjectItem*>(citem)->object()->getObject();
4521
        if (!obj->isAttachedToDocument())
4522
            continue;
4523
        auto it = info->find(obj->getNameInDocument());
4524
        if (it != info->end())
4525
            restoreItemExpansion(it->second, static_cast<DocumentObjectItem*>(citem));
4526
    }
4527
}
4528

4529
void DocumentItem::slotExpandObject(const Gui::ViewProviderDocumentObject& obj,
4530
    const Gui::TreeItemMode& mode, const App::DocumentObject* parent, const char* subname)
4531
{
4532
    getTree()->_updateStatus(false);
4533

4534
    if ((mode == TreeItemMode::ExpandItem ||
4535
        mode == TreeItemMode::ExpandPath) &&
4536
        obj.getDocument()->getDocument()->testStatus(App::Document::Restoring)) {
4537
        if (!_ExpandInfo)
4538
            _ExpandInfo.reset(new ExpandInfo);
4539
        _ExpandInfo->emplace(std::string("*") + obj.getObject()->getNameInDocument(), ExpandInfoPtr());
4540
        return;
4541
    }
4542

4543
    if (parent && parent->getDocument() != document()->getDocument()) {
4544
        auto it = getTree()->DocumentMap.find(Application::Instance->getDocument(parent->getDocument()));
4545
        if (it != getTree()->DocumentMap.end())
4546
            it->second->slotExpandObject(obj, mode, parent, subname);
4547
        return;
4548
    }
4549

4550
    FOREACH_ITEM(item, obj)
4551
        // All document object items must always have a parent, either another
4552
        // object item or document item. If not, then there is a bug somewhere
4553
        // else.
4554
        assert(item->parent());
4555

4556
    switch (mode) {
4557
    case TreeItemMode::ExpandPath:
4558
        if (!parent) {
4559
            QTreeWidgetItem* parentItem = item->parent();
4560
            while (parentItem) {
4561
                parentItem->setExpanded(true);
4562
                parentItem = parentItem->parent();
4563
            }
4564
            item->setExpanded(true);
4565
            break;
4566
        }
4567
        // fall through
4568
    case TreeItemMode::ExpandItem:
4569
        if (!parent) {
4570
            if (item->parent()->isExpanded())
4571
                item->setExpanded(true);
4572
        }
4573
        else {
4574
            App::DocumentObject* topParent = nullptr;
4575
            std::ostringstream ss;
4576
            item->getSubName(ss, topParent);
4577
            if (!topParent) {
4578
                if (parent != obj.getObject())
4579
                    continue;
4580
            }
4581
            else if (topParent != parent)
4582
                continue;
4583
            showItem(item, false, true);
4584
            item->setExpanded(true);
4585
        }
4586
        break;
4587
    case TreeItemMode::CollapseItem:
4588
        item->setExpanded(false);
4589
        break;
4590
    case TreeItemMode::ToggleItem:
4591
        if (item->isExpanded())
4592
            item->setExpanded(false);
4593
        else
4594
            item->setExpanded(true);
4595
        break;
4596

4597
    default:
4598
        break;
4599
    }
4600
    if (item->isExpanded())
4601
        populateItem(item);
4602
    if (parent)
4603
        return;
4604
    END_FOREACH_ITEM
4605
}
4606

4607
void DocumentItem::slotScrollToObject(const Gui::ViewProviderDocumentObject& obj)
4608
{
4609
    if (!obj.getObject() || !obj.getObject()->isAttachedToDocument())
4610
        return;
4611
    auto it = ObjectMap.find(obj.getObject());
4612
    if (it == ObjectMap.end() || it->second->items.empty())
4613
        return;
4614
    auto item = it->second->rootItem;
4615
    if (!item)
4616
        item = *it->second->items.begin();
4617
    getTree()->_updateStatus(false);
4618
    getTree()->scrollToItem(item);
4619
}
4620

4621
void DocumentItem::slotRecomputedObject(const App::DocumentObject& obj) {
4622
    if (obj.isValid())
4623
        return;
4624
    slotRecomputed(*obj.getDocument(), { const_cast<App::DocumentObject*>(&obj) });
4625
}
4626

4627
void DocumentItem::slotRecomputed(const App::Document&, const std::vector<App::DocumentObject*>& objs) {
4628
    auto tree = getTree();
4629
    for (auto obj : objs) {
4630
        if (!obj->isValid())
4631
            tree->ChangedObjects[obj].set(TreeWidget::CS_Error);
4632
    }
4633
    if (!tree->ChangedObjects.empty())
4634
        tree->_updateStatus();
4635
}
4636

4637
Gui::Document* DocumentItem::document() const
4638
{
4639
    return this->pDocument;
4640
}
4641

4642
void DocumentItem::testStatus()
4643
{
4644
    for (const auto& v : ObjectMap)
4645
        v.second->testStatus();
4646
}
4647

4648
void DocumentItem::setData(int column, int role, const QVariant& value)
4649
{
4650
    if (role == Qt::EditRole) {
4651
        QString label = value.toString();
4652
        pDocument->getDocument()->Label.setValue((const char*)label.toUtf8());
4653
    }
4654

4655
    QTreeWidgetItem::setData(column, role, value);
4656
}
4657

4658
void DocumentItem::clearSelection(DocumentObjectItem* exclude)
4659
{
4660
    // Block signals here otherwise we get a recursion and quadratic runtime
4661
    bool ok = treeWidget()->blockSignals(true);
4662
    FOREACH_ITEM_ALL(item);
4663
    _v.second->dirtyFlag = false;
4664
    if (item == exclude) {
4665
        if (item->selected > 0)
4666
            item->selected = -1;
4667
        else
4668
            item->selected = 0;
4669
        updateItemSelection(item);
4670
        // The set has been changed while calling updateItemSelection
4671
        // so that the iterator has become invalid -> Abort
4672
        if (_v.second->dirtyFlag) {
4673
            break;
4674
        }
4675
    }
4676
    else {
4677
        item->selected = 0;
4678
        item->mySubs.clear();
4679
        item->setSelected(false);
4680
        item->setCheckState(false);
4681
    }
4682
    END_FOREACH_ITEM;
4683
    treeWidget()->blockSignals(ok);
4684
}
4685

4686
void DocumentItem::updateSelection(QTreeWidgetItem* ti, bool unselect) {
4687
    for (int i = 0, count = ti->childCount(); i < count; ++i) {
4688
        auto child = ti->child(i);
4689
        if (child && child->type() == TreeWidget::ObjectType) {
4690
            auto childItem = static_cast<DocumentObjectItem*>(child);
4691
            if (unselect) {
4692
                childItem->setSelected(false);
4693
                childItem->setCheckState(false);
4694
            }
4695
            updateItemSelection(childItem);
4696
            if (unselect && childItem->isGroup()) {
4697
                // If the child item being force unselected by its group parent
4698
                // is itself a group, propagate the unselection to its own
4699
                // children
4700
                updateSelection(childItem, true);
4701
            }
4702
        }
4703
    }
4704

4705
    if (unselect)
4706
        return;
4707
    for (int i = 0, count = ti->childCount(); i < count; ++i)
4708
        updateSelection(ti->child(i));
4709
}
4710

4711
void DocumentItem::updateItemSelection(DocumentObjectItem* item)
4712
{
4713
    // Note: In several places of this function the selection is altered and the notification of
4714
    // the selection observers can trigger a recreation of all DocumentObjectItem so that the
4715
    // passed 'item' can become a dangling pointer.
4716
    // Thus,'item' mustn't be accessed any more after altering the selection.
4717
    // For further details see the bug analysis of #13107
4718
    bool selected = item->isSelected();
4719
    bool checked = item->checkState(0) == Qt::Checked;
4720

4721
    if (selected && !checked)
4722
        item->setCheckState(true);
4723

4724
    if (!selected && checked)
4725
        item->setCheckState(false);
4726

4727
    if ((selected && item->selected > 0) || (!selected && !item->selected)) {
4728
        return;
4729
    }
4730
    if (item->selected != -1)
4731
        item->mySubs.clear();
4732
    item->selected = selected;
4733

4734
    auto obj = item->object()->getObject();
4735
    if (!obj || !obj->isAttachedToDocument())
4736
        return;
4737

4738
    std::ostringstream str;
4739
    App::DocumentObject* topParent = nullptr;
4740
    item->getSubName(str, topParent);
4741
    if (topParent) {
4742
        if (!obj->redirectSubName(str, topParent, nullptr))
4743
            str << obj->getNameInDocument() << '.';
4744
        obj = topParent;
4745
    }
4746
    const char* objname = obj->getNameInDocument();
4747
    const char* docname = obj->getDocument()->getName();
4748
    const auto& subname = str.str();
4749

4750
#ifdef FC_DEBUG
4751
    if (!subname.empty()) {
4752
        assert(item->getParentItem());
4753
    }
4754
#endif
4755

4756
    if (!selected) {
4757
        Gui::Selection().rmvSelection(docname, objname, subname.c_str());
4758
        return;
4759
    }
4760

4761
    auto vobj = item->object();
4762
    selected = false;
4763
    if (!item->mySubs.empty()) {
4764
        for (auto& sub : item->mySubs) {
4765
            if (Gui::Selection().addSelection(docname, objname, (subname + sub).c_str())) {
4766
                selected = true;
4767
            }
4768
        }
4769
    }
4770
    if (!selected) {
4771
        item->mySubs.clear();
4772
        if (!Gui::Selection().addSelection(docname, objname, subname.c_str())) {
4773
            // Safely re-access the item
4774
            DocumentObjectItem* item2 = findItem(vobj->getObject(), subname);
4775
            if (item2) {
4776
                item2->selected = 0;
4777
                item2->setSelected(false);
4778
                item2->setCheckState(false);
4779
            }
4780
            return;
4781
        }
4782
    }
4783
    getTree()->syncView(vobj);
4784
}
4785

4786
App::DocumentObject* DocumentItem::getTopParent(App::DocumentObject* obj, std::string& subname) {
4787
    auto it = ObjectMap.find(obj);
4788
    if (it == ObjectMap.end() || it->second->items.empty())
4789
        return nullptr;
4790

4791
    // already a top parent
4792
    if (it->second->rootItem)
4793
        return obj;
4794

4795
    for (auto item : it->second->items) {
4796
        // non group object do not provide a coordinate system, hence its
4797
        // claimed child is still in the global coordinate space, so the
4798
        // child can still be considered a top level object
4799
        if (!item->isParentGroup())
4800
            return obj;
4801
    }
4802

4803
    // If no top level item, find an item that is closest to the top level
4804
    std::multimap<int, DocumentObjectItem*> items;
4805
    for (auto item : it->second->items) {
4806
        int i = 0;
4807
        for (auto parent = item->parent(); parent; ++i, parent = parent->parent()) {
4808
            if (parent->isHidden())
4809
                i += 1000;
4810
            ++i;
4811
        }
4812
        items.emplace(i, item);
4813
    }
4814

4815
    App::DocumentObject* topParent = nullptr;
4816
    std::ostringstream ss;
4817
    items.begin()->second->getSubName(ss, topParent);
4818
    if (!topParent) {
4819
        // this shouldn't happen
4820
        FC_WARN("No top parent for " << obj->getFullName() << '.' << subname);
4821
        return obj;
4822
    }
4823
    ss << obj->getNameInDocument() << '.' << subname;
4824
    FC_LOG("Subname correction " << obj->getFullName() << '.' << subname
4825
        << " -> " << topParent->getFullName() << '.' << ss.str());
4826
    subname = ss.str();
4827
    return topParent;
4828
}
4829

4830
DocumentObjectItem *DocumentItem::findItem(App::DocumentObject* obj, const std::string& subname) const
4831
{
4832
    auto it = ObjectMap.find(obj);
4833
    if (it == ObjectMap.end()) {
4834
        return nullptr;
4835
    }
4836

4837
    // There is only one instance in the tree view
4838
    if (it->second->items.size() == 1) {
4839
        return *(it->second->items.begin());
4840
    }
4841

4842
    // If there are multiple instances use the one with the same subname
4843
    DocumentObjectItem* item {};
4844
    for (auto jt : it->second->items) {
4845
        std::ostringstream str;
4846
        App::DocumentObject* topParent = nullptr;
4847
        jt->getSubName(str, topParent);
4848
        if (topParent) {
4849
            if (!obj->redirectSubName(str, topParent, nullptr)) {
4850
                str << obj->getNameInDocument() << '.';
4851
            }
4852
        }
4853

4854
        if (subname == str.str()) {
4855
            item = jt;
4856
            break;
4857
        }
4858
    }
4859

4860
    return item;
4861
}
4862

4863
DocumentObjectItem* DocumentItem::findItemByObject(
4864
    bool sync, App::DocumentObject* obj, const char* subname, bool select)
4865
{
4866
    if (!subname)
4867
        subname = "";
4868

4869
    auto it = ObjectMap.find(obj);
4870
    if (it == ObjectMap.end() || it->second->items.empty())
4871
        return nullptr;
4872

4873
    // prefer top level item of this object
4874
    if (it->second->rootItem)
4875
        return findItem(sync, it->second->rootItem, subname, select);
4876

4877
    for (auto item : it->second->items) {
4878
        // non group object do not provide a coordinate system, hence its
4879
        // claimed child is still in the global coordinate space, so the
4880
        // child can still be considered a top level object
4881
        if (!item->isParentGroup())
4882
            return findItem(sync, item, subname, select);
4883
    }
4884

4885
    // If no top level item, find an item that is closest to the top level
4886
    std::multimap<int, DocumentObjectItem*> items;
4887
    for (auto item : it->second->items) {
4888
        int i = 0;
4889
        for (auto parent = item->parent(); parent; ++i, parent = parent->parent())
4890
            ++i;
4891
        items.emplace(i, item);
4892
    }
4893
    for (auto& v : items) {
4894
        auto item = findItem(sync, v.second, subname, select);
4895
        if (item)
4896
            return item;
4897
    }
4898
    return nullptr;
4899
}
4900

4901
DocumentObjectItem* DocumentItem::findItem(
4902
    bool sync, DocumentObjectItem* item, const char* subname, bool select)
4903
{
4904
    if (item->isHidden())
4905
        item->setHidden(false);
4906

4907
    if (!subname || *subname == 0) {
4908
        if (select) {
4909
            item->selected += 2;
4910
            item->mySubs.clear();
4911
        }
4912
        return item;
4913
    }
4914

4915
    TREE_TRACE("find next " << subname);
4916

4917
    // try to find the next level object name
4918
    const char* nextsub = nullptr;
4919
    const char* dot = nullptr;
4920
    if ((dot = strchr(subname, '.')))
4921
        nextsub = dot + 1;
4922
    else {
4923
        if (select) {
4924
            item->selected += 2;
4925
            if (std::find(item->mySubs.begin(), item->mySubs.end(), subname) == item->mySubs.end())
4926
                item->mySubs.emplace_back(subname);
4927
        }
4928
        return item;
4929
    }
4930

4931
    std::string name(subname, nextsub - subname);
4932
    auto obj = item->object()->getObject();
4933
    auto subObj = obj->getSubObject(name.c_str());
4934
    if (!subObj || subObj == obj) {
4935
        if (!subObj && !getTree()->searchDoc)
4936
            TREE_LOG("sub object not found " << item->getName() << '.' << name.c_str());
4937
        if (select) {
4938
            item->selected += 2;
4939
            if (std::find(item->mySubs.begin(), item->mySubs.end(), subname) == item->mySubs.end())
4940
                item->mySubs.emplace_back(subname);
4941
        }
4942
        return item;
4943
    }
4944

4945
    if (select)
4946
        item->mySubs.clear();
4947

4948
    if (!item->populated && sync) {
4949
        //force populate the item
4950
        item->populated = true;
4951
        populateItem(item, true);
4952
    }
4953

4954
    for (int i = 0, count = item->childCount(); i < count; ++i) {
4955
        auto ti = item->child(i);
4956
        if (!ti || ti->type() != TreeWidget::ObjectType) continue;
4957
        auto child = static_cast<DocumentObjectItem*>(ti);
4958

4959
        if (child->object()->getObject() == subObj)
4960
            return findItem(sync, child, nextsub, select);
4961
    }
4962

4963
    // The sub object is not found. This could happen for geo group, since its
4964
    // children may be in more than one hierarchy down.
4965
    bool found = false;
4966
    DocumentObjectItem* res = nullptr;
4967
    auto it = ObjectMap.find(subObj);
4968
    if (it != ObjectMap.end()) {
4969
        for (auto child : it->second->items) {
4970
            if (child->isChildOfItem(item)) {
4971
                found = true;
4972
                res = findItem(sync, child, nextsub, select);
4973
                if (!select)
4974
                    return res;
4975
            }
4976
        }
4977
    }
4978

4979
    if (select && !found) {
4980
        // The sub object is still not found. Maybe it is a non-object sub-element.
4981
        // Select the current object instead.
4982
        TREE_TRACE("element " << subname << " not found");
4983
        item->selected += 2;
4984
        if (std::find(item->mySubs.begin(), item->mySubs.end(), subname) == item->mySubs.end())
4985
            item->mySubs.emplace_back(subname);
4986
    }
4987
    return res;
4988
}
4989

4990
void DocumentItem::selectItems(SelectionReason reason) {
4991
    const auto& sels = Selection().getSelection(pDocument->getDocument()->getName(), ResolveMode::NoResolve);
4992

4993
    bool sync = (sels.size() > 50 || reason == SR_SELECT) ? false : true;
4994

4995
    for (const auto& sel : sels)
4996
        findItemByObject(sync, sel.pObject, sel.SubName, true);
4997

4998
    DocumentObjectItem* newSelect = nullptr;
4999
    DocumentObjectItem* oldSelect = nullptr;
5000

5001
    FOREACH_ITEM_ALL(item)
5002
        if (item->selected == 1) {
5003
            // this means it is the old selection and is not in the current
5004
            // selection
5005
            item->selected = 0;
5006
            item->mySubs.clear();
5007
            item->setSelected(false);
5008
            item->setCheckState(false);
5009
        }
5010
        else if (item->selected) {
5011
            if (sync) {
5012
                if (item->selected == 2 && showItem(item, false, reason == SR_FORCE_EXPAND)) {
5013
                    // This means newly selected and can auto expand
5014
                    if (!newSelect)
5015
                        newSelect = item;
5016
                }
5017
                if (!newSelect && !oldSelect && !item->isHidden()) {
5018
                    bool visible = true;
5019
                    for (auto parent = item->parent(); parent; parent = parent->parent()) {
5020
                        if (!parent->isExpanded() || parent->isHidden()) {
5021
                            visible = false;
5022
                            break;
5023
                        }
5024
                    }
5025
                    if (visible)
5026
                        oldSelect = item;
5027
                }
5028
            }
5029
            item->selected = 1;
5030
            item->setSelected(true);
5031
            item->setCheckState(true);
5032
        }
5033
    END_FOREACH_ITEM;
5034

5035
    if (sync) {
5036
        if (!newSelect)
5037
            newSelect = oldSelect;
5038
        else
5039
            getTree()->syncView(newSelect->object());
5040
        if (newSelect)
5041
            getTree()->scrollToItem(newSelect);
5042
    }
5043
}
5044

5045
void DocumentItem::populateParents(const ViewProvider* vp, ViewParentMap& parentMap) {
5046
    auto it = parentMap.find(vp);
5047
    if (it == parentMap.end())
5048
        return;
5049
    for (auto parent : it->second) {
5050
        auto it = ObjectMap.find(parent->getObject());
5051
        if (it == ObjectMap.end())
5052
            continue;
5053

5054
        populateParents(parent, parentMap);
5055
        for (auto item : it->second->items) {
5056
            if (!item->isHidden() && !item->populated) {
5057
                item->populated = true;
5058
                populateItem(item, true);
5059
            }
5060
        }
5061
    }
5062
}
5063

5064
void DocumentItem::selectAllInstances(const ViewProviderDocumentObject& vpd) {
5065
    ViewParentMap parentMap;
5066
    auto pObject = vpd.getObject();
5067
    if (ObjectMap.find(pObject) == ObjectMap.end())
5068
        return;
5069

5070
    bool lock = getTree()->blockSelection(true);
5071

5072
    // We are trying to select all items corresponding to a given view
5073
    // provider, i.e. all appearance of the object inside all its parent items
5074
    //
5075
    // Build a map of object to all its parent
5076
    for (auto& v : ObjectMap) {
5077
        if (v.second->viewObject == &vpd) continue;
5078
        for (auto child : v.second->viewObject->claimChildren()) {
5079
            auto vp = getViewProvider(child);
5080
            if (!vp) continue;
5081
            parentMap[vp].push_back(v.second->viewObject);
5082
        }
5083
    }
5084

5085
    // now make sure all parent items are populated. In order to do that, we
5086
    // need to populate the oldest parent first
5087
    populateParents(&vpd, parentMap);
5088

5089
    DocumentObjectItem* first = nullptr;
5090
    FOREACH_ITEM(item, vpd);
5091
    if (showItem(item, true) && !first)
5092
        first = item;
5093
    END_FOREACH_ITEM;
5094

5095
    getTree()->blockSelection(lock);
5096
    if (first) {
5097
        treeWidget()->scrollToItem(first);
5098
        updateSelection();
5099
    }
5100
}
5101

5102
bool DocumentItem::showHidden() const {
5103
    return pDocument->getDocument()->ShowHidden.getValue();
5104
}
5105

5106
void DocumentItem::setShowHidden(bool show) {
5107
    pDocument->getDocument()->ShowHidden.setValue(show);
5108
}
5109

5110
bool DocumentItem::showItem(DocumentObjectItem* item, bool select, bool force) {
5111
    auto parent = item->parent();
5112
    if (item->isHidden()) {
5113
        if (!force)
5114
            return false;
5115
        item->setHidden(false);
5116
    }
5117

5118
    if (parent->type() == TreeWidget::ObjectType) {
5119
        if (!showItem(static_cast<DocumentObjectItem*>(parent), false))
5120
            return false;
5121
        auto pitem = static_cast<DocumentObjectItem*>(parent);
5122
        if (force || !pitem->object()->getObject()->testStatus(App::NoAutoExpand))
5123
            parent->setExpanded(true);
5124
        else if (!select)
5125
            return false;
5126
    }
5127
    else
5128
        parent->setExpanded(true);
5129

5130
    if (select) {
5131
        item->setSelected(true);
5132
        item->setCheckState(true);
5133
    }
5134
    return true;
5135
}
5136

5137
void DocumentItem::updateItemsVisibility(QTreeWidgetItem* item, bool show) {
5138
    if (item->type() == TreeWidget::ObjectType) {
5139
        auto objitem = static_cast<DocumentObjectItem*>(item);
5140
        objitem->setHidden(!show && !objitem->object()->showInTree());
5141
    }
5142
    for (int i = 0; i < item->childCount(); ++i)
5143
        updateItemsVisibility(item->child(i), show);
5144
}
5145

5146
void DocumentItem::updateSelection() {
5147
    bool lock = getTree()->blockSelection(true);
5148
    updateSelection(this, false);
5149
    getTree()->blockSelection(lock);
5150
}
5151

5152
// ----------------------------------------------------------------------------
5153

5154
static int countItems;
5155

5156
DocumentObjectItem::DocumentObjectItem(DocumentItem* ownerDocItem, DocumentObjectDataPtr data)
5157
    : QTreeWidgetItem(TreeWidget::ObjectType)
5158
    , myOwner(ownerDocItem), myData(data), previousStatus(-1), selected(0), populated(false)
5159
{
5160
    setFlags(flags() | Qt::ItemIsEditable | Qt::ItemIsUserCheckable);
5161
    setCheckState(false);
5162

5163
    myData->insertItem(this);
5164
    ++countItems;
5165
    TREE_LOG("Create item: " << countItems << ", " << object()->getObject()->getFullName());
5166
}
5167

5168
DocumentObjectItem::~DocumentObjectItem()
5169
{
5170
    --countItems;
5171
    TREE_LOG("Delete item: " << countItems << ", " << object()->getObject()->getFullName());
5172
    myData->removeItem(this);
5173

5174
    if (myData->rootItem == this)
5175
        myData->rootItem = nullptr;
5176

5177
    if (myOwner && myData->items.empty()) {
5178
        auto it = myOwner->_ParentMap.find(object()->getObject());
5179
        if (it != myOwner->_ParentMap.end() && !it->second.empty()) {
5180
            myOwner->PopulateObjects.push_back(*it->second.begin());
5181
            myOwner->getTree()->_updateStatus();
5182
        }
5183
    }
5184
}
5185

5186
void DocumentObjectItem::restoreBackground() {
5187
    this->setBackground(0, this->bgBrush);
5188
}
5189

5190
void DocumentObjectItem::setHighlight(bool set, Gui::HighlightMode high) {
5191
    QFont f = this->font(0);
5192
    auto highlight = [this, set](const QColor& col) {
5193
        if (set)
5194
            this->setBackground(0, col);
5195
        else
5196
            this->setBackground(0, QBrush());
5197
        this->bgBrush = this->background(0);
5198
    };
5199

5200
    switch (high) {
5201
    case HighlightMode::Bold:
5202
        f.setBold(set);
5203
        break;
5204
    case HighlightMode::Italic:
5205
        f.setItalic(set);
5206
        break;
5207
    case HighlightMode::Underlined:
5208
        f.setUnderline(set);
5209
        break;
5210
    case HighlightMode::Overlined:
5211
        f.setOverline(set);
5212
        break;
5213
    case HighlightMode::StrikeOut:
5214
        f.setStrikeOut(set);
5215
        break;
5216
    case HighlightMode::Blue:
5217
        highlight(QColor(200, 200, 255));
5218
        break;
5219
    case HighlightMode::LightBlue:
5220
        highlight(QColor(230, 230, 255));
5221
        break;
5222
    case HighlightMode::UserDefined:
5223
    {
5224
        QColor color(230, 230, 255);
5225
        if (set) {
5226
            ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
5227
            bool bold = hGrp->GetBool("TreeActiveBold", true);
5228
            bool italic = hGrp->GetBool("TreeActiveItalic", false);
5229
            bool underlined = hGrp->GetBool("TreeActiveUnderlined", false);
5230
            bool overlined = hGrp->GetBool("TreeActiveOverlined", false);
5231
            f.setBold(bold);
5232
            f.setItalic(italic);
5233
            f.setUnderline(underlined);
5234
            f.setOverline(overlined);
5235

5236
            unsigned long col = hGrp->GetUnsigned("TreeActiveColor", 1538528255);
5237
            color = App::Color::fromPackedRGB<QColor>(col);
5238
        }
5239
        else {
5240
            f.setBold(false);
5241
            f.setItalic(false);
5242
            f.setUnderline(false);
5243
            f.setOverline(false);
5244
        }
5245
        highlight(color);
5246
    }   break;
5247
    default:
5248
        break;
5249
    }
5250
    this->setFont(0, f);
5251
}
5252

5253
const char* DocumentObjectItem::getTreeName() const
5254
{
5255
    return myData->getTreeName();
5256
}
5257

5258
Gui::ViewProviderDocumentObject* DocumentObjectItem::object() const
5259
{
5260
    return myData->viewObject;
5261
}
5262

5263
void DocumentObjectItem::testStatus(bool resetStatus)
5264
{
5265
    QIcon icon, icon2;
5266
    testStatus(resetStatus, icon, icon2);
5267
}
5268

5269
namespace {
5270
enum Status {
5271
    Visible = 1 << 0,
5272
    Recompute = 1 << 1,
5273
    Error = 1 << 2,
5274
    Hidden = 1 << 3,
5275
    External = 1 << 4,
5276
    Freezed = 1 << 5
5277
};
5278
}
5279

5280
void DocumentObjectItem::testStatus(bool resetStatus, QIcon& icon1, QIcon& icon2)
5281
{
5282
    App::DocumentObject* pObject = object()->getObject();
5283

5284
    int visible = -1;
5285
    auto parentItem = getParentItem();
5286
    if (parentItem) {
5287
        Timing(testStatus1);
5288
        auto parent = parentItem->object()->getObject();
5289
        auto ext = parent->getExtensionByType<App::GroupExtension>(true, false);
5290
        if (!ext)
5291
            visible = parent->isElementVisible(pObject->getNameInDocument());
5292
        else {
5293
            // We are dealing with a plain group. It has special handling when
5294
            // linked, which allows it to have indpenedent visibility control.
5295
            // We need to go up the hierarchy and see if there is any link to
5296
            // it.
5297
            for (auto pp = parentItem->getParentItem(); pp; pp = pp->getParentItem()) {
5298
                auto obj = pp->object()->getObject();
5299
                if (!obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(), false)) {
5300
                    visible = pp->object()->getObject()->isElementVisible(pObject->getNameInDocument());
5301
                    break;
5302
                }
5303
            }
5304
        }
5305
    }
5306

5307
    Timing(testStatus2);
5308

5309
    if (visible < 0)
5310
        visible = object()->isShow() ? 1 : 0;
5311

5312
    auto obj = object()->getObject();
5313
    auto linked = obj->getLinkedObject(false);
5314
    bool external = object()->getDocument() != getOwnerDocument()->document() ||
5315
        (linked && linked->getDocument() != obj->getDocument());
5316
    bool freezed = pObject->isFreezed();
5317

5318
    int currentStatus =
5319
        ((freezed ? 1 : 0) << 5) |
5320
        ((external ? 1 : 0) << 4) |
5321
        ((object()->showInTree() ? 0 : 1) << 3) |
5322
        ((pObject->isError() ? 1 : 0) << 2) |
5323
        ((pObject->isTouched() || pObject->mustExecute() == 1 ? 1 : 0) << 1) |
5324
        (visible ? 1 : 0);
5325

5326
    TimingStop(testStatus2);
5327

5328
    if (!resetStatus && previousStatus == currentStatus)
5329
        return;
5330

5331
    _Timing(1, testStatus3);
5332

5333
    previousStatus = currentStatus;
5334

5335
    QIcon::Mode mode = QIcon::Normal;
5336
    if (currentStatus & Status::Visible) {
5337
        // Note: By default the foreground, i.e. text color is invalid
5338
        // to make use of the default color of the tree widget's palette.
5339
        // If we temporarily set this color to dark and reset to an invalid
5340
        // color again we cannot do it with setTextColor() or setForeground(),
5341
        // respectively, because for any reason the color would always switch
5342
        // to black which will lead to unreadable text if the system background
5343
        // hss already a dark color.
5344
        // However, it works if we set the appropriate role to an empty QVariant().
5345
        for (int column = 0; column < this->columnCount(); ++column) {
5346
            this->setData(column, Qt::ForegroundRole, QVariant());
5347
        }
5348
    }
5349
    else { // invisible
5350
        QStyleOptionViewItem opt;
5351
        // it can happen that a tree item is not attached to the tree widget (#0003025)
5352
        if (this->treeWidget())
5353
            opt.initFrom(this->treeWidget());
5354
        for (int column = 0; column < this->columnCount(); ++column) {
5355
            this->setForeground(column, opt.palette.color(QPalette::Disabled, QPalette::Text));
5356
        }
5357
        mode = QIcon::Disabled;
5358
    }
5359

5360
    _TimingStop(1, testStatus3);
5361

5362
    QIcon& icon = mode == QIcon::Normal ? icon1 : icon2;
5363

5364
    if (icon.isNull()) {
5365
        Timing(getIcon);
5366
        QPixmap px;
5367
        if (currentStatus & Status::Error) {
5368
            static QPixmap pxError;
5369
            if (pxError.isNull()) {
5370
                // object is in error state
5371
                pxError = Gui::BitmapFactory().pixmapFromSvg("overlay_error", QSize(10, 10));
5372
            }
5373
            px = pxError;
5374
        }
5375
        else if (currentStatus & Status::Recompute) {
5376
            static QPixmap pxRecompute;
5377
            if (pxRecompute.isNull()) {
5378
                // object must be recomputed
5379
                pxRecompute = Gui::BitmapFactory().pixmapFromSvg("overlay_recompute", QSize(10, 10));
5380
            }
5381
            px = pxRecompute;
5382
        }
5383

5384
        // get the original icon set
5385
        QIcon icon_org = object()->getIcon();
5386

5387
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
5388
        int w = getTree()->viewOptions().decorationSize.width();
5389
#else
5390
        QStyleOptionViewItem opt;
5391
        getTree()->initViewItemOption(&opt);
5392
        int w = opt.decorationSize.width();
5393
#endif
5394

5395
        QPixmap pxOn, pxOff;
5396

5397
        // if needed show small pixmap inside
5398
        if (!px.isNull()) {
5399
            pxOff = BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::Off),
5400
                px, BitmapFactoryInst::TopRight);
5401
            pxOn = BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::On),
5402
                px, BitmapFactoryInst::TopRight);
5403
        }
5404
        else {
5405
            pxOff = icon_org.pixmap(w, w, mode, QIcon::Off);
5406
            pxOn = icon_org.pixmap(w, w, mode, QIcon::On);
5407
        }
5408

5409
        if (currentStatus & Status::Hidden) {
5410
            static QPixmap pxHidden;
5411
            if (pxHidden.isNull()) {
5412
                pxHidden = Gui::BitmapFactory().pixmapFromSvg("TreeItemVisible", QSize(10, 10));
5413
            }
5414
            pxOff = BitmapFactory().merge(pxOff, pxHidden, BitmapFactoryInst::TopLeft);
5415
            pxOn = BitmapFactory().merge(pxOn, pxHidden, BitmapFactoryInst::TopLeft);
5416
        }
5417

5418
        if (currentStatus & Status::External) {
5419
            static QPixmap pxExternal;
5420
            if (pxExternal.isNull()) {
5421
                pxExternal = Gui::BitmapFactory().pixmapFromSvg("LinkOverlay",
5422
                                                              QSize(24, 24));
5423
            }
5424
            pxOff = BitmapFactory().merge(pxOff, pxExternal, BitmapFactoryInst::BottomRight);
5425
            pxOn = BitmapFactory().merge(pxOn, pxExternal, BitmapFactoryInst::BottomRight);
5426
        }
5427

5428
        if (currentStatus & Status::Freezed) {
5429
            static QPixmap pxFreeze;
5430
            if (pxFreeze.isNull()) {
5431
                // object is in freezed state
5432
                pxFreeze = Gui::BitmapFactory().pixmapFromSvg("Std_ToggleFreeze", QSize(16, 16));
5433
            }
5434
            pxOff = BitmapFactory().merge(pxOff, pxFreeze, BitmapFactoryInst::TopLeft);
5435
            pxOn = BitmapFactory().merge(pxOn, pxFreeze, BitmapFactoryInst::TopLeft);
5436
        }
5437

5438
        icon.addPixmap(pxOn, QIcon::Normal, QIcon::On);
5439
        icon.addPixmap(pxOff, QIcon::Normal, QIcon::Off);
5440

5441
        icon = object()->mergeColorfulOverlayIcons(icon);
5442

5443
        if (isVisibilityIconEnabled()) {
5444
            static QPixmap pxVisible, pxInvisible;
5445
            if (pxVisible.isNull()) {
5446
                pxVisible = BitmapFactory().pixmap("TreeItemVisible");
5447
            }
5448
            if (pxInvisible.isNull()) {
5449
                pxInvisible = BitmapFactory().pixmap("TreeItemInvisible");
5450
            }
5451

5452
            // Prepend the visibility pixmap to the final icon pixmaps and use these as the icon.
5453
            QIcon new_icon;
5454
            auto style = this->getTree()->style();
5455
            int const spacing = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
5456
            for (auto state: {QIcon::On, QIcon::Off}) {
5457
                QPixmap px_org = icon.pixmap(0xFFFF, 0xFFFF, QIcon::Normal, state);
5458

5459
                QPixmap px(2*px_org.width() + spacing, px_org.height());
5460
                px.fill(Qt::transparent);
5461

5462
                QPainter pt;
5463
                pt.begin(&px);
5464
                pt.setPen(Qt::NoPen);
5465
                pt.drawPixmap(0, 0, px_org.width(), px_org.height(), (currentStatus & Status::Visible) ? pxVisible : pxInvisible);
5466
                pt.drawPixmap(px_org.width() + spacing, 0, px_org.width(), px_org.height(), px_org);
5467
                pt.end();
5468

5469
                new_icon.addPixmap(px, QIcon::Normal, state);
5470
            }
5471
            icon = new_icon;
5472
        }
5473
    }
5474

5475
    _Timing(2, setIcon);
5476
    this->setIcon(0, icon);
5477
}
5478

5479
void DocumentObjectItem::displayStatusInfo()
5480
{
5481
    App::DocumentObject* Obj = object()->getObject();
5482

5483
    QString info = QApplication::translate(Obj->getTypeId().getName(), Obj->getStatusString());
5484

5485
    if (Obj->mustExecute() == 1 && !Obj->isError())
5486
        info += TreeWidget::tr(" (but must be executed)");
5487

5488
    QString status = TreeWidget::tr("%1, Internal name: %2")
5489
        .arg(info, QString::fromLatin1(Obj->getNameInDocument()));
5490

5491
    if (!Obj->isError())
5492
        getMainWindow()->showMessage(status);
5493
    else {
5494
        getMainWindow()->showStatus(MainWindow::Err, status);
5495
        QTreeWidget* tree = this->treeWidget();
5496
        QPoint pos = tree->visualItemRect(this).topRight();
5497
        QToolTip::showText(tree->mapToGlobal(pos), info);
5498
    }
5499
}
5500

5501
void DocumentObjectItem::setExpandedStatus(bool on)
5502
{
5503
    if (getOwnerDocument()->document() == object()->getDocument())
5504
        object()->getObject()->setStatus(App::Expand, on);
5505
}
5506

5507
void DocumentObjectItem::setData(int column, int role, const QVariant& value)
5508
{
5509
    QVariant myValue(value);
5510
    if (role == Qt::EditRole && column <= 1) {
5511
        auto obj = object()->getObject();
5512
        auto& label = column ? obj->Label2 : obj->Label;
5513

5514
        std::ostringstream str;
5515
        str << TreeWidget::tr("Rename").toStdString() << ' ' << getName() << '.' << label.getName();
5516

5517
        // Explicitly open and commit a transaction since this is a single change here
5518
        // For more details: https://forum.freecad.org/viewtopic.php?f=3&t=72351
5519
        App::Document* doc = obj->getDocument();
5520
        doc->openTransaction(str.str().c_str());
5521
        label.setValue(value.toString().toUtf8().constData());
5522
        doc->commitTransaction();
5523

5524
        myValue = QString::fromUtf8(label.getValue());
5525
    }
5526
    QTreeWidgetItem::setData(column, role, myValue);
5527
}
5528

5529
bool DocumentObjectItem::isChildOfItem(DocumentObjectItem* item)
5530
{
5531
    for (auto pitem = parent(); pitem; pitem = pitem->parent())
5532
        if (pitem == item)
5533
            return true;
5534
    return false;
5535
}
5536

5537
bool DocumentObjectItem::requiredAtRoot(bool excludeSelf) const {
5538
    if (myData->rootItem || object()->getDocument() != getOwnerDocument()->document())
5539
        return false;
5540
    bool checkMap = true;
5541
    for (auto item : myData->items) {
5542
        if (excludeSelf && item == this) continue;
5543
        auto pi = item->getParentItem();
5544
        if (!pi || pi->myData->removeChildrenFromRoot)
5545
            return false;
5546
        checkMap = false;
5547
    }
5548
    if (checkMap && myOwner) {
5549
        auto it = myOwner->_ParentMap.find(object()->getObject());
5550
        if (it != myOwner->_ParentMap.end()) {
5551
            // Reaching here means all items of this corresponding object is
5552
            // going to be deleted, but the object itself is not deleted and
5553
            // still being referred to by some parent item that is not expanded
5554
            // yet. So, we force populate at least one item of the parent
5555
            // object to make sure that there is at least one corresponding
5556
            // item for each object.
5557
            //
5558
            // PS: practically speaking, it won't hurt much to delete all the
5559
            // items, because the item will be auto created once the user
5560
            // expand its parent item. It only causes minor problems, such as,
5561
            // tree scroll to object command won't work properly.
5562

5563
            for (auto parent : it->second) {
5564
                if (getOwnerDocument()->populateObject(parent))
5565
                    return false;
5566
            }
5567
        }
5568
    }
5569
    return true;
5570
}
5571

5572
bool DocumentObjectItem::isLink() const {
5573
    auto obj = object()->getObject();
5574
    auto linked = obj->getLinkedObject(false);
5575
    return linked && obj != linked;
5576
}
5577

5578
bool DocumentObjectItem::isLinkFinal() const {
5579
    auto obj = object()->getObject();
5580
    auto linked = obj->getLinkedObject(false);
5581
    return linked && linked == linked->getLinkedObject(true);
5582
}
5583

5584

5585
bool DocumentObjectItem::isParentLink() const {
5586
    auto pi = getParentItem();
5587
    return pi && pi->isLink();
5588
}
5589

5590
enum GroupType {
5591
    NotGroup = 0,
5592
    LinkGroup = 1,
5593
    PartGroup = 2,
5594
    SuperGroup = 3, //reversed for future
5595
};
5596

5597
int DocumentObjectItem::isGroup() const {
5598
    auto obj = object()->getObject();
5599
    auto linked = obj->getLinkedObject(true);
5600
    if (linked && linked->hasExtension(
5601
        App::GeoFeatureGroupExtension::getExtensionClassTypeId()))
5602
        return PartGroup;
5603
    if (obj->hasChildElement())
5604
        return LinkGroup;
5605
    if (obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(), false)) {
5606
        for (auto parent = getParentItem(); parent; parent = parent->getParentItem()) {
5607
            auto pobj = parent->object()->getObject();
5608
            if (pobj->hasExtension(App::GroupExtension::getExtensionClassTypeId(), false))
5609
                continue;
5610
            if (pobj->isElementVisible(obj->getNameInDocument()) >= 0)
5611
                return LinkGroup;
5612
        }
5613
    }
5614
    return NotGroup;
5615
}
5616

5617
bool DocumentItem::isObjectShowable(App::DocumentObject* obj) {
5618
    auto itParents = _ParentMap.find(obj);
5619
    if (itParents == _ParentMap.end() || itParents->second.empty())
5620
        return true;
5621
    bool showable = true;
5622
    for (auto parent : itParents->second) {
5623
        if (parent->getDocument() != obj->getDocument())
5624
            continue;
5625
        if (!parent->hasChildElement()
5626
            && parent->getLinkedObject(false) == parent)
5627
            return true;
5628
        showable = false;
5629
    }
5630
    return showable;
5631
}
5632

5633
int DocumentObjectItem::isParentGroup() const {
5634
    auto pi = getParentItem();
5635
    return pi ? pi->isGroup() : 0;
5636
}
5637

5638
DocumentObjectItem* DocumentObjectItem::getParentItem() const {
5639
    if (parent()->type() != TreeWidget::ObjectType)
5640
        return nullptr;
5641
    return static_cast<DocumentObjectItem*>(parent());
5642
}
5643

5644
DocumentObjectItem* DocumentObjectItem::getNextSibling() const
5645
{
5646
    QTreeWidgetItem* parent = this->parent();
5647
    if (parent) {
5648
        int index = parent->indexOfChild(const_cast<DocumentObjectItem*>(this));
5649
        if (index >= 0) {
5650
            while (++index < parent->childCount()) {
5651
                QTreeWidgetItem* sibling = parent->child(index);
5652
                if (sibling->type() == TreeWidget::ObjectType) {
5653
                    return static_cast<DocumentObjectItem*>(sibling);
5654
                }
5655
            }
5656
        }
5657
    }
5658

5659
    return nullptr;
5660
}
5661

5662
DocumentObjectItem* DocumentObjectItem::getPreviousSibling() const
5663
{
5664
    QTreeWidgetItem* parent = this->parent();
5665
    if (parent) {
5666
        int index = parent->indexOfChild(const_cast<DocumentObjectItem*>(this));
5667
        while (index > 0) {
5668
            QTreeWidgetItem* sibling = parent->child(--index);
5669
            if (sibling->type() == TreeWidget::ObjectType) {
5670
                return static_cast<DocumentObjectItem*>(sibling);
5671
            }
5672
        }
5673
    }
5674

5675
    return nullptr;
5676
}
5677

5678
const char* DocumentObjectItem::getName() const {
5679
    const char* name = object()->getObject()->getNameInDocument();
5680
    return name ? name : "";
5681
}
5682

5683
int DocumentObjectItem::getSubName(std::ostringstream& str, App::DocumentObject*& topParent) const
5684
{
5685
    auto parent = getParentItem();
5686
    if (!parent)
5687
        return NotGroup;
5688
    int ret = parent->getSubName(str, topParent);
5689
    if (ret != SuperGroup) {
5690
        int group = parent->isGroup();
5691
        if (group == NotGroup) {
5692
            if (ret != PartGroup) {
5693
                // Handle this situation,
5694
                //
5695
                // LinkGroup
5696
                //    |--PartExtrude
5697
                //           |--Sketch
5698
                //
5699
                // This function traverse from top down, so, when seeing a
5700
                // non-group object 'PartExtrude', its following children should
5701
                // not be grouped, so must reset any previous parents here.
5702
                topParent = nullptr;
5703
                str.str(""); //reset the current subname
5704
                return NotGroup;
5705
            }
5706
            group = PartGroup;
5707
        }
5708
        ret = group;
5709
    }
5710

5711
    auto obj = parent->object()->getObject();
5712
    if (!obj || !obj->isAttachedToDocument()) {
5713
        topParent = nullptr;
5714
        str.str("");
5715
        return NotGroup;
5716
    }
5717
    if (!topParent)
5718
        topParent = obj;
5719
    else if (!obj->redirectSubName(str, topParent, object()->getObject()))
5720
        str << obj->getNameInDocument() << '.';
5721
    return ret;
5722
}
5723

5724
App::DocumentObject* DocumentObjectItem::getFullSubName(
5725
    std::ostringstream& str, DocumentObjectItem* parent) const
5726
{
5727
    auto pi = getParentItem();
5728
    if (this == parent || !pi || (!parent && !pi->isGroup()))
5729
        return object()->getObject();
5730

5731
    auto ret = pi->getFullSubName(str, parent);
5732
    str << getName() << '.';
5733
    return ret;
5734
}
5735

5736
App::DocumentObject* DocumentObjectItem::getRelativeParent(
5737
    std::ostringstream& str, DocumentObjectItem* cousin,
5738
    App::DocumentObject** topParent, std::string* topSubname) const
5739
{
5740
    std::ostringstream str2;
5741
    App::DocumentObject* top = nullptr, * top2 = nullptr;
5742
    getSubName(str, top);
5743
    if (topParent)
5744
        *topParent = top;
5745
    if (!top)
5746
        return nullptr;
5747
    if (topSubname)
5748
        *topSubname = str.str() + getName() + '.';
5749
    cousin->getSubName(str2, top2);
5750
    if (top != top2) {
5751
        str << getName() << '.';
5752
        return top;
5753
    }
5754

5755
    auto subname = str.str();
5756
    auto subname2 = str2.str();
5757
    const char* sub = subname.c_str();
5758
    const char* sub2 = subname2.c_str();
5759
    while (true) {
5760
        const char* dot = strchr(sub, '.');
5761
        if (!dot) {
5762
            str.str("");
5763
            return nullptr;
5764
        }
5765
        const char* dot2 = strchr(sub2, '.');
5766
        if (!dot2 || dot - sub != dot2 - sub2 || strncmp(sub, sub2, dot - sub) != 0) {
5767
            auto substr = subname.substr(0, dot - subname.c_str() + 1);
5768
            auto ret = top->getSubObject(substr.c_str());
5769
            if (!top) {
5770
                FC_ERR("invalid subname " << top->getFullName() << '.' << substr);
5771
                str.str("");
5772
                return nullptr;
5773
            }
5774
            str.str("");
5775
            str << dot + 1 << getName() << '.';
5776
            return ret;
5777
        }
5778
        sub = dot + 1;
5779
        sub2 = dot2 + 1;
5780
    }
5781
    str.str("");
5782
    return nullptr;
5783
}
5784

5785
void DocumentObjectItem::setCheckState(bool checked) {
5786
    if (isSelectionCheckBoxesEnabled())
5787
        QTreeWidgetItem::setCheckState(0, checked ? Qt::Checked : Qt::Unchecked);
5788
    else
5789
        setData(0, Qt::CheckStateRole, QVariant());
5790
}
5791

5792
DocumentItem* DocumentObjectItem::getParentDocument() const {
5793
    return getTree()->getDocumentItem(object()->getDocument());
5794
}
5795

5796
DocumentItem* DocumentObjectItem::getOwnerDocument() const {
5797
    return myOwner;
5798
}
5799

5800
TreeWidget* DocumentObjectItem::getTree() const {
5801
    return static_cast<TreeWidget*>(treeWidget());
5802
}
5803

5804
void DocumentObjectItem::getExpandedSnapshot(std::vector<bool>& snapshot) const
5805
{
5806
    snapshot.push_back(isExpanded());
5807

5808
    for (int i = 0; i < childCount(); ++i) {
5809
        static_cast<const DocumentObjectItem*>(child(i))->getExpandedSnapshot(snapshot);
5810
    }
5811
}
5812

5813
void DocumentObjectItem::applyExpandedSnapshot(const std::vector<bool>& snapshot, std::vector<bool>::const_iterator& from)
5814
{
5815
    setExpanded(*from++);
5816

5817
    for (int i = 0; i < childCount(); ++i) {
5818
        static_cast<DocumentObjectItem*>(child(i))->applyExpandedSnapshot(snapshot, from);
5819
    }
5820
}
5821

5822
#include "moc_Tree.cpp"
5823

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

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

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

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