Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
12 contributors

Users who have contributed to this file

@wwmayer @realthunder @ickby @yorikvanhavre @f3nix @mlampert @luzpaz @jrheinlaender @jriegel @plgarcia @usakhelo @Fat-Zer
4930 lines (4387 sloc) 175 KB
/***************************************************************************
* Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <boost/bind.hpp>
# include <QAction>
# include <QActionGroup>
# include <QApplication>
# include <qcursor.h>
# include <QVBoxLayout>
# include <qlayout.h>
# include <qstatusbar.h>
# include <QContextMenuEvent>
# include <QMenu>
# include <QPixmap>
# include <QTimer>
# include <QToolTip>
# include <QHeaderView>
# include <qmessagebox.h>
#endif
#include <Base/Console.h>
#include <Base/Sequencer.h>
#include <Base/Tools.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/DocumentObjectGroup.h>
#include <App/AutoTransaction.h>
#include <App/GeoFeatureGroupExtension.h>
#include <App/Link.h>
#include "Tree.h"
#include "Command.h"
#include "Document.h"
#include "BitmapFactory.h"
#include "ViewProviderDocumentObject.h"
#include "MenuManager.h"
#include "Application.h"
#include "MainWindow.h"
#include "View3DInventor.h"
#include "View3DInventorViewer.h"
#include "Macro.h"
#include "Workbench.h"
#include "Widgets.h"
#include "ExpressionCompleter.h"
FC_LOG_LEVEL_INIT("Tree",false,true,true)
#define _TREE_PRINT(_level,_func,_msg) \
_FC_PRINT(FC_LOG_INSTANCE,_level,_func, '['<<getTreeName()<<"] " << _msg)
#define TREE_MSG(_msg) _TREE_PRINT(FC_LOGLEVEL_MSG,NotifyMessage,_msg)
#define TREE_WARN(_msg) _TREE_PRINT(FC_LOGLEVEL_WARN,NotifyWarning,_msg)
#define TREE_ERR(_msg) _TREE_PRINT(FC_LOGLEVEL_ERR,NotifyError,_msg)
#define TREE_LOG(_msg) _TREE_PRINT(FC_LOGLEVEL_LOG,NotifyLog,_msg)
#define TREE_TRACE(_msg) _TREE_PRINT(FC_LOGLEVEL_TRACE,NotifyLog,_msg)
using namespace Gui;
/////////////////////////////////////////////////////////////////////////////////
std::unique_ptr<QPixmap> TreeWidget::documentPixmap;
std::unique_ptr<QPixmap> TreeWidget::documentPartialPixmap;
std::set<TreeWidget *> TreeWidget::Instances;
static TreeWidget *_LastSelectedTreeWidget;
const int TreeWidget::DocumentType = 1000;
const int TreeWidget::ObjectType = 1001;
bool _DragEventFilter;
TreeParams::TreeParams() {
handle = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
handle->Attach(this);
#undef FC_TREEPARAM_DEF
#define FC_TREEPARAM_DEF(_name,_type,_Type,_default) \
_##_name = handle->Get##_Type(#_name,_default);
FC_TREEPARAM_DEFS
}
#undef FC_TREEPARAM_DEF
#define FC_TREEPARAM_DEF(_name,_type,_Type,_default) \
void TreeParams::set##_name(_type value) {\
if(_##_name != value) {\
handle->Set##_Type(#_name,value);\
}\
}
FC_TREEPARAM_DEFS
void TreeParams::OnChange(Base::Subject<const char*> &, const char* sReason) {
#undef FC_TREEPARAM_DEF
#define FC_TREEPARAM_DEF(_name,_type,_Type,_default) \
if(strcmp(sReason,#_name)==0) {\
_##_name = handle->Get##_Type(#_name,_default);\
return;\
}
#undef FC_TREEPARAM_DEF2
#define FC_TREEPARAM_DEF2(_name,_type,_Type,_default) \
if(strcmp(sReason,#_name)==0) {\
_##_name = handle->Get##_Type(#_name,_default);\
on##_name##Changed();\
return;\
}
FC_TREEPARAM_DEFS
}
void TreeParams::onSyncSelectionChanged() {
if(!TreeParams::Instance()->SyncSelection() || !Gui::Selection().hasSelection())
return;
TreeWidget::scrollItemToTop();
}
void TreeParams::onDocumentModeChanged() {
App::GetApplication().setActiveDocument(App::GetApplication().getActiveDocument());
}
TreeParams *TreeParams::Instance() {
static TreeParams *instance;
if(!instance)
instance = new TreeParams;
return instance;
}
//////////////////////////////////////////////////////////////////////////////////////
struct Stats {
#define DEFINE_STATS \
DEFINE_STAT(testStatus1) \
DEFINE_STAT(testStatus2) \
DEFINE_STAT(testStatus3) \
DEFINE_STAT(getIcon) \
DEFINE_STAT(setIcon) \
#define DEFINE_STAT(_name) \
FC_DURATION_DECLARE(_name);\
int _name##_count;
DEFINE_STATS
void init() {
#undef DEFINE_STAT
#define DEFINE_STAT(_name) \
FC_DURATION_INIT(_name);\
_name##_count = 0;
DEFINE_STATS
}
void print() {
#undef DEFINE_STAT
#define DEFINE_STAT(_name) FC_DURATION_MSG(_name, #_name " count: " << _name##_count);
DEFINE_STATS
}
#undef DEFINE_STAT
#define DEFINE_STAT(_name) \
void time_##_name(FC_TIME_POINT &t) {\
++_name##_count;\
FC_DURATION_PLUS(_name,t);\
}
DEFINE_STATS
};
//static Stats _Stats;
struct TimingInfo {
bool timed = false;
FC_TIME_POINT t;
FC_DURATION &d;
TimingInfo(FC_DURATION &d)
:d(d)
{
_FC_TIME_INIT(t);
}
~TimingInfo() {
stop();
}
void stop() {
if(!timed) {
timed = true;
FC_DURATION_PLUS(d,t);
}
}
void reset() {
stop();
_FC_TIME_INIT(t);
}
};
// #define DO_TIMING
#ifdef DO_TIMING
#define _Timing(_idx,_name) ++_Stats._name##_count; TimingInfo _tt##_idx(_Stats._name)
#define Timing(_name) _Timing(0,_name)
#define _TimingStop(_idx,_name) _tt##_idx.stop();
#define TimingStop(_name) _TimingStop(0,_name);
#define TimingInit() _Stats.init();
#define TimingPrint() _Stats.print();
#else
#define _Timing(...) do{}while(0)
#define Timing(...) do{}while(0)
#define TimingInit() do{}while(0)
#define TimingPrint() do{}while(0)
#define _TimingStop(...) do{}while(0);
#define TimingStop(...) do{}while(0);
#endif
// ---------------------------------------------------------------------------
typedef std::set<DocumentObjectItem*> DocumentObjectItems;
class Gui::DocumentObjectData {
public:
DocumentItem *docItem;
DocumentObjectItems items;
ViewProviderDocumentObject *viewObject;
DocumentObjectItem *rootItem;
std::vector<App::DocumentObject*> children;
std::set<App::DocumentObject*> childSet;
bool removeChildrenFromRoot;
bool itemHidden;
std::string label;
std::string label2;
typedef boost::signals2::scoped_connection Connection;
Connection connectIcon;
Connection connectTool;
Connection connectStat;
DocumentObjectData(DocumentItem *docItem, ViewProviderDocumentObject* vpd)
: docItem(docItem), viewObject(vpd),rootItem(0)
{
// Setup connections
connectIcon = viewObject->signalChangeIcon.connect(
boost::bind(&DocumentObjectData::slotChangeIcon, this));
connectTool = viewObject->signalChangeToolTip.connect(
boost::bind(&DocumentObjectData::slotChangeToolTip, this, _1));
connectStat = viewObject->signalChangeStatusTip.connect(
boost::bind(&DocumentObjectData::slotChangeStatusTip, this, _1));
removeChildrenFromRoot = viewObject->canRemoveChildrenFromRoot();
itemHidden = !viewObject->showInTree();
label = viewObject->getObject()->Label.getValue();
label2 = viewObject->getObject()->Label2.getValue();
}
const char *getTreeName() const {
return docItem->getTreeName();
}
void updateChildren(DocumentObjectDataPtr other) {
children = other->children;
childSet = other->childSet;
}
bool updateChildren(bool checkVisibility) {
auto newChildren = viewObject->claimChildren();
auto obj = viewObject->getObject();
std::set<App::DocumentObject *> newSet;
bool updated = false;
for (auto child : newChildren) {
if(child && child->getNameInDocument()) {
if(!newSet.insert(child).second) {
TREE_WARN("duplicate child item " << obj->getFullName()
<< '.' << child->getNameInDocument());
}else if(!childSet.erase(child)) {
// this means new child detected
updated = true;
if(child->getDocument()==obj->getDocument() &&
child->getDocument()==docItem->document()->getDocument())
{
auto &parents = docItem->_ParentMap[child];
if(parents.insert(obj).second && child->Visibility.getValue()) {
bool showable = false;
for(auto parent : parents) {
if(!parent->hasChildElement()
&& parent->getLinkedObject(false)==parent)
{
showable = true;
break;
}
}
if(!showable)
child->Visibility.setValue(false);
}
}
}
}
}
for (auto child : childSet) {
if(newSet.find(child) == newSet.end()) {
// this means old child removed
updated = true;
docItem->_ParentMap[child].erase(obj);
}
}
// We still need to check the order of the children
updated = updated || children!=newChildren;
children.swap(newChildren);
childSet.swap(newSet);
if(updated && checkVisibility) {
for(auto child : children) {
if(!child || !child->getNameInDocument() || !child->Visibility.getValue())
continue;
if(child->getDocument()==obj->getDocument() && !docItem->isObjectShowable(child))
child->Visibility.setValue(false);
}
}
return updated;
}
void testStatus(bool resetStatus = false) {
QIcon icon,icon2;
for(auto item : items)
item->testStatus(resetStatus,icon,icon2);
}
void slotChangeIcon() {
testStatus(true);
}
void slotChangeToolTip(const QString& tip) {
for(auto item : items)
item->setToolTip(0, tip);
}
void slotChangeStatusTip(const QString& tip) {
for(auto item : items)
item->setStatusTip(0, tip);
}
};
// ---------------------------------------------------------------------------
class DocumentItem::ExpandInfo:
public std::unordered_map<std::string, DocumentItem::ExpandInfoPtr>
{
public:
void restore(Base::XMLReader &reader) {
int level = reader.level();
int count = reader.getAttributeAsInteger("count");
for(int i=0;i<count;++i) {
reader.readElement("Expand");
auto &entry = (*this)[reader.getAttribute("name")];
if(!reader.hasAttribute("count"))
continue;
entry.reset(new ExpandInfo);
entry->restore(reader);
}
reader.readEndElement("Expand",level-1);
}
};
// ---------------------------------------------------------------------------
TreeWidgetEditDelegate::TreeWidgetEditDelegate(QObject* parent)
: QStyledItemDelegate(parent)
{
}
QWidget* TreeWidgetEditDelegate::createEditor(
QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
{
auto ti = static_cast<QTreeWidgetItem*>(index.internalPointer());
if(ti->type()!=TreeWidget::ObjectType || index.column()>1)
return 0;
DocumentObjectItem *item = static_cast<DocumentObjectItem*>(ti);
App::DocumentObject *obj = item->object()->getObject();
auto &prop = index.column()?obj->Label2:obj->Label;
std::ostringstream str;
str << "Change " << obj->getNameInDocument() << '.' << prop.getName();
App::GetApplication().setActiveTransaction(str.str().c_str());
FC_LOG("create editor transaction " << App::GetApplication().getActiveTransaction());
ExpLineEdit *le = new ExpLineEdit(parent);
le->setFrame(false);
le->setReadOnly(prop.isReadOnly());
le->bind(App::ObjectIdentifier(prop));
le->setAutoApply(true);
return le;
}
// ---------------------------------------------------------------------------
TreeWidget::TreeWidget(const char *name, QWidget* parent)
: QTreeWidget(parent), SelectionObserver(true,0), contextItem(0)
, searchObject(0), searchDoc(0), searchContextDoc(0)
, editingItem(0), currentDocItem(0)
, myName(name)
{
Instances.insert(this);
if(!_LastSelectedTreeWidget)
_LastSelectedTreeWidget = this;
this->setDragEnabled(true);
this->setAcceptDrops(true);
this->setDropIndicatorShown(false);
this->setDragDropMode(QTreeWidget::InternalMove);
this->setRootIsDecorated(false);
this->setColumnCount(2);
this->setItemDelegate(new TreeWidgetEditDelegate(this));
this->showHiddenAction = new QAction(this);
this->showHiddenAction->setCheckable(true);
connect(this->showHiddenAction, SIGNAL(triggered()),
this, SLOT(onShowHidden()));
this->hideInTreeAction = new QAction(this);
this->hideInTreeAction->setCheckable(true);
connect(this->hideInTreeAction, SIGNAL(triggered()),
this, SLOT(onHideInTree()));
this->createGroupAction = new QAction(this);
connect(this->createGroupAction, SIGNAL(triggered()),
this, SLOT(onCreateGroup()));
this->relabelObjectAction = new QAction(this);
#ifndef Q_OS_MAC
this->relabelObjectAction->setShortcut(Qt::Key_F2);
#endif
connect(this->relabelObjectAction, SIGNAL(triggered()),
this, SLOT(onRelabelObject()));
this->finishEditingAction = new QAction(this);
connect(this->finishEditingAction, SIGNAL(triggered()),
this, SLOT(onFinishEditing()));
this->closeDocAction = new QAction(this);
connect(this->closeDocAction, SIGNAL(triggered()),
this, SLOT(onCloseDoc()));
this->reloadDocAction = new QAction(this);
connect(this->reloadDocAction, SIGNAL(triggered()),
this, SLOT(onReloadDoc()));
this->skipRecomputeAction = new QAction(this);
this->skipRecomputeAction->setCheckable(true);
connect(this->skipRecomputeAction, SIGNAL(toggled(bool)),
this, SLOT(onSkipRecompute(bool)));
this->allowPartialRecomputeAction = new QAction(this);
this->allowPartialRecomputeAction->setCheckable(true);
connect(this->allowPartialRecomputeAction, SIGNAL(toggled(bool)),
this, SLOT(onAllowPartialRecompute(bool)));
this->markRecomputeAction = new QAction(this);
connect(this->markRecomputeAction, SIGNAL(triggered()),
this, SLOT(onMarkRecompute()));
this->recomputeObjectAction = new QAction(this);
connect(this->recomputeObjectAction, SIGNAL(triggered()),
this, SLOT(onRecomputeObject()));
this->searchObjectsAction = new QAction(this);
this->searchObjectsAction->setText(tr("Search..."));
this->searchObjectsAction->setStatusTip(tr("Search for objects"));
connect(this->searchObjectsAction, SIGNAL(triggered()),
this, SLOT(onSearchObjects()));
// Setup connections
connectNewDocument = Application::Instance->signalNewDocument.connect(boost::bind(&TreeWidget::slotNewDocument, this, _1, _2));
connectDelDocument = Application::Instance->signalDeleteDocument.connect(boost::bind(&TreeWidget::slotDeleteDocument, this, _1));
connectRenDocument = Application::Instance->signalRenameDocument.connect(boost::bind(&TreeWidget::slotRenameDocument, this, _1));
connectActDocument = Application::Instance->signalActiveDocument.connect(boost::bind(&TreeWidget::slotActiveDocument, this, _1));
connectRelDocument = Application::Instance->signalRelabelDocument.connect(boost::bind(&TreeWidget::slotRelabelDocument, this, _1));
connectShowHidden = Application::Instance->signalShowHidden.connect(boost::bind(&TreeWidget::slotShowHidden, this, _1));
// Gui::Document::signalChangedObject informs the App::Document property
// change, not view provider's own property, which is what the signal below
// for
connectChangedViewObj = Application::Instance->signalChangedObject.connect(
boost::bind(&TreeWidget::slotChangedViewObject, this, _1,_2));
// make sure to show a horizontal scrollbar if needed
#if QT_VERSION >= 0x050000
this->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
#else
this->header()->setResizeMode(0, QHeaderView::ResizeToContents);
#endif
this->header()->setStretchLastSection(false);
// Add the first main label
this->rootItem = new QTreeWidgetItem(this);
this->rootItem->setFlags(Qt::ItemIsEnabled);
this->expandItem(this->rootItem);
this->setSelectionMode(QAbstractItemView::ExtendedSelection);
#if QT_VERSION >= 0x040200
// causes unexpected drop events (possibly only with Qt4.1.x)
this->setMouseTracking(true); // needed for itemEntered() to work
#endif
this->preselectTimer = new QTimer(this);
this->preselectTimer->setSingleShot(true);
this->statusTimer = new QTimer(this);
this->statusTimer->setSingleShot(false);
this->selectTimer = new QTimer(this);
this->selectTimer->setSingleShot(true);
connect(this->statusTimer, SIGNAL(timeout()),
this, SLOT(onUpdateStatus()));
connect(this, SIGNAL(itemEntered(QTreeWidgetItem*, int)),
this, SLOT(onItemEntered(QTreeWidgetItem*)));
connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)),
this, SLOT(onItemCollapsed(QTreeWidgetItem*)));
connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)),
this, SLOT(onItemExpanded(QTreeWidgetItem*)));
connect(this, SIGNAL(itemSelectionChanged()),
this, SLOT(onItemSelectionChanged()));
connect(this->preselectTimer, SIGNAL(timeout()),
this, SLOT(onPreSelectTimer()));
connect(this->selectTimer, SIGNAL(timeout()),
this, SLOT(onSelectTimer()));
preselectTime.start();
setupText();
if(!documentPixmap) {
documentPixmap.reset(new QPixmap(Gui::BitmapFactory().pixmap("Document")));
QIcon icon(*documentPixmap);
documentPartialPixmap.reset(new QPixmap(icon.pixmap(documentPixmap->size(),QIcon::Disabled)));
}
}
TreeWidget::~TreeWidget()
{
connectNewDocument.disconnect();
connectDelDocument.disconnect();
connectRenDocument.disconnect();
connectActDocument.disconnect();
connectRelDocument.disconnect();
connectShowHidden.disconnect();
connectChangedViewObj.disconnect();
Instances.erase(this);
if(_LastSelectedTreeWidget == this)
_LastSelectedTreeWidget = 0;
}
const char *TreeWidget::getTreeName() const {
return myName.c_str();
}
// reimpelement to select only objects in the active document
void TreeWidget::selectAll() {
auto gdoc = Application::Instance->getDocument(
App::GetApplication().getActiveDocument());
if(!gdoc)
return;
auto itDoc = DocumentMap.find(gdoc);
if(itDoc == DocumentMap.end())
return;
if(TreeParams::Instance()->RecordSelection())
Gui::Selection().selStackPush();
Gui::Selection().clearSelection();
Gui::Selection().setSelection(gdoc->getDocument()->getName(),gdoc->getDocument()->getObjects());
}
bool TreeWidget::isObjectShowable(App::DocumentObject *obj) {
if(!obj || !obj->getNameInDocument())
return true;
Gui::Document *doc = Application::Instance->getDocument(obj->getDocument());
if(!doc)
return true;
if(Instances.empty())
return true;
auto tree = *Instances.begin();
auto it = tree->DocumentMap.find(doc);
if(it != tree->DocumentMap.end())
return it->second->isObjectShowable(obj);
return true;
}
static bool _DisableCheckTopParent;
void TreeWidget::checkTopParent(App::DocumentObject *&obj, std::string &subname) {
if(_DisableCheckTopParent)
return;
if(Instances.size() && obj && obj->getNameInDocument()) {
auto tree = *Instances.begin();
auto it = tree->DocumentMap.find(Application::Instance->getDocument(obj->getDocument()));
if(it != tree->DocumentMap.end()) {
if(tree->statusTimer->isActive()) {
bool locked = tree->blockConnection(true);
tree->_updateStatus(false);
tree->blockConnection(locked);
}
auto parent = it->second->getTopParent(obj,subname);
if(parent)
obj = parent;
}
}
}
void TreeWidget::resetItemSearch() {
if(!searchObject)
return;
auto it = ObjectTable.find(searchObject);
if(it != ObjectTable.end()) {
for(auto &data : it->second) {
if(!data)
continue;
for(auto item : data->items)
static_cast<DocumentObjectItem*>(item)->restoreBackground();
}
}
searchObject = 0;
}
void TreeWidget::startItemSearch(QLineEdit *edit) {
resetItemSearch();
searchDoc = 0;
searchContextDoc = 0;
auto sels = selectedItems();
if(sels.size() == 1) {
if(sels.front()->type() == DocumentType) {
searchDoc = static_cast<DocumentItem*>(sels.front())->document();
} else if(sels.front()->type() == ObjectType) {
auto item = static_cast<DocumentObjectItem*>(sels.front());
searchDoc = item->object()->getDocument();
searchContextDoc = item->getOwnerDocument()->document();
}
}else
searchDoc = Application::Instance->activeDocument();
App::DocumentObject *obj = 0;
if(searchContextDoc && searchContextDoc->getDocument()->getObjects().size())
obj = searchContextDoc->getDocument()->getObjects().front();
else if(searchDoc && searchDoc->getDocument()->getObjects().size())
obj = searchDoc->getDocument()->getObjects().front();
if(obj)
static_cast<ExpressionLineEdit*>(edit)->setDocumentObject(obj);
}
void TreeWidget::itemSearch(const QString &text, bool select) {
resetItemSearch();
auto docItem = getDocumentItem(searchDoc);
if(!docItem) {
docItem = getDocumentItem(Application::Instance->activeDocument());
if(!docItem) {
FC_TRACE("item search no document");
resetItemSearch();
return;
}
}
auto doc = docItem->document()->getDocument();
const auto &objs = doc->getObjects();
if(objs.empty()) {
FC_TRACE("item search no objects");
return;
}
std::string txt(text.toUtf8().constData());
try {
if(txt.empty())
return;
if(txt.find("<<") == std::string::npos) {
auto pos = txt.find('.');
if(pos==std::string::npos)
txt += '.';
else if(pos!=txt.size()-1) {
txt.insert(pos+1,"<<");
if(txt.back()!='.')
txt += '.';
txt += ">>.";
}
}else if(txt.back() != '.')
txt += '.';
txt += "_self";
auto path = App::ObjectIdentifier::parse(objs.front(),txt);
if(path.getPropertyName() != "_self") {
FC_TRACE("Object " << txt << " not found in " << doc->getName());
return;
}
auto obj = path.getDocumentObject();
if(!obj) {
FC_TRACE("Object " << txt << " not found in " << doc->getName());
return;
}
std::string subname = path.getSubObjectName();
App::DocumentObject *parent = 0;
if(searchContextDoc) {
auto it = DocumentMap.find(searchContextDoc);
if(it!=DocumentMap.end()) {
parent = it->second->getTopParent(obj,subname);
if(parent) {
obj = parent;
docItem = it->second;
doc = docItem->document()->getDocument();
}
}
}
if(!parent) {
parent = docItem->getTopParent(obj,subname);
while(!parent) {
if(docItem->document()->getDocument() == obj->getDocument()) {
// this shouldn't happen
FC_LOG("Object " << txt << " not found in " << doc->getName());
return;
}
auto it = DocumentMap.find(Application::Instance->getDocument(obj->getDocument()));
if(it==DocumentMap.end())
return;
docItem = it->second;
parent = docItem->getTopParent(obj,subname);
}
obj = parent;
}
auto item = docItem->findItemByObject(true,obj,subname.c_str());
if(!item) {
FC_TRACE("item " << txt << " not found in " << doc->getName());
return;
}
scrollToItem(item);
Selection().setPreselect(obj->getDocument()->getName(),
obj->getNameInDocument(), subname.c_str(),0,0,0,2);
if(select) {
Gui::Selection().selStackPush();
Gui::Selection().clearSelection();
Gui::Selection().addSelection(obj->getDocument()->getName(),
obj->getNameInDocument(),subname.c_str());
Gui::Selection().selStackPush();
}else{
searchObject = item->object()->getObject();
item->setBackground(0, QColor(255, 255, 0, 100));
}
FC_TRACE("found item " << txt);
} catch(...)
{
FC_TRACE("item " << txt << " search exception in " << doc->getName());
}
}
Gui::Document *TreeWidget::selectedDocument() {
for(auto tree : Instances) {
if(!tree->isVisible())
continue;
auto sels = tree->selectedItems();
if(sels.size()==1 && sels[0]->type()==DocumentType)
return static_cast<DocumentItem*>(sels[0])->document();
}
return 0;
}
void TreeWidget::updateStatus(bool delay) {
for(auto tree : Instances)
tree->_updateStatus(delay);
}
void TreeWidget::_updateStatus(bool delay) {
if(!delay) {
if(ChangedObjects.size() || NewObjects.size())
onUpdateStatus();
return;
}
int timeout = TreeParams::Instance()->StatusTimeout();
if (timeout<0)
timeout = 1;
FC_LOG("delay update status");
statusTimer->start(timeout);
}
void TreeWidget::contextMenuEvent (QContextMenuEvent * e)
{
// ask workbenches and view provider, ...
MenuItem view;
Gui::Application::Instance->setupContextMenu("Tree", &view);
view << "Std_Expressions";
Workbench::createLinkMenu(&view);
QMenu contextMenu;
QMenu subMenu;
QMenu editMenu;
QActionGroup subMenuGroup(&subMenu);
subMenuGroup.setExclusive(true);
connect(&subMenuGroup, SIGNAL(triggered(QAction*)),
this, SLOT(onActivateDocument(QAction*)));
MenuManager::getInstance()->setupContextMenu(&view, contextMenu);
// get the current item
this->contextItem = itemAt(e->pos());
if (this->contextItem && this->contextItem->type() == DocumentType) {
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
App::Document* doc = docitem->document()->getDocument();
App::GetApplication().setActiveDocument(doc);
showHiddenAction->setChecked(docitem->showHidden());
contextMenu.addAction(this->showHiddenAction);
contextMenu.addAction(this->searchObjectsAction);
contextMenu.addAction(this->closeDocAction);
if(doc->testStatus(App::Document::PartialDoc))
contextMenu.addAction(this->reloadDocAction);
else {
for(auto d : doc->getDependentDocuments()) {
if(d->testStatus(App::Document::PartialDoc)) {
contextMenu.addAction(this->reloadDocAction);
break;
}
}
this->skipRecomputeAction->setChecked(doc->testStatus(App::Document::SkipRecompute));
contextMenu.addAction(this->skipRecomputeAction);
this->allowPartialRecomputeAction->setChecked(doc->testStatus(App::Document::AllowPartialRecompute));
if(doc->testStatus(App::Document::SkipRecompute))
contextMenu.addAction(this->allowPartialRecomputeAction);
contextMenu.addAction(this->markRecomputeAction);
contextMenu.addAction(this->createGroupAction);
}
contextMenu.addSeparator();
}
else if (this->contextItem && this->contextItem->type() == ObjectType) {
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>
(this->contextItem);
App::Document* doc = objitem->object()->getObject()->getDocument();
showHiddenAction->setChecked(doc->ShowHidden.getValue());
contextMenu.addAction(this->showHiddenAction);
hideInTreeAction->setChecked(!objitem->object()->showInTree());
contextMenu.addAction(this->hideInTreeAction);
if (objitem->object()->getObject()->isDerivedFrom(App::DocumentObjectGroup::getClassTypeId()))
contextMenu.addAction(this->createGroupAction);
contextMenu.addAction(this->markRecomputeAction);
contextMenu.addAction(this->recomputeObjectAction);
contextMenu.addAction(this->relabelObjectAction);
auto selItems = this->selectedItems();
// if only one item is selected setup the edit menu
if (selItems.size() == 1) {
objitem->object()->setupContextMenu(&editMenu, this, SLOT(onStartEditing()));
QList<QAction*> editAct = editMenu.actions();
if (!editAct.isEmpty()) {
QAction* topact = contextMenu.actions().front();
for (QList<QAction*>::iterator it = editAct.begin(); it != editAct.end(); ++it)
contextMenu.insertAction(topact,*it);
QAction* first = editAct.front();
contextMenu.setDefaultAction(first);
if (objitem->object()->isEditing())
contextMenu.insertAction(topact, this->finishEditingAction);
contextMenu.insertSeparator(topact);
}
}
}
// add a submenu to active a document if two or more exist
std::vector<App::Document*> docs = App::GetApplication().getDocuments();
if (docs.size() >= 2) {
contextMenu.addSeparator();
App::Document* activeDoc = App::GetApplication().getActiveDocument();
subMenu.setTitle(tr("Activate document"));
contextMenu.addMenu(&subMenu);
QAction* active = 0;
for (std::vector<App::Document*>::iterator it = docs.begin(); it != docs.end(); ++it) {
QString label = QString::fromUtf8((*it)->Label.getValue());
QAction* action = subMenuGroup.addAction(label);
action->setCheckable(true);
action->setStatusTip(tr("Activate document %1").arg(label));
action->setData(QByteArray((*it)->getName()));
if (*it == activeDoc) active = action;
}
if (active)
active->setChecked(true);
subMenu.addActions(subMenuGroup.actions());
}
if (contextMenu.actions().count() > 0) {
try {
contextMenu.exec(QCursor::pos());
} catch (Base::Exception &e) {
e.ReportException();
} catch (std::exception &e) {
FC_ERR("C++ exception: " << e.what());
} catch (...) {
FC_ERR("Unknown exception");
}
contextItem = 0;
}
}
void TreeWidget::hideEvent(QHideEvent *ev) {
// No longer required. Visibility is now handled inside onUpdateStatus() by
// UpdateDisabler.
#if 0
TREE_TRACE("detaching selection observer");
this->detachSelection();
selectTimer->stop();
#endif
QTreeWidget::hideEvent(ev);
}
void TreeWidget::showEvent(QShowEvent *ev) {
// No longer required. Visibility is now handled inside onUpdateStatus() by
// UpdateDisabler.
#if 0
TREE_TRACE("attaching selection observer");
this->attachSelection();
int timeout = TreeParams::Instance()->SelectionTimeout();
if(timeout<=0)
timeout = 1;
selectTimer->start(timeout);
_updateStatus();
#endif
QTreeWidget::showEvent(ev);
}
void TreeWidget::onCreateGroup()
{
QString name = tr("Group");
App::AutoTransaction trans("Create group");
if (this->contextItem->type() == DocumentType) {
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
App::Document* doc = docitem->document()->getDocument();
QString cmd = QString::fromLatin1("App.getDocument(\"%1\").addObject"
"(\"App::DocumentObjectGroup\",\"%2\")")
.arg(QString::fromLatin1(doc->getName()), name);
Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8());
}
else if (this->contextItem->type() == ObjectType) {
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>
(this->contextItem);
App::DocumentObject* obj = objitem->object()->getObject();
App::Document* doc = obj->getDocument();
QString cmd = QString::fromLatin1("App.getDocument(\"%1\").getObject(\"%2\")"
".newObject(\"App::DocumentObjectGroup\",\"%3\")")
.arg(QString::fromLatin1(doc->getName()),
QString::fromLatin1(obj->getNameInDocument()),
name);
Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8());
}
}
void TreeWidget::onRelabelObject()
{
QTreeWidgetItem* item = currentItem();
if (item)
editItem(item);
}
void TreeWidget::onStartEditing()
{
QAction* action = qobject_cast<QAction*>(sender());
if (action) {
if (this->contextItem && this->contextItem->type() == ObjectType) {
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>
(this->contextItem);
int edit = action->data().toInt();
App::DocumentObject* obj = objitem->object()->getObject();
if (!obj || !obj->getNameInDocument())
return;
auto doc = const_cast<Document*>(objitem->getOwnerDocument()->document());
MDIView *view = doc->getActiveView();
if (view) getMainWindow()->setActiveWindow(view);
// Always open a transaction here doesn't make much sense because:
// - many objects open transactions when really changing some properties
// - this leads to certain inconsistencies with the doubleClicked() method
// So, only the view provider class should decide what to do
#if 0
// open a transaction before starting edit mode
std::string cmd("Edit ");
cmd += obj->Label.getValue();
doc->openCommand(cmd.c_str());
bool ok = doc->setEdit(objitem->object(), edit);
if (!ok) doc->abortCommand();
#else
editingItem = objitem;
if(!doc->setEdit(objitem->object(), edit))
editingItem = 0;
#endif
}
}
}
void TreeWidget::onFinishEditing()
{
if (this->contextItem && this->contextItem->type() == ObjectType) {
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>
(this->contextItem);
App::DocumentObject* obj = objitem->object()->getObject();
if (!obj) return;
Gui::Document* doc = Gui::Application::Instance->getDocument(obj->getDocument());
doc->commitCommand();
doc->resetEdit();
doc->getDocument()->recompute();
}
}
void TreeWidget::onSkipRecompute(bool on)
{
// if a document item is selected then touch all objects
if (this->contextItem && this->contextItem->type() == DocumentType) {
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
App::Document* doc = docitem->document()->getDocument();
doc->setStatus(App::Document::SkipRecompute, on);
}
}
void TreeWidget::onAllowPartialRecompute(bool on)
{
// if a document item is selected then touch all objects
if (this->contextItem && this->contextItem->type() == DocumentType) {
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
App::Document* doc = docitem->document()->getDocument();
doc->setStatus(App::Document::AllowPartialRecompute, on);
}
}
void TreeWidget::onMarkRecompute()
{
// if a document item is selected then touch all objects
if (this->contextItem && this->contextItem->type() == DocumentType) {
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
App::Document* doc = docitem->document()->getDocument();
std::vector<App::DocumentObject*> obj = doc->getObjects();
for (std::vector<App::DocumentObject*>::iterator it = obj.begin(); it != obj.end(); ++it)
(*it)->enforceRecompute();
}
// mark all selected objects
else {
QList<QTreeWidgetItem*> items = this->selectedItems();
for (QList<QTreeWidgetItem*>::iterator it = items.begin(); it != items.end(); ++it) {
if ((*it)->type() == ObjectType) {
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>(*it);
App::DocumentObject* obj = objitem->object()->getObject();
obj->enforceRecompute();
}
}
}
}
void TreeWidget::onRecomputeObject() {
std::vector<App::DocumentObject*> objs;
for(auto ti : selectedItems()) {
if (ti->type() == ObjectType) {
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>(ti);
objs.push_back(objitem->object()->getObject());
objs.back()->enforceRecompute();
}
}
if(objs.empty())
return;
App::AutoTransaction committer("Recompute object");
objs.front()->getDocument()->recompute(objs,true);
}
DocumentItem *TreeWidget::getDocumentItem(const Gui::Document *doc) const {
auto it = DocumentMap.find(doc);
if(it != DocumentMap.end())
return it->second;
return 0;
}
void TreeWidget::selectAllInstances(const ViewProviderDocumentObject &vpd) {
if(!isConnectionAttached())
return;
if(selectTimer->isActive())
onSelectTimer();
else
_updateStatus(false);
for(const auto &v : DocumentMap)
v.second->selectAllInstances(vpd);
}
TreeWidget *TreeWidget::instance() {
auto res = _LastSelectedTreeWidget;
if(res && res->isVisible())
return res;
for(auto inst : Instances) {
if(!res) res = inst;
if(inst->isVisible())
return inst;
}
return res;
}
std::vector<TreeWidget::SelInfo> TreeWidget::getSelection(App::Document *doc)
{
std::vector<SelInfo> ret;
TreeWidget *tree = instance();
if(!tree || !tree->isConnectionAttached()) {
for(auto pTree : Instances)
if(pTree->isConnectionAttached()) {
tree = pTree;
break;
}
}
if(!tree) return ret;
if(tree->selectTimer->isActive())
tree->onSelectTimer();
else
tree->_updateStatus(false);
for(auto ti : tree->selectedItems()) {
if(ti->type() != ObjectType) continue;
auto item = static_cast<DocumentObjectItem*>(ti);
auto vp = item->object();
auto obj = vp->getObject();
if(!obj || !obj->getNameInDocument()) {
FC_WARN("skip invalid object");
continue;
}
if(doc && obj->getDocument()!=doc) {
FC_LOG("skip objects not from current document");
continue;
}
ViewProviderDocumentObject *parentVp = 0;
auto parent = item->getParentItem();
if(parent) {
parentVp = parent->object();
if(!parentVp->getObject()->getNameInDocument()) {
FC_WARN("skip '" << obj->getFullName() << "' with invalid parent");
continue;
}
}
ret.emplace_back();
auto &sel = ret.back();
sel.topParent = 0;
std::ostringstream ss;
item->getSubName(ss,sel.topParent);
if(!sel.topParent)
sel.topParent = obj;
else
ss << obj->getNameInDocument() << '.';
sel.subname = ss.str();
sel.parentVp = parentVp;
sel.vp = vp;
}
return ret;
}
void TreeWidget::selectAllLinks(App::DocumentObject *obj) {
if(!isConnectionAttached())
return;
if(!obj || !obj->getNameInDocument()) {
TREE_ERR("invalid object");
return;
}
if(selectTimer->isActive())
onSelectTimer();
else
_updateStatus(false);
for(auto link: App::GetApplication().getLinksTo(obj,App::GetLinkRecursive))
{
if(!link || !link->getNameInDocument()) {
TREE_ERR("invalid linked object");
continue;
}
auto vp = dynamic_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(link));
if(!vp) {
TREE_ERR("invalid view provider of the linked object");
continue;
}
for(auto &v : DocumentMap)
v.second->selectAllInstances(*vp);
}
}
void TreeWidget::onSearchObjects()
{
emitSearchObjects();
}
void TreeWidget::onActivateDocument(QAction* active)
{
// activate the specified document
QByteArray docname = active->data().toByteArray();
Gui::Document* doc = Application::Instance->getDocument((const char*)docname);
if (doc && !doc->setActiveView())
doc->setActiveView(0,View3DInventor::getClassTypeId());
}
Qt::DropActions TreeWidget::supportedDropActions () const
{
return Qt::LinkAction | Qt::CopyAction | Qt::MoveAction;
}
bool TreeWidget::event(QEvent *e)
{
#if 0
if (e->type() == QEvent::ShortcutOverride) {
QKeyEvent* ke = static_cast<QKeyEvent *>(e);
switch (ke->key()) {
case Qt::Key_Delete:
ke->accept();
}
}
#endif
return QTreeWidget::event(e);
}
bool TreeWidget::eventFilter(QObject *, QEvent *ev) {
switch (ev->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease: {
QKeyEvent *ke = static_cast<QKeyEvent *>(ev);
if (ke->key() != Qt::Key_Escape) {
// Qt 5 only recheck key modifier on mouse move, so generate a fake
// event to trigger drag cursor change
QMouseEvent *mouseEvent = new QMouseEvent(QEvent::MouseMove,
mapFromGlobal(QCursor::pos()), QCursor::pos(), Qt::NoButton,
QApplication::mouseButtons(), QApplication::queryKeyboardModifiers());
QApplication::postEvent(this,mouseEvent);
}
break;
}
default:
break;
}
return false;
}
void TreeWidget::keyPressEvent(QKeyEvent *event)
{
#if 0
if (event && event->matches(QKeySequence::Delete)) {
event->ignore();
}
#endif
if(event->matches(QKeySequence::Find)) {
event->accept();
onSearchObjects();
return;
}else if(event->key() == Qt::Key_Left) {
auto index = currentIndex();
if(index.column()==1) {
setCurrentIndex(index.parent().child(index.row(),0));
event->accept();
return;
}
}else if(event->key() == Qt::Key_Right) {
auto index = currentIndex();
if(index.column()==0) {
setCurrentIndex(index.parent().child(index.row(),1));
event->accept();
return;
}
}
QTreeWidget::keyPressEvent(event);
}
void TreeWidget::mouseDoubleClickEvent (QMouseEvent * event)
{
QTreeWidgetItem* item = itemAt(event->pos());
if (!item) return;
try {
if (item->type() == TreeWidget::DocumentType) {
//QTreeWidget::mouseDoubleClickEvent(event);
Gui::Document* doc = static_cast<DocumentItem*>(item)->document();
if (!doc) return;
if(doc->getDocument()->testStatus(App::Document::PartialDoc)) {
contextItem = item;
onReloadDoc();
return;
}
if(!doc->setActiveView())
doc->setActiveView(0,View3DInventor::getClassTypeId());
}
else if (item->type() == TreeWidget::ObjectType) {
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>(item);
objitem->getOwnerDocument()->document()->setActiveView(objitem->object());
auto manager = Application::Instance->macroManager();
auto lines = manager->getLines();
auto editDoc = Application::Instance->editDocument();
App::AutoTransaction committer("Double click", true);
std::ostringstream ss;
ss << Command::getObjectCmd(objitem->object()->getObject())
<< ".ViewObject.doubleClicked()";
if (!objitem->object()->doubleClicked())
QTreeWidget::mouseDoubleClickEvent(event);
else if(lines == manager->getLines())
manager->addLine(MacroManager::Gui,ss.str().c_str());
// If the double click starts an editing, let the transaction persist
if(!editDoc && Application::Instance->editDocument())
committer.setEnable(false);
}
} catch (Base::Exception &e) {
e.ReportException();
} catch (std::exception &e) {
FC_ERR("C++ exception: " << e.what());
} catch (...) {
FC_ERR("Unknown exception");
}
}
void TreeWidget::startDragging() {
if(state() != NoState)
return;
if(selectedItems().empty())
return;
setState(DraggingState);
startDrag(model()->supportedDragActions());
setState(NoState);
stopAutoScroll();
}
void TreeWidget::startDrag(Qt::DropActions supportedActions)
{
QTreeWidget::startDrag(supportedActions);
if(_DragEventFilter) {
_DragEventFilter = false;
qApp->removeEventFilter(this);
}
}
QMimeData * TreeWidget::mimeData (const QList<QTreeWidgetItem *> items) const
{
#if 0
// all selected items must reference an object from the same document
App::Document* doc=0;
for (QList<QTreeWidgetItem *>::ConstIterator it = items.begin(); it != items.end(); ++it) {
if ((*it)->type() != TreeWidget::ObjectType)
return 0;
App::DocumentObject* obj = static_cast<DocumentObjectItem *>(*it)->object()->getObject();
if (!doc)
doc = obj->getDocument();
else if (doc != obj->getDocument())
return 0;
}
#endif
return QTreeWidget::mimeData(items);
}
bool TreeWidget::dropMimeData(QTreeWidgetItem *parent, int index,
const QMimeData *data, Qt::DropAction action)
{
return QTreeWidget::dropMimeData(parent, index, data, action);
}
void TreeWidget::dragEnterEvent(QDragEnterEvent * event)
{
QTreeWidget::dragEnterEvent(event);
}
void TreeWidget::dragLeaveEvent(QDragLeaveEvent * event)
{
QTreeWidget::dragLeaveEvent(event);
}
void TreeWidget::dragMoveEvent(QDragMoveEvent *event)
{
#if QT_VERSION >= 0x050000
// Qt5 does not change drag cursor in response to modifier key press,
// because QDrag installs a event filter that eats up key event. We install
// a filter after Qt and generate fake mouse move event in response to key
// press event, which triggers QDrag to update its cursor
if(!_DragEventFilter) {
_DragEventFilter = true;
qApp->installEventFilter(this);
}
#endif
QTreeWidget::dragMoveEvent(event);
if (!event->isAccepted())
return;
auto modifier = QApplication::queryKeyboardModifiers();
QTreeWidgetItem* targetItem = itemAt(event->pos());
if (!targetItem || this->isItemSelected(targetItem)) {
leaveEvent(0);
event->ignore();
}
else if (targetItem->type() == TreeWidget::DocumentType) {
leaveEvent(0);
if(modifier== Qt::ControlModifier)
event->setDropAction(Qt::CopyAction);
else if(modifier== Qt::AltModifier)
event->setDropAction(Qt::LinkAction);
else
event->setDropAction(Qt::MoveAction);
}
else if (targetItem->type() == TreeWidget::ObjectType) {
onItemEntered(targetItem);
DocumentObjectItem* targetItemObj = static_cast<DocumentObjectItem*>(targetItem);
Gui::ViewProviderDocumentObject* vp = targetItemObj->object();
try {
auto items = selectedItems();
if(modifier == Qt::ControlModifier)
event->setDropAction(Qt::CopyAction);
else if(modifier== Qt::AltModifier && items.size()==1)
event->setDropAction(Qt::LinkAction);
else
event->setDropAction(Qt::MoveAction);
auto da = event->dropAction();
bool dropOnly = da==Qt::CopyAction || da==Qt::MoveAction;
if (da!=Qt::LinkAction && !vp->canDropObjects()) {
if(!(event->possibleActions() & Qt::LinkAction) || items.size()!=1) {
TREE_TRACE("cannot drop");
event->ignore();
return;
}
}
for(auto ti : items) {
if (ti->type() != TreeWidget::ObjectType) {
TREE_TRACE("cannot drop");
event->ignore();
return;
}
auto item = static_cast<DocumentObjectItem*>(ti);
auto obj = item->object()->getObject();
if(!dropOnly && !vp->canDragAndDropObject(obj)) {
// check if items can be dragged
auto parentItem = item->getParentItem();
if(parentItem
&& (!parentItem->object()->canDragObjects()
|| !parentItem->object()->canDragObject(item->object()->getObject())))
{
if(!(event->possibleActions() & Qt::CopyAction)) {
TREE_TRACE("Cannot drag object");
event->ignore();
return;
}
event->setDropAction(Qt::CopyAction);
}
}
std::ostringstream str;
auto owner = item->getRelativeParent(str,targetItemObj);
auto subname = str.str();
// let the view provider decide to accept the object or ignore it
if (da!=Qt::LinkAction && !vp->canDropObjectEx(obj,owner,subname.c_str(), item->mySubs)) {
if(event->possibleActions() & Qt::LinkAction) {
if(items.size()>1) {
TREE_TRACE("Cannot replace with more than one object");
event->ignore();
return;
}
if(!targetItemObj->getParentItem()) {
TREE_TRACE("Cannot replace without parent");
event->ignore();
return;
}
event->setDropAction(Qt::LinkAction);
return;
}
TREE_TRACE("cannot drop " << obj->getFullName() << ' '
<< (owner?owner->getFullName():"<No Owner>") << '.' << subname);
event->ignore();
return;
}
}
} catch (Base::Exception &e){
e.ReportException();
event->ignore();
} catch (std::exception &e) {
FC_ERR("C++ exception: " << e.what());
event->ignore();
} catch (...) {
FC_ERR("Unknown exception");
event->ignore();
}
}
else {
leaveEvent(0);
event->ignore();
}
}
struct ItemInfo {
std::string doc;
std::string obj;
std::string parentDoc;
std::string parent;
std::string ownerDoc;
std::string owner;
std::string subname;
std::string topDoc;
std::string topObj;
std::string topSubname;
std::vector<std::string> subs;
bool dragging = false;
};
struct ItemInfo2 {
std::string doc;
std::string obj;
std::string parentDoc;
std::string parent;
std::string topDoc;
std::string topObj;
std::string topSubname;
};
void TreeWidget::dropEvent(QDropEvent *event)
{
//FIXME: This should actually be done inside dropMimeData
bool touched = false;
QTreeWidgetItem* targetItem = itemAt(event->pos());
// not dropped onto an item
if (!targetItem)
return;
// one of the source items is also the destination item, that's not allowed
if (this->isItemSelected(targetItem))
return;
App::Document *thisDoc;
Base::EmptySequencer seq;
// filter out the selected items we cannot handle
std::vector<std::pair<DocumentObjectItem*,std::vector<std::string> > > items;
auto sels = selectedItems();
items.reserve(sels.size());
for(auto ti : sels) {
if (ti->type() != TreeWidget::ObjectType)
continue;
// ignore child elements if the parent is selected
if(sels.contains(ti->parent()))
continue;
if (ti == targetItem)
continue;
auto item = static_cast<DocumentObjectItem*>(ti);
items.emplace_back();
auto &info = items.back();
info.first = item;
info.second.insert(info.second.end(),item->mySubs.begin(),item->mySubs.end());
}
if (items.empty())
return; // nothing needs to be done
std::string errMsg;
if(QApplication::keyboardModifiers()== Qt::ControlModifier)
event->setDropAction(Qt::CopyAction);
else if(QApplication::keyboardModifiers()== Qt::AltModifier
&& (items.size()==1||targetItem->type()==TreeWidget::DocumentType))
event->setDropAction(Qt::LinkAction);
else
event->setDropAction(Qt::MoveAction);
auto da = event->dropAction();
bool dropOnly = da==Qt::CopyAction || da==Qt::LinkAction;
if (targetItem->type() == TreeWidget::ObjectType) {
// add object to group
DocumentObjectItem* targetItemObj = static_cast<DocumentObjectItem*>(targetItem);
thisDoc = targetItemObj->getOwnerDocument()->document()->getDocument();
Gui::ViewProviderDocumentObject* vp = targetItemObj->object();
if(!vp || !vp->getObject() || !vp->getObject()->getNameInDocument()) {
TREE_TRACE("invalid object");
return;
}
if (da!=Qt::LinkAction && !vp->canDropObjects()) {
if(!(event->possibleActions() & Qt::LinkAction) || items.size()!=1) {
TREE_TRACE("Cannot drop objects");
return; // no group like object
}
}
std::ostringstream targetSubname;
App::DocumentObject *targetParent = 0;
targetItemObj->getSubName(targetSubname,targetParent);
Selection().selStackPush();
Selection().clearCompleteSelection();
if(targetParent) {
targetSubname << vp->getObject()->getNameInDocument() << '.';
Selection().addSelection(targetParent->getDocument()->getName(),
targetParent->getNameInDocument(), targetSubname.str().c_str());
} else {
targetParent = targetItemObj->object()->getObject();
Selection().addSelection(targetParent->getDocument()->getName(),
targetParent->getNameInDocument());
}
bool syncPlacement = TreeParams::Instance()->SyncPlacement() && targetItemObj->isGroup();
bool setSelection = true;
std::vector<std::pair<App::DocumentObject*,std::string> > droppedObjects;
std::vector<ItemInfo> infos;
// Only keep text names here, because you never know when doing drag
// and drop some object may delete other objects.
infos.reserve(items.size());
for(auto &v : items) {
infos.emplace_back();
auto &info = infos.back();
auto item = v.first;
Gui::ViewProviderDocumentObject* vpc = item->object();
App::DocumentObject* obj = vpc->getObject();
std::ostringstream str;
App::DocumentObject *topParent=0;
auto owner = item->getRelativeParent(str,targetItemObj,&topParent,&info.topSubname);
if(syncPlacement && topParent) {
info.topDoc = topParent->getDocument()->getName();
info.topObj = topParent->getNameInDocument();
}
info.subname = str.str();
info.doc = obj->getDocument()->getName();
info.obj = obj->getNameInDocument();
if(owner) {
info.ownerDoc = owner->getDocument()->getName();
info.owner = owner->getNameInDocument();
}
info.subs.swap(v.second);
// check if items can be dragged
if(!dropOnly &&
item->myOwner == targetItemObj->myOwner &&
vp->canDragAndDropObject(item->object()->getObject()))
{
// check if items can be dragged
auto parentItem = item->getParentItem();
if(!parentItem)
info.dragging = true;
else if(parentItem->object()->canDragObjects()
&& parentItem->object()->canDragObject(item->object()->getObject()))
{
info.dragging = true;
auto vpp = parentItem->object();
info.parent = vpp->getObject()->getNameInDocument();
info.parentDoc = vpp->getObject()->getDocument()->getName();
}
}
if (da!=Qt::LinkAction
&& !vp->canDropObjectEx(obj,owner,info.subname.c_str(),item->mySubs))
{
if(event->possibleActions() & Qt::LinkAction) {
if(items.size()>1) {
TREE_TRACE("Cannot replace with more than one object");
return;
}
auto ext = vp->getObject()->getExtensionByType<App::LinkBaseExtension>(true);
if((!ext || !ext->getLinkedObjectProperty()) && !targetItemObj->getParentItem()) {
TREE_TRACE("Cannot replace without parent");
return;
}
da = Qt::LinkAction;
}
}
}
// Open command
App::AutoTransaction committer("Drop object");
try {
auto targetObj = targetItemObj->object()->getObject();
std::set<App::DocumentObject*> inList;
auto parentObj = targetObj;
if(da == Qt::LinkAction && targetItemObj->getParentItem())
parentObj = targetItemObj->getParentItem()->object()->getObject();
inList = parentObj->getInListEx(true);
inList.insert(parentObj);
std::string target = targetObj->getNameInDocument();
auto targetDoc = targetObj->getDocument();
for (auto &info : infos) {
auto &subname = info.subname;
targetObj = targetDoc->getObject(target.c_str());
vp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
Application::Instance->getViewProvider(targetObj));
if(!vp) {
FC_ERR("Cannot find drop traget object " << target);
break;
}
auto doc = App::GetApplication().getDocument(info.doc.c_str());
if(!doc) {
FC_WARN("Cannot find document " << info.doc);
continue;
}
auto obj = doc->getObject(info.obj.c_str());
auto vpc = dynamic_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(obj));
if(!vpc) {
FC_WARN("Cannot find dragging object " << info.obj);
continue;
}
ViewProviderDocumentObject *vpp = 0;
if(da!=Qt::LinkAction && info.parentDoc.size()) {
auto parentDoc = App::GetApplication().getDocument(info.parentDoc.c_str());
if(parentDoc) {
auto parent = parentDoc->getObject(info.parent.c_str());
vpp = dynamic_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(parent));
}
if(!vpp) {
FC_WARN("Cannot find dragging object's parent " << info.parent);
continue;
}
}
App::DocumentObject *owner = 0;
if(info.ownerDoc.size()) {
auto ownerDoc = App::GetApplication().getDocument(info.ownerDoc.c_str());
if(ownerDoc)
owner = ownerDoc->getObject(info.owner.c_str());
if(!owner) {
FC_WARN("Cannot find dragging object's top parent " << info.owner);
continue;
}
}
Base::Matrix4D mat;
App::PropertyPlacement *propPlacement = 0;
if(syncPlacement) {
if(info.topObj.size()) {
auto doc = App::GetApplication().getDocument(info.topDoc.c_str());
if(doc) {
auto topObj = doc->getObject(info.topObj.c_str());
if(topObj) {
auto sobj = topObj->getSubObject(info.topSubname.c_str(),0,&mat);
if(sobj == obj) {
propPlacement = Base::freecad_dynamic_cast<App::PropertyPlacement>(
obj->getPropertyByName("Placement"));
}
}
}
}else{
propPlacement = Base::freecad_dynamic_cast<App::PropertyPlacement>(
obj->getPropertyByName("Placement"));
if(propPlacement)
mat = propPlacement->getValue().toMatrix();
}
}
auto dropParent = targetParent;
auto manager = Application::Instance->macroManager();
std::ostringstream ss;
if(vpp) {
auto lines = manager->getLines();
ss << Command::getObjectCmd(vpp->getObject())
<< ".ViewObject.dragObject(" << Command::getObjectCmd(obj) << ')';
vpp->dragObject(obj);
if(manager->getLines() == lines)
manager->addLine(MacroManager::Gui,ss.str().c_str());
owner = 0;
subname.clear();
ss.str("");
obj = doc->getObject(info.obj.c_str());
if(!obj || !obj->getNameInDocument()) {
FC_WARN("Dropping object deleted: " << info.doc << '#' << info.obj);
continue;
}
}
if(da == Qt::MoveAction) {
// Try to adjust relative links to avoid cyclic dependency, may
// throw exception if failed
ss.str("");
ss << Command::getObjectCmd(obj) << ".adjustRelativeLinks("
<< Command::getObjectCmd(targetObj) << ")";
manager->addLine(MacroManager::Gui,ss.str().c_str());
std::set<App::DocumentObject*> visited;
if(obj->adjustRelativeLinks(inList,&visited)) {
inList = parentObj->getInListEx(true);
inList.insert(parentObj);
// TODO: link adjustment and placement adjustment does
// not work together at the moment.
propPlacement = 0;
}
}
if(inList.count(obj))
FC_THROWM(Base::RuntimeError,
"Dependency loop detected for " << obj->getFullName());
std::string dropName;
ss.str("");
if(da == Qt::LinkAction) {
if(targetItemObj->getParentItem()) {
auto parentItem = targetItemObj->getParentItem();
ss << Command::getObjectCmd(
parentItem->object()->getObject(),0,".replaceObject(",true)
<< Command::getObjectCmd(targetObj) << ","
<< Command::getObjectCmd(obj) << ")";
std::ostringstream ss;
dropParent = 0;
parentItem->getSubName(ss,dropParent);
if(dropParent)
ss << parentItem->object()->getObject()->getNameInDocument() << '.';
else
dropParent = parentItem->object()->getObject();
ss << obj->getNameInDocument() << '.';
dropName = ss.str();
} else {
TREE_WARN("ignore replace operation without parent");
continue;
}
Gui::Command::runCommand(Gui::Command::App, ss.str().c_str());
}else{
ss << Command::getObjectCmd(vp->getObject())
<< ".ViewObject.dropObject(" << Command::getObjectCmd(obj);
if(owner) {
ss << "," << Command::getObjectCmd(owner)
<< ",'" << subname << "',[";
}else
ss << ",None,'',[";
for(auto &sub : info.subs)
ss << "'" << sub << "',";
ss << "])";
auto lines = manager->getLines();
dropName = vp->dropObjectEx(obj,owner,subname.c_str(),info.subs);
if(manager->getLines() == lines)
manager->addLine(MacroManager::Gui,ss.str().c_str());
if(dropName.size())
dropName = targetSubname.str() + dropName;
}
touched = true;
// Construct the subname pointing to the dropped object
if(dropName.empty()) {
auto pos = targetSubname.tellp();
targetSubname << obj->getNameInDocument() << '.' << std::ends;
dropName = targetSubname.str();
targetSubname.seekp(pos);
}
Base::Matrix4D newMat;
auto sobj = dropParent->getSubObject(dropName.c_str(),0,&newMat);
if(!sobj) {
FC_LOG("failed to find dropped object "
<< dropParent->getFullName() << '.' << dropName);
setSelection = false;
continue;
}
if(da!=Qt::CopyAction && propPlacement) {
// try to adjust placement
if((info.dragging && sobj==obj) ||
(!info.dragging && sobj->getLinkedObject(false)==obj))
{
if(!info.dragging)
propPlacement = Base::freecad_dynamic_cast<App::PropertyPlacement>(
sobj->getPropertyByName("Placement"));
if(propPlacement) {
newMat *= propPlacement->getValue().inverse().toMatrix();
newMat.inverseGauss();
Base::Placement pla(newMat*mat);
propPlacement->setValueIfChanged(pla);
}
}
}
droppedObjects.emplace_back(dropParent,dropName);
}
Base::FlagToggler<> guard(_DisableCheckTopParent);
if(setSelection && droppedObjects.size()) {
Selection().selStackPush();
Selection().clearCompleteSelection();
for(auto &v : droppedObjects)
Selection().addSelection(v.first->getDocument()->getName(),
v.first->getNameInDocument(), v.second.c_str());
Selection().selStackPush();
}
} catch (const Base::Exception& e) {
e.ReportException();
errMsg = e.what();
} catch (std::exception &e) {
FC_ERR("C++ exception: " << e.what());
errMsg = e.what();
} catch (...) {
FC_ERR("Unknown exception");
errMsg = "Unknown exception";
}
if(errMsg.size()) {
committer.close(true);
QMessageBox::critical(getMainWindow(), QObject::tr("Drag & drop failed"),
QString::fromUtf8(errMsg.c_str()));
return;
}
}
else if (targetItem->type() == TreeWidget::DocumentType) {
auto targetDocItem = static_cast<DocumentItem*>(targetItem);
thisDoc = targetDocItem->document()->getDocument();
std::vector<ItemInfo2> infos;
infos.reserve(items.size());
bool syncPlacement = TreeParams::Instance()->SyncPlacement();
// check if items can be dragged
for(auto &v : items) {
auto item = v.first;
auto obj = item->object()->getObject();
auto parentItem = item->getParentItem();
if(!parentItem) {
if(da==Qt::MoveAction && obj->getDocument()==thisDoc)
continue;
}else if(dropOnly || item->myOwner!=targetItem) {
// We will not drag item out of parent if either, 1) the CTRL
// key is held, or 2) the dragging item is not inside the
// dropping document tree.
parentItem = 0;
}else if(!parentItem->object()->canDragObjects()
|| !parentItem->object()->canDragObject(obj))
{
TREE_ERR("'" << obj->getFullName() << "' cannot be dragged out of '" <<
parentItem->object()->getObject()->getFullName() << "'");
return;
}
infos.emplace_back();
auto &info = infos.back();
info.doc = obj->getDocument()->getName();
info.obj = obj->getNameInDocument();
if(parentItem) {
auto parent = parentItem->object()->getObject();
info.parentDoc = parent->getDocument()->getName();
info.parent = parent->getNameInDocument();
}
if(syncPlacement) {
std::ostringstream ss;
App::DocumentObject *topParent=0;
item->getSubName(ss,topParent);
if(topParent) {
info.topDoc = topParent->getDocument()->getName();
info.topObj = topParent->getNameInDocument();
ss << obj->getNameInDocument() << '.';
info.topSubname = ss.str();
}
}
}
// Because the existence of subname, we must de-select the drag the
// object manually. Just do a complete clear here for simplicity
Selection().selStackPush();
Selection().clearCompleteSelection();
// Open command
auto manager = Application::Instance->macroManager();
App::AutoTransaction committer(
da==Qt::LinkAction?"Link object":
da==Qt::CopyAction?"Copy object":"Move object");
try {
std::vector<App::DocumentObject*> droppedObjs;
for (auto &info : infos) {
auto doc = App::GetApplication().getDocument(info.doc.c_str());
if(!doc) continue;
auto obj = doc->getObject(info.obj.c_str());
auto vpc = dynamic_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(obj));
if(!vpc) {
FC_WARN("Cannot find dragging object " << info.obj);
continue;
}
Base::Matrix4D mat;
App::PropertyPlacement *propPlacement = 0;
if(syncPlacement) {
if(info.topObj.size()) {
auto doc = App::GetApplication().getDocument(info.topDoc.c_str());
if(doc) {
auto topObj = doc->getObject(info.topObj.c_str());
if(topObj) {
auto sobj = topObj->getSubObject(info.topSubname.c_str(),0,&mat);
if(sobj == obj) {
propPlacement = dynamic_cast<App::PropertyPlacement*>(
obj->getPropertyByName("Placement"));
}
}
}
}else{
propPlacement = dynamic_cast<App::PropertyPlacement*>(
obj->getPropertyByName("Placement"));
if(propPlacement)
mat = propPlacement->getValue().toMatrix();
}
}
if(da == Qt::LinkAction) {
std::string name = thisDoc->getUniqueObjectName("Link");
FCMD_DOC_CMD(thisDoc,"addObject('App::Link','" << name << "').setLink("
<< Command::getObjectCmd(obj) << ")");
auto link = thisDoc->getObject(name.c_str());
if(!link)
continue;
FCMD_OBJ_CMD(link,"Label='" << obj->getLinkedObject(true)->Label.getValue() << "'");
propPlacement = dynamic_cast<App::PropertyPlacement*>(link->getPropertyByName("Placement"));
if(propPlacement)
propPlacement->setValueIfChanged(Base::Placement(mat));
droppedObjs.push_back(link);
}else if(info.parent.size()) {
auto parentDoc = App::GetApplication().getDocument(info.parentDoc.c_str());
if(!parentDoc) {
FC_WARN("Canont find document " << info.parentDoc);
continue;
}
auto parent = parentDoc->getObject(info.parent.c_str());
auto vpp = dynamic_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(parent));
if(!vpp) {
FC_WARN("Cannot find dragging object's parent " << info.parent);
continue;
}
std::ostringstream ss;
ss << Command::getObjectCmd(vpp->getObject())
<< ".ViewObject.dragObject(" << Command::getObjectCmd(obj) << ')';
auto lines = manager->getLines();
vpp->dragObject(obj);
if(manager->getLines() == lines)
manager->addLine(MacroManager::Gui,ss.str().c_str());
//make sure it is not part of a geofeaturegroup anymore.
//When this has happen we need to handle all removed
//objects
auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(obj);
if(grp) {
FCMD_OBJ_CMD(grp,"removeObject(" << Command::getObjectCmd(obj) << ")");
}
// check if the object has been deleted
obj = doc->getObject(info.obj.c_str());
if(!obj || !obj->getNameInDocument())
continue;
droppedObjs.push_back(obj);
if(propPlacement)
propPlacement->setValueIfChanged(Base::Placement(mat));
} else {
std::ostringstream ss;
ss << "App.getDocument('" << thisDoc->getName() << "')."
<< (da==Qt::CopyAction?"copyObject(":"moveObject(")
<< Command::getObjectCmd(obj) << ", True)";
App::DocumentObject *res = 0;
if(da == Qt::CopyAction) {
auto copied = thisDoc->copyObject({obj},true);
if(copied.size())
res = copied.back();
}else
res = thisDoc->moveObject(obj,true);
if(res) {
propPlacement = dynamic_cast<App::PropertyPlacement*>(
res->getPropertyByName("Placement"));
if(propPlacement)
propPlacement->setValueIfChanged(Base::Placement(mat));
droppedObjs.push_back(res);
}
manager->addLine(MacroManager::App,ss.str().c_str());
}
}
touched = true;
Base::FlagToggler<> guard(_DisableCheckTopParent);
Selection().setSelection(thisDoc->getName(),droppedObjs);
} catch (const Base::Exception& e) {
e.ReportException();
errMsg = e.what();
} catch (std::exception &e) {
FC_ERR("C++ exception: " << e.what());
errMsg = e.what();
} catch (...) {
FC_ERR("Unknown exception");
errMsg = "Unknown exception";
}
if(errMsg.size()) {
committer.close(true);
QMessageBox::critical(getMainWindow(), QObject::tr("Drag & drop failed"),
QString::fromUtf8(errMsg.c_str()));
return;
}
}
if(touched && TreeParams::Instance()->RecomputeOnDrop())
thisDoc->recompute();
if(touched && TreeParams::Instance()->SyncView()) {
auto gdoc = Application::Instance->getDocument(thisDoc);
if(gdoc)
gdoc->setActiveView();
}
}
void TreeWidget::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const
{
QTreeWidget::drawRow(painter, options, index);
// Set the text and highlighted text color of a hidden object to a dark
//QTreeWidgetItem * item = itemFromIndex(index);
//if (item->type() == ObjectType && !(static_cast<DocumentObjectItem*>(item)->previousStatus & 1)) {
// QStyleOptionViewItem opt(options);
// opt.state ^= QStyle::State_Enabled;
// QColor c = opt.palette.color(QPalette::Inactive, QPalette::Dark);
// opt.palette.setColor(QPalette::Inactive, QPalette::Text, c);
// opt.palette.setColor(QPalette::Inactive, QPalette::HighlightedText, c);
// QTreeWidget::drawRow(painter, opt, index);
//}
//else {
// QTreeWidget::drawRow(painter, options, index);
//}
}
void TreeWidget::slotNewDocument(const Gui::Document& Doc, bool isMainDoc)
{
DocumentItem* item = new DocumentItem(&Doc, this->rootItem);
if(isMainDoc)
this->expandItem(item);
item->setIcon(0, *documentPixmap);
item->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue()));
DocumentMap[ &Doc ] = item;
}
void TreeWidget::slotStartOpenDocument() {
// No longer required. Visibility is now handled inside onUpdateStatus() by
// UpdateDisabler.
//
// setVisible(false);
}
void TreeWidget::slotFinishOpenDocument() {
// setVisible(true);
}
void TreeWidget::onReloadDoc() {
if (!this->contextItem || this->contextItem->type() != DocumentType)
return;
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
App::Document* doc = docitem->document()->getDocument();
std::string name = doc->FileName.getValue();
Application::Instance->reopen(doc);
for(auto &v : DocumentMap) {
if(name == v.first->getDocument()->FileName.getValue()) {
scrollToItem(v.second);
App::GetApplication().setActiveDocument(v.first->getDocument());
break;
}
}
}
void TreeWidget::onCloseDoc() {
if (!this->contextItem || this->contextItem->type() != DocumentType)
return;
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
App::Document* doc = docitem->document()->getDocument();
try {
Command::doCommand(Command::Doc, "App.closeDocument(\"%s\")", doc->getName());
} catch (const Base::Exception& e) {
e.ReportException();
} catch (std::exception &e) {
FC_ERR("C++ exception: " << e.what());
} catch (...) {
FC_ERR("Unknown exception");
}
}
void TreeWidget::slotRenameDocument(const Gui::Document& Doc)
{
// do nothing here
Q_UNUSED(Doc);
}
void TreeWidget::slotChangedViewObject(const Gui::ViewProvider& vp, const App::Property &prop)
{
if(!App::GetApplication().isRestoring()
&& vp.isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()))
{
const auto &vpd = static_cast<const ViewProviderDocumentObject&>(vp);
if(&prop == &vpd.ShowInTree) {
ChangedObjects.emplace(vpd.getObject(),0);
_updateStatus();
}
}
}
void TreeWidget::slotTouchedObject(const App::DocumentObject &obj) {
ChangedObjects.emplace(const_cast<App::DocumentObject*>(&obj),0);
_updateStatus();
}
void TreeWidget::slotShowHidden(const Gui::Document& Doc)
{
auto it = DocumentMap.find(&Doc);
if (it != DocumentMap.end())
it->second->updateItemsVisibility(it->second,it->second->showHidden());
}
void TreeWidget::slotRelabelDocument(const Gui::Document& Doc)
{
auto it = DocumentMap.find(&Doc);
if (it != DocumentMap.end()) {
it->second->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue()));
}
}
void TreeWidget::slotActiveDocument(const Gui::Document& Doc)
{
auto jt = DocumentMap.find(&Doc);
if (jt == DocumentMap.end())
return; // signal is emitted before the item gets created
int displayMode = TreeParams::Instance()->DocumentMode();
for (auto it = DocumentMap.begin();
it != DocumentMap.end(); ++it)
{
QFont f = it->second->font(0);
f.setBold(it == jt);
it->second->setHidden(0 == displayMode && it != jt);
if (2 == displayMode) {
it->second->setExpanded(it == jt);
}
// this must be done as last step
it->second->setFont(0, f);
}
}
struct UpdateDisabler {
QWidget &widget;
int &blocked;
bool visible;
bool focus;
// Note! DO NOT block signal here, or else
// QTreeWidgetItem::setChildIndicatorPolicy() does not work
UpdateDisabler(QWidget &w, int &blocked)
:widget(w),blocked(blocked)
{
if(++blocked > 1)
return;
focus = widget.hasFocus();
visible = widget.isVisible();
if(visible) {
// setUpdatesEnabled(false) does not seem to speed up anything.
// setVisible(false) on the other hand makes QTreeWidget::setData
// (i.e. any change to QTreeWidgetItem) faster by 10+ times.
//
// widget.setUpdatesEnabled(false);
widget.setVisible(false);
}
}
~UpdateDisabler() {
if(blocked<=0 || --blocked!=0)
return;
if(visible) {
widget.setVisible(true);
// widget.setUpdatesEnabled(true);
if(focus)
widget.setFocus();
}
}
};
void TreeWidget::onUpdateStatus(void)
{
if(this->state()==DraggingState || App::GetApplication().isRestoring()) {
_updateStatus();
return;
}
for(auto &v : DocumentMap) {
if(v.first->isPerformingTransaction()) {
// We have to delay item creation until undo/redo is done, because the
// object re-creation while in transaction may break tree view item
// update logic. For example, a parent object re-created before its
// children, but the parent's link property already contains all the
// (detached) children.
_updateStatus();
return;
}
}
FC_LOG("begin update status");
UpdateDisabler disabler(*this,updateBlocked);
std::vector<App::DocumentObject*> errors;
// Checking for new objects
for(auto &v : NewObjects) {
auto doc = App::GetApplication().getDocument(v.first.c_str());
if(!doc)
continue;
auto gdoc = Application::Instance->getDocument(doc);
if(!gdoc)
continue;
auto docItem = getDocumentItem(gdoc);
if(!docItem)
continue;
for(auto id : v.second) {
auto obj = doc->getObjectByID(id);
if(!obj)
continue;
if(obj->isError())
errors.push_back(obj);
if(docItem->ObjectMap.count(obj))
continue;
auto vpd = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(gdoc->getViewProvider(obj));
if(vpd)
docItem->createNewItem(*vpd);
}
}
NewObjects.clear();
// Update children of changed objects
for(auto &v : ChangedObjects) {
auto obj = v.first;
auto iter = ObjectTable.find(obj);
if(iter == ObjectTable.end())
continue;
if(v.second.test(CS_Error) && obj->isError())
errors.push_back(obj);
if(iter->second.size()) {
auto data = *iter->second.begin();
bool itemHidden = !data->viewObject->showInTree();
if(data->itemHidden != itemHidden) {
for(auto &data : iter->second) {
data->itemHidden = itemHidden;
if(data->docItem->showHidden())
continue;
for(auto item : data->items)
item->setHidden(itemHidden);
}
}
}
updateChildren(iter->first, iter->second, v.second.test(CS_Output), false);
}
ChangedObjects.clear();
FC_LOG("update item status");
TimingInit();
for (auto pos = DocumentMap.begin();pos!=DocumentMap.end();++pos) {
pos->second->testStatus();
}
TimingPrint();
// Checking for just restored documents
for(auto &v : DocumentMap) {
auto docItem = v.second;
for(auto obj : docItem->PopulateObjects)
docItem->populateObject(obj);
docItem->PopulateObjects.clear();
auto doc = v.first->getDocument();
if(!docItem->connectChgObject.connected()) {
docItem->connectChgObject = docItem->document()->signalChangedObject.connect(
boost::bind(&TreeWidget::slotChangeObject, this, _1, _2));
docItem->connectTouchedObject = doc->signalTouchedObject.connect(
boost::bind(&TreeWidget::slotTouchedObject, this, _1));
}
if(doc->testStatus(App::Document::PartialDoc))
docItem->setIcon(0, *documentPartialPixmap);
else if(docItem->_ExpandInfo) {
for(auto &entry : *docItem->_ExpandInfo) {
const char *name = entry.first.c_str();
bool legacy = name[0] == '*';
if(legacy)
++name;
auto obj = doc->getObject(name);
if(!obj)
continue;
auto iter = docItem->ObjectMap.find(obj);
if(iter==docItem->ObjectMap.end())
continue;
if(iter->second->rootItem)
docItem->restoreItemExpansion(entry.second,iter->second->rootItem);
else if(legacy && iter->second->items.size()) {
auto item = *iter->second->items.begin();
item->setExpanded(true);
}
}
}
docItem->_ExpandInfo.reset();
}
if(Selection().hasSelection() && !selectTimer->isActive() && !this->isConnectionBlocked()) {
this->blockConnection(true);
currentDocItem = 0;
for(auto &v : DocumentMap) {
v.second->setSelected(false);
v.second->selectItems();
}
this->blockConnection(false);
}
auto currentDocItem = getDocumentItem(Application::Instance->activeDocument());
QTreeWidgetItem *errItem = 0;
for(auto obj : errors) {
DocumentObjectDataPtr data;
if(currentDocItem) {
auto it = currentDocItem->ObjectMap.find(obj);
if(it!=currentDocItem->ObjectMap.end())
data = it->second;
}
if(!data) {
auto docItem = getDocumentItem(
Application::Instance->getDocument(obj->getDocument()));
if(docItem) {
auto it = docItem->ObjectMap.find(obj);
if(it!=docItem->ObjectMap.end())
data = it->second;
}
}
if(data) {
auto item = data->rootItem;
if(!item && data->items.size()) {
item = *data->items.begin();
data->docItem->showItem(item,false,true);
}
if(!errItem)
errItem = item;
}
}
if(errItem)
scrollToItem(errItem);
updateGeometries();
statusTimer->stop();
FC_LOG("done update status");
}
void TreeWidget::onItemEntered(QTreeWidgetItem * item)
{
// object item selected
if (item && item->type() == TreeWidget::ObjectType) {
DocumentObjectItem* objItem = static_cast<DocumentObjectItem*>(item);
objItem->displayStatusInfo();
if(TreeParams::Instance()->PreSelection()) {
int timeout = TreeParams::Instance()->PreSelectionDelay();
if(timeout < 0)
timeout = 1;
if(preselectTime.elapsed() < timeout)
onPreSelectTimer();
else{
timeout = TreeParams::Instance()->PreSelectionTimeout();
if(timeout < 0)
timeout = 1;
preselectTimer->start(timeout);
Selection().rmvPreselect();
}
}
} else if(TreeParams::Instance()->PreSelection())
Selection().rmvPreselect();
}
void TreeWidget::leaveEvent(QEvent *) {
if(!updateBlocked && TreeParams::Instance()->PreSelection()) {
preselectTimer->stop();
Selection().rmvPreselect();
}
}
void TreeWidget::onPreSelectTimer() {
if(!TreeParams::Instance()->PreSelection())
return;
auto item = itemAt(viewport()->mapFromGlobal(QCursor::pos()));
if(!item || item->type()!=TreeWidget::ObjectType)
return;
preselectTime.restart();
DocumentObjectItem* objItem = static_cast<DocumentObjectItem*>(item);
auto vp = objItem->object();
auto obj = vp->getObject();
std::ostringstream ss;
App::DocumentObject *parent = 0;
objItem->getSubName(ss,parent);
if(!parent)
parent = obj;
else if(!obj->redirectSubName(ss,parent,0))
ss << obj->getNameInDocument() << '.';
Selection().setPreselect(parent->getDocument()->getName(),parent->getNameInDocument(),
ss.str().c_str(),0,0,0,2);
}
void TreeWidget::onItemCollapsed(QTreeWidgetItem * item)
{
// object item collapsed
if (item && item->type() == TreeWidget::ObjectType) {
static_cast<DocumentObjectItem*>(item)->setExpandedStatus(false);
}
}
void TreeWidget::onItemExpanded(QTreeWidgetItem * item)
{
// object item expanded
if (item && item->type() == TreeWidget::ObjectType) {
DocumentObjectItem* objItem = static_cast<DocumentObjectItem*>(item);
objItem->setExpandedStatus(true);
objItem->getOwnerDocument()->populateItem(objItem,false,false);
}
}
void TreeWidget::scrollItemToTop()
{
auto doc = Application::Instance->activeDocument();
for(auto tree : Instances) {
if(!tree->isConnectionAttached() || tree->isConnectionBlocked())
continue;
tree->_updateStatus(false);
if(doc && Gui::Selection().hasSelection(doc->getDocument()->getName(),false)) {
auto it = tree->DocumentMap.find(doc);
if (it != tree->DocumentMap.end()) {
bool lock = tree->blockConnection(true);
it->second->selectItems(DocumentItem::SR_FORCE_EXPAND);
tree->blockConnection(lock);
}
} else {
tree->blockConnection(true);
for (int i=0; i<tree->rootItem->childCount(); i++) {
auto docItem = dynamic_cast<DocumentItem*>(tree->rootItem->child(i));
if(!docItem)
continue;
auto doc = docItem->document()->getDocument();
if(Gui::Selection().hasSelection(doc->getName())) {
tree->currentDocItem = docItem;
docItem->selectItems(DocumentItem::SR_FORCE_EXPAND);
tree->currentDocItem = 0;
break;
}
}
tree->blockConnection(false);
}
tree->selectTimer->stop();
tree->_updateStatus(false);
}
}
void TreeWidget::expandSelectedItems(TreeItemMode mode)
{
if(!isConnectionAttached())
return;
for(auto item : selectedItems()) {
switch (mode) {
case TreeItemMode::ExpandPath: {
QTreeWidgetItem* parentItem = item->parent();
while (parentItem) {
parentItem->setExpanded(true);
parentItem = parentItem->parent();
}
item->setExpanded(true);
break;
}
case TreeItemMode::ExpandItem:
item->setExpanded(true);
break;
case TreeItemMode::CollapseItem:
item->setExpanded(false);
break;
case TreeItemMode::ToggleItem:
if (item->isExpanded())
item->setExpanded(false);
else
item->setExpanded(true);
break;
}
}
}
void TreeWidget::setupText()
{
this->headerItem()->setText(0, tr("Labels & Attributes"));
this->headerItem()->setText(1, tr("Description"));
this->rootItem->setText(0, tr("Application"));
this->showHiddenAction->setText(tr("Show hidden items"));
this->showHiddenAction->setStatusTip(tr("Show hidden tree view items"));
this->hideInTreeAction->setText(tr("Hide item"));
this->hideInTreeAction->setStatusTip(tr("Hide the item in tree"));
this->createGroupAction->setText(tr("Create group..."));
this->createGroupAction->setStatusTip(tr("Create a group"));
this->relabelObjectAction->setText(tr("Rename"));
this->relabelObjectAction->setStatusTip(tr("Rename object"));
this->finishEditingAction->setText(tr("Finish editing"));
this->finishEditingAction->setStatusTip(tr("Finish editing object"));
this->closeDocAction->setText(tr("Close document"));
this->closeDocAction->setStatusTip(tr("Close the document"));
this->reloadDocAction->setText(tr("Reload document"));
this->reloadDocAction->setStatusTip(tr("Reload a partially loaded document"));
this->skipRecomputeAction->setText(tr("Skip recomputes"));
this->skipRecomputeAction->setStatusTip(tr("Enable or disable recomputations of document"));
this->allowPartialRecomputeAction->setText(tr("Allow partial recomputes"));
this->allowPartialRecomputeAction->setStatusTip(
tr("Enable or disable recomputating editing object when 'skip recomputation' is enabled"));
this->markRecomputeAction->setText(tr("Mark to recompute"));
this->markRecomputeAction->setStatusTip(tr("Mark this object to be recomputed"));
this->recomputeObjectAction->setText(tr("Recompute object"));
this->recomputeObjectAction->setStatusTip(tr("Recompute the selected object"));
}
void TreeWidget::syncView(ViewProviderDocumentObject *vp)
{
if(currentDocItem && TreeParams::Instance()->SyncView()) {
bool focus = hasFocus();
currentDocItem->document()->setActiveView(vp);
if(focus)
setFocus();
}
}
void TreeWidget::onShowHidden()
{
if (!this->contextItem) return;
DocumentItem *docItem = nullptr;
if(this->contextItem->type() == DocumentType)
docItem = static_cast<DocumentItem*>(contextItem);
else if(this->contextItem->type() == ObjectType)
docItem = static_cast<DocumentObjectItem*>(contextItem)->getOwnerDocument();
if(docItem)
docItem->setShowHidden(showHiddenAction->isChecked());
}
void TreeWidget::onHideInTree()
{
if (this->contextItem && this->contextItem->type() == ObjectType) {
auto item = static_cast<DocumentObjectItem*>(contextItem);
item->object()->ShowInTree.setValue(!hideInTreeAction->isChecked());
}
}
void TreeWidget::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange)
setupText();
QTreeWidget::changeEvent(e);
}
void TreeWidget::onItemSelectionChanged ()
{
if (!this->isConnectionAttached()
|| this->isConnectionBlocked()
|| updateBlocked)
return;
_LastSelectedTreeWidget = this;
// block tmp. the connection to avoid to notify us ourself
bool lock = this->blockConnection(true);
if(selectTimer->isActive())
onSelectTimer();
else
_updateStatus(false);
auto selItems = selectedItems();
// do not allow document item multi-selection
if(selItems.size()) {
auto firstType = selItems.back()->type();
for(auto it=selItems.begin();it!=selItems.end();) {
auto item = *it;
if((firstType==ObjectType && item->type()!=ObjectType)
|| (firstType==DocumentType && item!=selItems.back()))
{
item->setSelected(false);
it = selItems.erase(it);
} else
++it;
}
}
if(selItems.size()<=1) {
if(TreeParams::Instance()->RecordSelection())
Gui::Selection().selStackPush();
// This special handling to deal with possible discrepancy of
// Gui.Selection and Tree view selection because of newly added
// DocumentObject::redirectSubName()
Selection().clearCompleteSelection();
DocumentObjectItem *item=0;
if(selItems.size()) {
if(selItems.front()->type() == ObjectType)
item = static_cast<DocumentObjectItem*>(selItems.front());
else if(selItems.front()->type() == DocumentType) {
auto ditem = static_cast<DocumentItem*>(selItems.front());
if(TreeParams::Instance()->SyncView()) {
bool focus = hasFocus();
ditem->document()->setActiveView();
if(focus)
setFocus();
}
// For triggering property editor refresh
Gui::Selection().signalSelectionChanged(SelectionChanges());
}
}
for(auto &v : DocumentMap) {
currentDocItem = v.second;
v.second->clearSelection(item);
currentDocItem = 0;
}
if(TreeParams::Instance()->RecordSelection())
Gui::Selection().selStackPush();
}else{
for (auto pos = DocumentMap.begin();pos!=DocumentMap.end();++pos) {
currentDocItem = pos->second;
pos->second->updateSelection(pos->second);
currentDocItem = 0;
}
if(TreeParams::Instance()->RecordSelection())
Gui::Selection().selStackPush(true,true);
}
this->blockConnection(lock);
}
void TreeWidget::onSelectTimer() {
_updateStatus(false);
bool syncSelect = TreeParams::Instance()->SyncSelection();
bool locked = this->blockConnection(true);
if(Selection().hasSelection()) {
for(auto &v : DocumentMap) {
v.second->setSelected(false);
currentDocItem = v.second;
v.second->selectItems(syncSelect?DocumentItem::SR_EXPAND:DocumentItem::SR_SELECT);
currentDocItem = 0;
}
}else{
for(auto &v : DocumentMap)
v.second->clearSelection();
}
this->blockConnection(locked);
selectTimer->stop();
return;
}
void TreeWidget::onSelectionChanged(const SelectionChanges& msg)
{
switch (msg.Type)
{
case SelectionChanges::AddSelection:
case SelectionChanges::RmvSelection:
case SelectionChanges::SetSelection:
case SelectionChanges::ClrSelection: {
int timeout = TreeParams::Instance()->SelectionTimeout();
if(timeout<=0)
timeout = 1;
selectTimer->start(timeout);
break;
}
default:
break;
}
}
// ----------------------------------------------------------------------------
/* TRANSLATOR Gui::TreePanel */
TreePanel::TreePanel(const char *name, QWidget* parent)
: QWidget(parent)
{
this->treeWidget = new TreeWidget(name, this);
int indent = TreeParams::Instance()->Indentation();
if(indent)
this->treeWidget->setIndentation(indent);
QVBoxLayout* pLayout = new QVBoxLayout(this);
pLayout->setSpacing(0);
pLayout->setMargin (0);
pLayout->addWidget(this->treeWidget);
connect(this->treeWidget, SIGNAL(emitSearchObjects()),
this, SLOT(showEditor()));
this->searchBox = new Gui::ExpressionLineEdit(this,true);
pLayout->addWidget(this->searchBox);
this->searchBox->hide();
this->searchBox->installEventFilter(this);
#if QT_VERSION >= 0x040700
this->searchBox->setPlaceholderText(tr("Search"));
#endif
connect(this->searchBox, SIGNAL(returnPressed()),
this, SLOT(accept()));
connect(this->searchBox, SIGNAL(textChanged(QString)),
this, SLOT(itemSearch(QString)));
}
TreePanel::~TreePanel()
{
}
void TreePanel::accept()
{
QString text = this->searchBox->text();
hideEditor();
this->treeWidget->setFocus();
this->treeWidget->itemSearch(text,true);
}
bool TreePanel::eventFilter(QObject *obj, QEvent *ev)
{
if (obj != this->searchBox)
return false;
if (ev->type() == QEvent::KeyPress) {
bool consumed = false;
int key = static_cast<QKeyEvent*>(ev)->key();
switch (key) {
case Qt::Key_Escape:
hideEditor();
consumed = true;
treeWidget->setFocus();
break;
default:
break;
}
return consumed;
}
return false;
}
void TreePanel::showEditor()
{
this->searchBox->show();
this->searchBox->setFocus();
this->treeWidget->startItemSearch(searchBox);
}
void TreePanel::hideEditor()
{
static_cast<ExpressionLineEdit*>(this->searchBox)->setDocumentObject(0);
this->searchBox->clear();
this->searchBox->hide();
this->treeWidget->resetItemSearch();
auto sels = this->treeWidget->selectedItems();
if(sels.size())
this->treeWidget->scrollToItem(sels.front());
}
void TreePanel::itemSearch(const QString &text)
{
this->treeWidget->itemSearch(text,false);
}
// ----------------------------------------------------------------------------
/* TRANSLATOR Gui::TreeDockWidget */
TreeDockWidget::TreeDockWidget(Gui::Document* pcDocument,QWidget *parent)
: DockWindow(pcDocument,parent)
{
setWindowTitle(tr("Tree view"));
this->treeWidget = new TreeWidget("TreeView",this);
this->treeWidget->setRootIsDecorated(false);
int indent = TreeParams::Instance()->Indentation();
if(indent)
this->treeWidget->setIndentation(indent);
QGridLayout* pLayout = new QGridLayout(this);
pLayout->setSpacing(0);
pLayout->setMargin (0);
pLayout->addWidget(this->treeWidget, 0, 0 );
}
TreeDockWidget::~TreeDockWidget()
{
}
void TreeWidget::selectLinkedObject(App::DocumentObject *linked) {
if(!isConnectionAttached() || isConnectionBlocked())
return;
auto linkedVp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
Application::Instance->getViewProvider(linked));
if(!linkedVp) {
TREE_ERR("invalid linked view provider");
return;
}
auto linkedDoc = getDocumentItem(linkedVp->getDocument());
if(!linkedDoc) {
TREE_ERR("cannot find document of linked object");
return;
}
if(selectTimer->isActive())
onSelectTimer();
else
_updateStatus(false);
auto it = linkedDoc->ObjectMap.find(linked);
if(it == linkedDoc->ObjectMap.end()) {
TREE_ERR("cannot find tree item of linked object");
return;
}
auto linkedItem = it->second->rootItem;
if(!linkedItem)
linkedItem = *it->second->items.begin();
if(linkedDoc->showItem(linkedItem,true))
scrollToItem(linkedItem);
if(linkedDoc->document()->getDocument() != App::GetApplication().getActiveDocument()) {
bool focus = hasFocus();
linkedDoc->document()->setActiveView(linkedItem->object());
if(focus)
setFocus();
}
}
// ----------------------------------------------------------------------------
DocumentItem::DocumentItem(const Gui::Document* doc, QTreeWidgetItem * parent)
: QTreeWidgetItem(parent, TreeWidget::DocumentType), pDocument(const_cast<Gui::Document*>(doc))
{
// Setup connections
connectNewObject = doc->signalNewObject.connect(boost::bind(&DocumentItem::slotNewObject, this, _1));
connectDelObject = doc->signalDeletedObject.connect(
boost::bind(&TreeWidget::slotDeleteObject, getTree(), _1));
if(!App::GetApplication().isRestoring()) {
connectChgObject = doc->signalChangedObject.connect(
boost::bind(&TreeWidget::slotChangeObject, getTree(), _1, _2));
connectTouchedObject = doc->getDocument()->signalTouchedObject.connect(
boost::bind(&TreeWidget::slotTouchedObject, getTree(), _1));
}
connectEdtObject = doc->signalInEdit.connect(boost::bind(&DocumentItem::slotInEdit, this, _1));
connectResObject = doc->signalResetEdit.connect(boost::bind(&DocumentItem::slotResetEdit, this, _1));
connectHltObject = doc->signalHighlightObject.connect(
boost::bind(&DocumentItem::slotHighlightObject, this, _1,_2,_3,_4,_5));
connectExpObject = doc->signalExpandObject.connect(
boost::bind(&DocumentItem::slotExpandObject, this, _1,_2,_3,_4));
connectScrObject = doc->signalScrollToObject.connect(boost::bind(&DocumentItem::slotScrollToObject, this, _1));
auto adoc = doc->getDocument();
connectRecomputed = adoc->signalRecomputed.connect(boost::bind(&DocumentItem::slotRecomputed, this, _1, _2));
connectRecomputedObj = adoc->signalRecomputedObject.connect(
boost::bind(&DocumentItem::slotRecomputedObject, this, _1));
setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable/*|Qt::ItemIsEditable*/);
treeName = getTree()->getTreeName();
}
DocumentItem::~DocumentItem()
{
connectNewObject.disconnect();
connectDelObject.disconnect();
connectChgObject.disconnect();
connectTouchedObject.disconnect();
connectEdtObject.disconnect();
connectResObject.disconnect();
connectHltObject.disconnect();
connectExpObject.disconnect();
connectScrObject.disconnect();
connectRecomputed.disconnect();
connectRecomputedObj.disconnect();
}
TreeWidget *DocumentItem::getTree() const{
return static_cast<TreeWidget*>(treeWidget());
}
const char *DocumentItem::getTreeName() const {
return treeName;
}
#define FOREACH_ITEM(_item, _obj) \
auto _it = ObjectMap.end();\
if(_obj.getObject() && _obj.getObject()->getNameInDocument())\
_it = ObjectMap.find(_obj.getObject());\
if(_it != ObjectMap.end()) {\
for(auto _item : _it->second->items) {
#define FOREACH_ITEM_ALL(_item) \
for(auto _v : ObjectMap) {\
for(auto _item : _v.second->items) {
#define END_FOREACH_ITEM }}
void DocumentItem::slotInEdit(const Gui::ViewProviderDocumentObject& v)
{
(void)v;
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
unsigned long col = hGrp->GetUnsigned("TreeEditColor",4294902015);
QColor color((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff);
if(!getTree()->editingItem) {
auto doc = Application::Instance->editDocument();
if(!doc)
return;
ViewProviderDocumentObject *parentVp=0;
std::string subname;
auto vp = doc->getInEdit(&parentVp,&subname);
if(!parentVp)
parentVp = dynamic_cast<ViewProviderDocumentObject*>(vp);
if(parentVp)
getTree()->editingItem = findItemByObject(true,parentVp->getObject(),subname.c_str());
}
if(getTree()->editingItem)
getTree()->editingItem->setBackground(0,color);
else{
FOREACH_ITEM(item,v)
item->setBackground(0,color);
END_FOREACH_ITEM
}
}
void DocumentItem::slotResetEdit(const Gui::ViewProviderDocumentObject& v)
{
auto tree = getTree();
FOREACH_ITEM_ALL(item)
if(tree->editingItem) {
if(item == tree->editingItem) {
item->setData(0, Qt::BackgroundColorRole,QVariant());
break;
}
}else if(item->object() == &v)
item->setData(0, Qt::BackgroundColorRole,QVariant());
END_FOREACH_ITEM
tree->editingItem = 0;
}
void DocumentItem::slotNewObject(const Gui::ViewProviderDocumentObject& obj) {
if(!obj.getObject() || !obj.getObject()->getNameInDocument()) {
FC_ERR("view provider not attached");
return;
}
getTree()->NewObjects[pDocument->getDocument()->getName()].push_back(obj.getObject()->getID());
getTree()->_updateStatus();
}
bool DocumentItem::createNewItem(const Gui::ViewProviderDocumentObject& obj,
QTreeWidgetItem *parent, int index, DocumentObjectDataPtr data)
{
const char *name;
if (!obj.getObject() ||
!(name=obj.getObject()->getNameInDocument()) ||
obj.getObject()->testStatus(App::PartialObject))
return false;
if(!data) {
auto &pdata = ObjectMap[obj.getObject()];
if(!pdata) {
pdata = std::make_shared<DocumentObjectData>(
this, const_cast<ViewProviderDocumentObject*>(&obj));
auto &entry = getTree()->ObjectTable[obj.getObject()];
if(entry.size())
pdata->updateChildren(*entry.begin());
else
pdata->updateChildren(true);
entry.insert(pdata);
}else if(pdata->rootItem && parent==NULL) {
Base::Console().Warning("DocumentItem::slotNewObject: Cannot add view provider twice.\n");
return false;
}
data = pdata;
}
DocumentObjectItem* item = new DocumentObjectItem(this,data);
if(!parent || parent==this) {
parent = this;
data->rootItem = item;
if(index<0)
index = findRootIndex(obj.getObject());
}
if(index<0)
parent->addChild(item);
else
parent->insertChild(index,item);
assert(item->parent() == parent);
item->setText(0, QString::fromUtf8(data->label.c_str()));
if(data->label2.size())
item->setText(1, QString::fromUtf8(data->label2.c_str()));
if(!obj.showInTree() && !showHidden())
item->setHidden(true);
item->testStatus(true);
populateItem(item);
return true;
}
ViewProviderDocumentObject *DocumentItem::getViewProvider(App::DocumentObject *obj) {
// Note: It is possible that we receive an invalid pointer from
// claimChildren(), e.g. if multiple properties were changed in
// a transaction and slotChangedObject() is triggered by one
// property being reset before the invalid pointer has been
// removed from another. Currently this happens for
// PartDesign::Body when cancelling a new feature in the dialog.
// First the new feature is deleted, then the Tip property is
// reset, but claimChildren() accesses the Model property which
// still contains the pointer to the deleted feature
//
// return obj && obj->getNameInDocument() && pDocument->isIn(obj);
//
// TODO: is the above isIn() check still necessary? Will
// getNameInDocument() check be sufficient?
if(!obj || !obj->getNameInDocument()) return 0;
ViewProvider *vp;
if(obj->getDocument() == pDocument->getDocument())
vp = pDocument->getViewProvider(obj);
else
vp = Application::Instance->getViewProvider(obj);
if(!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()))
return 0;
return static_cast<ViewProviderDocumentObject*>(vp);
}
void TreeWidget::slotDeleteDocument(const Gui::Document& Doc)
{
NewObjects.erase(Doc.getDocument()->getName());
auto it = DocumentMap.find(&Doc);
if (it != DocumentMap.end()) {
UpdateDisabler disabler(*this,updateBlocked);
auto docItem = it->second;
for(auto &v : docItem->ObjectMap) {
for(auto item : v.second->items)
item->myOwner = 0;
auto obj = v.second->viewObject->getObject();
if(obj->getDocument() == Doc.getDocument()) {
_slotDeleteObject(*v.second->viewObject, docItem);
continue;
}
auto it = ObjectTable.find(obj);
assert(it!=ObjectTable.end());
assert(it->second.size()>1);
it->second.erase(v.second);
}
this->rootItem->takeChild(this->rootItem->indexOfChild(docItem));
delete docItem;
DocumentMap.erase(it);
}
}
void TreeWidget::slotDeleteObject(const Gui::ViewProviderDocumentObject& view) {
_slotDeleteObject(view, 0);
}
void TreeWidget::_slotDeleteObject(const Gui::ViewProviderDocumentObject& view, DocumentItem *deletingDoc)
{
auto obj = view.getObject();
auto itEntry = ObjectTable.find(obj);
if(itEntry == ObjectTable.end())
return;
if(itEntry->second.empty()) {
ObjectTable.erase(itEntry);
return;
}
TREE_LOG("delete object " << obj->getFullName());
bool needUpdate = false;
for(auto data : itEntry->second) {
DocumentItem *docItem = data->docItem;
if(docItem == deletingDoc)
continue;
auto doc = docItem->document()->getDocument();
auto &items = data->items;
if(obj->getDocument() == doc)
docItem->_ParentMap.erase(obj);
bool lock = blockConnection(true);
for(auto cit=items.begin(),citNext=cit;cit!=items.end();cit=citNext) {
++citNext;
(*cit)->myOwner = 0;
delete *cit;
}
blockConnection(lock);
// Check for any child of the deleted object that is not in the tree, and put it
// under document item.
for(auto child : data->children) {
if(!child || !child->getNameInDocument() || child->getDocument()!=doc)
continue;
docItem->_ParentMap[child].erase(obj);
auto cit = docItem->ObjectMap.find(child);
if(cit==docItem->ObjectMap.end() || cit->second->items.empty()) {
auto vpd = docItem->getViewProvider(child);
if(!vpd) continue;
if(docItem->createNewItem(*vpd))
needUpdate = true;
}else {
auto childItem = *cit->second->items.begin();
if(childItem->requiredAtRoot(false)) {
if(docItem->createNewItem(*childItem->object(),docItem,-1,childItem->myData))
needUpdate = true;
}
}
if(child->Visibility.getValue() && !docItem->isObjectShowable(child))
child->Visibility.setValue(false);
}
docItem->ObjectMap.erase(obj);
}
ObjectTable.erase(itEntry);
if(needUpdate)
_updateStatus();
}
bool DocumentItem::populateObject(App::DocumentObject *obj) {
// make sure at least one of the item corresponding to obj is populated
auto it = ObjectMap.find(obj);
if(it == ObjectMap.end())
return false;
auto &items = it->second->items;
if(items.empty())
return false;
for(auto item : items) {
if(item->populated)
return true;
}
TREE_LOG("force populate object " << obj->getFullName());
auto item = *items.begin();
item->populated = true;
populateItem(item,true);
return true;
}
void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh, bool delay)
{
(void)delay;
if (item->populated && !refresh)
return;
// Lazy loading policy: We will create an item for each children object if
// a) the item is expanded, or b) there is at least one free child, i.e.
// child originally located at root.
item->setChildIndicatorPolicy(item->myData->children.empty()?
QTreeWidgetItem::DontShowIndicator:QTreeWidgetItem::ShowIndicator);
if (!item->populated && !item->isExpanded()) {
bool doPopulate = false;
bool external = item->object()->getDocument()!=item->getOwnerDocument()->document();
if(external)
return;
auto obj = item->object()->getObject();
auto linked = obj->getLinkedObject(true);
if (linked && linked->getDocument()!=obj->getDocument())
return;
for(auto child : item->myData->children) {
auto it = ObjectMap.find(child);
if(it == ObjectMap.end() || it->second->items.empty()) {
auto vp = getViewProvider(child);
if(!vp) continue;
doPopulate = true;
break;
}
if(item->myData->removeChildrenFromRoot) {
if(it->second->rootItem) {
doPopulate = true;
break;
}
}
}
if (!doPopulate)
return;
}
item->populated = true;
bool checkHidden = !showHidden();
bool updated = false;
int i=-1;
// iterate through the claimed children, and try to synchronize them with the
// children tree item with the same order of appearance.
int childCount = item->childCount();
for(auto child : item->myData->children) {
++i; // the current index of the claimed child
bool found = false;
for (int j=i;j<childCount;++j) {
QTreeWidgetItem *ci = item->child(j);
if (ci->type() != TreeWidget::ObjectType)
continue;
DocumentObjectItem *childItem = static_cast<DocumentObjectItem*>(ci);
if (childItem->object()->getObject() != child)
continue;
found = true;
if (j!=i) { // fix index if it is changed
childItem->setHighlight(false);
item->removeChild(ci);
item->insertChild(i,ci);
assert(ci->parent()==item);
if(checkHidden)
updateItemsVisibility(ci,false);
}
// Check if the item just changed its policy of whether to remove
// children item from the root.
if(item->myData->removeChildrenFromRoot) {
if(childItem->myData->rootItem) {
assert(childItem != childItem->myData->rootItem);
bool lock = getTree()->blockConnection(true);
delete childItem->myData->rootItem;
getTree()->blockConnection(lock);
}
}else if(childItem->requiredAtRoot()) {
createNewItem(*childItem->object(),this,-1,childItem->myData);
updated = true;
}
break;
}
if (found)
continue;
// This algo will be recursively applied to newly created child items
// through slotNewObject -> populateItem
auto it = ObjectMap.find(child);
if(it==ObjectMap.end() || it->second->items.empty()) {
auto vp = getViewProvider(child);
if(!vp || !createNewItem(*vp,item,i,it==ObjectMap.end()?DocumentObjectDataPtr():it->second))
--i;
else
updated = true;
continue;
}
if(!item->myData->removeChildrenFromRoot || !it->second->rootItem) {
DocumentObjectItem *childItem = *it->second->items.begin();
if(!createNewItem(*childItem->object(),item,i,it->second))
--i;
else
updated = true;
}else {
DocumentObjectItem *childItem = it->second->rootItem;
if(item==childItem || item->isChildOfItem(childItem)) {
TREE_ERR("Cyclic dependency in "
<< item->object()->getObject()->getFullName()
<< '.' << childItem->object()->getObject()->getFullName());
--i;
continue;
}
it->second->rootItem = 0;
childItem->setHighlight(false);
this->removeChild(childItem);
item->insertChild(i,childItem);
assert(childItem->parent()==item);
if(checkHidden)
updateItemsVisibility(childItem,false);
}
}
for (++i;item->childCount()>i;) {
QTreeWidgetItem *ci = item->child(i);
if (ci->type() == TreeWidget::ObjectType) {
DocumentObjectItem* childItem = static_cast<DocumentObjectItem*>(ci);
if(childItem->requiredAtRoot()) {
item->removeChild(childItem);
auto index = findRootIndex(childItem->object()->getObject());
if(index>=0)
this->insertChild(index,childItem);
else
this->addChild(childItem);
assert(childItem->parent()==this);
if(checkHidden)
updateItemsVisibility(childItem,false);
childItem->myData->rootItem = childItem;
continue;
}
}
bool lock = getTree()->blockConnection(true);
delete ci;
getTree()->blockConnection(lock);
}
if(updated)
getTree()->_updateStatus();
}
int DocumentItem::findRootIndex(App::DocumentObject *childObj) {
if(!TreeParams::Instance()->KeepRootOrder() || !childObj || !childObj->getNameInDocument())
return -1;
// object id is monotonically increasing, so use this as a hint to insert
// object back so that we can have a stable order in root level.
int count = this->childCount();
if(!count)
return -1;
int first,last;
// find the last item
for(last=count-1;last>=0;--last) {
auto citem = this->child(last);
if(citem->type() == TreeWidget::ObjectType) {
auto obj = static_cast<DocumentObjectItem*>(citem)->object()->getObject();
if(obj->getID()<=childObj->getID())
return last+1;
break;
}
}
// find the first item
for(first=0;first<count;++first) {
auto citem = this->child(first);
if(citem->type() == TreeWidget::ObjectType) {
auto obj = static_cast<DocumentObjectItem*>(citem)->object()->getObject();
if(obj->getID()>=childObj->getID())
return first;
break;
}
}
// now do a binary search to find the lower bound, assuming the root level
// object is already in order
count = last-first;
int pos;
while (count > 0) {
int step = count / 2;
pos = first + step;
for(;pos<=last;++pos) {
auto citem = this->child(pos);
if(citem->type() != TreeWidget::ObjectType)
continue;
auto obj = static_cast<DocumentObjectItem*>(citem)->object()->getObject();
if(obj->getID()<childObj->getID()) {
first = ++pos;
count -= step+1;
} else
count = step;
break;
}
if(pos>last)
return -1;
}
if(first>last)
return -1;
return first;
}
void TreeWidget::slotChangeObject(
const Gui::ViewProviderDocumentObject& view, const App::Property &prop) {
auto obj = view.getObject();
if(!obj || !obj->getNameInDocument())
return;
auto itEntry = ObjectTable.find(obj);
if(itEntry == ObjectTable.end() || itEntry->second.empty())
return;
_updateStatus();
// Let's not waste time on the newly added Visibility property in
// DocumentObject.
if(&prop == &obj->Visibility)
return;
if(&prop == &obj->Label) {
const char *label = obj->Label.getValue();
auto firstData = *itEntry->second.begin();
if(firstData->label != label) {
for(auto data : itEntry->second) {
data->label = label;
auto displayName = QString::fromUtf8(label);
for(auto item : data->items)
item->setText(0, displayName);
}
}
return;
}
if(&prop == &obj->Label2) {
const char *label = obj->Label2.getValue();
auto firstData = *itEntry->second.begin();
if(firstData->label2 != label) {
for(auto data : itEntry->second) {
data->label2 = label;
auto displayName = QString::fromUtf8(label);
for(auto item : data->items)
item->setText(1, displayName);
}
}
return;
}
auto &s = ChangedObjects[obj];
if(prop.testStatus(App::Property::Output)
|| prop.testStatus(App::Property::NoRecompute))
{
s.set(CS_Output);
}
}
void TreeWidget::updateChildren(App::DocumentObject *obj,
const std::set<DocumentObjectDataPtr> &dataSet, bool propOutput, bool force)
{
bool childrenChanged = false;
std::vector<App::DocumentObject*> children;
bool removeChildrenFromRoot = true;
DocumentObjectDataPtr found;
for(auto data : dataSet) {
if(!found) {
found = data;
childrenChanged = found->updateChildren(force);
removeChildrenFromRoot = found->viewObject->canRemoveChildrenFromRoot();
if(!childrenChanged && found->removeChildrenFromRoot==removeChildrenFromRoot)
return;
}else if(childrenChanged)
data->updateChildren(found);
data->removeChildrenFromRoot = removeChildrenFromRoot;
DocumentItem* docItem = data->docItem;
for(auto item : data->items)
docItem->populateItem(item,true);
}
if(force)
return;
if(childrenChanged && propOutput) {
// When a property is marked as output, it will not touch its object,
// and thus, its property change will not be propagated through
// recomputation. So we have to manually check for each links here.
for(auto link : App::GetApplication().getLinksTo(obj,App::GetLinkRecursive)) {
if(ChangedObjects.count(link))
continue;
std::vector<App::DocumentObject*> linkedChildren;
DocumentObjectDataPtr found;
auto it = ObjectTable.find(link);
if(it == ObjectTable.end())
continue;
for(auto data : it->second) {
if(!found) {
found = data;
if(!found->updateChildren(false))
break;
}
data->updateChildren(found);
DocumentItem* docItem = data->docItem;
for(auto item : data->items)
docItem->populateItem(item,true);
}
}
}
if(childrenChanged) {
if(!selectTimer->isActive())
onSelectionChanged(SelectionChanges());
//if the item is in a GeoFeatureGroup we may need to update that too, as the claim children
//of the geofeaturegroup depends on what the childs claim
auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(obj);
if(grp && !ChangedObjects.count(grp)) {
auto iter = ObjectTable.find(grp);
if(iter!=ObjectTable.end())
updateChildren(grp,iter->second,true,false);
}
}
}
void DocumentItem::slotHighlightObject (const Gui::ViewProviderDocumentObject& obj,
const Gui::HighlightMode& high, bool set, const App::DocumentObject *parent, const char *subname)
{
getTree()->_updateStatus(false);
if(parent && parent->getDocument()!=document()->getDocument()) {
auto it = getTree()->DocumentMap.find(Application::Instance->getDocument(parent->getDocument()));
if(it!=getTree()->DocumentMap.end())
it->second->slotHighlightObject(obj,high,set,parent,subname);
return;
}
FOREACH_ITEM(item,obj)
if(parent) {
App::DocumentObject *topParent = 0;
std::ostringstream ss;
item->getSubName(ss,topParent);
if(!topParent) {
if(parent!=obj.getObject())
continue;
}else if(topParent!=parent)
continue;
}
item->setHighlight(set,high);
if(parent)
return;
END_FOREACH_ITEM
}
static unsigned int countExpandedItem(const QTreeWidgetItem *item) {
unsigned int size = 0;
for(int i=0,count=item->childCount();i<count;++i) {
auto citem = item->child(i);
if(citem->type()!=TreeWidget::ObjectType || !citem->isExpanded())
continue;
auto obj = static_cast<const DocumentObjectItem*>(citem)->object()->getObject();
if(obj->getNameInDocument())
size += strlen(obj->getNameInDocument()) + countExpandedItem(citem);
}
return size;
}
unsigned int DocumentItem::getMemSize(void) const {
return countExpandedItem(this);
}
static void saveExpandedItem(Base::Writer &writer, const QTreeWidgetItem *item) {
int itemCount = 0;
for(int i=0,count=item->childCount();i<count;++i) {
auto citem = item->child(i);
if(citem->type()!=TreeWidget::ObjectType || !citem->isExpanded())
continue;
auto obj = static_cast<const DocumentObjectItem*>(citem)->object()->getObject();
if(obj->getNameInDocument())
++itemCount;
}
if(!itemCount) {
writer.Stream() << "/>" << std::endl;
return;
}
writer.Stream() << " count=\"" << itemCount << "\">" <<std::endl;
writer.incInd();
for(int i=0,count=item->childCount();i<count;++i) {
auto citem = item->child(i);
if(citem->type()!=TreeWidget::ObjectType || !citem->isExpanded())
continue;
auto obj = static_cast<const DocumentObjectItem*>(citem)->object()->getObject();
if(obj->getNameInDocument()) {
writer.Stream() << writer.ind() << "<Expand name=\""
<< obj->getNameInDocument() << "\"";
saveExpandedItem(writer,static_cast<const DocumentObjectItem*>(citem));
}
}
writer.decInd();