1
/***************************************************************************
2
* Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de> *
4
* This file is part of the FreeCAD CAx development system. *
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. *
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. *
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 *
21
***************************************************************************/
24
#include "PreCompiled.h"
28
# include <QActionGroup>
29
# include <QApplication>
30
# include <QContextMenuEvent>
32
# include <QHeaderView>
34
# include <QMessageBox>
40
# include <QVBoxLayout>
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>
50
#include <App/Document.h>
51
#include <App/DocumentObjectGroup.h>
52
#include <App/AutoTransaction.h>
53
#include <App/GeoFeatureGroupExtension.h>
57
#include "BitmapFactory.h"
60
#include "ExpressionCompleter.h"
62
#include "MainWindow.h"
63
#include "MenuManager.h"
64
#include "TreeParams.h"
65
#include "View3DInventor.h"
66
#include "ViewProviderDocumentObject.h"
71
FC_LOG_LEVEL_INIT("Tree", false, true, true)
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)
82
namespace sp = std::placeholders;
84
/////////////////////////////////////////////////////////////////////////////////
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;
96
static bool isVisibilityIconEnabled() {
97
return TreeParams::getVisibilityIcon();
100
static bool isOnlyNameColumnDisplayed() {
101
return TreeParams::getHideInternalNames()
102
&& TreeParams::getHideColumn();
105
static bool isSelectionCheckBoxesEnabled() {
106
return TreeParams::getCheckBoxesSelection();
109
void TreeParams::onItemBackgroundChanged()
111
if (getItemBackground()) {
113
color.setPackedValue(getItemBackground());
115
col.setRedF(color.r);
116
col.setGreenF(color.g);
117
col.setBlueF(color.b);
118
col.setAlphaF(color.a);
119
_TreeItemBackground = QBrush(col);
121
_TreeItemBackground = QBrush();
125
//////////////////////////////////////////////////////////////////////////////////////
127
#define DEFINE_STATS \
128
DEFINE_STAT(testStatus1) \
129
DEFINE_STAT(testStatus2) \
130
DEFINE_STAT(testStatus3) \
131
DEFINE_STAT(getIcon) \
132
DEFINE_STAT(setIcon) \
134
#define DEFINE_STAT(_name) \
135
FC_DURATION_DECLARE(_name);\
142
#define DEFINE_STAT(_name) \
143
FC_DURATION_INIT(_name);\
151
#define DEFINE_STAT(_name) FC_DURATION_MSG(_name, #_name " count: " << _name##_count);
156
#define DEFINE_STAT(_name) \
157
void time_##_name(FC_TIME_POINT &t) {\
159
FC_DURATION_PLUS(_name,t);\
165
//static Stats _Stats;
171
explicit TimingInfo(FC_DURATION& d)
182
FC_DURATION_PLUS(d, t);
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();
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);
208
// ---------------------------------------------------------------------------
210
using DocumentObjectItems = std::set<DocumentObjectItem*>;
212
class Gui::DocumentObjectData {
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;
225
std::string internalName;
227
using Connection = boost::signals2::scoped_connection;
229
Connection connectIcon;
230
Connection connectTool;
231
Connection connectStat;
232
Connection connectHl;
234
DocumentObjectData(DocumentItem* docItem, ViewProviderDocumentObject* vpd)
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));
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();
257
void insertItem(DocumentObjectItem* item)
263
void removeItem(DocumentObjectItem* item)
265
auto it = items.find(item);
266
if (it == items.end()) {
275
const char* getTreeName() const {
276
return docItem->getTreeName();
279
void updateChildren(DocumentObjectDataPtr other) {
280
children = other->children;
281
childSet = other->childSet;
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);
293
if (child && child->isAttachedToDocument()) {
294
if (!newSet.insert(child).second) {
295
TREE_WARN("duplicate child item " << obj->getFullName()
296
<< '.' << child->getNameInDocument());
298
else if (!childSet.erase(child)) {
299
// this means new child detected
301
if (child->getDocument() == obj->getDocument() &&
302
child->getDocument() == docItem->document()->getDocument())
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)
315
childVp->setShowable(showable);
321
for (auto child : childSet) {
322
if (newSet.find(child) == newSet.end()) {
323
// this means old child removed
325
auto mapIt = docItem->_ParentMap.find(child);
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);
332
auto childVp = docItem->getViewProvider(child);
333
if (childVp && child->getDocument() == obj->getDocument())
334
childVp->setShowable(docItem->isObjectShowable(child));
338
// We still need to check the order of the children
339
updated = updated || children != newChildren;
340
children.swap(newChildren);
341
childSet.swap(newSet);
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));
353
void testStatus(bool resetStatus = false) {
355
for (auto item : items)
356
item->testStatus(resetStatus, icon, icon2);
359
void slotChangeIcon() {
363
void slotChangeToolTip(const QString& tip) {
364
for (auto item : items)
365
item->setToolTip(0, tip);
368
void slotChangeStatusTip(const QString& tip) {
369
for (auto item : items)
370
item->setStatusTip(0, tip);
373
void slotChangeHighlight(bool set, Gui::HighlightMode mode) {
374
for (auto item : items)
375
item->setHighlight(set, mode);
379
// ---------------------------------------------------------------------------
381
class DocumentItem::ExpandInfo :
382
public std::unordered_map<std::string, DocumentItem::ExpandInfoPtr>
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"))
393
entry.reset(new ExpandInfo);
394
entry->restore(reader);
396
reader.readEndElement("Expand", level - 1);
400
// ---------------------------------------------------------------------------
404
* TreeWidget item delegate for editing
406
class TreeWidgetItemDelegate: public QStyledItemDelegate {
407
typedef QStyledItemDelegate inherited;
409
// Beware, big scary hack incoming!
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.
418
// More information: https://github.com/FreeCAD/FreeCAD/pull/13807
419
QTreeView *artificial;
421
QRect calculateItemRect(const QStyleOptionViewItem &option) const;
424
explicit TreeWidgetItemDelegate(QObject* parent=nullptr);
426
virtual QWidget* createEditor(QWidget *parent,
427
const QStyleOptionViewItem &, const QModelIndex &index) const;
429
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
431
virtual void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const;
433
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
438
TreeWidgetItemDelegate::TreeWidgetItemDelegate(QObject* parent)
439
: QStyledItemDelegate(parent)
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
447
QRect TreeWidgetItemDelegate::calculateItemRect(const QStyleOptionViewItem &option) const
449
auto tree = static_cast<TreeWidget*>(parent());
450
auto style = tree->style();
452
QRect rect = option.rect;
454
const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, artificial) + 1;
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()
463
if (TreeParams::getCheckBoxesSelection()) {
464
// another 2 margin for checkbox
466
+ style->pixelMetric(QStyle::PM_IndicatorWidth)
467
+ style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
470
if (width < rect.width()) {
471
rect.setWidth(width);
477
void TreeWidgetItemDelegate::paint(QPainter *painter,
478
const QStyleOptionViewItem &option, const QModelIndex &index) const
480
QStyleOptionViewItem opt = option;
481
initStyleOption(&opt, index);
483
auto tree = static_cast<TreeWidget*>(parent());
484
auto style = tree->style();
486
// If only the first column is shown, we'll trim the color background when
487
// rendering as transparent overlay.
488
bool trimColumnSize = isOnlyNameColumnDisplayed();
490
if (index.column() == 0) {
491
if (tree->testAttribute(Qt::WA_NoSystemBackground)
492
&& (trimColumnSize || (opt.backgroundBrush.style() == Qt::NoBrush
493
&& _TreeItemBackground.style() != Qt::NoBrush)))
495
QRect rect = calculateItemRect(option);
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);
504
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, artificial);
507
void TreeWidgetItemDelegate::initStyleOption(QStyleOptionViewItem *option,
508
const QModelIndex &index) const
510
inherited::initStyleOption(option, index);
512
auto tree = static_cast<TreeWidget*>(parent());
513
auto item = tree->itemFromIndex(index);
519
auto mousePos = option->widget->mapFromGlobal(QCursor::pos());
520
auto isHovered = option->rect.contains(mousePos);
522
option->state &= ~QStyle::State_MouseOver;
525
QSize size = option->icon.actualSize(QSize(0xffff, 0xffff));
527
if (size.height() > 0) {
528
option->decorationSize = QSize(
529
size.width() * TreeWidget::iconSize() / size.height(),
530
TreeWidget::iconSize()
534
if (isOnlyNameColumnDisplayed()) {
535
option->rect = calculateItemRect(*option);
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);
543
QWidget* TreeWidgetItemDelegate::createEditor(
544
QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
546
auto ti = static_cast<QTreeWidgetItem*>(index.internalPointer());
547
if (ti->type() != TreeWidget::ObjectType || index.column() > 1)
549
auto item = static_cast<DocumentObjectItem*>(ti);
550
App::DocumentObject* obj = item->object()->getObject();
551
auto& prop = index.column() ? obj->Label2 : obj->Label;
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());
559
if(TreeParams::getLabelExpression()) {
560
ExpLineEdit *le = new ExpLineEdit(parent);
561
le->setAutoApply(true);
563
le->bind(App::ObjectIdentifier(prop));
566
editor = new QLineEdit(parent);
568
editor->setReadOnly(prop.isReadOnly());
572
QSize TreeWidgetItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
574
QSize size = QStyledItemDelegate::sizeHint(option, index);
575
int spacing = std::max(0, static_cast<int>(TreeParams::getItemSpacing()));
576
size.setHeight(size.height() + spacing);
579
// ---------------------------------------------------------------------------
581
TreeWidget::TreeWidget(const char* name, QWidget* parent)
582
: QTreeWidget(parent), SelectionObserver(true, ResolveMode::NoResolve)
583
, contextItem(nullptr)
584
, searchObject(nullptr)
586
, searchContextDoc(nullptr)
587
, editingItem(nullptr)
588
, currentDocItem(nullptr)
591
Instances.insert(this);
592
if (!_LastSelectedTreeWidget)
593
_LastSelectedTreeWidget = this;
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);
602
this->showHiddenAction = new QAction(this);
603
this->showHiddenAction->setCheckable(true);
604
connect(this->showHiddenAction, &QAction::triggered,
605
this, &TreeWidget::onShowHidden);
607
this->toggleVisibilityInTreeAction = new QAction(this);
608
connect(this->toggleVisibilityInTreeAction, &QAction::triggered,
609
this, &TreeWidget::onToggleVisibilityInTree);
611
this->createGroupAction = new QAction(this);
612
connect(this->createGroupAction, &QAction::triggered,
613
this, &TreeWidget::onCreateGroup);
615
this->relabelObjectAction = new QAction(this);
617
this->relabelObjectAction->setShortcut(Qt::Key_F2);
619
connect(this->relabelObjectAction, &QAction::triggered,
620
this, &TreeWidget::onRelabelObject);
622
this->finishEditingAction = new QAction(this);
623
connect(this->finishEditingAction, &QAction::triggered,
624
this, &TreeWidget::onFinishEditing);
626
this->selectDependentsAction = new QAction(this);
627
connect(this->selectDependentsAction, &QAction::triggered,
628
this, &TreeWidget::onSelectDependents);
630
this->closeDocAction = new QAction(this);
631
connect(this->closeDocAction, &QAction::triggered,
632
this, &TreeWidget::onCloseDoc);
634
this->reloadDocAction = new QAction(this);
635
connect(this->reloadDocAction, &QAction::triggered,
636
this, &TreeWidget::onReloadDoc);
638
this->skipRecomputeAction = new QAction(this);
639
this->skipRecomputeAction->setCheckable(true);
640
connect(this->skipRecomputeAction, &QAction::toggled,
641
this, &TreeWidget::onSkipRecompute);
643
this->allowPartialRecomputeAction = new QAction(this);
644
this->allowPartialRecomputeAction->setCheckable(true);
645
connect(this->allowPartialRecomputeAction, &QAction::toggled,
646
this, &TreeWidget::onAllowPartialRecompute);
648
this->markRecomputeAction = new QAction(this);
649
connect(this->markRecomputeAction, &QAction::triggered,
650
this, &TreeWidget::onMarkRecompute);
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);
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));
670
// Gui::Document::signalChangedObject informs the App::Document property
671
// change, not view provider's own property, which is what the signal below
673
connectChangedViewObj = Application::Instance->signalChangedObject.connect(
674
std::bind(&TreeWidget::slotChangedViewObject, this, sp::_1, sp::_2));
677
setupResizableColumn(this);
678
this->header()->setStretchLastSection(true);
679
QObject::connect(this->header(), &QHeaderView::sectionResized, [](int idx, int, int newSize) {
681
TreeParams::setColumnSize2(newSize);
683
TreeParams::setColumnSize3(newSize);
685
TreeParams::setColumnSize1(newSize);
688
// Add the first main label
689
this->rootItem = invisibleRootItem();
690
this->expandItem(this->rootItem);
691
this->setSelectionMode(QAbstractItemView::ExtendedSelection);
693
this->setMouseTracking(true); // needed for itemEntered() to work
696
this->preselectTimer = new QTimer(this);
697
this->preselectTimer->setSingleShot(true);
699
this->statusTimer = new QTimer(this);
700
this->statusTimer->setSingleShot(false);
702
this->selectTimer = new QTimer(this);
703
this->selectTimer->setSingleShot(true);
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();
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));
722
setColumnHidden(1, TreeParams::getHideColumn());
723
setColumnHidden(2, TreeParams::getHideInternalNames());
724
header()->setVisible(!TreeParams::getHideColumn() || !TreeParams::getHideInternalNames());
727
TreeWidget::~TreeWidget()
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;
741
const char* TreeWidget::getTreeName() const {
742
return myName.c_str();
745
// reimpelement to select only objects in the active document
746
void TreeWidget::selectAll() {
747
auto gdoc = Application::Instance->getDocument(
748
App::GetApplication().getActiveDocument());
751
auto itDoc = DocumentMap.find(gdoc);
752
if (itDoc == DocumentMap.end())
754
if (TreeParams::getRecordSelection())
755
Gui::Selection().selStackPush();
756
Gui::Selection().clearSelection();
757
Gui::Selection().setSelection(gdoc->getDocument()->getName(), gdoc->getDocument()->getObjects());
760
bool TreeWidget::isObjectShowable(App::DocumentObject* obj) {
761
if (!obj || !obj->isAttachedToDocument())
763
Gui::Document* doc = Application::Instance->getDocument(obj->getDocument());
766
if (Instances.empty())
768
auto tree = *Instances.begin();
769
auto it = tree->DocumentMap.find(doc);
770
if (it != tree->DocumentMap.end())
771
return it->second->isObjectShowable(obj);
775
static bool _DisableCheckTopParent;
777
void TreeWidget::checkTopParent(App::DocumentObject*& obj, std::string& subname) {
778
if (_DisableCheckTopParent)
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);
789
auto parent = it->second->getTopParent(obj, subname);
796
void TreeWidget::resetItemSearch() {
799
auto it = ObjectTable.find(searchObject);
800
if (it != ObjectTable.end()) {
801
for (auto& data : it->second) {
804
for (auto item : data->items)
805
static_cast<DocumentObjectItem*>(item)->restoreBackground();
808
searchObject = nullptr;
811
void TreeWidget::startItemSearch(QLineEdit* edit) {
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();
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();
827
searchDoc = Application::Instance->activeDocument();
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();
836
static_cast<ExpressionLineEdit*>(edit)->setDocumentObject(obj);
839
void TreeWidget::itemSearch(const QString& text, bool select) {
842
auto docItem = getDocumentItem(searchDoc);
844
docItem = getDocumentItem(Application::Instance->activeDocument());
846
FC_TRACE("item search no document");
852
auto doc = docItem->document()->getDocument();
853
const auto& objs = doc->getObjects();
855
FC_TRACE("item search no objects");
858
std::string txt(text.toUtf8().constData());
862
if (txt.find("<<") == std::string::npos) {
863
auto pos = txt.find('.');
864
if (pos == std::string::npos)
866
else if (pos != txt.size() - 1) {
867
txt.insert(pos + 1, "<<");
868
if (txt.back() != '.')
873
else if (txt.back() != '.')
876
auto path = App::ObjectIdentifier::parse(objs.front(), txt);
877
if (path.getPropertyName() != "_self") {
878
FC_TRACE("Object " << txt << " not found in " << doc->getName());
881
auto obj = path.getDocumentObject();
883
FC_TRACE("Object " << txt << " not found in " << doc->getName());
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);
894
docItem = it->second;
895
doc = docItem->document()->getDocument();
900
parent = docItem->getTopParent(obj, subname);
902
if (docItem->document()->getDocument() == obj->getDocument()) {
903
// this shouldn't happen
904
FC_LOG("Object " << txt << " not found in " << doc->getName());
907
auto it = DocumentMap.find(Application::Instance->getDocument(obj->getDocument()));
908
if (it == DocumentMap.end())
910
docItem = it->second;
911
parent = docItem->getTopParent(obj, subname);
915
auto item = docItem->findItemByObject(true, obj, subname.c_str());
917
FC_TRACE("item " << txt << " not found in " << doc->getName());
921
Selection().setPreselect(obj->getDocument()->getName(),
922
obj->getNameInDocument(), subname.c_str(), 0, 0, 0,
923
SelectionChanges::MsgSource::TreeView);
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();
932
searchObject = item->object()->getObject();
933
item->setBackground(0, QColor(255, 255, 0, 100));
935
FC_TRACE("found item " << txt);
939
FC_TRACE("item " << txt << " search exception in " << doc->getName());
943
Gui::Document* TreeWidget::selectedDocument() {
944
for (auto tree : Instances) {
945
if (!tree->isVisible())
947
auto sels = tree->selectedItems();
948
if (sels.size() == 1 && sels[0]->type() == DocumentType)
949
return static_cast<DocumentItem*>(sels[0])->document();
954
void TreeWidget::updateStatus(bool delay) {
955
for (auto tree : Instances)
956
tree->_updateStatus(delay);
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())) {
967
if (!ChangedObjects.empty() || !NewObjects.empty())
971
int timeout = TreeParams::getStatusTimeout();
974
statusTimer->start(timeout);
977
void TreeWidget::contextMenuEvent(QContextMenuEvent* e)
979
// ask workbenches and view provider, ...
981
Gui::Application::Instance->setupContextMenu("Tree", &view);
983
view << "Std_Properties" << "Separator" << "Std_Expressions";
984
Workbench::createLinkMenu(&view);
990
QActionGroup subMenuGroup(&subMenu);
991
subMenuGroup.setExclusive(true);
992
connect(&subMenuGroup, &QActionGroup::triggered,
993
this, &TreeWidget::onActivateDocument);
994
MenuManager::getInstance()->setupContextMenu(&view, contextMenu);
996
// get the current item
997
this->contextItem = itemAt(e->pos());
999
if (this->contextItem && this->contextItem->type() == DocumentType) {
1000
auto docitem = static_cast<DocumentItem*>(this->contextItem);
1001
App::Document* doc = docitem->document()->getDocument();
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);
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);
1014
for (auto d : doc->getDependentDocuments()) {
1015
if (d->testStatus(App::Document::PartialDoc)) {
1016
contextMenu.addAction(this->reloadDocAction);
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);
1029
contextMenu.addSeparator();
1031
else if (this->contextItem && this->contextItem->type() == ObjectType) {
1032
auto objitem = static_cast<DocumentObjectItem*>
1033
(this->contextItem);
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;
1047
showHiddenAction->setChecked(doc->ShowHidden.getValue());
1048
contextMenu.addAction(this->showHiddenAction);
1049
contextMenu.addAction(this->toggleVisibilityInTreeAction);
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);
1059
contextMenu.addSeparator();
1060
contextMenu.addAction(this->markRecomputeAction);
1061
contextMenu.addAction(this->recomputeObjectAction);
1062
contextMenu.addSeparator();
1064
// relabeling is only possible for a single selected document
1065
if (SelectedObjectsList.size() == 1)
1066
contextMenu.addAction(this->relabelObjectAction);
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);
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;
1105
active->setChecked(true);
1106
subMenu.addActions(subMenuGroup.actions());
1109
// add a submenu to present the settings of the tree.
1111
settingsMenu.setTitle(tr("Tree settings"));
1112
contextMenu.addSeparator();
1113
contextMenu.addMenu(&settingsMenu);
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);
1120
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
1121
action->setChecked(!hGrp->GetBool("HideColumn", true));
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());
1132
internalNameAction->setStatusTip(tr("Show an internal name column for items."));
1133
internalNameAction->setCheckable(true);
1135
internalNameAction->setChecked(!hGrp->GetBool("HideInternalNames", true));
1137
settingsMenu.addAction(internalNameAction);
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());
1146
if (contextMenu.actions().count() > 0) {
1148
contextMenu.exec(QCursor::pos());
1150
catch (Base::Exception& e) {
1151
e.ReportException();
1153
catch (std::exception& e) {
1154
FC_ERR("C++ exception: " << e.what());
1157
FC_ERR("Unknown exception");
1159
contextItem = nullptr;
1163
void TreeWidget::hideEvent(QHideEvent* ev) {
1164
QTreeWidget::hideEvent(ev);
1167
void TreeWidget::showEvent(QShowEvent* ev) {
1168
QTreeWidget::showEvent(ev);
1171
void TreeWidget::onCreateGroup()
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());
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()),
1193
Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8());
1197
void TreeWidget::onRelabelObject()
1199
QTreeWidgetItem* item = currentItem();
1204
void TreeWidget::onStartEditing()
1206
auto action = qobject_cast<QAction*>(sender());
1208
if (this->contextItem && this->contextItem->type() == ObjectType) {
1209
auto objitem = static_cast<DocumentObjectItem*>
1210
(this->contextItem);
1211
int edit = action->data().toInt();
1213
App::DocumentObject* obj = objitem->object()->getObject();
1214
if (!obj || !obj->isAttachedToDocument())
1216
auto doc = const_cast<Document*>(objitem->getOwnerDocument()->document());
1217
MDIView* view = doc->getActiveView();
1218
if (view) getMainWindow()->setActiveWindow(view);
1220
editingItem = objitem;
1221
if (!doc->setEdit(objitem->object(), edit))
1222
editingItem = nullptr;
1227
void TreeWidget::onFinishEditing()
1229
if (this->contextItem && this->contextItem->type() == ObjectType) {
1230
auto objitem = static_cast<DocumentObjectItem*>
1231
(this->contextItem);
1232
App::DocumentObject* obj = objitem->object()->getObject();
1235
Gui::Document* doc = Gui::Application::Instance->getDocument(obj->getDocument());
1236
doc->commitCommand();
1238
doc->getDocument()->recompute();
1242
// check if selection has dependent objects
1243
bool TreeWidget::CheckForDependents()
1245
// if the selected object is a document
1246
if (this->contextItem && this->contextItem->type() == DocumentType) {
1249
// it can be an object
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();
1257
auto subObjectList = obj->getOutList();
1258
if (!subObjectList.empty())
1267
// adds an App::DocumentObject* and its dependent objects to the selection
1268
void TreeWidget::addDependentToSelection(App::Document* doc, App::DocumentObject* docObject)
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));
1279
// add dependents of the selected tree object to selection
1280
void TreeWidget::onSelectDependents()
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.
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());
1294
// it can be an object
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);
1309
void TreeWidget::onSkipRecompute(bool on)
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);
1319
void TreeWidget::onAllowPartialRecompute(bool on)
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);
1329
void TreeWidget::onMarkRecompute()
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();
1339
// mark all selected objects
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();
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();
1364
App::AutoTransaction committer("Recompute object");
1365
objs.front()->getDocument()->recompute(objs, true);
1369
DocumentItem* TreeWidget::getDocumentItem(const Gui::Document* doc) const {
1370
auto it = DocumentMap.find(doc);
1371
if (it != DocumentMap.end())
1376
void TreeWidget::selectAllInstances(const ViewProviderDocumentObject& vpd) {
1377
if (!isSelectionAttached())
1380
if (selectTimer->isActive())
1383
_updateStatus(false);
1385
for (const auto& v : DocumentMap)
1386
v.second->selectAllInstances(vpd);
1389
static int &treeIconSize()
1391
static int _treeIconSize = -1;
1393
if (_treeIconSize < 0)
1394
_treeIconSize = TreeParams::getIconSize();
1395
return _treeIconSize;
1398
int TreeWidget::iconHeight() const
1400
return treeIconSize();
1403
void TreeWidget::setIconHeight(int height)
1405
if (treeIconSize() == height)
1408
treeIconSize() = height;
1409
if (treeIconSize() <= 0)
1410
treeIconSize() = std::max(10, iconSize());
1412
for(auto tree : Instances)
1413
tree->setIconSize(QSize(treeIconSize(), treeIconSize()));
1416
int TreeWidget::iconSize() {
1417
static int defaultSize;
1418
if (defaultSize == 0) {
1419
auto tree = instance();
1421
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1422
defaultSize = tree->viewOptions().decorationSize.width();
1424
QStyleOptionViewItem opt;
1425
tree->initViewItemOption(&opt);
1426
defaultSize = opt.decorationSize.width();
1430
defaultSize = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
1433
if (treeIconSize() > 0)
1434
return std::max(10, treeIconSize());
1438
TreeWidget* TreeWidget::instance() {
1439
auto res = _LastSelectedTreeWidget;
1440
if (res && res->isVisible())
1442
for (auto inst : Instances) {
1443
if (!res) res = inst;
1444
if (inst->isVisible())
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());
1471
std::vector<TreeWidget::SelInfo> TreeWidget::getSelection(App::Document* doc)
1473
std::vector<SelInfo> ret;
1475
TreeWidget* tree = instance();
1476
if (!tree || !tree->isSelectionAttached()) {
1477
for (auto pTree : Instances)
1478
if (pTree->isSelectionAttached()) {
1486
if (tree->selectTimer->isActive())
1487
tree->onSelectTimer();
1489
tree->_updateStatus(false);
1491
const auto items = tree->selectedItems();
1492
for (auto ti : items) {
1493
if (ti->type() != ObjectType)
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");
1502
if (doc && obj->getDocument() != doc) {
1503
FC_LOG("skip objects not from current document");
1506
ViewProviderDocumentObject* parentVp = nullptr;
1507
auto parent = item->getParentItem();
1509
parentVp = parent->object();
1510
if (!parentVp->getObject()->isAttachedToDocument()) {
1511
FC_WARN("skip '" << obj->getFullName() << "' with invalid parent");
1516
auto& sel = ret.back();
1517
sel.topParent = nullptr;
1518
std::ostringstream ss;
1519
item->getSubName(ss, sel.topParent);
1521
sel.topParent = obj;
1523
ss << obj->getNameInDocument() << '.';
1524
sel.subname = ss.str();
1525
sel.parentVp = parentVp;
1531
void TreeWidget::selectAllLinks(App::DocumentObject* obj) {
1532
if (!isSelectionAttached())
1535
if (!obj || !obj->isAttachedToDocument()) {
1536
TREE_ERR("invalid object");
1540
if (selectTimer->isActive())
1543
_updateStatus(false);
1545
for (auto link : App::GetApplication().getLinksTo(obj, App::GetLinkRecursive))
1547
if (!link || !link->isAttachedToDocument()) {
1548
TREE_ERR("invalid linked object");
1551
auto vp = dynamic_cast<ViewProviderDocumentObject*>(
1552
Application::Instance->getViewProvider(link));
1554
TREE_ERR("invalid view provider of the linked object");
1557
for (auto& v : DocumentMap)
1558
v.second->selectAllInstances(*vp);
1562
void TreeWidget::onSearchObjects()
1564
Q_EMIT emitSearchObjects();
1567
void TreeWidget::onActivateDocument(QAction* active)
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());
1576
Qt::DropActions TreeWidget::supportedDropActions() const
1578
return Qt::LinkAction | Qt::CopyAction | Qt::MoveAction;
1581
bool TreeWidget::event(QEvent* e)
1583
return QTreeWidget::event(e);
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);
1609
bool isTreeViewDragging()
1611
return _DraggingActive;
1616
void TreeWidget::keyPressEvent(QKeyEvent* event)
1618
if (event->matches(QKeySequence::Find)) {
1623
else if (event->modifiers() == Qt::AltModifier) {
1624
if (event->key() == Qt::Key_Left) {
1625
for (auto& item : selectedItems()) {
1626
item->setExpanded(false);
1631
else if (event->key() == Qt::Key_Right) {
1632
for (auto& item : selectedItems()) {
1633
item->setExpanded(true);
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);
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);
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()));
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()));
1675
QTreeWidget::keyPressEvent(event);
1678
void TreeWidget::mousePressEvent(QMouseEvent* event)
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);
1685
// Mouse position relative to viewport
1686
auto mousePos = event->pos();
1688
// Rect occupied by the item relative to viewport
1689
auto iconRect = visualItemRect(objitem);
1691
auto style = this->style();
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);
1701
int const margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
1702
iconRect.adjust(margin, 0, 0, 0);
1704
// We are interested in the first icon (visibility icon)
1705
iconRect.setWidth(iconSize());
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();
1712
App::DocumentObject* parent = nullptr;
1713
std::ostringstream subName;
1714
objitem->getSubName(subName, parent);
1716
// Try the ElementVisible API, if that is not supported toggle the Visibility property
1719
visible = parent->isElementVisible(objname);
1721
if (parent && visible >= 0) {
1722
parent->setElementVisible(objname, !visible);
1724
visible = obj->Visibility.getValue();
1725
obj->Visibility.setValue(!visible);
1731
QTreeWidget::mousePressEvent(event);
1734
void TreeWidget::mouseDoubleClickEvent(QMouseEvent* event)
1736
QTreeWidgetItem* item = itemAt(event->pos());
1741
if (item->type() == TreeWidget::DocumentType) {
1742
Gui::Document* doc = static_cast<DocumentItem*>(item)->document();
1745
if (doc->getDocument()->testStatus(App::Document::PartialDoc)) {
1750
if (!doc->setActiveView())
1751
doc->setActiveView(nullptr, View3DInventor::getClassTypeId());
1753
else if (item->type() == TreeWidget::ObjectType) {
1754
auto objitem = static_cast<DocumentObjectItem*>(item);
1755
ViewProviderDocumentObject* vp = objitem->object();
1757
objitem->getOwnerDocument()->document()->setActiveView(vp);
1758
auto manager = Application::Instance->macroManager();
1759
auto lines = manager->getLines();
1761
std::ostringstream ss;
1762
ss << Command::getObjectCmd(vp->getObject())
1763
<< ".ViewObject.doubleClicked()";
1765
const char* commandText = vp->getTransactionText();
1767
auto editDoc = Application::Instance->editDocument();
1768
App::AutoTransaction committer(commandText, true);
1770
if (!vp->doubleClicked())
1771
QTreeWidget::mouseDoubleClickEvent(event);
1772
else if (lines == manager->getLines())
1773
manager->addLine(MacroManager::Gui, ss.str().c_str());
1775
// If the double click starts an editing, let the transaction persist
1776
if (!editDoc && Application::Instance->editDocument())
1777
committer.setEnable(false);
1780
if (!vp->doubleClicked())
1781
QTreeWidget::mouseDoubleClickEvent(event);
1782
else if (lines == manager->getLines())
1783
manager->addLine(MacroManager::Gui, ss.str().c_str());
1787
catch (Base::Exception& e) {
1788
e.ReportException();
1790
catch (std::exception& e) {
1791
FC_ERR("C++ exception: " << e.what());
1794
FC_ERR("Unknown exception");
1798
void TreeWidget::startDragging() {
1799
if (state() != NoState)
1801
if (selectedItems().empty())
1804
setState(DraggingState);
1805
startDrag(model()->supportedDragActions());
1810
void TreeWidget::startDrag(Qt::DropActions supportedActions)
1812
Base::StateLocker guard(_DraggingActive);
1813
QTreeWidget::startDrag(supportedActions);
1814
if (_DragEventFilter) {
1815
_DragEventFilter = false;
1816
qApp->removeEventFilter(this);
1820
bool TreeWidget::dropMimeData(QTreeWidgetItem* parent, int index,
1821
const QMimeData* data, Qt::DropAction action)
1823
return QTreeWidget::dropMimeData(parent, index, data, action);
1826
void TreeWidget::dragEnterEvent(QDragEnterEvent* event)
1828
QTreeWidget::dragEnterEvent(event);
1831
void TreeWidget::dragLeaveEvent(QDragLeaveEvent* event)
1833
QTreeWidget::dragLeaveEvent(event);
1840
std::string parentDoc;
1842
std::string ownerDoc;
1844
std::string subname;
1847
std::string topSubname;
1848
std::vector<std::string> subs;
1849
bool dragging = false;
1855
std::string parentDoc;
1859
std::string topSubname;
1866
static std::vector<std::pair<DocumentObjectItem*, std::vector<std::string> > > filterItems(const QList<QTreeWidgetItem*>& sels, QTreeWidgetItem* targetItem)
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)
1873
// ignore child elements if the parent is selected
1874
if (sels.contains(ti->parent()))
1876
if (ti == targetItem)
1878
auto item = static_cast<DocumentObjectItem*>(ti);
1879
items.emplace_back();
1880
auto& info = items.back();
1882
const auto& subnames = item->getSubNames();
1883
info.second.insert(info.second.end(), subnames.begin(), subnames.end());
1888
static App::PropertyPlacement* getPlacement(const ItemInfo& info, const App::DocumentObject* obj, Base::Matrix4D& mat)
1890
App::PropertyPlacement* propPlacement = nullptr;
1891
if (!info.topObj.empty()) {
1892
auto doc = App::GetApplication().getDocument(info.topDoc.c_str());
1894
auto topObj = doc->getObject(info.topObj.c_str());
1896
auto sobj = topObj->getSubObject(info.topSubname.c_str(), nullptr, &mat);
1898
propPlacement = Base::freecad_dynamic_cast<App::PropertyPlacement>(
1899
obj->getPropertyByName("Placement"));
1905
propPlacement = Base::freecad_dynamic_cast<App::PropertyPlacement>(
1906
obj->getPropertyByName("Placement"));
1908
mat = propPlacement->getValue().toMatrix();
1911
return propPlacement;
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();
1920
return dragMoveEvent->position().toPoint();
1924
else if (auto* dropEvent = dynamic_cast<QDropEvent*>(event)) {
1925
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1926
return dropEvent->pos();
1928
return dropEvent->position().toPoint();
1932
// For unsupported event types or if casting fails
1933
return QPoint(-1, -1);
1936
Qt::DropAction getDropAction(int size, const int type)
1938
if (QApplication::keyboardModifiers() == Qt::ControlModifier) {
1939
return Qt::CopyAction;
1941
else if (QApplication::keyboardModifiers() == Qt::AltModifier
1942
&& (size == 1 || type == TreeWidget::DocumentType)) {
1943
return Qt::LinkAction;
1946
return Qt::MoveAction;
1951
void TreeWidget::dragMoveEvent(QDragMoveEvent* event)
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);
1962
QTreeWidget::dragMoveEvent(event);
1963
if (!event->isAccepted()) {
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.
1972
TargetItemInfo targetInfo = getTargetInfo(event);
1973
QTreeWidgetItem* targetItem = targetInfo.targetItem;
1979
auto items = selectedItems();
1981
auto da = getDropAction(items.size(), targetItem->type());
1982
event->setDropAction(da);
1984
if (targetItem->type() == TreeWidget::DocumentType) {
1985
leaveEvent(nullptr);
1987
else if (targetItem->type() == TreeWidget::ObjectType) {
1988
onItemEntered(targetItem);
1990
auto targetItemObj = static_cast<DocumentObjectItem*>(targetItem);
1991
Gui::ViewProviderDocumentObject* vp = targetItemObj->object();
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);
2001
targetItemObj = static_cast<DocumentObjectItem*>(targetInfo.targetItem);
2002
vp = targetItemObj->object();
2005
TREE_TRACE("cannot drop");
2010
if (da != Qt::LinkAction && !vp->canDropObjects()) {
2011
if (!(event->possibleActions() & Qt::LinkAction) || items.size() != 1) {
2012
TREE_TRACE("Cannot drop here");
2017
for (auto ti : items) {
2018
if (ti->type() != TreeWidget::ObjectType) {
2019
TREE_TRACE("cannot drop");
2023
auto item = static_cast<DocumentObjectItem*>(ti);
2025
auto obj = item->object()->getObject();
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)))
2032
TREE_TRACE("Cannot drag object");
2038
std::ostringstream str;
2039
auto owner = item->getRelativeParent(str, targetItemObj);
2040
auto subname = str.str();
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);
2050
catch (Base::Exception& e) {
2051
e.ReportException();
2054
catch (std::exception& e) {
2055
FC_ERR("C++ exception: " << e.what());
2059
FC_ERR("Unknown exception");
2064
leaveEvent(nullptr);
2069
TreeWidget::TargetItemInfo TreeWidget::getTargetInfo(QEvent* ev)
2071
TargetItemInfo targetInfo;
2073
QPoint pos = getPos(ev);
2074
if (pos == QPoint(-1, -1)) {
2075
return {}; // Return an empty struct
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()) {
2083
targetInfo.underMouseItem = targetInfo.targetItem;
2085
if (targetInfo.targetItem->type() == TreeWidget::ObjectType) {
2086
auto targetItemObj = static_cast<DocumentObjectItem*>(targetInfo.targetItem);
2087
targetInfo.targetDoc = targetItemObj->getOwnerDocument()->document()->getDocument();
2089
else if (targetInfo.targetItem->type() == TreeWidget::DocumentType) {
2090
auto targetDocItem = static_cast<DocumentItem*>(targetInfo.targetItem);
2091
targetInfo.targetDoc = targetDocItem->document()->getDocument();
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
2103
targetInfo.inBottomHalf = mouseY > itemMidPoint;
2104
targetInfo.inThresholdZone = ((mouseY < itemRect.top() + threshold) && !targetInfo.targetItem->isExpanded())
2105
|| (mouseY > itemRect.top() + itemRect.height() - threshold);
2109
bool TreeWidget::dropInDocument(QDropEvent* event, TargetItemInfo& targetInfo,
2110
std::vector<TreeWidget::ObjectItemSubname> items)
2113
auto da = event->dropAction();
2114
bool touched = false;
2116
std::vector<ItemInfo2> infos;
2117
infos.reserve(items.size());
2118
bool syncPlacement = TreeParams::getSyncPlacement();
2120
App::AutoTransaction committer(
2121
da == Qt::LinkAction ? "Link object" :
2122
da == Qt::CopyAction ? "Copy object" : "Move object");
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();
2130
bool allParentsOK = canDragFromParents(parentItem, obj, nullptr);
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() << "'");
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;
2144
infos.emplace_back();
2145
auto& info = infos.back();
2146
info.doc = obj->getDocument()->getName();
2147
info.obj = obj->getNameInDocument();
2149
auto parent = parentItem->object()->getObject();
2150
info.parentDoc = parent->getDocument()->getName();
2151
info.parent = parent->getNameInDocument();
2153
if (syncPlacement) {
2154
std::ostringstream ss;
2155
App::DocumentObject* topParent = nullptr;
2156
item->getSubName(ss, topParent);
2158
info.topDoc = topParent->getDocument()->getName();
2159
info.topObj = topParent->getNameInDocument();
2160
ss << obj->getNameInDocument() << '.';
2161
info.topSubname = ss.str();
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();
2171
auto manager = Application::Instance->macroManager();
2173
std::vector<App::DocumentObject*> droppedObjs;
2174
for (auto& info : infos) {
2175
auto doc = App::GetApplication().getDocument(info.doc.c_str());
2177
auto obj = doc->getObject(info.obj.c_str());
2178
auto vpc = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(obj));
2180
FC_WARN("Cannot find dragging object " << info.obj);
2185
App::PropertyPlacement* propPlacement = nullptr;
2186
if (syncPlacement) {
2187
if (!info.topObj.empty()) {
2188
auto doc = App::GetApplication().getDocument(info.topDoc.c_str());
2190
auto topObj = doc->getObject(info.topObj.c_str());
2192
auto sobj = topObj->getSubObject(info.topSubname.c_str(), nullptr, &mat);
2194
propPlacement = dynamic_cast<App::PropertyPlacement*>(obj->getPropertyByName("Placement"));
2200
propPlacement = dynamic_cast<App::PropertyPlacement*>(obj->getPropertyByName("Placement"));
2201
if (propPlacement) {
2202
mat = propPlacement->getValue().toMatrix();
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());
2214
FCMD_OBJ_CMD(link, "Label='" << obj->getLinkedObject(true)->Label.getValue() << "'");
2215
propPlacement = dynamic_cast<App::PropertyPlacement*>(link->getPropertyByName("Placement"));
2217
propPlacement->setValueIfChanged(Base::Placement(mat));
2218
droppedObjs.push_back(link);
2220
else if (!info.parent.empty()) {
2221
auto parentDoc = App::GetApplication().getDocument(info.parentDoc.c_str());
2223
FC_WARN("Canont find document " << info.parentDoc);
2226
auto parent = parentDoc->getObject(info.parent.c_str());
2227
auto vpp = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(parent));
2229
FC_WARN("Cannot find dragging object's parent " << info.parent);
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());
2241
//make sure it is not part of a geofeaturegroup anymore.
2242
//When this has happen we need to handle all removed
2244
auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(obj);
2246
FCMD_OBJ_CMD(grp, "removeObject(" << Command::getObjectCmd(obj) << ")");
2249
// check if the object has been deleted
2250
obj = doc->getObject(info.obj.c_str());
2251
if (!obj || !obj->isAttachedToDocument()) {
2254
droppedObjs.push_back(obj);
2255
if (propPlacement) {
2256
propPlacement->setValueIfChanged(Base::Placement(mat));
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();
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);
2277
// Moving a object from another document.
2278
res = targetInfo.targetDoc->moveObject(obj, true);
2281
propPlacement = dynamic_cast<App::PropertyPlacement*>( res->getPropertyByName("Placement"));
2282
if (propPlacement) {
2283
propPlacement->setValueIfChanged(Base::Placement(mat));
2285
droppedObjs.push_back(res);
2287
manager->addLine(MacroManager::App, ss.str().c_str());
2291
Base::FlagToggler<> guard(_DisableCheckTopParent);
2292
Selection().setSelection(targetInfo.targetDoc->getName(), droppedObjs);
2294
// If moved, then we sort objects properly.
2295
if (da == Qt::MoveAction) {
2296
sortDroppedObjects(targetInfo, droppedObjs);
2299
catch (const Base::Exception& e) {
2300
e.ReportException();
2303
catch (std::exception& e) {
2304
FC_ERR("C++ exception: " << e.what());
2308
FC_ERR("Unknown exception");
2309
errMsg = "Unknown exception";
2311
if (!errMsg.empty()) {
2312
committer.close(true);
2313
QMessageBox::critical(getMainWindow(), QObject::tr("Drag & drop failed"), QString::fromUtf8(errMsg.c_str()));
2319
bool TreeWidget::dropInObject(QDropEvent* event, TargetItemInfo& targetInfo,
2320
std::vector<TreeWidget::ObjectItemSubname> items)
2323
auto da = event->dropAction();
2324
bool touched = false;
2326
// add object to group
2327
auto targetItemObj = static_cast<DocumentObjectItem*>(targetInfo.targetItem);
2328
Gui::ViewProviderDocumentObject* vp = targetItemObj->object();
2330
if (!vp || !vp->getObject() || !vp->getObject()->isAttachedToDocument()) {
2331
TREE_TRACE("invalid object");
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);
2342
targetItemObj = static_cast<DocumentObjectItem*>(targetInfo.targetItem);
2343
vp = targetItemObj->object();
2345
if (!vp || !vp->getObject() || !vp->getObject()->isAttachedToDocument()) {
2346
TREE_TRACE("invalid object");
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
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();
2365
targetSubname << vp->getObject()->getNameInDocument() << '.';
2366
Selection().addSelection(targetParent->getDocument()->getName(), targetParent->getNameInDocument(), targetSubname.str().c_str());
2369
targetParent = targetObj;
2370
Selection().addSelection(targetParent->getDocument()->getName(), targetParent->getNameInDocument());
2374
App::AutoTransaction committer("Drop object");
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();
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();
2397
info.subname = str.str();
2398
info.doc = obj->getDocument()->getName();
2399
info.obj = obj->getNameInDocument();
2401
info.ownerDoc = owner->getDocument()->getName();
2402
info.owner = owner->getNameInDocument();
2405
info.subs.swap(v.second);
2407
// check if items can be dragged
2408
if (da == Qt::MoveAction && item->myOwner == targetItemObj->myOwner && vp->canDragAndDropObject(obj)) {
2409
auto parentItem = item->getParentItem();
2411
info.dragging = true;
2415
bool allParentsOK = canDragFromParents(parentItem, obj, targetObj);
2418
auto vpp = parentItem->object();
2419
info.dragging = true;
2420
info.parent = vpp->getObject()->getNameInDocument();
2421
info.parentDoc = vpp->getObject()->getDocument()->getName();
2424
committer.close(true);
2430
if (da != Qt::LinkAction
2431
&& !vp->canDropObjectEx(obj, owner, info.subname.c_str(), item->mySubs))
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");
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");
2445
da = Qt::LinkAction;
2451
std::set<App::DocumentObject*> inList;
2452
auto parentObj = targetObj;
2453
if (da == Qt::LinkAction && targetItemObj->getParentItem()) {
2454
parentObj = targetItemObj->getParentItem()->object()->getObject();
2456
inList = parentObj->getInListEx(true);
2457
inList.insert(parentObj);
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));
2466
FC_ERR("Cannot find drop target object " << target);
2470
auto doc = App::GetApplication().getDocument(info.doc.c_str());
2472
FC_WARN("Cannot find document " << info.doc);
2475
auto obj = doc->getObject(info.obj.c_str());
2476
auto vpc = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(obj));
2478
FC_WARN("Cannot find dragging object " << info.obj);
2482
ViewProviderDocumentObject* vpp = nullptr;
2483
if (da != Qt::LinkAction && !info.parentDoc.empty()) {
2484
auto parentDoc = App::GetApplication().getDocument(info.parentDoc.c_str());
2486
auto parent = parentDoc->getObject(info.parent.c_str());
2487
vpp = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(parent));
2490
FC_WARN("Cannot find dragging object's parent " << info.parent);
2495
App::DocumentObject* owner = nullptr;
2496
if (!info.ownerDoc.empty()) {
2497
auto ownerDoc = App::GetApplication().getDocument(info.ownerDoc.c_str());
2499
owner = ownerDoc->getObject(info.owner.c_str());
2501
FC_WARN("Cannot find dragging object's top parent " << info.owner);
2507
App::PropertyPlacement* propPlacement = nullptr;
2508
if (syncPlacement) {
2509
propPlacement = DropHandler::getPlacement(info, obj, mat);
2512
auto dropParent = targetParent;
2514
auto manager = Application::Instance->macroManager();
2515
std::ostringstream ss;
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());
2527
obj = doc->getObject(info.obj.c_str());
2528
if (!obj || !obj->isAttachedToDocument()) {
2529
FC_WARN("Dropping object deleted: " << info.doc << '#' << info.obj);
2534
if (da == Qt::MoveAction) {
2535
// Try to adjust relative links to avoid cyclic dependency, may
2536
// throw exception if failed
2538
ss << Command::getObjectCmd(obj) << ".adjustRelativeLinks("
2539
<< Command::getObjectCmd(targetObj) << ")";
2540
manager->addLine(MacroManager::Gui, ss.str().c_str());
2542
std::set<App::DocumentObject*> visited;
2543
if (obj->adjustRelativeLinks(inList, &visited)) {
2544
inList = parentObj->getInListEx(true);
2545
inList.insert(parentObj);
2547
// TODO: link adjustment and placement adjustment does
2548
// not work together at the moment.
2549
propPlacement = nullptr;
2553
if (inList.count(obj)) {
2554
FC_THROWM(Base::RuntimeError, "Dependency loop detected for " << obj->getFullName());
2558
std::string dropName;
2560
if (da == Qt::LinkAction) {
2561
auto parentItem = targetItemObj->getParentItem();
2563
ss << Command::getObjectCmd(
2564
parentItem->object()->getObject(), nullptr, ".replaceObject(", true)
2565
<< Command::getObjectCmd(targetObj) << ","
2566
<< Command::getObjectCmd(obj) << ")";
2568
std::ostringstream ss;
2570
dropParent = nullptr;
2571
parentItem->getSubName(ss, dropParent);
2573
ss << parentItem->object()->getObject()->getNameInDocument() << '.';
2575
dropParent = parentItem->object()->getObject();
2576
ss << obj->getNameInDocument() << '.';
2577
dropName = ss.str();
2580
TREE_WARN("ignore replace operation without parent");
2584
Gui::Command::runCommand(Gui::Command::App, ss.str().c_str());
2588
ss << Command::getObjectCmd(vp->getObject())
2589
<< ".ViewObject.dropObject(" << Command::getObjectCmd(obj);
2591
ss << "," << Command::getObjectCmd(owner)
2592
<< ",'" << subname << "',[";
2596
for (auto& sub : info.subs)
2597
ss << "'" << sub << "',";
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;
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);
2617
Base::Matrix4D newMat;
2618
auto sobj = dropParent->getSubObject(dropName.c_str(), nullptr, &newMat);
2620
FC_LOG("failed to find dropped object "
2621
<< dropParent->getFullName() << '.' << dropName);
2622
setSelection = false;
2626
if (da != Qt::CopyAction && propPlacement) {
2627
// try to adjust placement
2628
if ((info.dragging && sobj == obj) ||
2629
(!info.dragging && sobj->getLinkedObject(false) == obj))
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);
2642
droppedObjects.emplace_back(dropParent, dropName);
2643
draggedObjects.push_back(obj);
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());
2653
Selection().selStackPush();
2656
// If moved, then we sort objects properly.
2657
if (da == Qt::MoveAction && vp->acceptReorderingObjects()) {
2658
sortDroppedObjects(targetInfo, draggedObjects);
2661
catch (const Base::Exception& e) {
2662
e.ReportException();
2665
catch (std::exception& e) {
2666
FC_ERR("C++ exception: " << e.what());
2670
FC_ERR("Unknown exception");
2671
errMsg = "Unknown exception";
2673
if (!errMsg.empty()) {
2674
committer.close(true);
2675
QMessageBox::critical(getMainWindow(), QObject::tr("Drag & drop failed"),
2676
QString::fromUtf8(errMsg.c_str()));
2682
bool TreeWidget::canDragFromParents(DocumentObjectItem* parentItem, App::DocumentObject* obj, App::DocumentObject* target)
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;
2691
parentItem = parentItem->getParentItem();
2694
return allParentsOK;
2697
void TreeWidget::dropEvent(QDropEvent* event)
2699
//FIXME: This should actually be done inside dropMimeData
2701
TargetItemInfo targetInfo = getTargetInfo(event);
2702
if (!targetInfo.targetItem) {
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
2713
event->setDropAction(getDropAction(items.size(), targetInfo.targetItem->type()));
2715
bool touched = false;
2716
if (targetInfo.targetItem->type() == TreeWidget::ObjectType) {
2717
touched = dropInObject(event, targetInfo, items);
2719
else if (targetInfo.targetItem->type() == TreeWidget::DocumentType) {
2720
touched = dropInDocument(event, targetInfo, items);
2723
if (touched && TreeParams::getRecomputeOnDrop()) {
2724
targetInfo.targetDoc->recompute();
2726
if (touched && TreeParams::getSyncView()) {
2727
auto gdoc = Application::Instance->getDocument(targetInfo.targetDoc);
2729
gdoc->setActiveView();
2733
void TreeWidget::sortDroppedObjects(TargetItemInfo& targetInfo, std::vector<App::DocumentObject*> draggedObjects)
2735
if (targetInfo.targetItem == targetInfo.underMouseItem) {
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;
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);
2750
for (auto* draggedObj : draggedObjects) {
2751
sortedObjList.push_back(draggedObj);
2754
if (!targetInfo.inBottomHalf) {
2755
sortedObjList.push_back(obj);
2759
if (std::find(draggedObjects.begin(), draggedObjects.end(), obj) == draggedObjects.end()) {
2760
sortedObjList.push_back(obj);
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();
2771
auto propGroup = Base::freecad_dynamic_cast<App::PropertyLinkList>(targetObj->getPropertyByName("Group"));
2776
objList = propGroup->getValue();
2777
sortIntoList(objList); // Move dropped objects to correct position
2778
propGroup->setValue(sortedObjList);
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));
2789
return vpA->getTreeRank() < vpB->getTreeRank();
2791
return false; // Keep the original order if either vpA or vpB is nullptr
2794
// Then we move dropped objects to their correct position
2795
sortIntoList(objList);
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]));
2803
// Lastly we refresh the tree
2804
static_cast<DocumentItem*>(targetInfo.targetItem)->sortObjectItems();
2808
void TreeWidget::drawRow(QPainter* painter, const QStyleOptionViewItem& options, const QModelIndex& index) const
2810
QTreeWidget::drawRow(painter, options, index);
2813
void TreeWidget::slotNewDocument(const Gui::Document& Doc, bool isMainDoc)
2815
if (Doc.getDocument()->testStatus(App::Document::TempDoc))
2817
auto item = new DocumentItem(&Doc, this->rootItem);
2819
this->expandItem(item);
2820
item->setIcon(0, *documentPixmap);
2821
item->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue()));
2822
DocumentMap[&Doc] = item;
2826
void TreeWidget::onReloadDoc() {
2827
if (!this->contextItem || this->contextItem->type() != DocumentType)
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());
2842
void TreeWidget::onCloseDoc()
2844
if (!this->contextItem || this->contextItem->type() != DocumentType)
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());
2853
catch (const Base::Exception& e) {
2854
e.ReportException();
2856
catch (std::exception& e) {
2857
FC_ERR("C++ exception: " << e.what());
2860
FC_ERR("Unknown exception");
2864
void TreeWidget::slotRenameDocument(const Gui::Document& Doc)
2870
void TreeWidget::slotChangedViewObject(const Gui::ViewProvider& vp, const App::Property& prop)
2872
if (!App::GetApplication().isRestoring()
2873
&& vp.isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()))
2875
const auto& vpd = static_cast<const ViewProviderDocumentObject&>(vp);
2876
if (&prop == &vpd.ShowInTree) {
2877
ChangedObjects.emplace(vpd.getObject(), 0);
2883
void TreeWidget::slotTouchedObject(const App::DocumentObject& obj) {
2884
ChangedObjects.emplace(const_cast<App::DocumentObject*>(&obj), 0);
2888
void TreeWidget::slotShowHidden(const Gui::Document& Doc)
2890
auto it = DocumentMap.find(&Doc);
2891
if (it != DocumentMap.end())
2892
it->second->updateItemsVisibility(it->second, it->second->showHidden());
2895
void TreeWidget::slotRelabelDocument(const Gui::Document& Doc)
2897
auto it = DocumentMap.find(&Doc);
2898
if (it != DocumentMap.end()) {
2899
it->second->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue()));
2903
void TreeWidget::slotActiveDocument(const Gui::Document& Doc)
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)
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);
2918
// this must be done as last step
2919
it->second->setFont(0, f);
2923
struct UpdateDisabler {
2926
bool visible{false};
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)
2936
focus = widget.hasFocus();
2937
visible = widget.isVisible();
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.
2943
// widget.setUpdatesEnabled(false);
2945
widget.setVisible(false);
2949
if (blocked <= 0 || --blocked != 0)
2953
widget.setVisible(true);
2960
void TreeWidget::onUpdateStatus()
2962
if (this->state() == DraggingState || App::GetApplication().isRestoring()) {
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.
2979
FC_LOG("begin update status");
2981
UpdateDisabler disabler(*this, updateBlocked);
2983
std::vector<App::DocumentObject*> errors;
2985
// Checking for new objects
2986
for (auto& v : NewObjects) {
2987
auto doc = App::GetApplication().getDocument(v.first.c_str());
2990
auto gdoc = Application::Instance->getDocument(doc);
2993
auto docItem = getDocumentItem(gdoc);
2996
for (auto id : v.second) {
2997
auto obj = doc->getObjectByID(id);
3001
errors.push_back(obj);
3002
if (docItem->ObjectMap.count(obj))
3004
auto vpd = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(gdoc->getViewProvider(obj));
3006
docItem->createNewItem(*vpd);
3011
// Update children of changed objects
3012
for (auto& v : ChangedObjects) {
3015
auto iter = ObjectTable.find(obj);
3016
if (iter == ObjectTable.end())
3019
if (v.second.test(CS_Error) && obj->isError())
3020
errors.push_back(obj);
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())
3030
for (auto item : data->items)
3031
item->setHidden(itemHidden);
3036
updateChildren(iter->first, iter->second, v.second.test(CS_Output), false);
3038
ChangedObjects.clear();
3040
FC_LOG("update item status");
3042
for (auto pos = DocumentMap.begin(); pos != DocumentMap.end(); ++pos) {
3043
pos->second->testStatus();
3047
// Checking for just restored documents
3048
for (auto& v : DocumentMap) {
3049
auto docItem = v.second;
3051
for (auto obj : docItem->PopulateObjects)
3052
docItem->populateObject(obj);
3053
docItem->PopulateObjects.clear();
3055
auto doc = v.first->getDocument();
3057
if (!docItem->connectChgObject.connected()) {
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));
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] == '*';
3074
auto obj = doc->getObject(name);
3077
auto iter = docItem->ObjectMap.find(obj);
3078
if (iter == docItem->ObjectMap.end())
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);
3088
docItem->_ExpandInfo.reset();
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();
3098
this->blockSelection(false);
3101
auto activeDocItem = getDocumentItem(Application::Instance->activeDocument());
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())
3112
auto docItem = getDocumentItem(
3113
Application::Instance->getDocument(obj->getDocument()));
3115
auto it = docItem->ObjectMap.find(obj);
3116
if (it != docItem->ObjectMap.end())
3121
auto item = data->rootItem;
3122
if (!item && !data->items.empty()) {
3123
item = *data->items.begin();
3124
data->docItem->showItem(item, false, true);
3131
scrollToItem(errItem);
3134
statusTimer->stop();
3136
FC_LOG("done update status");
3139
void TreeWidget::onItemEntered(QTreeWidgetItem* item)
3141
// object item selected
3142
if (item && item->type() == TreeWidget::ObjectType) {
3143
auto objItem = static_cast<DocumentObjectItem*>(item);
3144
objItem->displayStatusInfo();
3146
if (TreeParams::getPreSelection()) {
3147
int timeout = TreeParams::getPreSelectionDelay();
3150
if (preselectTime.elapsed() < timeout)
3153
timeout = TreeParams::getPreSelectionTimeout();
3156
preselectTimer->start(timeout);
3157
Selection().rmvPreselect();
3161
else if (TreeParams::getPreSelection())
3162
Selection().rmvPreselect();
3165
void TreeWidget::leaveEvent(QEvent* event)
3168
if (!updateBlocked && TreeParams::getPreSelection()) {
3169
preselectTimer->stop();
3170
Selection().rmvPreselect();
3174
void TreeWidget::onPreSelectTimer() {
3175
if (!TreeParams::getPreSelection())
3177
auto item = itemAt(viewport()->mapFromGlobal(QCursor::pos()));
3178
if (!item || item->type() != TreeWidget::ObjectType)
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);
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);
3196
void TreeWidget::onItemCollapsed(QTreeWidgetItem* item)
3198
// object item collapsed
3199
if (item && item->type() == TreeWidget::ObjectType) {
3200
static_cast<DocumentObjectItem*>(item)->setExpandedStatus(false);
3204
void TreeWidget::onItemExpanded(QTreeWidgetItem* item)
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);
3214
void TreeWidget::scrollItemToTop()
3216
auto doc = Application::Instance->activeDocument();
3217
for (auto tree : Instances) {
3218
if (!tree->isSelectionAttached() || tree->isSelectionBlocked())
3221
tree->_updateStatus(false);
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);
3232
tree->blockSelection(true);
3233
for (int i = 0; i < tree->rootItem->childCount(); i++) {
3234
auto docItem = dynamic_cast<DocumentItem*>(tree->rootItem->child(i));
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;
3245
tree->blockSelection(false);
3247
tree->selectTimer->stop();
3248
tree->_updateStatus(false);
3252
void TreeWidget::expandSelectedItems(TreeItemMode mode)
3254
if (!isSelectionAttached())
3257
const auto items = selectedItems();
3258
for (auto item : items) {
3260
case TreeItemMode::ExpandPath: {
3261
QTreeWidgetItem* parentItem = item->parent();
3262
while (parentItem) {
3263
parentItem->setExpanded(true);
3264
parentItem = parentItem->parent();
3266
item->setExpanded(true);
3269
case TreeItemMode::ExpandItem:
3270
item->setExpanded(true);
3272
case TreeItemMode::CollapseItem:
3273
item->setExpanded(false);
3275
case TreeItemMode::ToggleItem:
3276
if (item->isExpanded())
3277
item->setExpanded(false);
3279
item->setExpanded(true);
3285
void TreeWidget::setupText()
3287
this->headerItem()->setText(0, tr("Labels & Attributes"));
3288
this->headerItem()->setText(1, tr("Description"));
3289
this->headerItem()->setText(2, tr("Internal name"));
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"));
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"));
3297
this->createGroupAction->setText(tr("Create group"));
3298
this->createGroupAction->setStatusTip(tr("Create a group"));
3300
this->relabelObjectAction->setText(tr("Rename"));
3301
this->relabelObjectAction->setStatusTip(tr("Rename object"));
3303
this->finishEditingAction->setText(tr("Finish editing"));
3304
this->finishEditingAction->setStatusTip(tr("Finish editing object"));
3306
this->selectDependentsAction->setText(tr("Add dependent objects to selection"));
3307
this->selectDependentsAction->setStatusTip(tr("Adds all dependent objects to the selection"));
3309
this->closeDocAction->setText(tr("Close document"));
3310
this->closeDocAction->setStatusTip(tr("Close the document"));
3312
this->reloadDocAction->setText(tr("Reload document"));
3313
this->reloadDocAction->setStatusTip(tr("Reload a partially loaded document"));
3315
this->skipRecomputeAction->setText(tr("Skip recomputes"));
3316
this->skipRecomputeAction->setStatusTip(tr("Enable or disable recomputations of document"));
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"));
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"));
3326
this->recomputeObjectAction->setText(tr("Recompute object"));
3327
this->recomputeObjectAction->setStatusTip(tr("Recompute the selected object"));
3328
this->recomputeObjectAction->setIcon(BitmapFactory().iconFromTheme("view-refresh"));
3331
void TreeWidget::syncView(ViewProviderDocumentObject* vp)
3333
if (currentDocItem && TreeParams::getSyncView()) {
3334
bool focus = hasFocus();
3335
currentDocItem->document()->setActiveView(vp);
3341
void TreeWidget::onShowHidden()
3343
if (!this->contextItem)
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();
3351
docItem->setShowHidden(showHiddenAction->isChecked());
3354
void TreeWidget::onToggleVisibilityInTree()
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();
3363
bool showInTree = !object->showInTree();
3366
object->ShowInTree.setValue(showInTree);
3369
auto ownerDocument = objectItem->getOwnerDocument();
3370
bool hidden = !ownerDocument->showHidden() && !showInTree;
3371
objectItem->setHidden(hidden);
3373
objectItem->setSelected(false);
3379
void TreeWidget::changeEvent(QEvent* e)
3381
if (e->type() == QEvent::LanguageChange)
3384
QTreeWidget::changeEvent(e);
3387
void TreeWidget::onItemSelectionChanged()
3389
if (!this->isSelectionAttached()
3390
|| this->isSelectionBlocked()
3394
_LastSelectedTreeWidget = this;
3396
// block tmp. the connection to avoid to notify us ourself
3397
bool lock = this->blockSelection(true);
3399
if (selectTimer->isActive())
3402
_updateStatus(false);
3404
auto selItems = selectedItems();
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();) {
3411
if ((firstType == ObjectType && item->type() != ObjectType)
3412
|| (firstType == DocumentType && item != selItems.back()))
3414
item->setSelected(false);
3415
it = selItems.erase(it);
3422
if (selItems.size() <= 1) {
3423
if (TreeParams::getRecordSelection())
3424
Gui::Selection().selStackPush();
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();
3442
// For triggering property editor refresh
3443
Gui::Selection().signalSelectionChanged(SelectionChanges());
3446
for (auto& v : DocumentMap) {
3447
currentDocItem = v.second;
3448
v.second->clearSelection(item);
3449
currentDocItem = nullptr;
3451
if (TreeParams::getRecordSelection())
3452
Gui::Selection().selStackPush();
3455
for (auto pos = DocumentMap.begin(); pos != DocumentMap.end(); ++pos) {
3456
currentDocItem = pos->second;
3457
pos->second->updateSelection(pos->second);
3458
currentDocItem = nullptr;
3460
if (TreeParams::getRecordSelection())
3461
Gui::Selection().selStackPush(true, true);
3464
this->blockSelection(lock);
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) {
3473
if (item->type() == ObjectType) {
3475
item->setCheckState(0, item->isSelected() ? Qt::Checked : Qt::Unchecked);
3477
item->setData(0, Qt::CheckStateRole, QVariant());
3480
tree->resizeColumnToContents(0);
3484
void TreeWidget::updateVisibilityIcons() {
3485
for (auto tree : TreeWidget::Instances) {
3486
QSignalBlocker blocker(tree);
3487
for (QTreeWidgetItemIterator it(tree); *it; ++it) {
3489
if (item->type() == ObjectType) {
3490
auto objitem = static_cast<DocumentObjectItem*>(item);
3491
objitem->testStatus(true);
3494
tree->resizeColumnToContents(0);
3498
QList<QTreeWidgetItem*> TreeWidget::childrenOfItem(const QTreeWidgetItem& item) const {
3499
QList children = QList<QTreeWidgetItem*>();
3501
// check item is in this tree
3502
if (!this->indexFromItem(&item).isValid())
3505
for (int i = 0; i < item.childCount(); i++) {
3506
children.append(item.child(i));
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);
3521
void TreeWidget::onSelectTimer() {
3523
_updateStatus(false);
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;
3536
for (auto& v : DocumentMap)
3537
v.second->clearSelection();
3539
this->blockSelection(locked);
3540
selectTimer->stop();
3544
void TreeWidget::onSelectionChanged(const SelectionChanges& msg)
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())) {
3554
case SelectionChanges::AddSelection:
3555
case SelectionChanges::RmvSelection:
3556
case SelectionChanges::SetSelection:
3557
case SelectionChanges::ClrSelection: {
3558
int timeout = TreeParams::getSelectionTimeout();
3561
selectTimer->start(timeout);
3569
// ----------------------------------------------------------------------------
3571
/* TRANSLATOR Gui::TreePanel */
3573
TreePanel::TreePanel(const char* name, QWidget* parent)
3576
this->treeWidget = new TreeWidget(name, this);
3577
int indent = TreeParams::getIndentation();
3579
this->treeWidget->setIndentation(indent);
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);
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);
3600
TreePanel::~TreePanel() = default;
3602
void TreePanel::accept()
3604
QString text = this->searchBox->text();
3606
this->treeWidget->setFocus();
3607
this->treeWidget->itemSearch(text, true);
3610
bool TreePanel::eventFilter(QObject* obj, QEvent* ev)
3612
if (obj != this->searchBox)
3615
if (ev->type() == QEvent::KeyPress) {
3616
bool consumed = false;
3617
int key = static_cast<QKeyEvent*>(ev)->key();
3619
case Qt::Key_Escape:
3622
treeWidget->setFocus();
3635
void TreePanel::showEditor()
3637
this->searchBox->show();
3638
this->searchBox->setFocus();
3639
this->treeWidget->startItemSearch(searchBox);
3642
void TreePanel::hideEditor()
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();
3650
this->treeWidget->scrollToItem(sels.front());
3653
void TreePanel::itemSearch(const QString& text)
3655
this->treeWidget->itemSearch(text, false);
3658
// ----------------------------------------------------------------------------
3660
/* TRANSLATOR Gui::TreeDockWidget */
3662
TreeDockWidget::TreeDockWidget(Gui::Document* pcDocument, QWidget* parent)
3663
: DockWindow(pcDocument, parent)
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);
3673
TreeDockWidget::~TreeDockWidget() = default;
3675
void TreeWidget::selectLinkedObject(App::DocumentObject* linked) {
3676
if (!isSelectionAttached() || isSelectionBlocked())
3679
auto linkedVp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
3680
Application::Instance->getViewProvider(linked));
3682
TREE_ERR("invalid linked view provider");
3685
auto linkedDoc = getDocumentItem(linkedVp->getDocument());
3687
TREE_ERR("cannot find document of linked object");
3691
if (selectTimer->isActive())
3694
_updateStatus(false);
3696
auto it = linkedDoc->ObjectMap.find(linked);
3697
if (it == linkedDoc->ObjectMap.end()) {
3698
TREE_ERR("cannot find tree item of linked object");
3701
auto linkedItem = it->second->rootItem;
3703
linkedItem = *it->second->items.begin();
3705
if (linkedDoc->showItem(linkedItem, true))
3706
scrollToItem(linkedItem);
3708
if (linkedDoc->document()->getDocument() != App::GetApplication().getActiveDocument()) {
3709
bool focus = hasFocus();
3710
linkedDoc->document()->setActiveView(linkedItem->object());
3716
// ----------------------------------------------------------------------------
3718
DocumentItem::DocumentItem(const Gui::Document* doc, QTreeWidgetItem* parent)
3719
: QTreeWidgetItem(parent, TreeWidget::DocumentType), pDocument(const_cast<Gui::Document*>(doc))
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));
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));
3745
setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable/*|Qt::ItemIsEditable*/);
3747
treeName = getTree()->getTreeName();
3750
DocumentItem::~DocumentItem()
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();
3765
TreeWidget* DocumentItem::getTree() const {
3766
return static_cast<TreeWidget*>(treeWidget());
3769
const char* DocumentItem::getTreeName() const {
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) {
3780
#define FOREACH_ITEM_ALL(_item) \
3781
for(const auto& _v : ObjectMap) {\
3782
for(auto _item : _v.second->items) {
3784
#define END_FOREACH_ITEM }}
3787
void DocumentItem::slotInEdit(const Gui::ViewProviderDocumentObject& v)
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));
3796
if (!getTree()->editingItem) {
3797
auto doc = Application::Instance->editDocument();
3800
ViewProviderDocumentObject* parentVp = nullptr;
3801
std::string subname;
3802
auto vp = doc->getInEdit(&parentVp, &subname);
3804
parentVp = dynamic_cast<ViewProviderDocumentObject*>(vp);
3806
getTree()->editingItem = findItemByObject(true, parentVp->getObject(), subname.c_str());
3809
if (getTree()->editingItem)
3810
getTree()->editingItem->setBackground(0, color);
3812
FOREACH_ITEM(item, v)
3813
item->setBackground(0, color);
3818
void DocumentItem::slotResetEdit(const Gui::ViewProviderDocumentObject& v)
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());
3828
else if (item->object() == &v)
3829
item->setData(0, Qt::BackgroundRole, QVariant());
3831
tree->editingItem = nullptr;
3834
void DocumentItem::slotNewObject(const Gui::ViewProviderDocumentObject& obj) {
3835
if (!obj.getObject() || !obj.getObject()->isAttachedToDocument()) {
3836
FC_ERR("view provider not attached");
3839
getTree()->NewObjects[pDocument->getDocument()->getName()].push_back(obj.getObject()->getID());
3840
getTree()->_updateStatus();
3843
bool DocumentItem::createNewItem(const Gui::ViewProviderDocumentObject& obj,
3844
QTreeWidgetItem* parent, int index, DocumentObjectDataPtr data)
3846
if (!obj.getObject() ||
3847
!obj.getObject()->isAttachedToDocument() ||
3848
obj.getObject()->testStatus(App::PartialObject))
3852
auto& pdata = ObjectMap[obj.getObject()];
3854
pdata = std::make_shared<DocumentObjectData>(
3855
this, const_cast<ViewProviderDocumentObject*>(&obj));
3856
auto& entry = getTree()->ObjectTable[obj.getObject()];
3858
pdata->updateChildren(*entry.begin());
3860
pdata->updateChildren(true);
3861
entry.insert(pdata);
3863
else if (pdata->rootItem && !parent) {
3864
Base::Console().Warning("DocumentItem::slotNewObject: Cannot add view provider twice.\n");
3870
auto item = new DocumentObjectItem(this, data);
3871
if (!parent || parent == this) {
3873
data->rootItem = item;
3875
index = findRootIndex(obj.getObject());
3878
parent->addChild(item);
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);
3894
ViewProviderDocumentObject* DocumentItem::getViewProvider(App::DocumentObject* obj) {
3895
return Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
3896
Application::Instance->getViewProvider(obj));
3899
void TreeWidget::slotDeleteDocument(const Gui::Document& Doc)
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);
3914
auto it = ObjectTable.find(obj);
3915
assert(it != ObjectTable.end());
3916
assert(it->second.size() > 1);
3917
it->second.erase(v.second);
3919
this->rootItem->takeChild(this->rootItem->indexOfChild(docItem));
3921
DocumentMap.erase(it);
3925
void TreeWidget::slotDeleteObject(const Gui::ViewProviderDocumentObject& view) {
3926
_slotDeleteObject(view, nullptr);
3929
void TreeWidget::_slotDeleteObject(const Gui::ViewProviderDocumentObject& view, DocumentItem* deletingDoc)
3931
auto obj = view.getObject();
3932
auto itEntry = ObjectTable.find(obj);
3933
if (itEntry == ObjectTable.end())
3936
if (itEntry->second.empty()) {
3937
ObjectTable.erase(itEntry);
3941
TREE_LOG("delete object " << obj->getFullName());
3943
bool needUpdate = false;
3945
for (const auto& data : itEntry->second) {
3946
DocumentItem* docItem = data->docItem;
3947
if (docItem == deletingDoc)
3950
auto doc = docItem->document()->getDocument();
3951
auto& items = data->items;
3953
if (obj->getDocument() == doc)
3954
docItem->_ParentMap.erase(obj);
3956
bool lock = blockSelection(true);
3957
for (auto cit = items.begin(), citNext = cit; cit != items.end(); cit = citNext) {
3959
(*cit)->myOwner = nullptr;
3962
blockSelection(lock);
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)
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))
3977
auto childItem = *cit->second->items.begin();
3978
if (childItem->requiredAtRoot(false)) {
3979
if (docItem->createNewItem(*childItem->object(), docItem, -1, childItem->myData))
3983
childVp->setShowable(docItem->isObjectShowable(child));
3985
docItem->ObjectMap.erase(obj);
3987
ObjectTable.erase(itEntry);
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())
3998
auto& items = it->second->items;
4001
for (auto item : items) {
4002
if (item->populated)
4005
TREE_LOG("force populate object " << obj->getFullName());
4006
auto item = *items.begin();
4007
item->populated = true;
4008
populateItem(item, true);
4012
void DocumentItem::populateItem(DocumentObjectItem* item, bool refresh, bool delay)
4016
if (item->populated && !refresh)
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.
4023
item->setChildIndicatorPolicy(item->myData->children.empty() ?
4024
QTreeWidgetItem::DontShowIndicator : QTreeWidgetItem::ShowIndicator);
4026
if (!item->populated && !item->isExpanded()) {
4027
bool doPopulate = false;
4029
bool external = item->object()->getDocument() != item->getOwnerDocument()->document();
4032
auto obj = item->object()->getObject();
4033
auto linked = obj->getLinkedObject(true);
4034
if (linked && linked->getDocument() != obj->getDocument())
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);
4044
if (item->myData->removeChildrenFromRoot) {
4045
if (it->second->rootItem) {
4056
item->populated = true;
4057
bool checkHidden = !showHidden();
4058
bool updated = false;
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) {
4066
++i; // the current index of the claimed child
4069
for (int j = i; j < childCount; ++j) {
4070
QTreeWidgetItem* ci = item->child(j);
4071
if (ci->type() != TreeWidget::ObjectType)
4074
auto childItem = static_cast<DocumentObjectItem*>(ci);
4075
if (childItem->object()->getObject() != child)
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);
4085
updateItemsVisibility(ci, false);
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);
4098
else if (childItem->requiredAtRoot()) {
4099
createNewItem(*childItem->object(), this, -1, childItem->myData);
4108
// This algo will be recursively applied to newly created child items
4109
// through slotNewObject -> populateItem
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))
4121
if (!item->myData->removeChildrenFromRoot || !it->second->rootItem) {
4122
DocumentObjectItem* childItem = *it->second->items.begin();
4123
if (!createNewItem(*childItem->object(), item, i, it->second))
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());
4137
it->second->rootItem = nullptr;
4138
childItem->setHighlight(false);
4139
this->removeChild(childItem);
4140
item->insertChild(i, childItem);
4141
assert(childItem->parent() == item);
4143
updateItemsVisibility(childItem, false);
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());
4155
this->insertChild(index, childItem);
4157
this->addChild(childItem);
4158
assert(childItem->parent() == this);
4160
updateItemsVisibility(childItem, false);
4161
childItem->myData->rootItem = childItem;
4166
bool lock = getTree()->blockSelection(true);
4168
getTree()->blockSelection(lock);
4171
getTree()->_updateStatus();
4174
int DocumentItem::findRootIndex(App::DocumentObject* childObj) {
4175
if (!TreeParams::getKeepRootOrder() || !childObj || !childObj->isAttachedToDocument())
4178
// Use view provider's tree rank to find correct place at the root level.
4180
int count = this->childCount();
4186
auto getTreeRank = [](Gui::ViewProviderDocumentObject* vp) -> int {
4187
if (vp->getTreeRank() == -1) {
4188
vp->setTreeRank(vp->getObject()->getID());
4190
return vp->getTreeRank();
4193
auto vpc = dynamic_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(childObj));
4194
int childTreeRank = getTreeRank(vpc);
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) {
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) {
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;
4225
int step = count / 2;
4227
for (; pos <= last; ++pos) {
4228
auto citem = this->child(pos);
4229
if (citem->type() != TreeWidget::ObjectType)
4231
auto vp = static_cast<DocumentObjectItem*>(citem)->object();
4232
if (vp->getTreeRank() < childTreeRank) {
4248
void DocumentItem::sortObjectItems()
4250
QSignalBlocker guard(getTree());
4252
std::vector<DocumentObjectItem*> sortedItems;
4253
sortedItems.reserve(this->childCount());
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));
4262
std::stable_sort(sortedItems.begin(), sortedItems.end(),
4263
[](DocumentObjectItem* a, DocumentObjectItem* b) {
4264
return a->object()->getTreeRank() < b->object()->getTreeRank();
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) {
4275
DocumentObjectItem* sortedItem = sortedItems[sortedIndex++];
4276
if (sortedItem == treeItem) {
4281
sortedItem->getExpandedSnapshot(expansion);
4283
this->removeChild(sortedItem);
4284
this->insertChild(i, sortedItem);
4285
if (!showHidden()) {
4286
updateItemsVisibility(sortedItem, false);
4289
std::vector<bool>::const_iterator expFrom = expansion.cbegin();
4290
sortedItem->applyExpandedSnapshot(expansion, expFrom);
4294
void TreeWidget::slotChangeObject(
4295
const Gui::ViewProviderDocumentObject& view, const App::Property& prop) {
4297
auto obj = view.getObject();
4298
if (!obj || !obj->isAttachedToDocument())
4301
auto itEntry = ObjectTable.find(obj);
4302
if (itEntry == ObjectTable.end() || itEntry->second.empty())
4307
// Let's not waste time on the newly added Visibility property in
4309
if (&prop == &obj->Visibility)
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);
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);
4340
auto& s = ChangedObjects[obj];
4341
if (prop.testStatus(App::Property::Output)
4342
|| prop.testStatus(App::Property::NoRecompute))
4348
void TreeWidget::updateChildren(App::DocumentObject* obj,
4349
const std::set<DocumentObjectDataPtr>& dataSet, bool propOutput, bool force)
4351
bool childrenChanged = false;
4352
std::vector<App::DocumentObject*> children;
4353
bool removeChildrenFromRoot = true;
4355
DocumentObjectDataPtr found;
4356
for (auto data : dataSet) {
4359
childrenChanged = found->updateChildren(force);
4360
removeChildrenFromRoot = found->viewObject->canRemoveChildrenFromRoot();
4361
if (!childrenChanged && found->removeChildrenFromRoot == removeChildrenFromRoot)
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);
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))
4382
std::vector<App::DocumentObject*> linkedChildren;
4383
DocumentObjectDataPtr found;
4384
auto it = ObjectTable.find(link);
4385
if (it == ObjectTable.end())
4387
for (auto data : it->second) {
4390
if (!found->updateChildren(false))
4393
data->updateChildren(found);
4394
DocumentItem* docItem = data->docItem;
4395
for (auto item : data->items)
4396
docItem->populateItem(item, true);
4401
if (childrenChanged) {
4402
if (!selectTimer->isActive())
4403
onSelectionChanged(SelectionChanges());
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);
4416
void DocumentItem::slotHighlightObject(const Gui::ViewProviderDocumentObject& obj,
4417
const Gui::HighlightMode& high, bool set, const App::DocumentObject* parent, const char* subname)
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);
4426
FOREACH_ITEM(item, obj)
4428
App::DocumentObject* topParent = nullptr;
4429
std::ostringstream ss;
4430
item->getSubName(ss, topParent);
4432
if (parent != obj.getObject())
4436
item->setHighlight(set, high);
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())
4448
auto obj = static_cast<const DocumentObjectItem*>(citem)->object()->getObject();
4449
if (obj->isAttachedToDocument())
4450
size += strlen(obj->getNameInDocument()) + countExpandedItem(citem);
4455
unsigned int DocumentItem::getMemSize() const {
4456
return countExpandedItem(this);
4459
static void saveExpandedItem(Base::Writer& writer, const QTreeWidgetItem* item) {
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())
4465
auto obj = static_cast<const DocumentObjectItem*>(citem)->object()->getObject();
4466
if (obj->isAttachedToDocument())
4471
writer.Stream() << "/>" << std::endl;
4475
writer.Stream() << " count=\"" << itemCount << "\">" << std::endl;
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())
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));
4489
writer.Stream() << writer.ind() << "</Expand>" << std::endl;
4492
void DocumentItem::Save(Base::Writer& writer) const {
4493
writer.Stream() << writer.ind() << "<Expand ";
4494
saveExpandedItem(writer, this);
4497
void DocumentItem::Restore(Base::XMLReader& reader) {
4498
reader.readElement("Expand");
4499
if (!reader.hasAttribute("count"))
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());
4507
docItem->_ExpandInfo = _ExpandInfo;
4512
void DocumentItem::restoreItemExpansion(const ExpandInfoPtr& info, DocumentObjectItem* item) {
4513
item->setExpanded(true);
4516
for (int i = 0, count = item->childCount(); i < count; ++i) {
4517
auto citem = item->child(i);
4518
if (citem->type() != TreeWidget::ObjectType)
4520
auto obj = static_cast<DocumentObjectItem*>(citem)->object()->getObject();
4521
if (!obj->isAttachedToDocument())
4523
auto it = info->find(obj->getNameInDocument());
4524
if (it != info->end())
4525
restoreItemExpansion(it->second, static_cast<DocumentObjectItem*>(citem));
4529
void DocumentItem::slotExpandObject(const Gui::ViewProviderDocumentObject& obj,
4530
const Gui::TreeItemMode& mode, const App::DocumentObject* parent, const char* subname)
4532
getTree()->_updateStatus(false);
4534
if ((mode == TreeItemMode::ExpandItem ||
4535
mode == TreeItemMode::ExpandPath) &&
4536
obj.getDocument()->getDocument()->testStatus(App::Document::Restoring)) {
4538
_ExpandInfo.reset(new ExpandInfo);
4539
_ExpandInfo->emplace(std::string("*") + obj.getObject()->getNameInDocument(), ExpandInfoPtr());
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);
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
4554
assert(item->parent());
4557
case TreeItemMode::ExpandPath:
4559
QTreeWidgetItem* parentItem = item->parent();
4560
while (parentItem) {
4561
parentItem->setExpanded(true);
4562
parentItem = parentItem->parent();
4564
item->setExpanded(true);
4568
case TreeItemMode::ExpandItem:
4570
if (item->parent()->isExpanded())
4571
item->setExpanded(true);
4574
App::DocumentObject* topParent = nullptr;
4575
std::ostringstream ss;
4576
item->getSubName(ss, topParent);
4578
if (parent != obj.getObject())
4581
else if (topParent != parent)
4583
showItem(item, false, true);
4584
item->setExpanded(true);
4587
case TreeItemMode::CollapseItem:
4588
item->setExpanded(false);
4590
case TreeItemMode::ToggleItem:
4591
if (item->isExpanded())
4592
item->setExpanded(false);
4594
item->setExpanded(true);
4600
if (item->isExpanded())
4607
void DocumentItem::slotScrollToObject(const Gui::ViewProviderDocumentObject& obj)
4609
if (!obj.getObject() || !obj.getObject()->isAttachedToDocument())
4611
auto it = ObjectMap.find(obj.getObject());
4612
if (it == ObjectMap.end() || it->second->items.empty())
4614
auto item = it->second->rootItem;
4616
item = *it->second->items.begin();
4617
getTree()->_updateStatus(false);
4618
getTree()->scrollToItem(item);
4621
void DocumentItem::slotRecomputedObject(const App::DocumentObject& obj) {
4624
slotRecomputed(*obj.getDocument(), { const_cast<App::DocumentObject*>(&obj) });
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);
4633
if (!tree->ChangedObjects.empty())
4634
tree->_updateStatus();
4637
Gui::Document* DocumentItem::document() const
4639
return this->pDocument;
4642
void DocumentItem::testStatus()
4644
for (const auto& v : ObjectMap)
4645
v.second->testStatus();
4648
void DocumentItem::setData(int column, int role, const QVariant& value)
4650
if (role == Qt::EditRole) {
4651
QString label = value.toString();
4652
pDocument->getDocument()->Label.setValue((const char*)label.toUtf8());
4655
QTreeWidgetItem::setData(column, role, value);
4658
void DocumentItem::clearSelection(DocumentObjectItem* exclude)
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;
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) {
4678
item->mySubs.clear();
4679
item->setSelected(false);
4680
item->setCheckState(false);
4683
treeWidget()->blockSignals(ok);
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);
4692
childItem->setSelected(false);
4693
childItem->setCheckState(false);
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
4700
updateSelection(childItem, true);
4707
for (int i = 0, count = ti->childCount(); i < count; ++i)
4708
updateSelection(ti->child(i));
4711
void DocumentItem::updateItemSelection(DocumentObjectItem* item)
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;
4721
if (selected && !checked)
4722
item->setCheckState(true);
4724
if (!selected && checked)
4725
item->setCheckState(false);
4727
if ((selected && item->selected > 0) || (!selected && !item->selected)) {
4730
if (item->selected != -1)
4731
item->mySubs.clear();
4732
item->selected = selected;
4734
auto obj = item->object()->getObject();
4735
if (!obj || !obj->isAttachedToDocument())
4738
std::ostringstream str;
4739
App::DocumentObject* topParent = nullptr;
4740
item->getSubName(str, topParent);
4742
if (!obj->redirectSubName(str, topParent, nullptr))
4743
str << obj->getNameInDocument() << '.';
4746
const char* objname = obj->getNameInDocument();
4747
const char* docname = obj->getDocument()->getName();
4748
const auto& subname = str.str();
4751
if (!subname.empty()) {
4752
assert(item->getParentItem());
4757
Gui::Selection().rmvSelection(docname, objname, subname.c_str());
4761
auto vobj = item->object();
4763
if (!item->mySubs.empty()) {
4764
for (auto& sub : item->mySubs) {
4765
if (Gui::Selection().addSelection(docname, objname, (subname + sub).c_str())) {
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);
4776
item2->selected = 0;
4777
item2->setSelected(false);
4778
item2->setCheckState(false);
4783
getTree()->syncView(vobj);
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())
4791
// already a top parent
4792
if (it->second->rootItem)
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())
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) {
4807
for (auto parent = item->parent(); parent; ++i, parent = parent->parent()) {
4808
if (parent->isHidden())
4812
items.emplace(i, item);
4815
App::DocumentObject* topParent = nullptr;
4816
std::ostringstream ss;
4817
items.begin()->second->getSubName(ss, topParent);
4819
// this shouldn't happen
4820
FC_WARN("No top parent for " << obj->getFullName() << '.' << subname);
4823
ss << obj->getNameInDocument() << '.' << subname;
4824
FC_LOG("Subname correction " << obj->getFullName() << '.' << subname
4825
<< " -> " << topParent->getFullName() << '.' << ss.str());
4830
DocumentObjectItem *DocumentItem::findItem(App::DocumentObject* obj, const std::string& subname) const
4832
auto it = ObjectMap.find(obj);
4833
if (it == ObjectMap.end()) {
4837
// There is only one instance in the tree view
4838
if (it->second->items.size() == 1) {
4839
return *(it->second->items.begin());
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);
4849
if (!obj->redirectSubName(str, topParent, nullptr)) {
4850
str << obj->getNameInDocument() << '.';
4854
if (subname == str.str()) {
4863
DocumentObjectItem* DocumentItem::findItemByObject(
4864
bool sync, App::DocumentObject* obj, const char* subname, bool select)
4869
auto it = ObjectMap.find(obj);
4870
if (it == ObjectMap.end() || it->second->items.empty())
4873
// prefer top level item of this object
4874
if (it->second->rootItem)
4875
return findItem(sync, it->second->rootItem, subname, select);
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);
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) {
4889
for (auto parent = item->parent(); parent; ++i, parent = parent->parent())
4891
items.emplace(i, item);
4893
for (auto& v : items) {
4894
auto item = findItem(sync, v.second, subname, select);
4901
DocumentObjectItem* DocumentItem::findItem(
4902
bool sync, DocumentObjectItem* item, const char* subname, bool select)
4904
if (item->isHidden())
4905
item->setHidden(false);
4907
if (!subname || *subname == 0) {
4909
item->selected += 2;
4910
item->mySubs.clear();
4915
TREE_TRACE("find next " << subname);
4917
// try to find the next level object name
4918
const char* nextsub = nullptr;
4919
const char* dot = nullptr;
4920
if ((dot = strchr(subname, '.')))
4924
item->selected += 2;
4925
if (std::find(item->mySubs.begin(), item->mySubs.end(), subname) == item->mySubs.end())
4926
item->mySubs.emplace_back(subname);
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());
4938
item->selected += 2;
4939
if (std::find(item->mySubs.begin(), item->mySubs.end(), subname) == item->mySubs.end())
4940
item->mySubs.emplace_back(subname);
4946
item->mySubs.clear();
4948
if (!item->populated && sync) {
4949
//force populate the item
4950
item->populated = true;
4951
populateItem(item, true);
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);
4959
if (child->object()->getObject() == subObj)
4960
return findItem(sync, child, nextsub, select);
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.
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)) {
4972
res = findItem(sync, child, nextsub, select);
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);
4990
void DocumentItem::selectItems(SelectionReason reason) {
4991
const auto& sels = Selection().getSelection(pDocument->getDocument()->getName(), ResolveMode::NoResolve);
4993
bool sync = (sels.size() > 50 || reason == SR_SELECT) ? false : true;
4995
for (const auto& sel : sels)
4996
findItemByObject(sync, sel.pObject, sel.SubName, true);
4998
DocumentObjectItem* newSelect = nullptr;
4999
DocumentObjectItem* oldSelect = nullptr;
5001
FOREACH_ITEM_ALL(item)
5002
if (item->selected == 1) {
5003
// this means it is the old selection and is not in the current
5006
item->mySubs.clear();
5007
item->setSelected(false);
5008
item->setCheckState(false);
5010
else if (item->selected) {
5012
if (item->selected == 2 && showItem(item, false, reason == SR_FORCE_EXPAND)) {
5013
// This means newly selected and can auto expand
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()) {
5030
item->setSelected(true);
5031
item->setCheckState(true);
5037
newSelect = oldSelect;
5039
getTree()->syncView(newSelect->object());
5041
getTree()->scrollToItem(newSelect);
5045
void DocumentItem::populateParents(const ViewProvider* vp, ViewParentMap& parentMap) {
5046
auto it = parentMap.find(vp);
5047
if (it == parentMap.end())
5049
for (auto parent : it->second) {
5050
auto it = ObjectMap.find(parent->getObject());
5051
if (it == ObjectMap.end())
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);
5064
void DocumentItem::selectAllInstances(const ViewProviderDocumentObject& vpd) {
5065
ViewParentMap parentMap;
5066
auto pObject = vpd.getObject();
5067
if (ObjectMap.find(pObject) == ObjectMap.end())
5070
bool lock = getTree()->blockSelection(true);
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
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);
5081
parentMap[vp].push_back(v.second->viewObject);
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);
5089
DocumentObjectItem* first = nullptr;
5090
FOREACH_ITEM(item, vpd);
5091
if (showItem(item, true) && !first)
5095
getTree()->blockSelection(lock);
5097
treeWidget()->scrollToItem(first);
5102
bool DocumentItem::showHidden() const {
5103
return pDocument->getDocument()->ShowHidden.getValue();
5106
void DocumentItem::setShowHidden(bool show) {
5107
pDocument->getDocument()->ShowHidden.setValue(show);
5110
bool DocumentItem::showItem(DocumentObjectItem* item, bool select, bool force) {
5111
auto parent = item->parent();
5112
if (item->isHidden()) {
5115
item->setHidden(false);
5118
if (parent->type() == TreeWidget::ObjectType) {
5119
if (!showItem(static_cast<DocumentObjectItem*>(parent), false))
5121
auto pitem = static_cast<DocumentObjectItem*>(parent);
5122
if (force || !pitem->object()->getObject()->testStatus(App::NoAutoExpand))
5123
parent->setExpanded(true);
5128
parent->setExpanded(true);
5131
item->setSelected(true);
5132
item->setCheckState(true);
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());
5142
for (int i = 0; i < item->childCount(); ++i)
5143
updateItemsVisibility(item->child(i), show);
5146
void DocumentItem::updateSelection() {
5147
bool lock = getTree()->blockSelection(true);
5148
updateSelection(this, false);
5149
getTree()->blockSelection(lock);
5152
// ----------------------------------------------------------------------------
5154
static int countItems;
5156
DocumentObjectItem::DocumentObjectItem(DocumentItem* ownerDocItem, DocumentObjectDataPtr data)
5157
: QTreeWidgetItem(TreeWidget::ObjectType)
5158
, myOwner(ownerDocItem), myData(data), previousStatus(-1), selected(0), populated(false)
5160
setFlags(flags() | Qt::ItemIsEditable | Qt::ItemIsUserCheckable);
5161
setCheckState(false);
5163
myData->insertItem(this);
5165
TREE_LOG("Create item: " << countItems << ", " << object()->getObject()->getFullName());
5168
DocumentObjectItem::~DocumentObjectItem()
5171
TREE_LOG("Delete item: " << countItems << ", " << object()->getObject()->getFullName());
5172
myData->removeItem(this);
5174
if (myData->rootItem == this)
5175
myData->rootItem = nullptr;
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();
5186
void DocumentObjectItem::restoreBackground() {
5187
this->setBackground(0, this->bgBrush);
5190
void DocumentObjectItem::setHighlight(bool set, Gui::HighlightMode high) {
5191
QFont f = this->font(0);
5192
auto highlight = [this, set](const QColor& col) {
5194
this->setBackground(0, col);
5196
this->setBackground(0, QBrush());
5197
this->bgBrush = this->background(0);
5201
case HighlightMode::Bold:
5204
case HighlightMode::Italic:
5207
case HighlightMode::Underlined:
5208
f.setUnderline(set);
5210
case HighlightMode::Overlined:
5213
case HighlightMode::StrikeOut:
5214
f.setStrikeOut(set);
5216
case HighlightMode::Blue:
5217
highlight(QColor(200, 200, 255));
5219
case HighlightMode::LightBlue:
5220
highlight(QColor(230, 230, 255));
5222
case HighlightMode::UserDefined:
5224
QColor color(230, 230, 255);
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);
5232
f.setItalic(italic);
5233
f.setUnderline(underlined);
5234
f.setOverline(overlined);
5236
unsigned long col = hGrp->GetUnsigned("TreeActiveColor", 1538528255);
5237
color = App::Color::fromPackedRGB<QColor>(col);
5242
f.setUnderline(false);
5243
f.setOverline(false);
5250
this->setFont(0, f);
5253
const char* DocumentObjectItem::getTreeName() const
5255
return myData->getTreeName();
5258
Gui::ViewProviderDocumentObject* DocumentObjectItem::object() const
5260
return myData->viewObject;
5263
void DocumentObjectItem::testStatus(bool resetStatus)
5266
testStatus(resetStatus, icon, icon2);
5280
void DocumentObjectItem::testStatus(bool resetStatus, QIcon& icon1, QIcon& icon2)
5282
App::DocumentObject* pObject = object()->getObject();
5285
auto parentItem = getParentItem();
5287
Timing(testStatus1);
5288
auto parent = parentItem->object()->getObject();
5289
auto ext = parent->getExtensionByType<App::GroupExtension>(true, false);
5291
visible = parent->isElementVisible(pObject->getNameInDocument());
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
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());
5307
Timing(testStatus2);
5310
visible = object()->isShow() ? 1 : 0;
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();
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) |
5326
TimingStop(testStatus2);
5328
if (!resetStatus && previousStatus == currentStatus)
5331
_Timing(1, testStatus3);
5333
previousStatus = currentStatus;
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());
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));
5357
mode = QIcon::Disabled;
5360
_TimingStop(1, testStatus3);
5362
QIcon& icon = mode == QIcon::Normal ? icon1 : icon2;
5364
if (icon.isNull()) {
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));
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));
5384
// get the original icon set
5385
QIcon icon_org = object()->getIcon();
5387
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
5388
int w = getTree()->viewOptions().decorationSize.width();
5390
QStyleOptionViewItem opt;
5391
getTree()->initViewItemOption(&opt);
5392
int w = opt.decorationSize.width();
5395
QPixmap pxOn, pxOff;
5397
// if needed show small pixmap inside
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);
5405
pxOff = icon_org.pixmap(w, w, mode, QIcon::Off);
5406
pxOn = icon_org.pixmap(w, w, mode, QIcon::On);
5409
if (currentStatus & Status::Hidden) {
5410
static QPixmap pxHidden;
5411
if (pxHidden.isNull()) {
5412
pxHidden = Gui::BitmapFactory().pixmapFromSvg("TreeItemVisible", QSize(10, 10));
5414
pxOff = BitmapFactory().merge(pxOff, pxHidden, BitmapFactoryInst::TopLeft);
5415
pxOn = BitmapFactory().merge(pxOn, pxHidden, BitmapFactoryInst::TopLeft);
5418
if (currentStatus & Status::External) {
5419
static QPixmap pxExternal;
5420
if (pxExternal.isNull()) {
5421
pxExternal = Gui::BitmapFactory().pixmapFromSvg("LinkOverlay",
5424
pxOff = BitmapFactory().merge(pxOff, pxExternal, BitmapFactoryInst::BottomRight);
5425
pxOn = BitmapFactory().merge(pxOn, pxExternal, BitmapFactoryInst::BottomRight);
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));
5434
pxOff = BitmapFactory().merge(pxOff, pxFreeze, BitmapFactoryInst::TopLeft);
5435
pxOn = BitmapFactory().merge(pxOn, pxFreeze, BitmapFactoryInst::TopLeft);
5438
icon.addPixmap(pxOn, QIcon::Normal, QIcon::On);
5439
icon.addPixmap(pxOff, QIcon::Normal, QIcon::Off);
5441
icon = object()->mergeColorfulOverlayIcons(icon);
5443
if (isVisibilityIconEnabled()) {
5444
static QPixmap pxVisible, pxInvisible;
5445
if (pxVisible.isNull()) {
5446
pxVisible = BitmapFactory().pixmap("TreeItemVisible");
5448
if (pxInvisible.isNull()) {
5449
pxInvisible = BitmapFactory().pixmap("TreeItemInvisible");
5452
// Prepend the visibility pixmap to the final icon pixmaps and use these as the 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);
5459
QPixmap px(2*px_org.width() + spacing, px_org.height());
5460
px.fill(Qt::transparent);
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);
5469
new_icon.addPixmap(px, QIcon::Normal, state);
5475
_Timing(2, setIcon);
5476
this->setIcon(0, icon);
5479
void DocumentObjectItem::displayStatusInfo()
5481
App::DocumentObject* Obj = object()->getObject();
5483
QString info = QApplication::translate(Obj->getTypeId().getName(), Obj->getStatusString());
5485
if (Obj->mustExecute() == 1 && !Obj->isError())
5486
info += TreeWidget::tr(" (but must be executed)");
5488
QString status = TreeWidget::tr("%1, Internal name: %2")
5489
.arg(info, QString::fromLatin1(Obj->getNameInDocument()));
5491
if (!Obj->isError())
5492
getMainWindow()->showMessage(status);
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);
5501
void DocumentObjectItem::setExpandedStatus(bool on)
5503
if (getOwnerDocument()->document() == object()->getDocument())
5504
object()->getObject()->setStatus(App::Expand, on);
5507
void DocumentObjectItem::setData(int column, int role, const QVariant& value)
5509
QVariant myValue(value);
5510
if (role == Qt::EditRole && column <= 1) {
5511
auto obj = object()->getObject();
5512
auto& label = column ? obj->Label2 : obj->Label;
5514
std::ostringstream str;
5515
str << TreeWidget::tr("Rename").toStdString() << ' ' << getName() << '.' << label.getName();
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();
5524
myValue = QString::fromUtf8(label.getValue());
5526
QTreeWidgetItem::setData(column, role, myValue);
5529
bool DocumentObjectItem::isChildOfItem(DocumentObjectItem* item)
5531
for (auto pitem = parent(); pitem; pitem = pitem->parent())
5537
bool DocumentObjectItem::requiredAtRoot(bool excludeSelf) const {
5538
if (myData->rootItem || object()->getDocument() != getOwnerDocument()->document())
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)
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.
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.
5563
for (auto parent : it->second) {
5564
if (getOwnerDocument()->populateObject(parent))
5572
bool DocumentObjectItem::isLink() const {
5573
auto obj = object()->getObject();
5574
auto linked = obj->getLinkedObject(false);
5575
return linked && obj != linked;
5578
bool DocumentObjectItem::isLinkFinal() const {
5579
auto obj = object()->getObject();
5580
auto linked = obj->getLinkedObject(false);
5581
return linked && linked == linked->getLinkedObject(true);
5585
bool DocumentObjectItem::isParentLink() const {
5586
auto pi = getParentItem();
5587
return pi && pi->isLink();
5594
SuperGroup = 3, //reversed for future
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()))
5603
if (obj->hasChildElement())
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))
5610
if (pobj->isElementVisible(obj->getNameInDocument()) >= 0)
5617
bool DocumentItem::isObjectShowable(App::DocumentObject* obj) {
5618
auto itParents = _ParentMap.find(obj);
5619
if (itParents == _ParentMap.end() || itParents->second.empty())
5621
bool showable = true;
5622
for (auto parent : itParents->second) {
5623
if (parent->getDocument() != obj->getDocument())
5625
if (!parent->hasChildElement()
5626
&& parent->getLinkedObject(false) == parent)
5633
int DocumentObjectItem::isParentGroup() const {
5634
auto pi = getParentItem();
5635
return pi ? pi->isGroup() : 0;
5638
DocumentObjectItem* DocumentObjectItem::getParentItem() const {
5639
if (parent()->type() != TreeWidget::ObjectType)
5641
return static_cast<DocumentObjectItem*>(parent());
5644
DocumentObjectItem* DocumentObjectItem::getNextSibling() const
5646
QTreeWidgetItem* parent = this->parent();
5648
int index = parent->indexOfChild(const_cast<DocumentObjectItem*>(this));
5650
while (++index < parent->childCount()) {
5651
QTreeWidgetItem* sibling = parent->child(index);
5652
if (sibling->type() == TreeWidget::ObjectType) {
5653
return static_cast<DocumentObjectItem*>(sibling);
5662
DocumentObjectItem* DocumentObjectItem::getPreviousSibling() const
5664
QTreeWidgetItem* parent = this->parent();
5666
int index = parent->indexOfChild(const_cast<DocumentObjectItem*>(this));
5668
QTreeWidgetItem* sibling = parent->child(--index);
5669
if (sibling->type() == TreeWidget::ObjectType) {
5670
return static_cast<DocumentObjectItem*>(sibling);
5678
const char* DocumentObjectItem::getName() const {
5679
const char* name = object()->getObject()->getNameInDocument();
5680
return name ? name : "";
5683
int DocumentObjectItem::getSubName(std::ostringstream& str, App::DocumentObject*& topParent) const
5685
auto parent = getParentItem();
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,
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
5711
auto obj = parent->object()->getObject();
5712
if (!obj || !obj->isAttachedToDocument()) {
5713
topParent = nullptr;
5719
else if (!obj->redirectSubName(str, topParent, object()->getObject()))
5720
str << obj->getNameInDocument() << '.';
5724
App::DocumentObject* DocumentObjectItem::getFullSubName(
5725
std::ostringstream& str, DocumentObjectItem* parent) const
5727
auto pi = getParentItem();
5728
if (this == parent || !pi || (!parent && !pi->isGroup()))
5729
return object()->getObject();
5731
auto ret = pi->getFullSubName(str, parent);
5732
str << getName() << '.';
5736
App::DocumentObject* DocumentObjectItem::getRelativeParent(
5737
std::ostringstream& str, DocumentObjectItem* cousin,
5738
App::DocumentObject** topParent, std::string* topSubname) const
5740
std::ostringstream str2;
5741
App::DocumentObject* top = nullptr, * top2 = nullptr;
5742
getSubName(str, top);
5748
*topSubname = str.str() + getName() + '.';
5749
cousin->getSubName(str2, top2);
5751
str << getName() << '.';
5755
auto subname = str.str();
5756
auto subname2 = str2.str();
5757
const char* sub = subname.c_str();
5758
const char* sub2 = subname2.c_str();
5760
const char* dot = strchr(sub, '.');
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());
5770
FC_ERR("invalid subname " << top->getFullName() << '.' << substr);
5775
str << dot + 1 << getName() << '.';
5785
void DocumentObjectItem::setCheckState(bool checked) {
5786
if (isSelectionCheckBoxesEnabled())
5787
QTreeWidgetItem::setCheckState(0, checked ? Qt::Checked : Qt::Unchecked);
5789
setData(0, Qt::CheckStateRole, QVariant());
5792
DocumentItem* DocumentObjectItem::getParentDocument() const {
5793
return getTree()->getDocumentItem(object()->getDocument());
5796
DocumentItem* DocumentObjectItem::getOwnerDocument() const {
5800
TreeWidget* DocumentObjectItem::getTree() const {
5801
return static_cast<TreeWidget*>(treeWidget());
5804
void DocumentObjectItem::getExpandedSnapshot(std::vector<bool>& snapshot) const
5806
snapshot.push_back(isExpanded());
5808
for (int i = 0; i < childCount(); ++i) {
5809
static_cast<const DocumentObjectItem*>(child(i))->getExpandedSnapshot(snapshot);
5813
void DocumentObjectItem::applyExpandedSnapshot(const std::vector<bool>& snapshot, std::vector<bool>::const_iterator& from)
5815
setExpanded(*from++);
5817
for (int i = 0; i < childCount(); ++i) {
5818
static_cast<DocumentObjectItem*>(child(i))->applyExpandedSnapshot(snapshot, from);
5822
#include "moc_Tree.cpp"