From c18bf118217aa624688d3d3fcbf28be78c624b63 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 11 Jul 2019 13:27:54 +0800 Subject: [PATCH] Gui: refactor tree view * Major refactor of tree view to support external linking. * Item update and selection change are now mostly handled by timer for performance improvement. * Major change to drag and drop for better support of switching between copy, move and replace action, and auto adjustment of placement and relative link. * Add second column for user changable object description. * Unified tree view options and action into command group Std_TreeViewActions. * Modified object search function to find external objects using Expression syntax. --- src/Gui/CombiView.cpp | 3 +- src/Gui/CommandView.cpp | 559 ++- src/Gui/DlgGeneral.ui | 24 +- src/Gui/DlgGeneralImp.cpp | 10 + src/Gui/Tree.cpp | 4730 +++++++++++++++++++----- src/Gui/Tree.h | 347 +- src/Gui/ViewProviderDocumentObject.cpp | 10 +- src/Gui/Workbench.cpp | 53 +- src/Gui/Workbench.h | 2 + 9 files changed, 4574 insertions(+), 1164 deletions(-) diff --git a/src/Gui/CombiView.cpp b/src/Gui/CombiView.cpp index b6a887f6ce..42a705cec1 100644 --- a/src/Gui/CombiView.cpp +++ b/src/Gui/CombiView.cpp @@ -63,8 +63,7 @@ CombiView::CombiView(Gui::Document* pcDocument, QWidget *parent) QSplitter *splitter = new QSplitter(); splitter->setOrientation(Qt::Vertical); - // tree widget - tree = new TreePanel(this); + tree = new TreePanel("ComboView", this); splitter->addWidget(tree); // property view diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index 841bcdf885..dc4ee87fe2 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -787,47 +787,8 @@ StdCmdToggleVisibility::StdCmdToggleVisibility() void StdCmdToggleVisibility::activated(int iMsg) { - Q_UNUSED(iMsg); - // go through all documents - const std::vector docs = App::GetApplication().getDocuments(); - for (std::vector::const_iterator it = docs.begin(); it != docs.end(); ++it) { - Document *pcDoc = Application::Instance->getDocument(*it); - std::vector sel = Selection().getObjectsOfType - (App::DocumentObject::getClassTypeId(), (*it)->getName()); - - // in case a group object and an object of the group is selected then ignore the group object - std::vector ignore; - for (std::vector::iterator ft=sel.begin();ft!=sel.end();++ft) { - if ((*ft)->getTypeId().isDerivedFrom(App::DocumentObjectGroup::getClassTypeId())) { - App::DocumentObjectGroup* grp = static_cast(*ft); - std::vector sub = grp->Group.getValues(); - for (std::vector::iterator st = sub.begin(); st != sub.end(); ++st) { - if (std::find(sel.begin(), sel.end(), *st) != sel.end()) { - ignore.push_back(*ft); - break; - } - } - } - } - - if (!ignore.empty()) { - std::sort(sel.begin(), sel.end()); - std::sort(ignore.begin(), ignore.end()); - std::vector diff; - std::back_insert_iterator > biit(diff); - std::set_difference(sel.begin(), sel.end(), ignore.begin(), ignore.end(), biit); - sel = diff; - } - - for (std::vector::const_iterator ft=sel.begin();ft!=sel.end();++ft) { - if (pcDoc && pcDoc->isShow((*ft)->getNameInDocument())) - doCommand(Gui,"Gui.getDocument(\"%s\").getObject(\"%s\").Visibility=False" - , (*it)->getName(), (*ft)->getNameInDocument()); - else - doCommand(Gui,"Gui.getDocument(\"%s\").getObject(\"%s\").Visibility=True" - , (*it)->getName(), (*ft)->getNameInDocument()); - } - } + Q_UNUSED(iMsg); + Selection().setVisible(-1); } bool StdCmdToggleVisibility::isActive(void) @@ -900,17 +861,8 @@ StdCmdShowSelection::StdCmdShowSelection() void StdCmdShowSelection::activated(int iMsg) { - Q_UNUSED(iMsg); - // go through all documents - const std::vector docs = App::GetApplication().getDocuments(); - for (std::vector::const_iterator it = docs.begin(); it != docs.end(); ++it) { - const std::vector sel = Selection().getObjectsOfType - (App::DocumentObject::getClassTypeId(), (*it)->getName()); - for(std::vector::const_iterator ft=sel.begin();ft!=sel.end();++ft) { - doCommand(Gui,"Gui.getDocument(\"%s\").getObject(\"%s\").Visibility=True" - , (*it)->getName(), (*ft)->getNameInDocument()); - } - } + Q_UNUSED(iMsg); + Selection().setVisible(true); } bool StdCmdShowSelection::isActive(void) @@ -936,17 +888,8 @@ StdCmdHideSelection::StdCmdHideSelection() void StdCmdHideSelection::activated(int iMsg) { - Q_UNUSED(iMsg); - // go through all documents - const std::vector docs = App::GetApplication().getDocuments(); - for (std::vector::const_iterator it = docs.begin(); it != docs.end(); ++it) { - const std::vector sel = Selection().getObjectsOfType - (App::DocumentObject::getClassTypeId(), (*it)->getName()); - for(std::vector::const_iterator ft=sel.begin();ft!=sel.end();++ft) { - doCommand(Gui,"Gui.getDocument(\"%s\").getObject(\"%s\").Visibility=False" - , (*it)->getName(), (*ft)->getNameInDocument()); - } - } + Q_UNUSED(iMsg); + Selection().setVisible(false); } bool StdCmdHideSelection::isActive(void) @@ -1482,6 +1425,7 @@ StdViewDock::StdViewDock() sStatusTip = QT_TR_NOOP("Display the active view either in fullscreen, in undocked or docked mode"); sAccel = "V, D"; eType = Alter3DView; + bCanLog = false; } void StdViewDock::activated(int iMsg) @@ -1510,6 +1454,7 @@ StdViewUndock::StdViewUndock() sStatusTip = QT_TR_NOOP("Display the active view either in fullscreen, in undocked or docked mode"); sAccel = "V, U"; eType = Alter3DView; + bCanLog = false; } void StdViewUndock::activated(int iMsg) @@ -1571,6 +1516,7 @@ StdViewFullscreen::StdViewFullscreen() sPixmap = "view-fullscreen"; sAccel = "F11"; eType = Alter3DView; + bCanLog = false; } void StdViewFullscreen::activated(int iMsg) @@ -2616,27 +2562,125 @@ void StdBoxSelection::activated(int iMsg) // Std_TreeSelection //=========================================================================== -DEF_STD_CMD(StdCmdTreeSelection) +DEF_STD_CMD(StdTreeSelection) -StdCmdTreeSelection::StdCmdTreeSelection() +StdTreeSelection::StdTreeSelection() : Command("Std_TreeSelection") { - sGroup = QT_TR_NOOP("View"); + sGroup = QT_TR_NOOP("TreeView"); sMenuText = QT_TR_NOOP("Go to selection"); sToolTipText = QT_TR_NOOP("Scroll to first selected item"); sWhatsThis = "Std_TreeSelection"; sStatusTip = QT_TR_NOOP("Scroll to first selected item"); eType = Alter3DView; + sPixmap = "tree-goto-sel"; + sAccel = "T,G"; } -void StdCmdTreeSelection::activated(int iMsg) +void StdTreeSelection::activated(int iMsg) { Q_UNUSED(iMsg); + TreeWidget::scrollItemToTop(); +} + +//=========================================================================== +// Std_TreeCollapse +//=========================================================================== + +DEF_STD_CMD(StdCmdTreeCollapse) + +StdCmdTreeCollapse::StdCmdTreeCollapse() + : Command("Std_TreeCollapse") +{ + sGroup = QT_TR_NOOP("View"); + sMenuText = QT_TR_NOOP("Collapse selected item"); + sToolTipText = QT_TR_NOOP("Collapse currently selected tree items"); + sWhatsThis = "Std_TreeCollapse"; + sStatusTip = QT_TR_NOOP("Collapse currently selected tree items"); + eType = Alter3DView; +} + +void StdCmdTreeCollapse::activated(int iMsg) +{ + Q_UNUSED(iMsg); QList tree = Gui::getMainWindow()->findChildren(); - for (QList::iterator it = tree.begin(); it != tree.end(); ++it) { - Gui::Document* doc = Gui::Application::Instance->activeDocument(); - (*it)->scrollItemToTop(doc); - } + for (QList::iterator it = tree.begin(); it != tree.end(); ++it) + (*it)->expandSelectedItems(TreeItemMode::CollapseItem); +} + +//=========================================================================== +// Std_TreeExpand +//=========================================================================== + +DEF_STD_CMD(StdCmdTreeExpand) + +StdCmdTreeExpand::StdCmdTreeExpand() + : Command("Std_TreeExpand") +{ + sGroup = QT_TR_NOOP("View"); + sMenuText = QT_TR_NOOP("Expand selected item"); + sToolTipText = QT_TR_NOOP("Expand currently selected tree items"); + sWhatsThis = "Std_TreeExpand"; + sStatusTip = QT_TR_NOOP("Expand currently selected tree items"); + eType = Alter3DView; +} + +void StdCmdTreeExpand::activated(int iMsg) +{ + Q_UNUSED(iMsg); + QList tree = Gui::getMainWindow()->findChildren(); + for (QList::iterator it = tree.begin(); it != tree.end(); ++it) + (*it)->expandSelectedItems(TreeItemMode::ExpandItem); +} + +//=========================================================================== +// Std_TreeSelectAllInstance +//=========================================================================== + +DEF_STD_CMD_A(StdCmdTreeSelectAllInstances) + +StdCmdTreeSelectAllInstances::StdCmdTreeSelectAllInstances() + : Command("Std_TreeSelectAllInstances") +{ + sGroup = QT_TR_NOOP("View"); + sMenuText = QT_TR_NOOP("Select all instances"); + sToolTipText = QT_TR_NOOP("Select all instances of the current selected object"); + sWhatsThis = "Std_TreeSelectAllInstances"; + sStatusTip = QT_TR_NOOP("Select all instances of the current selected object"); + sPixmap = "sel-instance"; + eType = AlterSelection; +} + +bool StdCmdTreeSelectAllInstances::isActive(void) +{ + const auto &sels = Selection().getSelectionEx("*",App::DocumentObject::getClassTypeId(),true,true); + if(sels.empty()) + return false; + auto obj = sels[0].getObject(); + if(!obj || !obj->getNameInDocument()) + return false; + return dynamic_cast( + Application::Instance->getViewProvider(obj))!=0; +} + +void StdCmdTreeSelectAllInstances::activated(int iMsg) +{ + Q_UNUSED(iMsg); + const auto &sels = Selection().getSelectionEx("*",App::DocumentObject::getClassTypeId(),true,true); + if(sels.empty()) + return; + auto obj = sels[0].getObject(); + if(!obj || !obj->getNameInDocument()) + return; + auto vpd = dynamic_cast( + Application::Instance->getViewProvider(obj)); + if(!vpd) + return; + Selection().selStackPush(); + Selection().clearCompleteSelection(); + for(auto tree : getMainWindow()->findChildren()) + tree->selectAllInstances(*vpd); + Selection().selStackPush(); } //=========================================================================== @@ -2875,132 +2919,374 @@ void CmdViewMeasureToggleAll::activated(int iMsg) } //=========================================================================== +// Std_SelBack +//=========================================================================== + +DEF_STD_CMD_A(StdCmdSelBack); + +StdCmdSelBack::StdCmdSelBack() + :Command("Std_SelBack") +{ + sGroup = QT_TR_NOOP("View"); + sMenuText = QT_TR_NOOP("&Back"); + sToolTipText = QT_TR_NOOP("Go back to previous selection"); + sWhatsThis = "Std_SelBack"; + sStatusTip = QT_TR_NOOP("Go back to previous selection"); + sPixmap = "sel-back"; + sAccel = "S, B"; + eType = AlterSelection; +} + +void StdCmdSelBack::activated(int iMsg) +{ + Q_UNUSED(iMsg); + Selection().selStackGoBack(); +} + +bool StdCmdSelBack::isActive(void) +{ + return Selection().selStackBackSize()>1; +} + +//=========================================================================== +// Std_SelForward +//=========================================================================== + +DEF_STD_CMD_A(StdCmdSelForward); + +StdCmdSelForward::StdCmdSelForward() + :Command("Std_SelForward") +{ + sGroup = QT_TR_NOOP("View"); + sMenuText = QT_TR_NOOP("&Forward"); + sToolTipText = QT_TR_NOOP("Repeat the backed selection"); + sWhatsThis = "Std_SelForward"; + sStatusTip = QT_TR_NOOP("Repeat the backed selection"); + sPixmap = "sel-forward"; + sAccel = "S, F"; + eType = AlterSelection; +} + +void StdCmdSelForward::activated(int iMsg) +{ + Q_UNUSED(iMsg); + Selection().selStackGoForward(); +} + +bool StdCmdSelForward::isActive(void) +{ + return !!Selection().selStackForwardSize(); +} + +//======================================================================= // Std_TreeSingleDocument //=========================================================================== -DEF_STD_CMD(StdTreeSingleDocument) +#define TREEVIEW_DOC_CMD_DEF(_name,_v) \ +DEF_STD_CMD_AC(StdTree##_name) \ +void StdTree##_name::activated(int){ \ + FC_TREEPARAM_SET(DocumentMode,_v);\ + if(_pcAction) _pcAction->setChecked(true,true);\ +}\ +Action * StdTree##_name::createAction(void) {\ + Action *pcAction = Command::createAction();\ + pcAction->setCheckable(true);\ + pcAction->setIcon(QIcon());\ + _pcAction = pcAction;\ + isActive();\ + return pcAction;\ +}\ +bool StdTree##_name::isActive() {\ + bool checked = FC_TREEPARAM(DocumentMode)==_v;\ + if(_pcAction && _pcAction->isChecked()!=checked)\ + _pcAction->setChecked(checked,true);\ + return true;\ +} + +TREEVIEW_DOC_CMD_DEF(SingleDocument,0) StdTreeSingleDocument::StdTreeSingleDocument() : Command("Std_TreeSingleDocument") { - sGroup = QT_TR_NOOP("View"); - sMenuText = QT_TR_NOOP("Single Document"); + sGroup = QT_TR_NOOP("TreeView"); + sMenuText = QT_TR_NOOP("Single document"); sToolTipText = QT_TR_NOOP("Only display the active document in the tree view"); sWhatsThis = "Std_TreeSingleDocument"; sStatusTip = QT_TR_NOOP("Only display the active document in the tree view"); + sPixmap = "tree-doc-single"; eType = 0; } -void StdTreeSingleDocument::activated(int iMsg) -{ - Q_UNUSED(iMsg); -} - //=========================================================================== // Std_TreeMultiDocument //=========================================================================== -DEF_STD_CMD(StdTreeMultiDocument) +TREEVIEW_DOC_CMD_DEF(MultiDocument,1) StdTreeMultiDocument::StdTreeMultiDocument() : Command("Std_TreeMultiDocument") { - sGroup = QT_TR_NOOP("View"); - sMenuText = QT_TR_NOOP("Multi Document"); + sGroup = QT_TR_NOOP("TreeView"); + sMenuText = QT_TR_NOOP("Multi document"); sToolTipText = QT_TR_NOOP("Display all documents in the tree view"); sWhatsThis = "Std_TreeMultiDocument"; sStatusTip = QT_TR_NOOP("Display all documents in the tree view"); + sPixmap = "tree-doc-multi"; eType = 0; } -void StdTreeMultiDocument::activated(int iMsg) -{ - Q_UNUSED(iMsg); -} - - //=========================================================================== // Std_TreeCollapseDocument //=========================================================================== -DEF_STD_CMD(StdTreeCollapseDocument) +TREEVIEW_DOC_CMD_DEF(CollapseDocument,2) StdTreeCollapseDocument::StdTreeCollapseDocument() : Command("Std_TreeCollapseDocument") { - sGroup = QT_TR_NOOP("View"); + sGroup = QT_TR_NOOP("TreeView"); sMenuText = QT_TR_NOOP("Collapse/Expand"); sToolTipText = QT_TR_NOOP("Expand active document and collapse all others"); sWhatsThis = "Std_TreeCollapseDocument"; sStatusTip = QT_TR_NOOP("Expand active document and collapse all others"); + sPixmap = "tree-doc-collapse"; eType = 0; } -void StdTreeCollapseDocument::activated(int iMsg) +//=========================================================================== +// Std_TreeSyncView +//=========================================================================== +#define TREEVIEW_CMD_DEF(_name) \ +DEF_STD_CMD_AC(StdTree##_name) \ +void StdTree##_name::activated(int){ \ + auto checked = !FC_TREEPARAM(_name);\ + FC_TREEPARAM_SET(_name,checked);\ + if(_pcAction) _pcAction->setChecked(checked,true);\ +}\ +Action * StdTree##_name::createAction(void) {\ + Action *pcAction = Command::createAction();\ + pcAction->setCheckable(true);\ + pcAction->setIcon(QIcon());\ + _pcAction = pcAction;\ + isActive();\ + return pcAction;\ +}\ +bool StdTree##_name::isActive() {\ + bool checked = FC_TREEPARAM(_name);\ + if(_pcAction && _pcAction->isChecked()!=checked)\ + _pcAction->setChecked(checked,true);\ + return true;\ +} + +TREEVIEW_CMD_DEF(SyncView) + +StdTreeSyncView::StdTreeSyncView() + : Command("Std_TreeSyncView") { - Q_UNUSED(iMsg); + sGroup = QT_TR_NOOP("TreeView"); + sMenuText = QT_TR_NOOP("Sync view"); + sToolTipText = QT_TR_NOOP("Auto switch to the 3D view containing the selected item"); + sStatusTip = sToolTipText; + sWhatsThis = "Std_TreeSyncView"; + sPixmap = "tree-sync-view"; + sAccel = "T,1"; + eType = 0; } +//=========================================================================== +// Std_TreeSyncSelection +//=========================================================================== +TREEVIEW_CMD_DEF(SyncSelection) + +StdTreeSyncSelection::StdTreeSyncSelection() + : Command("Std_TreeSyncSelection") +{ + sGroup = QT_TR_NOOP("TreeView"); + sMenuText = QT_TR_NOOP("Sync selection"); + sToolTipText = QT_TR_NOOP("Auto expand tree item when the corresponding object is selected in 3D view"); + sStatusTip = sToolTipText; + sWhatsThis = "Std_TreeSyncSelection"; + sPixmap = "tree-sync-sel"; + sAccel = "T,2"; + eType = 0; +} //=========================================================================== -// Std_TreeViewDocument +// Std_TreeSyncPlacement //=========================================================================== +TREEVIEW_CMD_DEF(SyncPlacement) -DEF_STD_CMD_AC(StdTreeViewDocument); +StdTreeSyncPlacement::StdTreeSyncPlacement() + : Command("Std_TreeSyncPlacement") +{ + sGroup = QT_TR_NOOP("TreeView"); + sMenuText = QT_TR_NOOP("Sync placement"); + sToolTipText = QT_TR_NOOP("Auto adjust placement on drag and drop objects across coordinate systems"); + sStatusTip = sToolTipText; + sWhatsThis = "Std_TreeSyncPlacement"; + sPixmap = "tree-sync-pla"; + sAccel = "T,3"; + eType = 0; +} -StdTreeViewDocument::StdTreeViewDocument() - : Command("Std_TreeViewDocument") +//=========================================================================== +// Std_TreePreSelection +//=========================================================================== +TREEVIEW_CMD_DEF(PreSelection) + +StdTreePreSelection::StdTreePreSelection() + : Command("Std_TreePreSelection") +{ + sGroup = QT_TR_NOOP("TreeView"); + sMenuText = QT_TR_NOOP("Pre-selection"); + sToolTipText = QT_TR_NOOP("Preselect the object in 3D view when mouse over the tree item"); + sStatusTip = sToolTipText; + sWhatsThis = "Std_TreePreSelection"; + sPixmap = "tree-pre-sel"; + sAccel = "T,4"; + eType = 0; +} + +//=========================================================================== +// Std_TreeRecordSelection +//=========================================================================== +TREEVIEW_CMD_DEF(RecordSelection) + +StdTreeRecordSelection::StdTreeRecordSelection() + : Command("Std_TreeRecordSelection") +{ + sGroup = QT_TR_NOOP("TreeView"); + sMenuText = QT_TR_NOOP("Record selection"); + sToolTipText = QT_TR_NOOP("Record selection in tree view in order to go back/forward using navigation button"); + sStatusTip = sToolTipText; + sWhatsThis = "Std_TreeRecordSelection"; + sPixmap = "tree-rec-sel"; + sAccel = "T,5"; + eType = 0; +} + +//=========================================================================== +// Std_TreeDrag +//=========================================================================== +DEF_STD_CMD(StdTreeDrag) + +StdTreeDrag::StdTreeDrag() + : Command("Std_TreeDrag") +{ + sGroup = QT_TR_NOOP("TreeView"); + sMenuText = QT_TR_NOOP("Initiate dragging"); + sToolTipText = QT_TR_NOOP("Initiate dragging of current selected tree items"); + sStatusTip = sToolTipText; + sWhatsThis = "Std_TreeDrag"; + sPixmap = "tree-item-drag"; + sAccel = "T,D"; + eType = 0; +} + +void StdTreeDrag::activated(int) +{ + if(Gui::Selection().hasSelection()) { + for(auto tree : getMainWindow()->findChildren()) { + if(tree->isVisible()) { + tree->startDragging(); + break; + } + } + } +} + +//====================================================================== +// Std_TreeViewActions +//=========================================================================== +// +class StdCmdTreeViewActions : public Gui::Command +{ +public: + StdCmdTreeViewActions(); + virtual const char* className() const {return "StdCmdTreeViewActions";} +protected: + virtual void activated(int iMsg); + virtual Gui::Action * createAction(void); + + std::vector > cmds; +}; + +StdCmdTreeViewActions::StdCmdTreeViewActions() + : Command("Std_TreeViewActions") { sGroup = QT_TR_NOOP("View"); - sMenuText = QT_TR_NOOP("Document Tree"); - sToolTipText = QT_TR_NOOP("Set visibility of inactive documents in tree view"); - sWhatsThis = "Std_TreeViewDocument"; - sStatusTip = QT_TR_NOOP("Set visibility of inactive documents in tree view"); + sMenuText = QT_TR_NOOP("TreeView actions"); + sToolTipText = QT_TR_NOOP("TreeView behavior options and actions"); + sWhatsThis = "Std_TreeViewActions"; + sStatusTip = QT_TR_NOOP("TreeView behavior options and actions"); eType = 0; + bCanLog = false; - CommandManager &rcCmdMgr = Application::Instance->commandManager(); - rcCmdMgr.addCommand(new StdTreeSingleDocument()); - rcCmdMgr.addCommand(new StdTreeMultiDocument()); - rcCmdMgr.addCommand(new StdTreeCollapseDocument()); + CommandManager &mgr = Application::Instance->commandManager(); + cmds.reserve(12); + cmds.emplace_back(new StdTreeSyncView(),cmds.size()); + mgr.addCommand(cmds.back().first); + cmds.emplace_back(new StdTreeSyncSelection(),cmds.size()); + mgr.addCommand(cmds.back().first); + cmds.emplace_back(new StdTreeSyncPlacement(),cmds.size()); + mgr.addCommand(cmds.back().first); + cmds.emplace_back(new StdTreePreSelection(),cmds.size()); + mgr.addCommand(cmds.back().first); + cmds.emplace_back(new StdTreeRecordSelection(),cmds.size()); + mgr.addCommand(cmds.back().first); + + cmds.emplace_back(nullptr,0); + + cmds.emplace_back(new StdTreeSingleDocument(),cmds.size()+1); + mgr.addCommand(cmds.back().first); + cmds.emplace_back(new StdTreeMultiDocument(),cmds.size()+1); + mgr.addCommand(cmds.back().first); + cmds.emplace_back(new StdTreeCollapseDocument(),cmds.size()-2); + mgr.addCommand(cmds.back().first); + + cmds.emplace_back(nullptr,0); + + cmds.emplace_back(new StdTreeDrag(),cmds.size()); + mgr.addCommand(cmds.back().first); + + cmds.emplace_back(new StdTreeSelection(),cmds.size()); + mgr.addCommand(cmds.back().first); } -Action * StdTreeViewDocument::createAction(void) -{ +Action * StdCmdTreeViewActions::createAction(void) { ActionGroup* pcAction = new ActionGroup(this, getMainWindow()); pcAction->setDropDownMenu(true); - pcAction->setText(QCoreApplication::translate(this->className(), sMenuText)); + pcAction->setExclusive(false); + applyCommandData(this->className(), pcAction); + pcAction->setCheckable(true); - CommandManager &grp = Application::Instance->commandManager(); - Command* cmd0 = grp.getCommandByName("Std_TreeSingleDocument"); - Command* cmd1 = grp.getCommandByName("Std_TreeMultiDocument"); - Command* cmd2 = grp.getCommandByName("Std_TreeCollapseDocument"); - cmd0->addToGroup(pcAction, true); - cmd1->addToGroup(pcAction, true); - cmd2->addToGroup(pcAction, true); + for(auto &v : cmds) { + if(!v.first) + pcAction->addAction(QString::fromLatin1(""))->setSeparator(true); + else + v.first->addToGroup(pcAction); + } + pcAction->setIcon(BitmapFactory().iconFromTheme(cmds[0].first->getPixmap())); + pcAction->setChecked(cmds[0].first->getAction()->isChecked(),true); return pcAction; } -void StdTreeViewDocument::activated(int iMsg) +void StdCmdTreeViewActions::activated(int iMsg) { - ParameterGrp::handle group = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View"); - group->SetInt("TreeViewDocument", iMsg); - App::GetApplication().setActiveDocument(App::GetApplication().getActiveDocument()); -} + if(iMsg<0 || iMsg>=(int)cmds.size()) + return; + auto &v = cmds[iMsg]; + if(!v.first) + return; -bool StdTreeViewDocument::isActive(void) -{ - ActionGroup* grp = qobject_cast(_pcAction); - if (grp) { - ParameterGrp::handle group = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View"); - int mode = group->GetInt("TreeViewDocument", 0); - int index = grp->checkedAction(); - if (index != mode) { - grp->setCheckedAction(mode); - } + if(triggerSource()!=TriggerChildAction) + v.first->invoke(0); + + Action* cmdAction = v.first->getAction(); + if(_pcAction && cmdAction) { + _pcAction->setIcon(BitmapFactory().iconFromTheme(v.first->getPixmap())); + _pcAction->setChecked(cmdAction->isChecked(),true); + _pcAction->setProperty("defaultAction", QVariant((int)v.second)); } - - return true; } //=========================================================================== @@ -3065,6 +3351,9 @@ void CreateViewStdCommands(void) rcCmdMgr.addCommand(new StdViewBoxZoom()); rcCmdMgr.addCommand(new StdBoxSelection()); rcCmdMgr.addCommand(new StdCmdTreeSelection()); + rcCmdMgr.addCommand(new StdCmdTreeExpand()); + rcCmdMgr.addCommand(new StdCmdTreeCollapse()); + rcCmdMgr.addCommand(new StdCmdTreeSelectAllInstances()); rcCmdMgr.addCommand(new StdCmdMeasureDistance()); rcCmdMgr.addCommand(new StdCmdSceneInspector()); rcCmdMgr.addCommand(new StdCmdTextureMapping()); @@ -3073,7 +3362,9 @@ void CreateViewStdCommands(void) rcCmdMgr.addCommand(new StdCmdAxisCross()); rcCmdMgr.addCommand(new CmdViewMeasureClearAll()); rcCmdMgr.addCommand(new CmdViewMeasureToggleAll()); - rcCmdMgr.addCommand(new StdTreeViewDocument()); + rcCmdMgr.addCommand(new StdCmdSelBack()); + rcCmdMgr.addCommand(new StdCmdSelForward()); + rcCmdMgr.addCommand(new StdCmdTreeViewActions()); } } // namespace Gui diff --git a/src/Gui/DlgGeneral.ui b/src/Gui/DlgGeneral.ui index 181b61afdd..d1d8e73e90 100644 --- a/src/Gui/DlgGeneral.ui +++ b/src/Gui/DlgGeneral.ui @@ -6,8 +6,8 @@ 0 0 - 396 - 553 + 425 + 578 @@ -195,6 +195,26 @@ See the FreeCAD Wiki for details about the image. + + + + 6 + + + 0 + + + + + Tree view mode: + + + + + + + + diff --git a/src/Gui/DlgGeneralImp.cpp b/src/Gui/DlgGeneralImp.cpp index 274caadaa9..7350ad9fbe 100644 --- a/src/Gui/DlgGeneralImp.cpp +++ b/src/Gui/DlgGeneralImp.cpp @@ -134,6 +134,8 @@ void DlgGeneralImp::saveSettings() hGrp->SetInt("ToolbarIconSize", pixel); getMainWindow()->setIconSize(QSize(pixel,pixel)); + hGrp->SetInt("TreeViewMode",ui->treeMode->currentIndex()); + hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow"); hGrp->SetBool("TiledBackground", ui->tiledBackground->isChecked()); QMdiArea* mdi = getMainWindow()->findChild(); @@ -248,6 +250,14 @@ void DlgGeneralImp::loadSettings() } ui->toolbarIconSize->setCurrentIndex(index); + ui->treeMode->addItem(tr("CombiView")); + ui->treeMode->addItem(tr("TreeView + PropertyView")); + ui->treeMode->addItem(tr("Both")); + index = hGrp->GetInt("TreeViewMode"); + if (index<0 || index>2) + index=0; + ui->treeMode->setCurrentIndex(index); + hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow"); ui->tiledBackground->setChecked(hGrp->GetBool("TiledBackground", false)); diff --git a/src/Gui/Tree.cpp b/src/Gui/Tree.cpp index 758dcffb27..e149f9dfd3 100644 --- a/src/Gui/Tree.cpp +++ b/src/Gui/Tree.cpp @@ -38,13 +38,17 @@ # include # include # include +# include #endif #include +#include + #include #include #include #include +#include #include "Tree.h" #include "Command.h" @@ -56,33 +60,400 @@ #include "MainWindow.h" #include "View3DInventor.h" #include "View3DInventorViewer.h" +#include "Macro.h" +#include "Workbench.h" #include "Widgets.h" +FC_LOG_LEVEL_INIT("Tree",false,true,true); + +#define _TREE_PRINT(_level,_func,_msg) \ + _FC_PRINT(FC_LOG_INSTANCE,_level,_func, '['< TreeWidget::documentPixmap; +std::unique_ptr TreeWidget::documentPartialPixmap; +std::set TreeWidget::Instances; +static TreeWidget *_LastSelectedTreeWidget; const int TreeWidget::DocumentType = 1000; const int TreeWidget::ObjectType = 1001; +bool _DragEventFilter; +TreeParams::TreeParams() { + GET_TREEVIEW_PARAM(hGrp); + handle = hGrp; + handle->Attach(this); -/* TRANSLATOR Gui::TreeWidget */ -TreeWidget::TreeWidget(QWidget* parent) - : QTreeWidget(parent), contextItem(0), fromOutside(false) +#define FC_TREEPARAM_INIT(_name,_type,_Type,_default) \ + _##_name = handle->Get##_Type(#_name,_default); + +#undef FC_TREEPARAM_DEF +#define FC_TREEPARAM_DEF FC_TREEPARAM_INIT + FC_TREEPARAM_DEFS +} + +#define FC_TREEPARAM_SET_FUNC(_name,_type,_Type,_default) \ +void TreeParams::set##_name(_type value) {\ + if(_##_name != value) {\ + handle->Set##_Type(#_name,value);\ + }\ +} + +#undef FC_TREEPARAM_DEF +#define FC_TREEPARAM_DEF FC_TREEPARAM_SET_FUNC +FC_TREEPARAM_DEFS + +void TreeParams::OnChange(Base::Subject &, const char* sReason) { +#define FC_TREEPARAM_CHANGE(_name,_type,_Type,_default) \ + if(strcmp(sReason,#_name)==0) {\ + _##_name = handle->Get##_Type(#_name,_default);\ + on##_name##Changed();\ + return;\ + } + +#undef FC_TREEPARAM_DEF +#define FC_TREEPARAM_DEF FC_TREEPARAM_CHANGE + FC_TREEPARAM_DEFS +} + +void TreeParams::onPreSelectionChanged() {} + +void TreeParams::onSyncSelectionChanged() { + if(!FC_TREEPARAM(SyncSelection) || !Gui::Selection().hasSelection()) + return; + TreeWidget::scrollItemToTop(); +} + +void TreeParams::onSyncViewChanged() {} +void TreeParams::onSyncPlacementChanged() {} +void TreeParams::onRecordSelectionChanged() {} +void TreeParams::onRecomputeOnDropChanged() {} +void TreeParams::onKeepRootOrderChanged() {} +void TreeParams::onTreeActiveAutoExpandChanged() {} + +void TreeParams::onDocumentModeChanged() { + App::GetApplication().setActiveDocument(App::GetApplication().getActiveDocument()); +} + +void TreeParams::onStatusTimeoutChanged() {} +void TreeParams::onSelectionTimeoutChanged() {} +void TreeParams::onPreSelectionTimeoutChanged() {} +void TreeParams::onPreSelectionDelayChanged() {} + +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 DocumentObjectItems; + +class Gui::DocumentObjectData { +public: + DocumentItem *docItem; + DocumentObjectItems items; + ViewProviderDocumentObject *viewObject; + DocumentObjectItem *rootItem; + std::vector children; + std::set 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 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 { +public: + void restore(Base::XMLReader &reader) { + int level = reader.level(); + int count = reader.getAttributeAsInteger("count"); + for(int i=0;irestore(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(index.internalPointer()); + if(ti->type()!=TreeWidget::ObjectType || index.column()>1) + return 0; + DocumentObjectItem *item = static_cast(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); - this->createGroupAction->setText(tr("Create group...")); - this->createGroupAction->setStatusTip(tr("Create a group")); connect(this->createGroupAction, SIGNAL(triggered()), this, SLOT(onCreateGroup())); this->relabelObjectAction = new QAction(this); - this->relabelObjectAction->setText(tr("Rename")); - this->relabelObjectAction->setStatusTip(tr("Rename object")); #ifndef Q_OS_MAC this->relabelObjectAction->setShortcut(Qt::Key_F2); #endif @@ -90,24 +461,34 @@ TreeWidget::TreeWidget(QWidget* parent) this, SLOT(onRelabelObject())); this->finishEditingAction = new QAction(this); - this->finishEditingAction->setText(tr("Finish editing")); - this->finishEditingAction->setStatusTip(tr("Finish editing object")); 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); - this->skipRecomputeAction->setText(tr("Skip recomputes")); - this->skipRecomputeAction->setStatusTip(tr("Enable or disable recomputations of document")); 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); - this->markRecomputeAction->setText(tr("Mark to recompute")); - this->markRecomputeAction->setStatusTip(tr("Mark this object to be recomputed")); 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")); @@ -115,15 +496,19 @@ TreeWidget::TreeWidget(QWidget* parent) this, SLOT(onSearchObjects())); // Setup connections - connectNewDocument = Application::Instance->signalNewDocument.connect(boost::bind(&TreeWidget::slotNewDocument, this, _1)); + 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)); - QStringList labels; - labels << tr("Labels & Attributes"); - this->setHeaderLabels(labels); // make sure to show a horizontal scrollbar if needed #if QT_VERSION >= 0x050000 this->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); @@ -134,7 +519,6 @@ TreeWidget::TreeWidget(QWidget* parent) // Add the first main label this->rootItem = new QTreeWidgetItem(this); - this->rootItem->setText(0, tr("Application")); this->rootItem->setFlags(Qt::ItemIsEnabled); this->expandItem(this->rootItem); this->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -143,10 +527,17 @@ TreeWidget::TreeWidget(QWidget* parent) 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(onTestStatus())); + this, SLOT(onUpdateStatus())); connect(this, SIGNAL(itemEntered(QTreeWidgetItem*, int)), this, SLOT(onItemEntered(QTreeWidgetItem*))); connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), @@ -155,12 +546,18 @@ TreeWidget::TreeWidget(QWidget* parent) 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(); - this->statusTimer->setSingleShot(true); - this->statusTimer->start(300); - documentPixmap = new QPixmap(Gui::BitmapFactory().pixmap("Document")); - - this->setDefaultDropAction(Qt::MoveAction); + 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() @@ -170,15 +567,223 @@ TreeWidget::~TreeWidget() 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(); +} + +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; +} + +void TreeWidget::checkTopParent(App::DocumentObject *&obj, std::string &subname) { + 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()) + tree->_updateStatus(false); + 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(item)->restoreBackground(); + } + } + searchObject = 0; +} + +void TreeWidget::startItemSearch() { + resetItemSearch(); + searchDoc = 0; + searchContextDoc = 0; + if (contextItem) { + if(contextItem->type() == DocumentType) { + searchDoc = static_cast(contextItem)->document(); + } else if(contextItem->type() == ObjectType) { + searchDoc = static_cast(contextItem)->object()->getDocument(); + } + }else{ + auto sels = selectedItems(); + if(sels.size() == 1) { + auto item = static_cast(sels.front()); + searchDoc = item->object()->getDocument(); + searchContextDoc = item->getOwnerDocument()->document(); + } + } +} + +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(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 = FC_TREEPARAM(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; + view << "Std_TreeViewActions"; + Gui::Application::Instance->setupContextMenu("Tree", &view); + view << "Std_Expressions"; + Workbench::createLinkMenu(&view); + QMenu contextMenu; + QMenu subMenu; QMenu editMenu; QActionGroup subMenuGroup(&subMenu); @@ -189,56 +794,86 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e) // get the current item this->contextItem = itemAt(e->pos()); + if (this->contextItem && this->contextItem->type() == DocumentType) { - if (!contextMenu.actions().isEmpty()) - contextMenu.addSeparator(); DocumentItem* docitem = static_cast(this->contextItem); App::Document* doc = docitem->document()->getDocument(); - this->skipRecomputeAction->setChecked(doc->testStatus(App::Document::SkipRecompute)); - contextMenu.addAction(this->skipRecomputeAction); - contextMenu.addAction(this->markRecomputeAction); - contextMenu.addAction(this->createGroupAction); + 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 (this->contextItem); - if (objitem->object()->getObject()->isDerivedFrom(App::DocumentObjectGroup - ::getClassTypeId())) { - QList acts = contextMenu.actions(); - if (!acts.isEmpty()) { - QAction* first = acts.front(); - QAction* sep = contextMenu.insertSeparator(first); - contextMenu.insertAction(sep, this->createGroupAction); - } - else - contextMenu.addAction(this->createGroupAction); - } - if (!contextMenu.actions().isEmpty()) - contextMenu.addSeparator(); - contextMenu.addAction(this->markRecomputeAction); - contextMenu.addAction(this->relabelObjectAction); + auto selItems = this->selectedItems(); // if only one item is selected setup the edit menu - if (this->selectedItems().size() == 1) { + if (selItems.size() == 1) { + + auto cmd = Gui::Application::Instance->commandManager().getCommandByName("Std_LinkSelectLinked"); + if(cmd && cmd->isActive()) + cmd->addTo(&contextMenu); + cmd = Gui::Application::Instance->commandManager().getCommandByName("Std_LinkSelectLinkedFinal"); + if(cmd && cmd->isActive()) + cmd->addTo(&contextMenu); + objitem->object()->setupContextMenu(&editMenu, this, SLOT(onStartEditing())); QList editAct = editMenu.actions(); if (!editAct.isEmpty()) { - QAction* topact = contextMenu.actions().front(); + contextMenu.addSeparator(); for (QList::iterator it = editAct.begin(); it != editAct.end(); ++it) - contextMenu.insertAction(topact, *it); + contextMenu.addAction(*it); QAction* first = editAct.front(); contextMenu.setDefaultAction(first); if (objitem->object()->isEditing()) - contextMenu.insertAction(topact, this->finishEditingAction); - contextMenu.insertSeparator(topact); + contextMenu.addAction(finishEditingAction); } } + + contextMenu.addSeparator(); + + 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); + + contextMenu.addAction(this->searchObjectsAction); + + if (objitem->object()->getObject()->isDerivedFrom(App::DocumentObjectGroup::getClassTypeId())) + contextMenu.addAction(this->createGroupAction); + + contextMenu.addAction(this->markRecomputeAction); + contextMenu.addAction(this->recomputeObjectAction); + contextMenu.addAction(this->relabelObjectAction); } + // add a submenu to active a document if two or more exist std::vector docs = App::GetApplication().getDocuments(); if (docs.size() >= 2) { + contextMenu.addSeparator(); App::Document* activeDoc = App::GetApplication().getActiveDocument(); subMenu.setTitle(tr("Activate document")); contextMenu.addMenu(&subMenu); @@ -257,23 +892,49 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e) subMenu.addActions(subMenuGroup.actions()); } - if (contextMenu.actions().count() > 0) + if (contextMenu.actions().count() > 0) { contextMenu.exec(QCursor::pos()); + 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 = FC_TREEPARAM(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(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::Document* gui = Gui::Application::Instance->getDocument(doc); - gui->openCommand("Create group"); Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8()); - gui->commitCommand(); } else if (this->contextItem->type() == ObjectType) { DocumentObjectItem* objitem = static_cast @@ -285,10 +946,7 @@ void TreeWidget::onCreateGroup() .arg(QString::fromLatin1(doc->getName()), QString::fromLatin1(obj->getNameInDocument()), name); - Gui::Document* gui = Gui::Application::Instance->getDocument(doc); - gui->openCommand("Create group"); Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8()); - gui->commitCommand(); } } @@ -307,9 +965,11 @@ void TreeWidget::onStartEditing() DocumentObjectItem* objitem = static_cast (this->contextItem); int edit = action->data().toInt(); + App::DocumentObject* obj = objitem->object()->getObject(); - if (!obj) return; - Gui::Document* doc = Gui::Application::Instance->getDocument(obj->getDocument()); + if (!obj || !obj->getNameInDocument()) + return; + auto doc = const_cast(objitem->getOwnerDocument()->document()); MDIView *view = doc->getActiveView(); if (view) getMainWindow()->setActiveWindow(view); @@ -325,7 +985,9 @@ void TreeWidget::onStartEditing() bool ok = doc->setEdit(objitem->object(), edit); if (!ok) doc->abortCommand(); #else - doc->setEdit(objitem->object(), edit); + editingItem = objitem; + if(!doc->setEdit(objitem->object(), edit)) + editingItem = 0; #endif } } @@ -355,6 +1017,16 @@ void TreeWidget::onSkipRecompute(bool 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(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 @@ -378,6 +1050,154 @@ void TreeWidget::onMarkRecompute() } } +void TreeWidget::onRecomputeObject() { + std::vector objs; + for(auto ti : selectedItems()) { + if (ti->type() == ObjectType) { + DocumentObjectItem* objitem = static_cast(ti); + objs.push_back(objitem->object()->getObject()); + objs.back()->enforceRecompute(); + } + } + if(objs.empty()) + return; + App::AutoTransaction committer("Recompute object"); + std::string msg; + try { + objs.front()->getDocument()->recompute(objs,true); + }catch (Base::Exception &e) { + e.ReportException(); + msg = e.what(); + }catch (std::exception &e) { + msg = e.what(); + } + if(msg.size()) { + QMessageBox::critical(getMainWindow(), QObject::tr("Recompute failed"), + QString::fromUtf8(msg.c_str())); + } +} + + +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::getSelection(App::Document *doc) +{ + std::vector 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(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("invlaid 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( + 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(); @@ -388,50 +1208,13 @@ 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) return; - MDIView *view = doc->getActiveView(); - if (!view) return; - getMainWindow()->setActiveWindow(view); + if (doc) + doc->setActiveView(); } Qt::DropActions TreeWidget::supportedDropActions () const { - Qt::DropActions da = QTreeWidget::supportedDropActions(); - QList idxs = selectedIndexes(); - - if (idxs.size() == 1) { - // as we can't know here where the drop action will occur that is the best we can do - da |= Qt::LinkAction; - } - - for (QList::Iterator it = idxs.begin(); it != idxs.end(); ++it) { - QTreeWidgetItem* item = itemFromIndex(*it); - if (item->type() != TreeWidget::ObjectType) { - return Qt::IgnoreAction; - } - QTreeWidgetItem* parent = item->parent(); - if (parent) { - if (parent->type() != TreeWidget::ObjectType) { - da &= ~Qt::MoveAction; - } - else { - // Now check for object with a parent that is an ObjectType, too. - // If this object is *not* selected and *not* a group we are not allowed to remove - // its child (e.g. the sketch of a pad). - Gui::ViewProvider* vp = static_cast(parent)->object(); - App::DocumentObject* obj = static_cast(item)-> - object()->getObject(); - if (!vp->canDragObjects() || !vp->canDragObject(obj)) { - da &= ~Qt::MoveAction; - } - } - } - else { - da &= ~Qt::MoveAction; - } - } - - return da; + return Qt::LinkAction | Qt::CopyAction | Qt::MoveAction; } bool TreeWidget::event(QEvent *e) @@ -448,6 +1231,27 @@ bool TreeWidget::event(QEvent *e) return QTreeWidget::event(e); } +bool TreeWidget::eventFilter(QObject *, QEvent *ev) { + switch (ev->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: { + QKeyEvent *ke = static_cast(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 @@ -455,6 +1259,25 @@ void TreeWidget::keyPressEvent(QKeyEvent *event) 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); } @@ -464,30 +1287,55 @@ void TreeWidget::mouseDoubleClickEvent (QMouseEvent * event) if (!item) return; if (item->type() == TreeWidget::DocumentType) { //QTreeWidget::mouseDoubleClickEvent(event); - const Gui::Document* doc = static_cast(item)->document(); + Gui::Document* doc = static_cast(item)->document(); if (!doc) return; - MDIView *view = doc->getActiveView(); - if (!view) return; - getMainWindow()->setActiveWindow(view); + if(doc->getDocument()->testStatus(App::Document::PartialDoc)) { + contextItem = item; + onReloadDoc(); + return; + } + doc->setActiveView(); } else if (item->type() == TreeWidget::ObjectType) { DocumentObjectItem* objitem = static_cast(item); - App::DocumentObject* obj = objitem->object()->getObject(); - Gui::Document* doc = Gui::Application::Instance->getDocument(obj->getDocument()); - MDIView *view = doc->getActiveView(); - if (view) getMainWindow()->setActiveWindow(view); + objitem->getOwnerDocument()->document()->setActiveView(objitem->object()); + auto manager = Application::Instance->macroManager(); + auto lines = manager->getLines(); + 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()); } } +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 items) const { +#if 0 // all selected items must reference an object from the same document App::Document* doc=0; for (QList::ConstIterator it = items.begin(); it != items.end(); ++it) { @@ -499,6 +1347,7 @@ QMimeData * TreeWidget::mimeData (const QList items) const else if (doc != obj->getDocument()) return 0; } +#endif return QTreeWidget::mimeData(items); } @@ -518,160 +1367,155 @@ void TreeWidget::dragLeaveEvent(QDragLeaveEvent * event) QTreeWidget::dragLeaveEvent(event); } -QList TreeWidget::buildListChildren(QTreeWidgetItem* targetitem, Gui::ViewProviderDocumentObject* vp) -{ - // to check we do not drop the part on itself - QList children; - children << vp->getObject(); - for (int i=0; ichildCount(); i++) { - Gui::ViewProviderDocumentObject* vpc = static_cast(targetitem->child(i))->object(); - children << vpc->getObject(); - } - return children; -} - 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 { - QTreeWidgetItem* parentTarget = targetItem->parent(); - Qt::DropAction da = event->proposedAction(); + 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); - if (targetItem->type() == TreeWidget::DocumentType) { - QList idxs = selectedIndexes(); - App::Document* doc = static_cast(targetItem)-> - document()->getDocument(); - for (QList::Iterator it = idxs.begin(); it != idxs.end(); ++it) { - QTreeWidgetItem* item = itemFromIndex(*it); - if (item->type() != TreeWidget::ObjectType) { - event->ignore(); - return; - } - App::DocumentObject* obj = static_cast(item)-> - object()->getObject(); - if (doc != obj->getDocument()) { - event->ignore(); - return; - } + DocumentObjectItem* targetItemObj = static_cast(targetItem); + Gui::ViewProviderDocumentObject* vp = targetItemObj->object(); - // Dropping an object on the document that is not part of a composed object is non-sense - // Dropping an object on document means to remove the object of a composed object - QTreeWidgetItem* parent = item->parent(); - if (!parent || parent->type() != TreeWidget::ObjectType) { - event->ignore(); - return; - } - } + try { + auto items = selectedItems(); - // Link is non-sense on a document. - if (da == Qt::LinkAction) { - if (event->possibleActions() & Qt::MoveAction) { - event->setDropAction(Qt::MoveAction); - event->accept(); - } - else { + 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(); } } - } - else if (targetItem->type() == TreeWidget::ObjectType) { - DocumentObjectItem* targetItemObj = static_cast(targetItem); - Gui::ViewProviderDocumentObject* vpTarget = targetItemObj->object(); - QList children = buildListChildren(targetItem, vpTarget); - App::DocumentObject* targetObj = vpTarget->getObject(); - App::Document* docTarget = targetObj->getDocument(); - - QList idxsSelected = selectedIndexes(); - - std::vector dropObjects; - dropObjects.reserve(idxsSelected.size()); - - for (QList::Iterator it = idxsSelected.begin(); it != idxsSelected.end(); ++it) { - QTreeWidgetItem* item = itemFromIndex(*it); - if (item->type() != TreeWidget::ObjectType) { + for(auto ti : items) { + if (ti->type() != TreeWidget::ObjectType) { + TREE_TRACE("cannot drop"); event->ignore(); return; } + auto item = static_cast(ti); - Gui::ViewProviderDocumentObject* vpDropped = static_cast(item)->object(); - App::DocumentObject* objDropped = vpDropped->getObject(); - if (docTarget != objDropped->getDocument()) { - event->ignore(); - return; - } + auto obj = item->object()->getObject(); - dropObjects.push_back(objDropped); - - // To avoid a cylic dependency it must be made sure to not allow to - // drag'n'drop a tree item onto a child or grandchild item of it. - bool canDrop = !targetObj->isInInListRecursive(objDropped); - - // if the item is already a child of the target item there is nothing to do - canDrop = canDrop && vpTarget->canDropObjects() && !children.contains(objDropped); - - if (!canDrop) { - if (idxsSelected.size() == 1) { - if (parentTarget) { - if (parentTarget->type() == TreeWidget::ObjectType) { - DocumentObjectItem* parentTargetItemObj = - static_cast(parentTarget); - Gui::ViewProviderDocumentObject* vpParent = parentTargetItemObj->object(); - App::DocumentObject* objParentTarget = vpParent->getObject(); - - if (!buildListChildren(parentTarget, vpParent).contains(objDropped) && - !objParentTarget->isInInListRecursive(objDropped)) { - if (da == Qt::CopyAction || da == Qt::MoveAction) { - event->setDropAction(Qt::LinkAction); - event->accept(); - return; - } - else if (da == Qt::LinkAction) { - event->acceptProposedAction(); - return; - } - } - } - } - } - - event->ignore(); - return; - } - else { - // let the view provider decide to accept the object or ignore it - if (!vpTarget->canDropObject(objDropped)) { - event->ignore(); - return; - } - else if (da == Qt::LinkAction) { - if (!parentTarget || parentTarget->type() != TreeWidget::ObjectType) { - // no need to test compatibility between parent and objDropped and objParentTarget - // as test between objDropped and targetObj has been tested before + 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():"") << '.' << subname); + event->ignore(); + return; + } } - event->acceptProposedAction(); - } - else { + }catch(Base::Exception &e){ + e.ReportException(); 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 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) @@ -680,107 +1524,553 @@ void TreeWidget::dropEvent(QDropEvent *event) if (this->isItemSelected(targetItem)) return; + App::Document *thisDoc; + + Base::EmptySequencer seq; + // filter out the selected items we cannot handle - QList items; - QList idxs = selectedIndexes(); - for (QList::Iterator it = idxs.begin(); it != idxs.end(); ++it) { - // ignore child elements if the parent is selected (issue #0001456) - QModelIndex parent = (*it).parent(); - if (idxs.contains(parent)) + std::vector > > items; + auto sels = selectedItems(); + items.reserve(sels.size()); + for(auto ti : sels) { + if (ti->type() != TreeWidget::ObjectType) continue; - QTreeWidgetItem* item = itemFromIndex(*it); - if (item == targetItem) + // ignore child elements if the parent is selected + if(sels.contains(ti->parent())) continue; - if (item->parent() == targetItem) + if (ti == targetItem) continue; - items.push_back(item); + auto item = static_cast(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.isEmpty()) + if (items.empty()) return; // nothing needs to be done + 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(targetItem); + thisDoc = targetItemObj->getOwnerDocument()->document()->getDocument(); + Gui::ViewProviderDocumentObject* vp = targetItemObj->object(); - Qt::DropAction da = event->dropAction(); - - Gui::ViewProviderDocumentObject* vpTarget = targetItemObj->object(); - Gui::Document* gui = vpTarget->getDocument(); - - if (da == Qt::LinkAction) { - // Open command - gui->openCommand("Drop object"); - for (QList::Iterator it = items.begin(); it != items.end(); ++it) { - Gui::ViewProviderDocumentObject* vpDropped = static_cast(*it)->object(); - App::DocumentObject* objDropped = vpDropped->getObject(); - - // does this have a parent object - QTreeWidgetItem* parent = targetItemObj->parent(); - - if (parent && parent->type() == TreeWidget::ObjectType) { - Gui::ViewProvider* vpParent = static_cast(parent)->object(); - // now add the object to the target object - vpParent->replaceObject(vpTarget->getObject(), objDropped); - } - - } - gui->commitCommand(); + if(!vp || !vp->getObject() || !vp->getObject()->getNameInDocument()) { + TREE_TRACE("invalid object"); + return; } - else { - if (!vpTarget->canDropObjects()) { + + if (da!=Qt::LinkAction && !vp->canDropObjects()) { + if(!(event->possibleActions() & Qt::LinkAction) || items.size()!=1) { + TREE_TRACE("Cannot drop objects"); return; // no group like object } + } - bool dropOnly = da == Qt::CopyAction; + 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()); + } - // Open command - gui->openCommand("Drag object"); - for (QList::Iterator it = items.begin(); it != items.end(); ++it) { - Gui::ViewProviderDocumentObject* vpDropped = static_cast(*it)->object(); - App::DocumentObject* objDropped = vpDropped->getObject(); + bool syncPlacement = FC_TREEPARAM(SyncPlacement) && targetItemObj->isGroup(); - if (!dropOnly) { - // does this have a parent object - QTreeWidgetItem* parent = (*it)->parent(); - if (parent && parent->type() == TreeWidget::ObjectType) { - Gui::ViewProvider* vpParent = static_cast(parent)->object(); - vpParent->dragObject(objDropped); + bool setSelection = true; + std::vector > droppedObjects; + + std::vector 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 + && 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(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 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( + 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( + 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( + Application::Instance->getViewProvider(parent)); + } + if(!vpp) { + FC_WARN("Cannot find dragging object's parent " << info.parent); + continue; } } - // now add the object to the target object - vpTarget->dropObject(objDropped); + 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( + obj->getPropertyByName("Placement")); + } + } + } + }else{ + propPlacement = Base::freecad_dynamic_cast( + 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 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( + sobj->getPropertyByName("Placement")); + if(propPlacement) { + newMat *= propPlacement->getValue().inverse().toMatrix(); + newMat.inverse(); + Base::Placement pla(newMat*mat); + propPlacement->setValueIfChanged(pla); + } + } + } + droppedObjects.emplace_back(dropParent,dropName); } - gui->commitCommand(); + 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) { + committer.close(true); + QMessageBox::critical(getMainWindow(), QObject::tr("Drag & drop failed"), + QString::fromLatin1(e.what())); + e.ReportException(); + return; } } else if (targetItem->type() == TreeWidget::DocumentType) { - // Open command - bool commit = false; - App::Document* doc = static_cast(targetItem)->document()->getDocument(); - Gui::Document* gui = Gui::Application::Instance->getDocument(doc); - gui->openCommand("Move object"); - for (QList::Iterator it = items.begin(); it != items.end(); ++it) { - Gui::ViewProviderDocumentObject* vpDropped = static_cast(*it)->object(); - App::DocumentObject* objDropped = vpDropped->getObject(); + auto targetDocItem = static_cast(targetItem); + thisDoc = targetDocItem->document()->getDocument(); - // does this have a parent object - QTreeWidgetItem* parent = (*it)->parent(); - if (parent && parent->type() == TreeWidget::ObjectType) { - Gui::ViewProvider* vpParent = static_cast(parent)->object(); - if (vpParent->canDragObject(objDropped)) { - vpParent->dragObject(objDropped); - commit = true; + std::vector infos; + infos.reserve(items.size()); + bool syncPlacement = FC_TREEPARAM(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(); - if (commit) - gui->commitCommand(); - else - gui->abortCommand(); + // 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 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( + 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( + obj->getPropertyByName("Placement")); + } + } + } + }else{ + propPlacement = dynamic_cast( + 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(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( + 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( + res->getPropertyByName("Placement")); + if(propPlacement) + propPlacement->setValueIfChanged(Base::Placement(mat)); + droppedObjs.push_back(res); + } + manager->addLine(MacroManager::App,ss.str().c_str()); + } + } + touched = true; + Selection().setSelection(thisDoc->getName(),droppedObjs); + + } catch (const Base::Exception& e) { + committer.close(true); + QMessageBox::critical(getMainWindow(), QObject::tr("Drag & drop failed"), + QString::fromLatin1(e.what())); + e.ReportException(); + return; + } } + + if(touched && FC_TREEPARAM(RecomputeOnDrop)) + thisDoc->recompute(); } void TreeWidget::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const @@ -801,22 +2091,52 @@ void TreeWidget::drawRow(QPainter *painter, const QStyleOptionViewItem &options, //} } -void TreeWidget::slotNewDocument(const Gui::Document& Doc) +void TreeWidget::slotNewDocument(const Gui::Document& Doc, bool isMainDoc) { DocumentItem* item = new DocumentItem(&Doc, this->rootItem); - this->expandItem(item); + if(isMainDoc) + this->expandItem(item); item->setIcon(0, *documentPixmap); item->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue())); DocumentMap[ &Doc ] = item; } -void TreeWidget::slotDeleteDocument(const Gui::Document& Doc) -{ - std::map::iterator it = DocumentMap.find(&Doc); - if (it != DocumentMap.end()) { - this->rootItem->takeChild(this->rootItem->indexOfChild(it->second)); - delete it->second; - DocumentMap.erase(it); +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(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(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(); } } @@ -826,9 +2146,27 @@ void TreeWidget::slotRenameDocument(const Gui::Document& Doc) 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(vp); + if(&prop == &vpd.ShowInTree) + ChangedObjects.emplace(vpd.getObject(),0); + } +} + +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) { - std::map::iterator it = DocumentMap.find(&Doc); + auto it = DocumentMap.find(&Doc); if (it != DocumentMap.end()) { it->second->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue())); } @@ -836,12 +2174,11 @@ void TreeWidget::slotRelabelDocument(const Gui::Document& Doc) void TreeWidget::slotActiveDocument(const Gui::Document& Doc) { - std::map::iterator jt = DocumentMap.find(&Doc); + auto jt = DocumentMap.find(&Doc); if (jt == DocumentMap.end()) return; // signal is emitted before the item gets created - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); - int displayMode = hGrp->GetInt("TreeViewDocument", 0); - for (std::map::iterator it = DocumentMap.begin(); + int displayMode = FC_TREEPARAM(DocumentMode); + for (auto it = DocumentMap.begin(); it != DocumentMap.end(); ++it) { QFont f = it->second->font(0); @@ -855,35 +2192,278 @@ void TreeWidget::slotActiveDocument(const Gui::Document& Doc) } } +static int _UpdateBlocked; -void TreeWidget::onTestStatus(void) +struct UpdateDisabler { + QWidget &widget; + bool visible; + bool focus; + + // Note! DO NOT block signal here, or else + // QTreeWidgetItem::setChildIndicatorPolicy() does not work + UpdateDisabler(QWidget &w) + :widget(w) + { + if(++_UpdateBlocked > 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(_UpdateBlocked<=0 || --_UpdateBlocked!=0) + return; + + if(visible) { + widget.setVisible(true); + // widget.setUpdatesEnabled(true); + if(focus) + widget.setFocus(); + } + } +}; + +void TreeWidget::onUpdateStatus(void) { - if (isVisible()) { - std::map::iterator pos; - for (pos = DocumentMap.begin();pos!=DocumentMap.end();++pos) { - pos->second->testStatus(); + 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; } } - this->statusTimer->setSingleShot(true); - this->statusTimer->start(300); + FC_LOG("begin update status"); + + UpdateDisabler disabler(*this); + + std::vector 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(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(); + + if(docItem->connectChgObject.connected()) + continue; + docItem->connectChgObject = docItem->document()->signalChangedObject.connect( + boost::bind(&TreeWidget::slotChangeObject, this, _1, _2)); + + auto doc = v.first->getDocument(); + 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->blockConnection(true); + currentDocItem = 0; + for(auto &v : DocumentMap) { + v.second->setSelected(false); + v.second->selectItems(false); + } + 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* obj = static_cast(item); - obj->displayStatusInfo(); + DocumentObjectItem* objItem = static_cast(item); + objItem->displayStatusInfo(); + + if(FC_TREEPARAM(PreSelection)) { + int timeout = FC_TREEPARAM(PreSelectionDelay); + if(timeout < 0) + timeout = 1; + if(preselectTime.elapsed() < timeout) + onPreSelectTimer(); + else{ + timeout = FC_TREEPARAM(PreSelectionTimeout); + if(timeout < 0) + timeout = 1; + preselectTimer->start(timeout); + Selection().rmvPreselect(); + } + } + } else if(FC_TREEPARAM(PreSelection)) + Selection().rmvPreselect(); +} + +void TreeWidget::leaveEvent(QEvent *) { + if(!_UpdateBlocked && FC_TREEPARAM(PreSelection)) { + preselectTimer->stop(); + Selection().rmvPreselect(); } } +void TreeWidget::onPreSelectTimer() { + if(!FC_TREEPARAM(PreSelection)) + return; + auto item = itemAt(viewport()->mapFromGlobal(QCursor::pos())); + if(!item || item->type()!=TreeWidget::ObjectType) + return; + + preselectTime.restart(); + DocumentObjectItem* objItem = static_cast(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) { - DocumentObjectItem* obj = static_cast(item); - obj->setExpandedStatus(false); + static_cast(item)->setExpandedStatus(false); } } @@ -891,167 +2471,292 @@ void TreeWidget::onItemExpanded(QTreeWidgetItem * item) { // object item expanded if (item && item->type() == TreeWidget::ObjectType) { - DocumentObjectItem* obj = static_cast(item); - obj->setExpandedStatus(true); - auto it = DocumentMap.find(obj->object()->getDocument()); - if(it==DocumentMap.end()) - Base::Console().Warning("DocumentItem::onItemExpanded: cannot find object document\n"); - else - it->second->populateItem(obj); + DocumentObjectItem* objItem = static_cast(item); + objItem->setExpandedStatus(true); + objItem->getOwnerDocument()->populateItem(objItem,false,false); } } -void TreeWidget::scrollItemToTop(Gui::Document* doc) +void TreeWidget::scrollItemToTop() { - std::map::iterator it; - it = DocumentMap.find(doc); - if (it != DocumentMap.end()) { - DocumentItem* root = it->second; - QTreeWidgetItemIterator it(root, QTreeWidgetItemIterator::Selected); - for (; *it; ++it) { - if ((*it)->type() == TreeWidget::ObjectType) { - this->scrollToItem(*it, QAbstractItemView::PositionAtTop); - break; + auto doc = Application::Instance->activeDocument(); + for(auto tree : Instances) { + if(!tree->isConnectionAttached()) + 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(true); + tree->blockConnection(lock); } + } else { + tree->blockConnection(true); + for (int i=0; irootItem->childCount(); i++) { + auto docItem = dynamic_cast(tree->rootItem->child(i)); + if(!docItem) + continue; + auto doc = docItem->document()->getDocument(); + if(Gui::Selection().hasSelection(doc->getName())) { + tree->currentDocItem = docItem; + docItem->selectItems(true); + 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 Gui::ExpandPath: { + QTreeWidgetItem* parentItem = item->parent(); + while (parentItem) { + parentItem->setExpanded(true); + parentItem = parentItem->parent(); + } + item->setExpanded(true); + break; + } + case Gui::ExpandItem: + item->setExpanded(true); + break; + case Gui::CollapseItem: + item->setExpanded(false); + break; + case Gui::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 && FC_TREEPARAM(SyncView)) { + currentDocItem->document()->setActiveView(vp); + setFocus(); + } +} + +void TreeWidget::onShowHidden() { + if (!this->contextItem) return; + DocumentItem *docItem = nullptr; + if(this->contextItem->type() == DocumentType) + docItem = static_cast(contextItem); + else if(this->contextItem->type() == ObjectType) + docItem = static_cast(contextItem)->getOwnerDocument(); + if(docItem) + docItem->setShowHidden(showHiddenAction->isChecked()); +} + +void TreeWidget::onHideInTree() { + if (this->contextItem && this->contextItem->type() == ObjectType) { + auto item = static_cast(contextItem); + item->object()->ShowInTree.setValue(!hideInTreeAction->isChecked()); + } +} + + void TreeWidget::changeEvent(QEvent *e) { - if (e->type() == QEvent::LanguageChange) { - this->headerItem()->setText(0, tr("Labels & Attributes")); - this->rootItem->setText(0, tr("Application")); - - 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->skipRecomputeAction->setText(tr("Skip recomputes")); - this->skipRecomputeAction->setStatusTip(tr("Enable or disable recomputations of document")); - - this->markRecomputeAction->setText(tr("Mark to recompute")); - this->markRecomputeAction->setStatusTip(tr("Mark this object to be recomputed")); - } + if (e->type() == QEvent::LanguageChange) + setupText(); QTreeWidget::changeEvent(e); } void TreeWidget::onItemSelectionChanged () { - // we already got notified by the selection to update the tree items - if (this->isConnectionBlocked()) + if (!this->isConnectionAttached() + || this->isConnectionBlocked() + || _UpdateBlocked) return; + _LastSelectedTreeWidget = this; + + if(selectTimer->isActive()) + onSelectTimer(); + else + _updateStatus(false); + // block tmp. the connection to avoid to notify us ourself bool lock = this->blockConnection(true); - std::map::iterator pos; - for (pos = DocumentMap.begin();pos!=DocumentMap.end();++pos) { - pos->second->updateSelection(); + + auto selItems = selectedItems(); + + // do not allow document item multi-selection + auto itDoc = selItems.end(); + auto itObj = selItems.end(); + for(auto it=selItems.begin();it!=selItems.end();) { + auto item = *it; + if(item->type() == ObjectType) { + itObj = it; + if(itDoc!=selItems.end()) { + (*itDoc)->setSelected(false); + selItems.erase(itDoc); + itDoc = selItems.end(); + } + }else if(item->type() == DocumentType) { + if(itObj!=selItems.end()) { + item->setSelected(false); + it = selItems.erase(it); + continue; + }else if(itDoc!=selItems.end()) { + (*itDoc)->setSelected(false); + selItems.erase(itDoc); + } + itDoc = it; + } + ++it; } + + if(selItems.size()<=1) { + if(FC_TREEPARAM(RecordSelection)) + Gui::Selection().selStackPush(); + + // This special handling to deal with possible discrepency 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(selItems.front()); + else if(selItems.front()->type() == DocumentType) { + auto ditem = static_cast(selItems.front()); + if(FC_TREEPARAM(SyncView)) { + ditem->document()->setActiveView(); + setFocus(); + } + // For triggering property editor refresh + Gui::Selection().signalSelectionChanged(SelectionChanges()); + } + } + for(auto &v : DocumentMap) { + currentDocItem = v.second; + v.second->clearSelection(item); + currentDocItem = 0; + } + if(FC_TREEPARAM(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(FC_TREEPARAM(RecordSelection)) + Gui::Selection().selStackPush(true,true); + } + this->blockConnection(lock); } +void TreeWidget::onSelectTimer() { + + _updateStatus(false); + + bool syncSelect = FC_TREEPARAM(SyncSelection); + this->blockConnection(true); + if(Selection().hasSelection()) { + for(auto &v : DocumentMap) { + v.second->setSelected(false); + currentDocItem = v.second; + v.second->selectItems(syncSelect); + currentDocItem = 0; + } + }else{ + for(auto &v : DocumentMap) + v.second->clearSelection(); + } + this->blockConnection(false); + selectTimer->stop(); + return; +} + void TreeWidget::onSelectionChanged(const SelectionChanges& msg) { switch (msg.Type) { case SelectionChanges::AddSelection: - { - Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName); - std::map::iterator it; - it = DocumentMap.find(pDoc); - bool lock = this->blockConnection(true); - if (it!= DocumentMap.end()) - it->second->setObjectSelected(msg.pObjectName,true); - this->blockConnection(lock); - } break; case SelectionChanges::RmvSelection: - { - Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName); - std::map::iterator it; - it = DocumentMap.find(pDoc); - bool lock = this->blockConnection(true); - if (it!= DocumentMap.end()) - it->second->setObjectSelected(msg.pObjectName,false); - this->blockConnection(lock); - } break; case SelectionChanges::SetSelection: - { - Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName); - std::map::iterator it; - it = DocumentMap.find(pDoc); - // we get notified from the selection and must only update the selection on the tree, - // thus no need to notify again the selection. See also onItemSelectionChanged(). - if (it != DocumentMap.end()) { - bool lock = this->blockConnection(true); - it->second->selectItems(); - this->blockConnection(lock); - } - } break; - case SelectionChanges::ClrSelection: - { - // clears the complete selection - if (strcmp(msg.pDocName,"") == 0) { - this->clearSelection (); - } - else { - // clears the selection of the given document - Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName); - std::map::iterator it; - it = DocumentMap.find(pDoc); - if (it != DocumentMap.end()) { - it->second->clearSelection(); - } - } - this->update(); - } break; - case SelectionChanges::SetPreselect: - { - Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName); - std::map::iterator it; - it = DocumentMap.find(pDoc); - if (it!= DocumentMap.end()) - it->second->setObjectHighlighted(msg.pObjectName,true); - } break; - case SelectionChanges::RmvPreselect: - { - Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName); - std::map::iterator it; - it = DocumentMap.find(pDoc); - if (it!= DocumentMap.end()) - it->second->setObjectHighlighted(msg.pObjectName,false); - } break; + case SelectionChanges::ClrSelection: { + int timeout = FC_TREEPARAM(SelectionTimeout); + if(timeout<=0) + timeout = 1; + selectTimer->start(timeout); + break; + } default: break; } } -void TreeWidget::setItemsSelected (const QList items, bool select) -{ - if (items.isEmpty()) - return; - QItemSelection range; - for (QList::const_iterator it = items.begin(); it != items.end(); ++it) - range.select(this->indexFromItem(*it),this->indexFromItem(*it)); - selectionModel()->select(range, select ? - QItemSelectionModel::Select : - QItemSelectionModel::Deselect); -} - // ---------------------------------------------------------------------------- /* TRANSLATOR Gui::TreePanel */ -TreePanel::TreePanel(QWidget* parent) +TreePanel::TreePanel(const char *name, QWidget* parent) : QWidget(parent) { - this->treeWidget = new TreeWidget(this); + this->treeWidget = new TreeWidget(name, this); ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView"); this->treeWidget->setIndentation(hGrp->GetInt("Indentation", this->treeWidget->indentation())); @@ -1072,7 +2777,7 @@ TreePanel::TreePanel(QWidget* parent) connect(this->searchBox, SIGNAL(returnPressed()), this, SLOT(accept())); connect(this->searchBox, SIGNAL(textEdited(QString)), - this, SLOT(findMatchingItems(QString))); + this, SLOT(itemSearch(QString))); } TreePanel::~TreePanel() @@ -1082,12 +2787,8 @@ TreePanel::~TreePanel() void TreePanel::accept() { QString text = this->searchBox->text(); - if (!text.isEmpty()) { - for (int i=0; itopLevelItemCount(); i++) { - selectTreeItem(treeWidget->topLevelItem(i), text); - } - } hideEditor(); + this->treeWidget->itemSearch(text,true); } bool TreePanel::eventFilter(QObject *obj, QEvent *ev) @@ -1118,67 +2819,22 @@ void TreePanel::showEditor() { this->searchBox->show(); this->searchBox->setFocus(); + this->treeWidget->startItemSearch(); } void TreePanel::hideEditor() { this->searchBox->clear(); this->searchBox->hide(); - for (int i=0; itopLevelItemCount(); i++) { - resetBackground(treeWidget->topLevelItem(i)); - } + this->treeWidget->resetItemSearch(); + auto sels = this->treeWidget->selectedItems(); + if(sels.size()) + this->treeWidget->scrollToItem(sels.front()); } -void TreePanel::findMatchingItems(const QString& text) +void TreePanel::itemSearch(const QString &text) { - if (text.isEmpty()) { - for (int i=0; itopLevelItemCount(); i++) { - resetBackground(treeWidget->topLevelItem(i)); - } - } - else { - for (int i=0; itopLevelItemCount(); i++) { - searchTreeItem(treeWidget->topLevelItem(i), text); - } - } -} - -void TreePanel::searchTreeItem(QTreeWidgetItem* item, const QString& text) -{ - for (int i=0; ichildCount(); i++) { - QTreeWidgetItem* child = item->child(i); - child->setBackground(0, QBrush()); - child->setExpanded(false); - if (child->text(0).indexOf(text, 0, Qt::CaseInsensitive) >= 0) { - child->setBackground(0, QColor(255, 255, 0, 100)); - QTreeWidgetItem* parent = child->parent(); - while (parent) { - parent->setExpanded(true); - parent = parent->parent(); - } - } - searchTreeItem(child, text); - } -} - -void TreePanel::selectTreeItem(QTreeWidgetItem* item, const QString& text) -{ - for (int i=0; ichildCount(); i++) { - QTreeWidgetItem* child = item->child(i); - if (child->text(0).indexOf(text, 0, Qt::CaseInsensitive) >= 0) { - child->setSelected(true); - } - selectTreeItem(child, text); - } -} - -void TreePanel::resetBackground(QTreeWidgetItem* item) -{ - for (int i=0; ichildCount(); i++) { - QTreeWidgetItem* child = item->child(i); - child->setBackground(0, QBrush()); - resetBackground(child); - } + this->treeWidget->itemSearch(text,false); } // ---------------------------------------------------------------------------- @@ -1189,9 +2845,9 @@ TreeDockWidget::TreeDockWidget(Gui::Document* pcDocument,QWidget *parent) : DockWindow(pcDocument,parent) { setWindowTitle(tr("Tree view")); - this->treeWidget = new TreeWidget(this); + this->treeWidget = new TreeWidget("TreeView",this); this->treeWidget->setRootIsDecorated(false); - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView"); + GET_TREEVIEW_PARAM(hGrp); this->treeWidget->setIndentation(hGrp->GetInt("Indentation", this->treeWidget->indentation())); QGridLayout* pLayout = new QGridLayout(this); @@ -1204,24 +2860,70 @@ TreeDockWidget::~TreeDockWidget() { } +void TreeWidget::selectLinkedObject(App::DocumentObject *linked) { + if(!isConnectionAttached()) + return; + + auto linkedVp = Base::freecad_dynamic_cast( + 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()) + linkedDoc->document()->setActiveView(linkedItem->object()); +} + // ---------------------------------------------------------------------------- DocumentItem::DocumentItem(const Gui::Document* doc, QTreeWidgetItem * parent) - : QTreeWidgetItem(parent, TreeWidget::DocumentType), pDocument(doc) + : QTreeWidgetItem(parent, TreeWidget::DocumentType), pDocument(const_cast(doc)) { // Setup connections connectNewObject = doc->signalNewObject.connect(boost::bind(&DocumentItem::slotNewObject, this, _1)); - connectDelObject = doc->signalDeletedObject.connect(boost::bind(&DocumentItem::slotDeleteObject, this, _1)); - connectChgObject = doc->signalChangedObject.connect(boost::bind(&DocumentItem::slotChangeObject, this, _1)); - connectRenObject = doc->signalRelabelObject.connect(boost::bind(&DocumentItem::slotRenameObject, this, _1)); - connectActObject = doc->signalActivatedObject.connect(boost::bind(&DocumentItem::slotActiveObject, 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)); 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)); - connectExpObject = doc->signalExpandObject.connect(boost::bind(&DocumentItem::slotExpandObject, this, _1,_2)); + 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::ItemIsEditable*/); + setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable/*|Qt::ItemIsEditable*/); + + treeName = getTree()->getTreeName(); } DocumentItem::~DocumentItem() @@ -1229,98 +2931,142 @@ DocumentItem::~DocumentItem() connectNewObject.disconnect(); connectDelObject.disconnect(); connectChgObject.disconnect(); - connectRenObject.disconnect(); - connectActObject.disconnect(); connectEdtObject.disconnect(); connectResObject.disconnect(); connectHltObject.disconnect(); connectExpObject.disconnect(); connectScrObject.disconnect(); + connectRecomputed.disconnect(); + connectRecomputedObj.disconnect(); +} + +TreeWidget *DocumentItem::getTree() const{ + return static_cast(treeWidget()); +} + +const char *DocumentItem::getTreeName() const { + return treeName; } #define FOREACH_ITEM(_item, _obj) \ - auto _it = ObjectMap.find(std::string(_obj.getObject()->getNameInDocument()));\ - if(_it == ObjectMap.end() || _it->second->empty()) return;\ - for(auto _item : *_it->second){{ + 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) { - -#define FOREACH_ITEM_NAME(_item,_name) \ - auto _it = ObjectMap.find(_name);\ - if(_it != ObjectMap.end()) {\ - for(auto _item : *_it->second) { + 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); - FOREACH_ITEM(item,v) - item->setBackgroundColor(0,QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff)); - END_FOREACH_ITEM + 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(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) { - FOREACH_ITEM(item,v) - item->setData(0, Qt::BackgroundColorRole,QVariant()); + 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) { - createNewItem(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, DocumentObjectItemsPtr ptrs) + QTreeWidgetItem *parent, int index, DocumentObjectDataPtr data) { const char *name; - if (!obj.showInTree() || !(name=obj.getObject()->getNameInDocument())) + if (!obj.getObject() || + !(name=obj.getObject()->getNameInDocument()) || + obj.getObject()->testStatus(App::PartialObject)) return false; - if (!ptrs) { - auto &items = ObjectMap[name]; - if (!items) { - items.reset(new DocumentObjectItems); - } - else if(items->size() && parent==NULL) { + if(!data) { + auto &pdata = ObjectMap[obj.getObject()]; + if(!pdata) { + pdata = std::make_shared( + this, const_cast(&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; } - ptrs = items; + data = pdata; } - std::string displayName = obj.getObject()->Label.getValue(); - DocumentObjectItem* item = new DocumentObjectItem( - const_cast(&obj), ptrs); - - if (!parent) + DocumentObjectItem* item = new DocumentObjectItem(this,data); + if(!parent || parent==this) { parent = this; - if (index<0) + 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); - // Couldn't be added and thus don't continue populating it - // and delete it again - if (!item->parent()) { - delete item; - } - else { - item->setIcon(0, obj.getIcon()); - item->setText(0, QString::fromUtf8(displayName.c_str())); - populateItem(item); - } - + populateItem(item); return true; } -static bool canCreateItem(const App::DocumentObject *obj, const Document *doc) -{ +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 @@ -1330,41 +3076,138 @@ static bool canCreateItem(const App::DocumentObject *obj, const Document *doc) // 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() && doc->getDocument()->isIn(obj); + // + // 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(vp); } -void DocumentItem::slotDeleteObject(const Gui::ViewProviderDocumentObject& view) +void TreeWidget::slotDeleteDocument(const Gui::Document& Doc) { - auto it = ObjectMap.find(std::string(view.getObject()->getNameInDocument())); - if (it == ObjectMap.end() || it->second->empty()) + NewObjects.erase(Doc.getDocument()->getName()); + auto it = DocumentMap.find(&Doc); + if (it != DocumentMap.end()) { + UpdateDisabler disabler(*this); + 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; - auto &items = *(it->second); - for (auto cit=items.begin(),citNext=cit;cit!=items.end();cit=citNext) { - ++citNext; - delete *cit; + if(itEntry->second.empty()) { + ObjectTable.erase(itEntry); + return; } - if (items.empty()) - ObjectMap.erase(it); + TREE_LOG("delete object " << obj->getFullName()); - // Check for any child of the deleted object is not in the tree, and put it - // under document item. - const auto &children = view.claimChildren(); - for (auto child : children) { - if (!canCreateItem(child,pDocument)) + bool needUpdate = false; + + for(auto data : itEntry->second) { + DocumentItem *docItem = data->docItem; + if(docItem == deletingDoc) continue; - auto it = ObjectMap.find(child->getNameInDocument()); - if (it==ObjectMap.end() || it->second->empty()) { - ViewProvider* vp = pDocument->getViewProvider(child); - if (!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) - continue; - createNewItem(static_cast(*vp)); + + auto doc = docItem->document()->getDocument(); + auto &items = data->items; + + if(obj->getDocument() == doc) + docItem->_ParentMap.erase(obj); + + for(auto cit=items.begin(),citNext=cit;cit!=items.end();cit=citNext) { + ++citNext; + (*cit)->myOwner = 0; + delete *cit; } + + // 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(); } -void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh) +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) { if (item->populated && !refresh) return; @@ -1373,28 +3216,32 @@ void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh) // a) the item is expanded, or b) there is at least one free child, i.e. // child originally located at root. - const auto &children = item->object()->claimChildren(); - - item->setChildIndicatorPolicy(children.empty()? + item->setChildIndicatorPolicy(item->myData->children.empty()? QTreeWidgetItem::DontShowIndicator:QTreeWidgetItem::ShowIndicator); if (!item->populated && !item->isExpanded()) { bool doPopulate = false; - for (auto child : children) { - if (!canCreateItem(child,pDocument)) - continue; - auto it = ObjectMap.find(child->getNameInDocument()); - if (it == ObjectMap.end() || it->second->empty()) { - ViewProvider* vp = pDocument->getViewProvider(child); - if (!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) - continue; + + 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 ((*it->second->begin())->parent() == this) { - doPopulate = true; - break; + if(item->myData->removeChildrenFromRoot) { + if(it->second->rootItem) { + doPopulate = true; + break; + } } } @@ -1403,18 +3250,19 @@ void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh) } 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. - for (auto child : children) { - if (!canCreateItem(child,pDocument)) - continue; + // iterate through the claimed children, and try to synchronize them with the + // children tree item with the same order of apperance. + int childCount = item->childCount(); + for(auto child : item->myData->children) { ++i; // the current index of the claimed child bool found = false; - for (int j=0,count=item->childCount();jchild(j); if (ci->type() != TreeWidget::ObjectType) continue; @@ -1425,11 +3273,26 @@ void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh) found = true; if (j!=i) { // fix index if it is changed + childItem->setHighlight(false); item->removeChild(ci); item->insertChild(i,ci); - if (!ci->parent()) { - delete 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; } @@ -1440,200 +3303,418 @@ void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh) // This algo will be recursively applied to newly created child items // through slotNewObject -> populateItem - auto it = ObjectMap.find(child->getNameInDocument()); - if (it==ObjectMap.end() || it->second->empty()) { - ViewProvider* vp = pDocument->getViewProvider(child); - if (!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()) || - !createNewItem(static_cast(*vp),item,i, - it==ObjectMap.end()?DocumentObjectItemsPtr():it->second)) + 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; } - DocumentObjectItem *childItem = *it->second->begin(); - if (childItem->parent() != this) { + if(!item->myData->removeChildrenFromRoot || !it->second->rootItem) { + DocumentObjectItem *childItem = *it->second->items.begin(); if(!createNewItem(*childItem->object(),item,i,it->second)) --i; - } - else { - if (item==childItem || item->isChildOfItem(childItem)) { - Base::Console().Error("Gui::DocumentItem::populateItem(): Cyclic dependency in %s and %s\n", - item->object()->getObject()->Label.getValue(), - childItem->object()->getObject()->Label.getValue()); + 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); - if (!childItem->parent()) { - delete childItem; - } + assert(childItem->parent()==item); + if(checkHidden) + updateItemsVisibility(childItem,false); } } - App::GeoFeatureGroupExtension* grp = nullptr; - if (item->object()->getObject()->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId())) - grp = item->object()->getObject()->getExtensionByType(); - - // When removing a child element then it must either be moved to a new - // parent or deleted. Just removing and leaving breaks the Qt internal - // notification (See #0003201). for (++i;item->childCount()>i;) { - QTreeWidgetItem *childItem = item->child(i); - item->removeChild(childItem); - if (childItem->type() == TreeWidget::ObjectType) { - DocumentObjectItem* obj = static_cast(childItem); - // Add the child item back to document root if it is the only - // instance. Now, because of the lazy loading strategy, this may - // not truly be the last instance of the object. It may belong to - // other parents not expanded yet. We don't want to traverse the - // whole tree to confirm that. Just let it be. If the other - // parent(s) later expanded, this child item will be moved from - // root to its parent. - if (obj->myselves->size()==1) { - // We only make a difference for geofeaturegroups, - // as otherwise it comes to confusing behavior to the user when things - // get claimed within the group (e.g. pad/sketch, or group) - if (!grp || !grp->hasObject(obj->object()->getObject(), true)) { + QTreeWidgetItem *ci = item->child(i); + if (ci->type() == TreeWidget::ObjectType) { + DocumentObjectItem* childItem = static_cast(ci); + if(childItem->requiredAtRoot()) { + item->removeChild(childItem); + auto index = findRootIndex(childItem->object()->getObject()); + if(index>=0) + this->insertChild(index,childItem); + else this->addChild(childItem); - continue; + 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(delay); +} + +int DocumentItem::findRootIndex(App::DocumentObject *childObj) { + if(!FC_TREEPARAM(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(citem)->object()->getObject(); + if(obj->getID()<=childObj->getID()) + return last+1; + break; + } + } + + // find the first item + for(first=0;firstchild(first); + if(citem->type() == TreeWidget::ObjectType) { + auto obj = static_cast(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(citem)->object()->getObject(); + if(obj->getID()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 &dataSet, bool propOutput, bool force) +{ + bool childrenChanged = false; + std::vector 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 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); } } - - delete childItem; } -} -void DocumentItem::slotChangeObject(const Gui::ViewProviderDocumentObject& view) -{ - QString displayName = QString::fromUtf8(view.getObject()->Label.getValue()); - FOREACH_ITEM(item,view) - item->setText(0, displayName); - populateItem(item, true); - END_FOREACH_ITEM + 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(view.getObject()); - if (grp) { - FOREACH_ITEM_NAME(item, grp->getNameInDocument()) - populateItem(item, true); - END_FOREACH_ITEM - } -} - -void DocumentItem::slotRenameObject(const Gui::ViewProviderDocumentObject& obj) -{ - // Do nothing here because the Label is set in slotChangeObject - Q_UNUSED(obj); -} - -void DocumentItem::slotActiveObject(const Gui::ViewProviderDocumentObject& obj) -{ -#if 0 - std::string objectName = obj.getObject()->getNameInDocument(); - if (ObjectMap.find(objectName) == ObjectMap.end()) - return; // signal is emitted before the item gets created - - for (auto v : ObjectMap) { - for (auto item : *v.second) { - QFont f = item->font(0); - f.setBold(item->object() == &obj); - item->setFont(0,f); + //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); } } -#else - Q_UNUSED(obj); -#endif } - -void DocumentItem::slotHighlightObject (const Gui::ViewProviderDocumentObject& obj, const Gui::HighlightMode& high, bool set) + +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) - QFont f = item->font(0); - auto highlight = [&item, &set](const QColor& col){ - if (set) - item->setBackgroundColor(0, col); - else - item->setData(0, Qt::BackgroundColorRole,QVariant()); - }; - - switch (high) { - case Gui::Bold: - f.setBold(set); - break; - case Gui::Italic: - f.setItalic(set); - break; - case Gui::Underlined: - f.setUnderline(set); - break; - case Gui::Overlined: - f.setOverline(set); - break; - case Gui::Blue: - highlight(QColor(200,200,255)); - break; - case Gui::LightBlue: - highlight(QColor(230,230,255)); - break; - case Gui::UserDefined: - { - QColor color(230,230,255); - if (set) { - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView"); - bool bold = hGrp->GetBool("TreeActiveBold",true); - bool italic = hGrp->GetBool("TreeActiveItalic",false); - bool underlined = hGrp->GetBool("TreeActiveUnderlined",false); - bool overlined = hGrp->GetBool("TreeActiveOverlined",false); - f.setBold(bold); - f.setItalic(italic); - f.setUnderline(underlined); - f.setOverline(overlined); - - unsigned long col = hGrp->GetUnsigned("TreeActiveColor",3873898495); - color = QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff); - } - else { - f.setBold(false); - f.setItalic(false); - f.setUnderline(false); - f.setOverline(false); - } - highlight(color); - } break; - default: - break; + 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->setFont(0,f); + item->setHighlight(set,high); + if(parent) + return; END_FOREACH_ITEM } -void DocumentItem::slotExpandObject (const Gui::ViewProviderDocumentObject& obj,const Gui::TreeItemMode& mode) +static unsigned int countExpandedItem(const QTreeWidgetItem *item) { + unsigned int size = 0; + for(int i=0,count=item->childCount();ichild(i); + if(citem->type()!=TreeWidget::ObjectType || !citem->isExpanded()) + continue; + auto obj = static_cast(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();ichild(i); + if(citem->type()!=TreeWidget::ObjectType || !citem->isExpanded()) + continue; + auto obj = static_cast(citem)->object()->getObject(); + if(obj->getNameInDocument()) + ++itemCount; + } + + if(!itemCount) { + writer.Stream() << "/>" << std::endl; + return; + } + + writer.Stream() << " count=\"" << itemCount << "\">" <childCount();ichild(i); + if(citem->type()!=TreeWidget::ObjectType || !citem->isExpanded()) + continue; + auto obj = static_cast(citem)->object()->getObject(); + if(obj->getNameInDocument()) { + writer.Stream() << writer.ind() << "getNameInDocument() << "\""; + saveExpandedItem(writer,static_cast(citem)); + } + } + writer.decInd(); + writer.Stream() << writer.ind() << "" << std::endl; +} + +void DocumentItem::Save (Base::Writer &writer) const { + writer.Stream() << writer.ind() << "restore(reader); + for(auto inst : TreeWidget::Instances) { + if(inst!=getTree()) { + auto docItem = inst->getDocumentItem(document()); + if(docItem) + docItem->_ExpandInfo = _ExpandInfo; + } + } +} + +void DocumentItem::restoreItemExpansion(const ExpandInfoPtr &info, DocumentObjectItem *item) { + item->setExpanded(true); + if(!info) + return; + for(int i=0,count=item->childCount();ichild(i); + if(citem->type() != TreeWidget::ObjectType) + continue; + auto obj = static_cast(citem)->object()->getObject(); + if(!obj->getNameInDocument()) + continue; + auto it = info->find(obj->getNameInDocument()); + if(it != info->end()) + restoreItemExpansion(it->second,static_cast(citem)); + } +} + +void DocumentItem::slotExpandObject (const Gui::ViewProviderDocumentObject& obj, + const Gui::TreeItemMode& mode, const App::DocumentObject *parent, const char *subname) { - // In the past it was checked if the parent item is collapsed and if yes nothing was done. - // Now with the auto-expand mechanism of active part containers or bodies it must be made - // sure to expand all parent items when expanding a child item. - // Example: - // When there are two nested part containers and if first the outer and then the inner is - // activated the outer will be collapsed and thus hides the inner item. - // Alternatively, this could be handled inside ActiveObjectList::setObject() but querying - // the parent-children relationship of the view providers is rather inefficient. + getTree()->_updateStatus(false); + + if((mode==Gui::ExpandItem||mode==Gui::ExpandPath) + && obj.getDocument()->getDocument()->testStatus(App::Document::Restoring)) + { + if(!_ExpandInfo) + _ExpandInfo.reset(new ExpandInfo); + _ExpandInfo->emplace(std::string("*")+obj.getObject()->getNameInDocument(),ExpandInfoPtr()); + return; + } + if(parent && parent->getDocument()!=document()->getDocument()) { + auto it = getTree()->DocumentMap.find(Application::Instance->getDocument(parent->getDocument())); + if(it!=getTree()->DocumentMap.end()) + it->second->slotExpandObject(obj,mode,parent,subname); + return; + } + FOREACH_ITEM(item,obj) + // All document object items must always have a parent, either another + // object item or document item. If not, then there is a bug somewhere + // else. + assert(item->parent()); + switch (mode) { - case Gui::ExpandPath: { - QTreeWidgetItem* parent = item->parent(); - while (parent) { - parent->setExpanded(true); - parent = parent->parent(); + case Gui::ExpandPath: + if(!parent) { + QTreeWidgetItem* parentItem = item->parent(); + while (parentItem) { + parentItem->setExpanded(true); + parentItem = parentItem->parent(); + } + item->setExpanded(true); + break; + } + // fall through + case Gui::ExpandItem: + if(!parent) { + if(item->parent()->isExpanded()) + item->setExpanded(true); + }else{ + App::DocumentObject *topParent = 0; + std::ostringstream ss; + item->getSubName(ss,topParent); + if(!topParent) { + if(parent!=obj.getObject()) + continue; + }else if(topParent!=parent) + continue; + showItem(item,false,true); } - item->setExpanded(true); - } break; - case Gui::ExpandItem: - item->setExpanded(true); break; case Gui::CollapseItem: item->setExpanded(false); @@ -1648,19 +3729,44 @@ void DocumentItem::slotExpandObject (const Gui::ViewProviderDocumentObject& obj, default: break; } - populateItem(item); + if(item->isExpanded()) + populateItem(item); + if(parent) + return; END_FOREACH_ITEM } void DocumentItem::slotScrollToObject(const Gui::ViewProviderDocumentObject& obj) { - FOREACH_ITEM(item,obj) - QTreeWidget* tree = item->treeWidget(); - tree->scrollToItem(item, QAbstractItemView::PositionAtTop); - END_FOREACH_ITEM + if(!obj.getObject() || !obj.getObject()->getNameInDocument()) + return; + auto it = ObjectMap.find(obj.getObject()); + if(it == ObjectMap.end() || it->second->items.empty()) + return; + auto item = it->second->rootItem; + if(!item) + item = *it->second->items.begin(); + getTree()->_updateStatus(false); + getTree()->scrollToItem(item); } -const Gui::Document* DocumentItem::document() const +void DocumentItem::slotRecomputedObject(const App::DocumentObject &obj) { + if(obj.isValid()) + return; + slotRecomputed(*obj.getDocument(), {const_cast(&obj)}); +} + +void DocumentItem::slotRecomputed(const App::Document &, const std::vector &objs) { + auto tree = getTree(); + for(auto obj : objs) { + if(!obj->isValid()) + tree->ChangedObjects[obj].set(TreeWidget::CS_Error); + } + if(tree->ChangedObjects.size()) + tree->_updateStatus(); +} + +Gui::Document* DocumentItem::document() const { return this->pDocument; } @@ -1672,7 +3778,7 @@ const Gui::Document* DocumentItem::document() const // // // std::map::iterator pos; -// pos = ObjectMap.find(Obj->getNameInDocument()); +// pos = ObjectMap.find(Obj); // if (pos != ObjectMap.end()) { // QFont f = pos->second->font(0); // f.setUnderline(mark); @@ -1682,9 +3788,8 @@ const Gui::Document* DocumentItem::document() const void DocumentItem::testStatus(void) { - FOREACH_ITEM_ALL(item); - item->testStatus(); - END_FOREACH_ITEM; + for(const auto &v : ObjectMap) + v.second->testStatus(); } void DocumentItem::setData (int column, int role, const QVariant & value) @@ -1697,196 +3802,599 @@ void DocumentItem::setData (int column, int role, const QVariant & value) QTreeWidgetItem::setData(column, role, value); } -void DocumentItem::setObjectHighlighted(const char* name, bool select) -{ - Q_UNUSED(select); - Q_UNUSED(name); - // FOREACH_ITEM_NAME(item,name); - //pos->second->setData(0, Qt::TextColorRole, QVariant(Qt::red)); - //treeWidget()->setItemSelected(pos->second, select); - // END_FOREACH_ITEM; -} - -void DocumentItem::setObjectSelected(const char* name, bool select) -{ - FOREACH_ITEM_NAME(item,name); - treeWidget()->setItemSelected(item, select); - END_FOREACH_ITEM; -} - -void DocumentItem::clearSelection(void) +void DocumentItem::clearSelection(DocumentObjectItem *exclude) { // Block signals here otherwise we get a recursion and quadratic runtime bool ok = treeWidget()->blockSignals(true); FOREACH_ITEM_ALL(item); - item->setSelected(false); + if(item==exclude) { + if(item->selected>0) + item->selected = -1; + else + item->selected = 0; + updateItemSelection(item); + }else{ + item->selected = 0; + item->mySubs.clear(); + item->setSelected(false); + } END_FOREACH_ITEM; treeWidget()->blockSignals(ok); } -void DocumentItem::updateSelection(void) -{ - std::vector sel; - FOREACH_ITEM_ALL(item); - if (treeWidget()->isItemSelected(item)) - sel.push_back(item->object()->getObject()); - END_FOREACH_ITEM; - - Gui::Selection().setSelection(pDocument->getDocument()->getName(), sel); +void DocumentItem::updateSelection(QTreeWidgetItem *ti, bool unselect) { + for(int i=0,count=ti->childCount();ichild(i); + if(child && child->type()==TreeWidget::ObjectType) { + auto childItem = static_cast(child); + if(unselect) + childItem->setSelected(false); + updateItemSelection(childItem); + if(unselect && childItem->isGroup()) { + // If the child item being force unselected by its group parent + // is itself a group, propagate the unselection to its own + // children + updateSelection(childItem,true); + } + } + } + + if(unselect) return; + for(int i=0,count=ti->childCount();ichild(i)); } -namespace Gui { -struct ObjectItem_Less : public std::binary_function -{ - bool operator()(DocumentObjectItem* x, DocumentObjectItem* y) const - { - return x->object()->getObject() < y->object()->getObject(); - } -}; +void DocumentItem::updateItemSelection(DocumentObjectItem *item) { + bool selected = item->isSelected(); + if((selected && item->selected>0) || (!selected && !item->selected)) + return; + if(item->selected != -1) + item->mySubs.clear(); + item->selected = selected; -struct ObjectItem_Equal : public std::binary_function -{ - bool operator()(DocumentObjectItem* x, App::DocumentObject* y) const - { - return x->object()->getObject() == y; + auto obj = item->object()->getObject(); + if(!obj || !obj->getNameInDocument()) + return; + + std::ostringstream str; + App::DocumentObject *topParent = 0; + item->getSubName(str,topParent); + if(topParent) { + if(topParent->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId())) { + // remove legacy selection, i.e. those without subname + Gui::Selection().rmvSelection(obj->getDocument()->getName(), + obj->getNameInDocument(),0); + } + if(!obj->redirectSubName(str,topParent,0)) + str << obj->getNameInDocument() << '.'; + obj = topParent; } -}; + const char *objname = obj->getNameInDocument(); + const char *docname = obj->getDocument()->getName(); + const auto &subname = str.str(); + + if(subname.size()) { + auto parentItem = item->getParentItem(); + assert(parentItem); + if(selected && parentItem->selected) { + // When a group item is selected, all its children objects are + // highlighted in the 3D view. So, when an item of some group is + // newly selected, we must force unselect its parent in order to + // show the selection highlight. Besides, select both the parent + // group and its children doesn't make much sense. + // + // UPDATE: There are legit use case of both parent and child + // selection, for example, to disambiguate under which group to + // operate on the child. + // + // TREE_TRACE("force unselect parent"); + // parentItem->setSelected(false); + // updateItemSelection(parentItem); + } + } + + if(selected && item->isGroup()) { + // Same reasoning as above. When a group item is newly selected, We + // choose to force unselect all its children to void messing up the + // selection highlight + // + // UPDATE: same as above, child and parent selection is now re-enabled. + // + // TREE_TRACE("force unselect all children"); + // updateSelection(item,true); + } + + if(!selected) { + Gui::Selection().rmvSelection(docname,objname,subname.c_str()); + return; + } + selected = false; + if(item->mySubs.size()) { + for(auto &sub : item->mySubs) { + if(Gui::Selection().addSelection(docname,objname,(subname+sub).c_str())) + selected = true; + } + } + if(!selected) { + item->mySubs.clear(); + if(!Gui::Selection().addSelection(docname,objname,subname.c_str())) { + item->selected = 0; + item->setSelected(false); + return; + } + } + getTree()->syncView(item->object()); } -void DocumentItem::selectItems(void) +App::DocumentObject *DocumentItem::getTopParent(App::DocumentObject *obj, std::string &subname) { + auto it = ObjectMap.find(obj); + if(it == ObjectMap.end() || it->second->items.empty()) + return 0; + + // already a top parent + if(it->second->rootItem) + return obj; + + for(auto item : it->second->items) { + // non group object do not provide a cooridnate system, hence its + // claimed child is still in the global coordinate space, so the + // child can still be considered a top level object + if(!item->isParentGroup()) + return obj; + } + + // If no top level item, find an item that is closest to the top level + std::multimap items; + for(auto item : it->second->items) { + int i=0; + for(auto parent=item->parent();parent;++i,parent=parent->parent()) { + if(parent->isHidden()) + i += 1000; + ++i; + } + items.emplace(i,item); + } + + App::DocumentObject *topParent = 0; + std::ostringstream ss; + items.begin()->second->getSubName(ss,topParent); + if(!topParent) { + // this shouldn't happen + FC_WARN("No top parent for " << obj->getFullName() << '.' << subname); + return obj; + } + ss << obj->getNameInDocument() << '.' << subname; + FC_LOG("Subname correction " << obj->getFullName() << '.' << subname + << " -> " << topParent->getFullName() << '.' << ss.str()); + subname = ss.str(); + return topParent; +} + +DocumentObjectItem *DocumentItem::findItemByObject( + bool sync, App::DocumentObject *obj, const char *subname, bool select) { - // get an array of all tree items of the document and sort it in ascending order - // with regard to their document object - std::vector items; - FOREACH_ITEM_ALL(item); - items.push_back(item); + if(!subname) + subname = ""; + + auto it = ObjectMap.find(obj); + if(it == ObjectMap.end() || it->second->items.empty()) + return 0; + + // prefer top level item of this object + if(it->second->rootItem) + return findItem(sync,it->second->rootItem,subname,select); + + for(auto item : it->second->items) { + // non group object do not provide a cooridnate system, hence its + // claimed child is still in the global coordinate space, so the + // child can still be considered a top level object + if(!item->isParentGroup()) + return findItem(sync,item,subname,select); + } + + // If no top level item, find an item that is closest to the top level + std::multimap items; + for(auto item : it->second->items) { + int i=0; + for(auto parent=item->parent();parent;++i,parent=parent->parent()) + ++i; + items.emplace(i,item); + } + for(auto &v : items) { + auto item = findItem(sync,v.second,subname,select); + if(item) + return item; + } + return 0; +} + +DocumentObjectItem *DocumentItem::findItem( + bool sync, DocumentObjectItem *item, const char *subname, bool select) +{ + if(item->isHidden()) + item->setHidden(false); + + if(!subname || *subname==0) { + if(select) { + item->selected+=2; + item->mySubs.clear(); + } + return item; + } + + TREE_TRACE("find next " << subname); + + // try to find the next level object name + const char *nextsub = 0; + const char *dot = 0; + if((dot=strchr(subname,'.'))) + nextsub = dot+1; + else { + if(select) { + item->selected+=2; + if(std::find(item->mySubs.begin(),item->mySubs.end(),subname)==item->mySubs.end()) + item->mySubs.push_back(subname); + } + return item; + } + + std::string name(subname,nextsub-subname); + auto obj = item->object()->getObject(); + auto subObj = obj->getSubObject(name.c_str()); + if(!subObj || subObj==obj) { + if(!subObj && !getTree()->searchDoc) + TREE_WARN("sub object not found " << item->getName() << '.' << name.c_str()); + if(select) { + item->selected += 2; + if(std::find(item->mySubs.begin(),item->mySubs.end(),subname)==item->mySubs.end()) + item->mySubs.push_back(subname); + } + return item; + } + + if(select) + item->mySubs.clear(); + + if(!item->populated && sync) { + //force populate the item + item->populated = true; + populateItem(item,true); + } + + for(int i=0,count=item->childCount();ichild(i); + if(!ti || ti->type()!=TreeWidget::ObjectType) continue; + auto child = static_cast(ti); + + if(child->object()->getObject() == subObj) + return findItem(sync,child,nextsub,select); + } + + // The sub object is not found. This could happen for geo group, since its + // children may be in more than one hierarchy down. + bool found = false; + DocumentObjectItem *res=0; + auto it = ObjectMap.find(subObj); + if(it != ObjectMap.end()) { + for(auto child : it->second->items) { + if(child->isChildOfItem(item)) { + found = true; + res = findItem(sync,child,nextsub,select); + if(!select) + return res; + } + } + } + + if(select && !found) { + // The sub object is still not found. Maybe it is a non-object sub-element. + // Select the current object instead. + TREE_TRACE("element " << subname << " not found"); + item->selected+=2; + if(std::find(item->mySubs.begin(),item->mySubs.end(),subname)==item->mySubs.end()) + item->mySubs.push_back(subname); + } + return res; +} + +void DocumentItem::selectItems(bool sync) { + const auto &sels = Selection().getSelection(pDocument->getDocument()->getName(),false); + for(const auto &sel : sels) + findItemByObject(sync,sel.pObject,sel.SubName,true); + + DocumentObjectItem *first = 0; + DocumentObjectItem *last = 0; + + FOREACH_ITEM_ALL(item) + if(item->selected == 1) { + // this means it is the old selection and is not in the current + // selection + item->selected = 0; + item->setSelected(false); + }else if(item->selected) { + if(item->selected == 2) { + if(!first) + first = item; + if(sync) + showItem(item,false,true); + } + item->selected = 1; + item->setSelected(true); + last = item; + } END_FOREACH_ITEM; - std::sort(items.begin(), items.end(), ObjectItem_Less()); - // get and sort all selected document objects of the given document - std::vector objs; - std::vector obj = Selection().getSelection(pDocument->getDocument()->getName()); - for (std::vector::iterator jt = obj.begin(); jt != obj.end(); ++jt) { - objs.push_back(jt->pObject); + if(sync) { + if(!first) + first = last; + if(first) { + getTree()->scrollToItem(first); + getTree()->syncView(first->object()); + } } - std::sort(objs.begin(), objs.end()); +} - // The document objects in 'objs' is a subset of the document objects stored - // in 'items'. Since both arrays are sorted we get the wanted tree items in - // linear time. - std::vector common; - std::vector::iterator item_it = items.begin(); - for (std::vector::iterator it = objs.begin(); it != objs.end(); ++it) { - item_it = std::find_if(item_it, items.end(), std::bind2nd(ObjectItem_Equal(), *it)); - if (item_it == items.end()) - break; // should never ever happen - common.push_back(*item_it); +void DocumentItem::populateParents(const ViewProvider *vp, ViewParentMap &parentMap) { + auto it = parentMap.find(vp); + if(it == parentMap.end()) return; + for(auto parent : it->second) { + auto it = ObjectMap.find(parent->getObject()); + if(it==ObjectMap.end()) + continue; + + populateParents(parent,parentMap); + for(auto item : it->second->items) { + if(!item->isHidden() && !item->populated) { + item->populated = true; + populateItem(item,true); + } + } + } +} + +void DocumentItem::selectAllInstances(const ViewProviderDocumentObject &vpd) { + ViewParentMap parentMap; + auto pObject = vpd.getObject(); + if(ObjectMap.find(pObject) == ObjectMap.end()) + return; + + bool lock = getTree()->blockConnection(true); + + // We are trying to select all items corresponding to a given view + // provider, i.e. all apperance of the object inside all its parent items + // + // Build a map of object to all its parent + for(auto &v : ObjectMap) { + if(v.second->viewObject == &vpd) continue; + for(auto child : v.second->viewObject->claimChildren()) { + auto vp = getViewProvider(child); + if(!vp) continue; + parentMap[vp].push_back(v.second->viewObject); + } } - // get all unselected items of the given document - std::sort(common.begin(), common.end()); - std::sort(items.begin(), items.end()); - std::vector diff; - std::back_insert_iterator > biit(diff); - std::set_difference(items.begin(), items.end(), common.begin(), common.end(), biit); + // now make sure all parent items are populated. In order to do that, we + // need to populate the oldest parent first + populateParents(&vpd,parentMap); - // select the appropriate items - QList selitems; - for (std::vector::iterator it = common.begin(); it != common.end(); ++it) - selitems.append(*it); - static_cast(treeWidget())->setItemsSelected(selitems, true); - // deselect the appropriate items - QList deselitems; - for (std::vector::iterator it = diff.begin(); it != diff.end(); ++it) - deselitems.append(*it); - static_cast(treeWidget())->setItemsSelected(deselitems, false); + DocumentObjectItem *first = 0; + FOREACH_ITEM(item,vpd); + if(showItem(item,true) && !first) + first = item; + END_FOREACH_ITEM; + + getTree()->blockConnection(lock); + if(first) { + treeWidget()->scrollToItem(first); + updateSelection(); + } +} + +bool DocumentItem::showHidden() const { + return pDocument->getDocument()->ShowHidden.getValue(); +} + +void DocumentItem::setShowHidden(bool show) { + pDocument->getDocument()->ShowHidden.setValue(show); +} + +bool DocumentItem::showItem(DocumentObjectItem *item, bool select, bool force) { + auto parent = item->parent(); + if(item->isHidden()) { + if(!force) + return false; + item->setHidden(false); + } + + if(parent->type()==TreeWidget::ObjectType && + !showItem(static_cast(parent),false)) + return false; + + parent->setExpanded(true); + if(select) item->setSelected(true); + return true; +} + +void DocumentItem::updateItemsVisibility(QTreeWidgetItem *item, bool show) { + if(item->type() == TreeWidget::ObjectType) { + auto objitem = static_cast(item); + objitem->setHidden(!show && !objitem->object()->showInTree()); + } + for(int i=0;ichildCount();++i) + updateItemsVisibility(item->child(i),show); +} + +void DocumentItem::updateSelection() { + bool lock = getTree()->blockConnection(true); + updateSelection(this,false); + getTree()->blockConnection(lock); } // ---------------------------------------------------------------------------- -DocumentObjectItem::DocumentObjectItem(Gui::ViewProviderDocumentObject* pcViewProvider, - DocumentObjectItemsPtr selves) - : QTreeWidgetItem(TreeWidget::ObjectType), previousStatus(-1), viewObject(pcViewProvider) - , myselves(selves), populated(false) +static int countItems; + +DocumentObjectItem::DocumentObjectItem(DocumentItem *ownerDocItem, DocumentObjectDataPtr data) + : QTreeWidgetItem(TreeWidget::ObjectType) + , myOwner(ownerDocItem), myData(data), previousStatus(-1),selected(0),populated(false) { setFlags(flags()|Qt::ItemIsEditable); - // Setup connections - connectIcon = pcViewProvider->signalChangeIcon.connect(boost::bind(&DocumentObjectItem::slotChangeIcon, this)); - connectTool = pcViewProvider->signalChangeToolTip.connect(boost::bind(&DocumentObjectItem::slotChangeToolTip, this, _1)); - connectStat = pcViewProvider->signalChangeStatusTip.connect(boost::bind(&DocumentObjectItem::slotChangeStatusTip, this, _1)); - myselves->insert(this); + myData->items.insert(this); + ++countItems; + TREE_LOG("Create item: " << countItems << ", " << object()->getObject()->getFullName()); } DocumentObjectItem::~DocumentObjectItem() { - auto it = myselves->find(this); - if(it == myselves->end()) + --countItems; + TREE_LOG("Delete item: " << countItems << ", " << object()->getObject()->getFullName()); + auto it = myData->items.find(this); + if(it == myData->items.end()) assert(0); else - myselves->erase(it); + myData->items.erase(it); - connectIcon.disconnect(); - connectTool.disconnect(); - connectStat.disconnect(); + if(myData->rootItem == this) + myData->rootItem = 0; + + if(myOwner && myData->items.empty()) { + auto it = myOwner->_ParentMap.find(object()->getObject()); + if(it!=myOwner->_ParentMap.end() && it->second.size()) { + myOwner->PopulateObjects.push_back(*it->second.begin()); + myOwner->getTree()->_updateStatus(); + } + } +} + +void DocumentObjectItem::restoreBackground() { + this->setBackground(0,this->bgBrush); +} + +void DocumentObjectItem::setHighlight(bool set, Gui::HighlightMode high) { + QFont f = this->font(0); + auto highlight = [=](const QColor& col){ + if (set) + this->setBackground(0, col); + else + this->setBackground(0, QBrush()); + this->bgBrush = this->background(0); + }; + + switch (high) { + case Gui::Bold: + f.setBold(set); + break; + case Gui::Italic: + f.setItalic(set); + break; + case Gui::Underlined: + f.setUnderline(set); + break; + case Gui::Overlined: + f.setOverline(set); + break; + case Gui::Blue: + highlight(QColor(200,200,255)); + break; + case Gui::LightBlue: + highlight(QColor(230,230,255)); + break; + case Gui::UserDefined: + { + QColor color(230,230,255); + if (set) { + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView"); + bool bold = hGrp->GetBool("TreeActiveBold",true); + bool italic = hGrp->GetBool("TreeActiveItalic",false); + bool underlined = hGrp->GetBool("TreeActiveUnderlined",false); + bool overlined = hGrp->GetBool("TreeActiveOverlined",false); + f.setBold(bold); + f.setItalic(italic); + f.setUnderline(underlined); + f.setOverline(overlined); + + unsigned long col = hGrp->GetUnsigned("TreeActiveColor",3873898495); + color = QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff); + } + else { + f.setBold(false); + f.setItalic(false); + f.setUnderline(false); + f.setOverline(false); + } + highlight(color); + } break; + default: + break; + } +} + +const char *DocumentObjectItem::getTreeName() const { + return myData->getTreeName(); } Gui::ViewProviderDocumentObject* DocumentObjectItem::object() const { - return viewObject; + return myData->viewObject; } -void DocumentObjectItem::testStatus() +void DocumentObjectItem::testStatus(bool resetStatus) { + QIcon icon,icon2; + testStatus(resetStatus,icon,icon2); +} + +void DocumentObjectItem::testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2) { - App::DocumentObject* pObject = viewObject->getObject(); + App::DocumentObject* pObject = object()->getObject(); + + int visible = -1; + auto parentItem = getParentItem(); + if(parentItem) { + Timing(testStatus1); + auto parent = parentItem->object()->getObject(); + auto ext = parent->getExtensionByType(true,false); + if(!ext) + visible = parent->isElementVisible(pObject->getNameInDocument()); + else { + // We are dealing with a plain group. It has special handling when + // linked, which allows it to have indpenedent visibility control. + // We need to go up the hierarchy and see if there is any link to + // it. + for(auto pp=parentItem->getParentItem();pp;pp=pp->getParentItem()) { + auto obj = pp->object()->getObject(); + if(!obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(),false)) { + visible = pp->object()->getObject()->isElementVisible(pObject->getNameInDocument()); + break; + } + } + } + } + + Timing(testStatus2); + + if(visible<0) + visible = object()->isShow()?1:0; + + auto obj = object()->getObject(); + auto linked = obj->getLinkedObject(false); + bool external = object()->getDocument()!=getOwnerDocument()->document() || + (linked && linked->getDocument()!=obj->getDocument()); - // if status has changed then continue int currentStatus = - ((pObject->isError() ? 1 : 0) << 2) | - ((pObject->mustRecompute() == 1 ? 1 : 0) << 1) | - (viewObject->isShow() ? 1 : 0); - if (previousStatus == currentStatus) - return; - previousStatus = currentStatus; + ((external?0:1)<<4) | + ((object()->showInTree() ? 0 : 1) << 3) | + ((pObject->isError() ? 1 : 0) << 2) | + ((pObject->isTouched()||pObject->mustExecute()== 1 ? 1 : 0) << 1) | + (visible ? 1 : 0); - QPixmap px; - if (currentStatus & 4) { - // object is in error state - static const char * const feature_error_xpm[]={ - "9 9 3 1", - ". c None", - "# c #ff0000", - "a c #ffffff", - "...###...", - ".##aaa##.", - ".##aaa##.", - "###aaa###", - "###aaa###", - "#########", - ".##aaa##.", - ".##aaa##.", - "...###..."}; - px = QPixmap(feature_error_xpm); - } - else if (currentStatus & 2) { - // object must be recomputed - static const char * const feature_recompute_xpm[]={ - "9 9 3 1", - ". c None", - "# c #0000ff", - "a c #ffffff", - "...###...", - ".######aa", - ".#####aa.", - "#####aa##", - "#aa#aa###", - "#aaaa####", - ".#aa####.", - ".#######.", - "...###..."}; - px = QPixmap(feature_recompute_xpm); - } + TimingStop(testStatus2); + + if (!resetStatus && previousStatus==currentStatus) + return; + + _Timing(1,testStatus3); + + previousStatus = currentStatus; QIcon::Mode mode = QIcon::Normal; if (currentStatus & 1) { // visible @@ -1917,32 +4425,133 @@ void DocumentObjectItem::testStatus() mode = QIcon::Disabled; } - // get the original icon set - QIcon icon_org = viewObject->getIcon(); - QIcon icon_mod; - int w = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize); + _TimingStop(1,testStatus3); - // if needed show small pixmap inside - if (!px.isNull()) { - icon_mod.addPixmap(BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::Off), - px,BitmapFactoryInst::TopRight), QIcon::Normal, QIcon::Off); - icon_mod.addPixmap(BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::On ), - px,BitmapFactoryInst::TopRight), QIcon::Normal, QIcon::Off); - } - else { - icon_mod.addPixmap(icon_org.pixmap(w, w, mode, QIcon::Off), QIcon::Normal, QIcon::Off); - icon_mod.addPixmap(icon_org.pixmap(w, w, mode, QIcon::On ), QIcon::Normal, QIcon::On ); + QIcon &icon = mode==QIcon::Normal?icon1:icon2; + + if(icon.isNull()) { + Timing(getIcon); + QPixmap px; + if (currentStatus & 4) { + static QPixmap pxError; + if(pxError.isNull()) { + // object is in error state + const char * const feature_error_xpm[]={ + "9 9 3 1", + ". c None", + "# c #ff0000", + "a c #ffffff", + "...###...", + ".##aaa##.", + ".##aaa##.", + "###aaa###", + "###aaa###", + "#########", + ".##aaa##.", + ".##aaa##.", + "...###..."}; + pxError = QPixmap(feature_error_xpm); + } + px = pxError; + } + else if (currentStatus & 2) { + static QPixmap pxRecompute; + if(pxRecompute.isNull()) { + // object must be recomputed + const char * const feature_recompute_xpm[]={ + "9 9 3 1", + ". c None", + "# c #0000ff", + "a c #ffffff", + "...###...", + ".######aa", + ".#####aa.", + "#####aa##", + "#aa#aa###", + "#aaaa####", + ".#aa####.", + ".#######.", + "...###..."}; + pxRecompute = QPixmap(feature_recompute_xpm); + } + px = pxRecompute; + } + + // get the original icon set + QIcon icon_org = object()->getIcon(); + + int w = getTree()->viewOptions().decorationSize.width(); + + QPixmap pxOn,pxOff; + + // if needed show small pixmap inside + if (!px.isNull()) { + pxOff = BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::Off), + px,BitmapFactoryInst::TopRight); + pxOn = BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::On ), + px,BitmapFactoryInst::TopRight); + } else { + pxOff = icon_org.pixmap(w, w, mode, QIcon::Off); + pxOn = icon_org.pixmap(w, w, mode, QIcon::On); + } + + if(currentStatus & 8) {// hidden item + static QPixmap pxHidden; + if(pxHidden.isNull()) { + const char * const feature_hidden_xpm[]={ + "9 7 3 1", + ". c None", + "# c #000000", + "a c #ffffff", + "...###...", + "..#aaa#..", + ".#a###a#.", + "#aa###aa#", + ".#a###a#.", + "..#aaa#..", + "...###..."}; + pxHidden = QPixmap(feature_hidden_xpm); + } + pxOff = BitmapFactory().merge(pxOff, pxHidden, BitmapFactoryInst::TopLeft); + pxOn = BitmapFactory().merge(pxOn, pxHidden, BitmapFactoryInst::TopLeft); + } + + if(external) {// external item + static QPixmap pxExternal; + if(pxExternal.isNull()) { + const char * const feature_external_xpm[]={ + "7 7 3 1", + ". c None", + "# c #000000", + "a c #ffffff", + "..###..", + ".#aa##.", + "..#aa##", + "..##aa#", + "..#aa##", + ".#aa##.", + "..###.."}; + pxExternal = QPixmap(feature_external_xpm); + } + pxOff = BitmapFactory().merge(pxOff, pxExternal, BitmapFactoryInst::BottomRight); + pxOn = BitmapFactory().merge(pxOn, pxExternal, BitmapFactoryInst::BottomRight); + } + + icon.addPixmap(pxOn, QIcon::Normal, QIcon::On); + icon.addPixmap(pxOff, QIcon::Normal, QIcon::Off); } - this->setIcon(0, icon_mod); + + _Timing(2,setIcon); + this->setIcon(0, icon); } void DocumentObjectItem::displayStatusInfo() { - App::DocumentObject* Obj = viewObject->getObject(); + App::DocumentObject* Obj = object()->getObject(); QString info = QString::fromLatin1(Obj->getStatusString()); - if ( Obj->mustExecute() == 1 ) + if ( Obj->mustExecute() == 1 && !Obj->isError()) info += QString::fromLatin1(" (but must be executed)"); QString status = TreeWidget::tr("%1, Internal name: %2") .arg(info, @@ -1958,54 +4567,257 @@ void DocumentObjectItem::displayStatusInfo() void DocumentObjectItem::setExpandedStatus(bool on) { - App::DocumentObject* Obj = viewObject->getObject(); - Obj->setStatus(App::Expand, on); + if(getOwnerDocument()->document() == object()->getDocument()) + object()->getObject()->setStatus(App::Expand, on); } void DocumentObjectItem::setData (int column, int role, const QVariant & value) { - QTreeWidgetItem::setData(column, role, value); - if (role == Qt::EditRole) { - QString label = value.toString(); - App::DocumentObject* obj = viewObject->getObject(); - App::Document* doc = obj->getDocument(); - doc->openTransaction(TreeWidget::tr("Rename object").toUtf8()); - obj->Label.setValue((const char*)label.toUtf8()); - doc->commitTransaction(); + QVariant myValue(value); + if (role == Qt::EditRole && column<=1) { + auto obj = object()->getObject(); + auto &label = column?obj->Label2:obj->Label; + std::ostringstream ss; + ss << "Change " << getName() << '.' << label.getName(); + App::AutoTransaction committer(ss.str().c_str()); + label.setValue((const char *)value.toString().toUtf8()); + myValue = QString::fromUtf8(label.getValue()); } + QTreeWidgetItem::setData(column, role, myValue); } bool DocumentObjectItem::isChildOfItem(DocumentObjectItem* item) { - int numChild = item->childCount(); - for (int i=0; ichild(i); - if (child == this) + for(auto pitem=parent();pitem;pitem=pitem->parent()) + if(pitem == item) return true; - if (child->type() == TreeWidget::ObjectType) { - DocumentObjectItem* obj = static_cast(child); - if (this->isChildOfItem(obj)) - return true; - } - } - return false; } -void DocumentObjectItem::slotChangeIcon() -{ - previousStatus = -1; - testStatus(); +bool DocumentObjectItem::requiredAtRoot(bool excludeSelf) const{ + if(myData->rootItem || object()->getDocument()!=getOwnerDocument()->document()) + return false; + bool checkMap = true; + for(auto item : myData->items) { + if(excludeSelf && item == this) continue; + auto pi = item->getParentItem(); + if(!pi || pi->myData->removeChildrenFromRoot) + return false; + checkMap = false; + } + if(checkMap && myOwner) { + auto it = myOwner->_ParentMap.find(object()->getObject()); + if(it!=myOwner->_ParentMap.end()) { + // Reaching here means all items of this corresponding object is + // going to be deleted, but the object itself is not deleted and + // still being refered to by some parent item that is not expanded + // yet. So, we force populate at least one item of the parent + // object to make sure that there is at least one corresponding + // item for each object. + // + // PS: practically speaking, it won't hurt much to delete all the + // items, because the item will be auto created once the user + // expand its parent item. It only causes minor problems, such as, + // tree scroll to object command won't work properly. + + for(auto parent : it->second) { + if(getOwnerDocument()->populateObject(parent)) + return false; + } + } + } + return true; } -void DocumentObjectItem::slotChangeToolTip(const QString& tip) -{ - this->setToolTip(0, tip); +bool DocumentObjectItem::isLink() const { + auto obj = object()->getObject(); + auto linked = obj->getLinkedObject(false); + return linked && obj!=linked; } -void DocumentObjectItem::slotChangeStatusTip(const QString& tip) +bool DocumentObjectItem::isLinkFinal() const { + auto obj = object()->getObject(); + auto linked = obj->getLinkedObject(false); + return linked && linked == linked->getLinkedObject(true); +} + + +bool DocumentObjectItem::isParentLink() const { + auto pi = getParentItem(); + return pi && pi->isLink(); +} + +enum GroupType { + NotGroup = 0, + LinkGroup = 1, + PartGroup = 2, + SuperGroup = 3, //reversed for future +}; + +int DocumentObjectItem::isGroup() const { + auto obj = object()->getObject(); + auto linked = obj->getLinkedObject(true); + if(linked && linked->hasExtension( + App::GeoFeatureGroupExtension::getExtensionClassTypeId())) + return PartGroup; + if(obj->hasChildElement()) + return LinkGroup; + if(obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(),false)) { + for(auto parent=getParentItem();parent;parent=parent->getParentItem()) { + auto pobj = parent->object()->getObject(); + if(pobj->hasExtension(App::GroupExtension::getExtensionClassTypeId(),false)) + continue; + if(pobj->isElementVisible(obj->getNameInDocument())>=0) + return LinkGroup; + } + } + return NotGroup; +} + +bool DocumentItem::isObjectShowable(App::DocumentObject *obj) { + auto itParents = _ParentMap.find(obj); + if(itParents == _ParentMap.end() || itParents->second.empty()) + return true; + bool showable = true; + for(auto parent : itParents->second) { + if(parent->getDocument() != obj->getDocument()) + continue; + if(!parent->hasChildElement() + && parent->getLinkedObject(false)==parent) + return true; + showable = false; + } + return showable; +} + +int DocumentObjectItem::isParentGroup() const { + auto pi = getParentItem(); + return pi?pi->isGroup():0; +} + +DocumentObjectItem *DocumentObjectItem::getParentItem() const{ + if(parent()->type()!=TreeWidget::ObjectType) + return 0; + return static_cast(parent()); +} + +const char *DocumentObjectItem::getName() const { + const char *name = object()->getObject()->getNameInDocument(); + return name?name:""; +} + +int DocumentObjectItem::getSubName(std::ostringstream &str, App::DocumentObject *&topParent) const { - this->setStatusTip(0, tip); + auto parent = getParentItem(); + if(!parent) + return NotGroup; + int ret = parent->getSubName(str,topParent); + if(ret != SuperGroup) { + int group = parent->isGroup(); + if(group == NotGroup) { + if(ret!=PartGroup) { + // Handle this situation, + // + // LinkGroup + // |--PartExtrude + // |--Sketch + // + // This function traverse from top down, so, when seeing a + // non-group object 'PartExtrude', its following children should + // not be grouped, so must reset any previous parents here. + topParent = 0; + str.str(""); //reset the current subname + return NotGroup; + } + group = PartGroup; + } + ret = group; + } + + auto obj = parent->object()->getObject(); + if(!obj || !obj->getNameInDocument()) { + topParent = 0; + str.str(""); + return NotGroup; + } + if(!topParent) + topParent = obj; + else if(!obj->redirectSubName(str,topParent,object()->getObject())) + str << obj->getNameInDocument() << '.'; + return ret; +} + +App::DocumentObject *DocumentObjectItem::getFullSubName( + std::ostringstream &str, DocumentObjectItem *parent) const +{ + auto pi = getParentItem(); + if(this==parent || !pi || (!parent && !pi->isGroup())) + return object()->getObject(); + + auto ret = pi->getFullSubName(str,parent); + str << getName() << '.'; + return ret; +} + +App::DocumentObject *DocumentObjectItem::getRelativeParent( + std::ostringstream &str, DocumentObjectItem *cousin, + App::DocumentObject **topParent, std::string *topSubname) const +{ + std::ostringstream str2; + App::DocumentObject *top=0,*top2=0; + getSubName(str,top); + if(topParent) + *topParent = top; + if(!top) + return 0; + if(topSubname) + *topSubname = str.str() + getName() + '.'; + cousin->getSubName(str2,top2); + if(top!=top2) { + str << getName() << '.'; + return top; + } + + auto subname = str.str(); + auto subname2 = str2.str(); + const char *sub = subname.c_str(); + const char *sub2 = subname2.c_str(); + while(1) { + const char *dot = strchr(sub,'.'); + if(!dot) { + str.str(""); + return 0; + } + const char *dot2 = strchr(sub2,'.'); + if(!dot2 || dot-sub!=dot2-sub2 || strncmp(sub,sub2,dot-sub)!=0) { + auto substr = subname.substr(0,dot-subname.c_str()+1); + auto ret = top->getSubObject(substr.c_str()); + if(!top) { + FC_ERR("invalid subname " << top->getFullName() << '.' << substr); + str.str(""); + return 0; + } + str.str(""); + str << dot+1 << getName() << '.'; + return ret; + } + sub = dot+1; + sub2 = dot2+1; + } + str.str(""); + return 0; +} + +DocumentItem *DocumentObjectItem::getParentDocument() const { + return getTree()->getDocumentItem(object()->getDocument()); +} + +DocumentItem *DocumentObjectItem::getOwnerDocument() const { + return myOwner; +} + +TreeWidget *DocumentObjectItem::getTree() const{ + return static_cast(treeWidget()); } #include "moc_Tree.cpp" diff --git a/src/Gui/Tree.h b/src/Gui/Tree.h index adec613d9f..3fb5a3f375 100644 --- a/src/Gui/Tree.h +++ b/src/Gui/Tree.h @@ -25,7 +25,11 @@ #define GUI_TREE_H #include +#include +#include +#include +#include #include #include @@ -38,8 +42,9 @@ namespace Gui { class ViewProviderDocumentObject; class DocumentObjectItem; -typedef std::set DocumentObjectItems; -typedef std::shared_ptr DocumentObjectItemsPtr; +class DocumentObjectData; +typedef std::shared_ptr DocumentObjectDataPtr; + class DocumentItem; /// highlight modes for the tree items @@ -68,16 +73,56 @@ class TreeWidget : public QTreeWidget, public SelectionObserver Q_OBJECT public: - TreeWidget(QWidget* parent=0); + TreeWidget(const char *name, QWidget* parent=0); ~TreeWidget(); - void scrollItemToTop(Gui::Document*); - void setItemsSelected (const QList items, bool select); + static void scrollItemToTop(); + void selectAllInstances(const ViewProviderDocumentObject &vpd); + void selectLinkedObject(App::DocumentObject *linked); + void selectAllLinks(App::DocumentObject *obj); + void expandSelectedItems(TreeItemMode mode); + + bool eventFilter(QObject *, QEvent *ev); + + struct SelInfo { + App::DocumentObject *topParent; + std::string subname; + ViewProviderDocumentObject *parentVp; + ViewProviderDocumentObject *vp; + }; + /* Return a list of selected object of a give document and their parent + * + * This function can return the non-group parent of the selected object, + * which Gui::Selection() cannot provide. + */ + static std::vector getSelection(App::Document *doc=0); + + static TreeWidget *instance(); static const int DocumentType; static const int ObjectType; void markItem(const App::DocumentObject* Obj,bool mark); + void syncView(ViewProviderDocumentObject *vp); + + const char *getTreeName() const; + + static void updateStatus(bool delay=true); + + static bool isObjectShowable(App::DocumentObject *obj); + + // Check if obj can be considered as a top level object + static void checkTopParent(App::DocumentObject *&obj, std::string &subname); + + DocumentItem *getDocumentItem(const Gui::Document *) const; + + static Gui::Document *selectedDocument(); + + void startDragging(); + + void resetItemSearch(); + void startItemSearch(); + void itemSearch(const QString &text, bool select); protected: /// Observer message from the Selection @@ -99,8 +144,12 @@ protected: bool event(QEvent *e); void keyPressEvent(QKeyEvent *event); void mouseDoubleClickEvent(QMouseEvent * event); - QList buildListChildren(QTreeWidgetItem* targetitem, - Gui::ViewProviderDocumentObject* vp); + +protected: + void showEvent(QShowEvent *) override; + void hideEvent(QHideEvent *) override; + void leaveEvent(QEvent *) override; + void _updateStatus(bool delay=true); protected Q_SLOTS: void onCreateGroup(); @@ -109,7 +158,15 @@ protected Q_SLOTS: void onStartEditing(); void onFinishEditing(); void onSkipRecompute(bool on); + void onAllowPartialRecompute(bool on); + void onReloadDoc(); + void onCloseDoc(); void onMarkRecompute(); + void onRecomputeObject(); + void onPreSelectTimer(); + void onSelectTimer(); + void onShowHidden(); + void onHideInTree(); void onSearchObjects(); private Q_SLOTS: @@ -117,34 +174,74 @@ private Q_SLOTS: void onItemEntered(QTreeWidgetItem * item); void onItemCollapsed(QTreeWidgetItem * item); void onItemExpanded(QTreeWidgetItem * item); - void onTestStatus(void); + void onUpdateStatus(void); Q_SIGNALS: void emitSearchObjects(); private: - void slotNewDocument(const Gui::Document&); + void slotNewDocument(const Gui::Document&, bool); void slotDeleteDocument(const Gui::Document&); void slotRenameDocument(const Gui::Document&); void slotActiveDocument(const Gui::Document&); void slotRelabelDocument(const Gui::Document&); + void slotShowHidden(const Gui::Document &); + void slotChangedViewObject(const Gui::ViewProvider &, const App::Property &); + void slotStartOpenDocument(); + void slotFinishOpenDocument(); + void _slotDeleteObject(const Gui::ViewProviderDocumentObject&, DocumentItem *deletingDoc); + void slotDeleteObject(const Gui::ViewProviderDocumentObject&); + void slotChangeObject(const Gui::ViewProviderDocumentObject&, const App::Property &prop); void changeEvent(QEvent *e); + void setupText(); + + void updateChildren(App::DocumentObject *obj, + const std::set &data, bool output, bool force); private: QAction* createGroupAction; QAction* relabelObjectAction; QAction* finishEditingAction; QAction* skipRecomputeAction; + QAction* allowPartialRecomputeAction; QAction* markRecomputeAction; + QAction* recomputeObjectAction; + QAction* showHiddenAction; + QAction* hideInTreeAction; + QAction* reloadDocAction; + QAction* closeDocAction; QAction* searchObjectsAction; - QTreeWidgetItem* contextItem; - + QTreeWidgetItem *contextItem; + App::DocumentObject *searchObject; + Gui::Document *searchDoc; + Gui::Document *searchContextDoc; + DocumentObjectItem *editingItem; + DocumentItem *currentDocItem; QTreeWidgetItem* rootItem; QTimer* statusTimer; - static QPixmap* documentPixmap; - std::map DocumentMap; - bool fromOutside; + QTimer* selectTimer; + QTimer* preselectTimer; + QTime preselectTime; + static std::unique_ptr documentPixmap; + static std::unique_ptr documentPartialPixmap; + std::unordered_map DocumentMap; + std::unordered_map > ObjectTable; + + enum ChangedObjectStatus { + CS_Output, + CS_Error, + }; + std::unordered_map > ChangedObjects; + + std::unordered_map > NewObjects; + + static std::set Instances; + + std::string myName; // for debugging purpose + + friend class DocumentItem; + friend class DocumentObjectItem; typedef boost::signals2::connection Connection; Connection connectNewDocument; @@ -152,6 +249,8 @@ private: Connection connectRenDocument; Connection connectActDocument; Connection connectRelDocument; + Connection connectShowHidden; + Connection connectChangedViewObj; }; /** The link between the tree and a document. @@ -159,21 +258,42 @@ private: * the visibility and the functions of the document. * \author Jürgen Riegel */ -class DocumentItem : public QTreeWidgetItem +class DocumentItem : public QTreeWidgetItem, public Base::Persistence { public: DocumentItem(const Gui::Document* doc, QTreeWidgetItem * parent); ~DocumentItem(); - const Gui::Document* document() const; - void setObjectHighlighted(const char*, bool); - void setObjectSelected(const char*, bool); - void clearSelection(void); - void updateSelection(void); - void selectItems(void); + Gui::Document* document() const; + void clearSelection(DocumentObjectItem *exclude=0); + void updateSelection(QTreeWidgetItem *, bool unselect=false); + void updateSelection(); + void updateItemSelection(DocumentObjectItem *); + void selectItems(bool sync); void testStatus(void); void setData(int column, int role, const QVariant & value); - void populateItem(DocumentObjectItem *item, bool refresh = false); + void populateItem(DocumentObjectItem *item, bool refresh=false, bool delayUpdate=true); + bool populateObject(App::DocumentObject *obj); + void selectAllInstances(const ViewProviderDocumentObject &vpd); + bool showItem(DocumentObjectItem *item, bool select, bool force=false); + void updateItemsVisibility(QTreeWidgetItem *item, bool show); + void updateLinks(const ViewProviderDocumentObject &view); + ViewProviderDocumentObject *getViewProvider(App::DocumentObject *); + + bool showHidden() const; + void setShowHidden(bool show); + + TreeWidget *getTree() const; + const char *getTreeName() const; + + bool isObjectShowable(App::DocumentObject *obj); + + virtual unsigned int getMemSize (void) const override; + virtual void Save (Base::Writer &) const override; + virtual void Restore(Base::XMLReader &) override; + + class ExpandInfo; + typedef std::shared_ptr ExpandInfoPtr; protected: /** Adds a view provider to the document item. @@ -183,35 +303,59 @@ protected: /** Removes a view provider from the document item. * If this view provider is not added nothing happens. */ - void slotDeleteObject (const Gui::ViewProviderDocumentObject&); - void slotChangeObject (const Gui::ViewProviderDocumentObject&); - void slotRenameObject (const Gui::ViewProviderDocumentObject&); - void slotActiveObject (const Gui::ViewProviderDocumentObject&); void slotInEdit (const Gui::ViewProviderDocumentObject&); void slotResetEdit (const Gui::ViewProviderDocumentObject&); - void slotHighlightObject (const Gui::ViewProviderDocumentObject&,const Gui::HighlightMode&,bool); - void slotExpandObject (const Gui::ViewProviderDocumentObject&,const Gui::TreeItemMode&); + void slotHighlightObject (const Gui::ViewProviderDocumentObject&,const Gui::HighlightMode&,bool, + const App::DocumentObject *parent, const char *subname); + void slotExpandObject (const Gui::ViewProviderDocumentObject&,const Gui::TreeItemMode&, + const App::DocumentObject *parent, const char *subname); void slotScrollToObject (const Gui::ViewProviderDocumentObject&); + void slotRecomputed (const App::Document &doc, const std::vector &objs); + void slotRecomputedObject(const App::DocumentObject &); + + bool updateObject(const Gui::ViewProviderDocumentObject&, const App::Property &prop); bool createNewItem(const Gui::ViewProviderDocumentObject&, QTreeWidgetItem *parent=0, int index=-1, - DocumentObjectItemsPtr ptrs = DocumentObjectItemsPtr()); - + DocumentObjectDataPtr ptrs = DocumentObjectDataPtr()); + + int findRootIndex(App::DocumentObject *childObj); + + DocumentObjectItem *findItemByObject(bool sync, + App::DocumentObject *obj, const char *subname, bool select=false); + + DocumentObjectItem *findItem(bool sync, DocumentObjectItem *item, const char *subname, bool select=true); + + App::DocumentObject *getTopParent(App::DocumentObject *obj, std::string &subname); + + typedef std::unordered_map > ViewParentMap; + void populateParents(const ViewProvider *vp, ViewParentMap &); + private: - const Gui::Document* pDocument; - std::map ObjectMap; + const char *treeName; // for debugging purpose + Gui::Document* pDocument; + std::unordered_map ObjectMap; + std::unordered_map > _ParentMap; + std::vector PopulateObjects; + + ExpandInfoPtr _ExpandInfo; + void restoreItemExpansion(const ExpandInfoPtr &, DocumentObjectItem *); typedef boost::signals2::connection Connection; Connection connectNewObject; Connection connectDelObject; Connection connectChgObject; - Connection connectRenObject; - Connection connectActObject; Connection connectEdtObject; Connection connectResObject; Connection connectHltObject; Connection connectExpObject; Connection connectScrObject; + Connection connectRecomputed; + Connection connectRecomputedObj; + + friend class TreeWidget; + friend class DocumentObjectData; + friend class DocumentObjectItem; }; /** The link between the tree and a document object. @@ -222,31 +366,68 @@ private: class DocumentObjectItem : public QTreeWidgetItem { public: - DocumentObjectItem(Gui::ViewProviderDocumentObject* pcViewProvider, - DocumentObjectItemsPtr selves); + DocumentObjectItem(DocumentItem *ownerDocItem, DocumentObjectDataPtr data); ~DocumentObjectItem(); Gui::ViewProviderDocumentObject* object() const; - void testStatus(); + void testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2); + void testStatus(bool resetStatus); void displayStatusInfo(); void setExpandedStatus(bool); void setData(int column, int role, const QVariant & value); bool isChildOfItem(DocumentObjectItem*); -protected: - void slotChangeIcon(); - void slotChangeToolTip(const QString&); - void slotChangeStatusTip(const QString&); + void restoreBackground(); + + // Get the parent document (where the object is stored) of this item + DocumentItem *getParentDocument() const; + // Get the owner document (where the object is displayed, either stored or + // linked in) of this object + DocumentItem *getOwnerDocument() const; + + // check if a new item is required at root + bool requiredAtRoot(bool excludeSelf=true) const; + + // return the owner, and full quanlified subname + App::DocumentObject *getFullSubName(std::ostringstream &str, + DocumentObjectItem *parent = 0) const; + + // return the immediate decendent of the common ancestor of this item and + // 'cousin'. + App::DocumentObject *getRelativeParent( + std::ostringstream &str, + DocumentObjectItem *cousin, + App::DocumentObject **topParent=0, + std::string *topSubname=0) const; + + // return the top most linked group owner's name, and subname. This method + // is necssary despite have getFullSubName above is because native geo group + // cannot handle selection with sub name. So only a linked group can have + // subname in selection + int getSubName(std::ostringstream &str, App::DocumentObject *&topParent) const; + + void setHighlight(bool set, Gui::HighlightMode mode = Gui::LightBlue); + + const char *getName() const; + const char *getTreeName() const; + + bool isLink() const; + bool isLinkFinal() const; + bool isParentLink() const; + int isGroup() const; + int isParentGroup() const; + + DocumentObjectItem *getParentItem() const; + TreeWidget *getTree() const; private: + QBrush bgBrush; + DocumentItem *myOwner; + DocumentObjectDataPtr myData; + std::vector mySubs; typedef boost::signals2::connection Connection; int previousStatus; - Gui::ViewProviderDocumentObject* viewObject; - Connection connectIcon; - Connection connectTool; - Connection connectStat; - - DocumentObjectItemsPtr myselves; + int selected; bool populated; friend class TreeWidget; @@ -258,7 +439,7 @@ class TreePanel : public QWidget Q_OBJECT public: - TreePanel(QWidget* parent=nullptr); + TreePanel(const char *name, QWidget* parent=nullptr); virtual ~TreePanel(); bool eventFilter(QObject *obj, QEvent *ev); @@ -267,16 +448,11 @@ private Q_SLOTS: void accept(); void showEditor(); void hideEditor(); - void findMatchingItems(const QString&); - -private: - void searchTreeItem(QTreeWidgetItem* item, const QString& text); - void selectTreeItem(QTreeWidgetItem* item, const QString& text); - void resetBackground(QTreeWidgetItem* item); + void itemSearch(const QString &text); private: QLineEdit* searchBox; - QTreeWidget* treeWidget; + TreeWidget* treeWidget; }; /** @@ -295,8 +471,65 @@ private: QTreeWidget* treeWidget; }; + +/** + * TreeWidget item delegate for editing + */ +class TreeWidgetEditDelegate: public QStyledItemDelegate { + Q_OBJECT +public: + TreeWidgetEditDelegate(QObject* parent=0); + virtual QWidget* createEditor(QWidget *parent, + const QStyleOptionViewItem &, const QModelIndex &index) const; +}; + + +/// Helper class to read/write tree view options +class GuiExport TreeParams : public ParameterGrp::ObserverType { +public: + TreeParams(); + void OnChange(Base::Subject &, const char* sReason); + static TreeParams *Instance(); + +#define FC_TREEPARAM_DEFS \ + FC_TREEPARAM_DEF(SyncSelection,bool,Bool,true) \ + FC_TREEPARAM_DEF(SyncView,bool,Bool,true) \ + FC_TREEPARAM_DEF(PreSelection,bool,Bool,true) \ + FC_TREEPARAM_DEF(SyncPlacement,bool,Bool,false) \ + FC_TREEPARAM_DEF(RecordSelection,bool,Bool,true) \ + FC_TREEPARAM_DEF(DocumentMode,int,Int,1) \ + FC_TREEPARAM_DEF(StatusTimeout,int,Int,100) \ + FC_TREEPARAM_DEF(SelectionTimeout,int,Int,100) \ + FC_TREEPARAM_DEF(PreSelectionTimeout,int,Int,500) \ + FC_TREEPARAM_DEF(PreSelectionDelay,int,Int,700) \ + FC_TREEPARAM_DEF(RecomputeOnDrop,bool,Bool,true) \ + FC_TREEPARAM_DEF(KeepRootOrder,bool,Bool,true) \ + FC_TREEPARAM_DEF(TreeActiveAutoExpand,bool,Bool,true) \ + +#define FC_TREEPARAM_FUNCS(_name,_type,_Type,_default) \ + _type _name() const {return _##_name;} \ + void set##_name(_type);\ + void on##_name##Changed(); + +#undef FC_TREEPARAM_DEF +#define FC_TREEPARAM_DEF FC_TREEPARAM_FUNCS + FC_TREEPARAM_DEFS + +private: + +#define FC_TREEPARAM_DECLARE(_name,_type,_Type,_default) \ + _type _##_name; + +#undef FC_TREEPARAM_DEF +#define FC_TREEPARAM_DEF FC_TREEPARAM_DECLARE + FC_TREEPARAM_DEFS + + ParameterGrp::handle handle; +}; + +#define FC_TREEPARAM(_name) (Gui::TreeParams::Instance()->_name()) +#define FC_TREEPARAM_SET(_name,_v) Gui::TreeParams::Instance()->set##_name(_v) + } - #endif // GUI_TREE_H - diff --git a/src/Gui/ViewProviderDocumentObject.cpp b/src/Gui/ViewProviderDocumentObject.cpp index ba557ff54e..f1704a47c6 100644 --- a/src/Gui/ViewProviderDocumentObject.cpp +++ b/src/Gui/ViewProviderDocumentObject.cpp @@ -204,13 +204,21 @@ void ViewProviderDocumentObject::hide(void) void ViewProviderDocumentObject::show(void) { + if(TreeWidget::isObjectShowable(getObject())) + ViewProvider::show(); + else { + Visibility.setValue(false); + if(getObject()) + getObject()->Visibility.setValue(false); + return; + } + // use this bit to check whether 'Visibility' must be adjusted if (Visibility.testStatus(App::Property::User2) == false) { Visibility.setStatus(App::Property::User2, true); Visibility.setValue(true); Visibility.setStatus(App::Property::User2, false); } - ViewProvider::show(); } void ViewProviderDocumentObject::updateView() diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 4f10e92555..324ba63551 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -340,6 +340,35 @@ void Workbench::createMainWindowPopupMenu(MenuItem*) const { } +void Workbench::createLinkMenu(MenuItem *item) { + if(!item || !App::GetApplication().getActiveDocument()) + return; + MenuItem* linkMenu = new MenuItem; + linkMenu->setCommand("Link actions"); + *linkMenu << "Std_LinkMakeGroup" << "Std_LinkMake"; + + auto &rMgr = Application::Instance->commandManager(); + const char *cmds[] = {"Std_LinkMakeRelative",0,"Std_LinkUnlink","Std_LinkReplace", + "Std_LinkImport","Std_LinkImportAll",0,"Std_LinkSelectLinked", + "Std_LinkSelectLinkedFinal","Std_LinkSelectAllLinks"}; + bool separator = true; + for(size_t i=0;iisActive()) { + separator = true; + *linkMenu << cmds[i]; + } + } + *item << linkMenu; +} + void Workbench::activated() { } @@ -446,6 +475,9 @@ void StdWorkbench::setupContextMenu(const char* recipient, MenuItem* item) const { if (strcmp(recipient,"View") == 0) { + createLinkMenu(item); + *item << "Separator"; + MenuItem* StdViews = new MenuItem; StdViews->setCommand( "Standard views" ); @@ -470,9 +502,9 @@ void StdWorkbench::setupContextMenu(const char* recipient, MenuItem* item) const { if (Gui::Selection().countObjectsOfType(App::DocumentObject::getClassTypeId()) > 0) { *item << "Std_ToggleVisibility" << "Std_ShowSelection" << "Std_HideSelection" - << "Std_ToggleSelectability" << "Separator" << "Std_SetAppearance" - << "Std_RandomColor" << "Std_Cut" << "Std_Copy" << "Std_Paste" - << "Separator" << "Std_Delete"; + << "Std_ToggleSelectability" << "Std_TreeSelectAllInstances" << "Separator" + << "Std_SetAppearance" << "Std_RandomColor" << "Separator" + << "Std_Cut" << "Std_Copy" << "Std_Paste" << "Std_Delete" << "Separator"; } } } @@ -492,8 +524,8 @@ MenuItem* StdWorkbench::setupMenuBar() const file->setCommand("&File"); *file << "Std_New" << "Std_Open" << "Separator" << "Std_CloseActiveWindow" << "Std_CloseAllWindows" << "Separator" << "Std_Save" << "Std_SaveAs" - << "Std_SaveCopy" << "Std_Revert" << "Separator" << "Std_Import" - << "Std_Export" << "Std_MergeProjects" << "Std_ProjectInfo" + << "Std_SaveCopy" << "Std_SaveAll" << "Std_Revert" << "Separator" << "Std_Import" + << "Std_Export" << "Std_MergeProjects" << "Std_ProjectInfo" << "Separator" << "Std_Print" << "Std_PrintPreview" << "Std_PrintPdf" << "Separator" << "Std_RecentFiles" << "Separator" << "Std_Quit"; @@ -547,15 +579,16 @@ MenuItem* StdWorkbench::setupMenuBar() const view->setCommand("&View"); *view << "Std_ViewCreate" << "Std_OrthographicCamera" << "Std_PerspectiveCamera" << "Std_MainFullscreen" << "Separator" << stdviews << "Std_FreezeViews" << "Std_DrawStyle" << "Separator" << view3d << zoom - << "Std_ViewDockUndockFullscreen" << "Std_TreeViewDocument" << "Std_AxisCross" << "Std_ToggleClipPlane" + << "Std_ViewDockUndockFullscreen" << "Std_AxisCross" << "Std_ToggleClipPlane" << "Std_TextureMapping" #ifdef BUILD_VR << "Std_ViewVR" #endif << "Separator" << visu << "Std_ToggleVisibility" << "Std_ToggleNavigation" - << "Std_SetAppearance" << "Std_RandomColor" << "Separator" - << "Std_Workbench" << "Std_ToolBarMenu" << "Std_DockViewMenu" << "Separator" + << "Std_SetAppearance" << "Std_RandomColor" << "Separator" + << "Std_Workbench" << "Std_ToolBarMenu" << "Std_DockViewMenu" << "Separator" + << "Std_TreeViewActions" << "Std_ViewStatusBar"; // Tools @@ -625,7 +658,8 @@ ToolBarItem* StdWorkbench::setupToolBars() const // View ToolBarItem* view = new ToolBarItem( root ); view->setCommand("View"); - *view << "Std_ViewFitAll" << "Std_ViewFitSelection" << "Std_DrawStyle" << "Separator" << "Std_ViewIsometric" << "Separator" << "Std_ViewFront" + *view << "Std_ViewFitAll" << "Std_ViewFitSelection" << "Std_DrawStyle" + << "Separator" << "Std_TreeViewActions" << "Std_ViewIsometric" << "Separator" << "Std_ViewFront" << "Std_ViewTop" << "Std_ViewRight" << "Separator" << "Std_ViewRear" << "Std_ViewBottom" << "Std_ViewLeft" << "Separator" << "Std_MeasureDistance" ; @@ -671,6 +705,7 @@ DockWindowItems* StdWorkbench::setupDockWindows() const //Dagview through parameter. ParameterGrp::handle group = App::GetApplication().GetUserParameter(). GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DAGView"); + bool enabled = group->GetBool("Enabled", false); if (enabled) root->addDockWidget("Std_DAGView", Qt::RightDockWidgetArea, false, false); diff --git a/src/Gui/Workbench.h b/src/Gui/Workbench.h index cf9359b397..2badac7901 100644 --- a/src/Gui/Workbench.h +++ b/src/Gui/Workbench.h @@ -95,6 +95,8 @@ public: /// remove the added TaskWatcher void removeTaskWatcher(void); + static void createLinkMenu(MenuItem *); + protected: /** Returns a MenuItem tree structure of menus for this workbench. */ virtual MenuItem* setupMenuBar() const=0;