diff --git a/src/App/DocumentObserver.cpp b/src/App/DocumentObserver.cpp index 7f3bdb1216..4e8fa83035 100644 --- a/src/App/DocumentObserver.cpp +++ b/src/App/DocumentObserver.cpp @@ -283,6 +283,11 @@ SubObjectT::SubObjectT(const DocumentObject *obj, const char *s) { } +SubObjectT::SubObjectT(const DocumentObject *obj) + :DocumentObjectT(obj) +{ +} + SubObjectT::SubObjectT(const DocumentObjectT& obj, const char *s) :DocumentObjectT(obj),subname(s?s:"") { @@ -327,6 +332,22 @@ SubObjectT &SubObjectT::operator=(SubObjectT &&other) return *this; } +SubObjectT &SubObjectT::operator=(const DocumentObjectT &other) +{ + if (this == &other) + return *this; + static_cast(*this) = other; + subname.clear(); + return *this; +} + +SubObjectT &SubObjectT::operator=(const DocumentObject *other) +{ + static_cast(*this) = other; + subname.clear(); + return *this; +} + bool SubObjectT::operator==(const SubObjectT &other) const { return static_cast(*this) == other && subname == other.subname; @@ -398,6 +419,43 @@ std::vector SubObjectT::getSubObjectList() const { return {}; } +std::string SubObjectT::getObjectFullName(const char *docName) const +{ + std::ostringstream ss; + if (!docName || getDocumentName() != docName) { + ss << getDocumentName(); + if (auto doc = getDocument()) { + if (doc->Label.getStrValue() != getDocumentName()) + ss << "(" << doc->Label.getValue() << ")"; + } + ss << "#"; + } + ss << getObjectName(); + if (getObjectLabel().size() && getObjectLabel() != getObjectName()) + ss << " (" << getObjectLabel() << ")"; + return ss.str(); +} + +std::string SubObjectT::getSubObjectFullName(const char *docName) const +{ + if (subname.empty()) + return getObjectFullName(docName); + std::ostringstream ss; + if (!docName || getDocumentName() != docName) { + ss << getDocumentName(); + if (auto doc = getDocument()) { + if (doc->Label.getStrValue() != getDocumentName()) + ss << "(" << doc->Label.getValue() << ")"; + } + ss << "#"; + } + ss << getObjectName() << "." << subname; + auto sobj = getSubObject(); + if (sobj && sobj->Label.getStrValue() != sobj->getNameInDocument()) + ss << " (" << sobj->Label.getValue() << ")"; + return ss.str(); +} + // ----------------------------------------------------------------------------- PropertyLinkT::PropertyLinkT() diff --git a/src/App/DocumentObserver.h b/src/App/DocumentObserver.h index 38f36a78e2..6630a497d3 100644 --- a/src/App/DocumentObserver.h +++ b/src/App/DocumentObserver.h @@ -176,6 +176,9 @@ public: /*! Constructor */ SubObjectT(const DocumentObject*, const char *subname); + /*! Constructor */ + SubObjectT(const DocumentObject*); + /*! Constructor */ SubObjectT(const char *docName, const char *objName, const char *subname); @@ -185,15 +188,37 @@ public: /*! Assignment operator */ SubObjectT &operator=(SubObjectT &&); + /*! Assignment operator */ + SubObjectT &operator=(const DocumentObjectT&); + + /*! Assignment operator */ + SubObjectT &operator=(const App::DocumentObject*); + /*! Equality operator */ bool operator==(const SubObjectT&) const; /// Set the subname path to the sub-object void setSubName(const char *subname); + /// Set the subname path to the sub-object + void setSubName(const std::string &subname) { + setSubName(subname.c_str()); + } + /// Return the subname path const std::string &getSubName() const; + /** Return docname#objname (label) + * @param docName: optional document name. The document prefix will only be printed + * if it is different then the given 'doc'. + */ + std::string getObjectFullName(const char *docName=nullptr) const; + + /** Return docname#objname.subname (label) + * @param doc: optional document name. The document prefix will only be printed + * if it is different then the given 'doc'. + */ + std::string getSubObjectFullName(const char *docName=nullptr) const; /// Return the subname path without sub-element std::string getSubNameNoElement() const; diff --git a/src/Gui/DlgObjectSelection.cpp b/src/Gui/DlgObjectSelection.cpp index 1526a8053c..f1783736db 100644 --- a/src/Gui/DlgObjectSelection.cpp +++ b/src/Gui/DlgObjectSelection.cpp @@ -24,6 +24,7 @@ #ifndef _PreComp_ # include # include +# include #endif #include @@ -32,8 +33,10 @@ #include "DlgObjectSelection.h" #include "ui_DlgObjectSelection.h" #include "Application.h" +#include "MainWindow.h" #include "ViewProviderDocumentObject.h" - +#include "MetaTypes.h" +#include "ViewParams.h" FC_LOG_LEVEL_INIT("Gui",true,true) @@ -43,90 +46,115 @@ using namespace Gui; DlgObjectSelection::DlgObjectSelection( const std::vector &objs, QWidget* parent, Qt::WindowFlags fl) - : QDialog(parent, fl), ui(new Ui_DlgObjectSelection) + : QDialog(parent, fl) { - /** - * make a copy of the originally selected objects - * so we can return them if the user clicks useOriginalsBtn - */ - this->originalSelections = objs; + init(objs, {}); +} +DlgObjectSelection::DlgObjectSelection( + const std::vector &objs, + const std::vector &excludes, + QWidget* parent, + Qt::WindowFlags fl) + : QDialog(parent, fl) +{ + init(objs, excludes); +} + +static bool inline setCheckState(QTreeWidgetItem *item, Qt::CheckState state, bool forced=true) +{ + if (!forced) { + if (item->isSelected()) { + if (state == Qt::Unchecked || item->checkState(0) == Qt::Unchecked) + return false; + } + if (item->checkState(0) == state) + return false; + } + // auto objT = qvariant_cast(item->data(0, Qt::UserRole)); + // FC_MSG(objT.getObjectFullName() << (state == Qt::Unchecked ? " unchecked" : + // (state == Qt::Checked ? " checked" : " partial"))); + item->setCheckState(0, state); + return true; +} + +void DlgObjectSelection::init(const std::vector &objs, + const std::vector &excludes) + +{ + initSels = objs; + std::sort(initSels.begin(), initSels.end()); + + deps = App::Document::getDependencyList(objs, App::Document::DepSort); + depSet.insert(deps.begin(), deps.end()); + + ui = new Ui_DlgObjectSelection; ui->setupUi(this); + hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General"); + ui->checkBoxAutoDeps->setChecked(hGrp->GetBool("ObjectSelectionAutoDeps", true)); + connect(ui->checkBoxAutoDeps, SIGNAL(toggled(bool)), this, SLOT(onAutoDeps(bool))); + // make sure to show a horizontal scrollbar if needed ui->depList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); ui->depList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); ui->depList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); - ui->depList->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); + ui->inList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + ui->inList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + ui->inList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - ui->depList->header()->setStretchLastSection(false); - ui->depList->headerItem()->setText(0, tr("Dependency")); + ui->depList->headerItem()->setText(0, tr("Depending on")); ui->depList->headerItem()->setText(1, tr("Document")); ui->depList->headerItem()->setText(2, tr("Name")); - ui->depList->headerItem()->setText(3, tr("State")); - ui->treeWidget->headerItem()->setText(0, tr("Hierarchy")); + ui->inList->headerItem()->setText(0, tr("Depended by")); + ui->inList->headerItem()->setText(1, tr("Document")); + ui->inList->headerItem()->setText(2, tr("Name")); + + ui->treeWidget->headerItem()->setText(0, tr("Selections")); ui->treeWidget->header()->setStretchLastSection(false); - for(auto obj : App::Document::getDependencyList(objs)) { - auto &info = objMap[obj]; - info.depItem = new QTreeWidgetItem(ui->depList); - auto vp = Gui::Application::Instance->getViewProvider(obj); - if(vp) info.depItem->setIcon(0, vp->getIcon()); - info.depItem->setText(0, QString::fromUtf8((obj)->Label.getValue())); - info.depItem->setText(1, QString::fromUtf8(obj->getDocument()->getName())); - info.depItem->setText(2, QString::fromLatin1(obj->getNameInDocument())); - info.depItem->setText(3, tr("Selected")); - info.depItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled); - info.depItem->setCheckState(0,Qt::Checked); - } - for(auto obj : objs) { - auto &info = objMap[obj]; - info.items.push_back(createItem(obj,nullptr)); - info.items.back()->setCheckState(0,Qt::Checked); - } - - for(auto &v : objMap) { - for(auto obj : v.first->getOutListRecursive()) { - if(obj == v.first) - continue; - auto it = objMap.find(obj); - if(it == objMap.end()) - continue; - v.second.outList[obj] = &it->second; - } - for(auto obj : v.first->getInListRecursive()) { - if(obj == v.first) - continue; - auto it = objMap.find(obj); - if(it == objMap.end()) - continue; - v.second.inList[obj] = &it->second; - } - } - /** - * create useOriginalsBtn and add to the button box - * tried adding to .ui file, but could never get the - * formatting exactly the way I wanted it. -- - */ - useOriginalsBtn = new QPushButton(tr("&Use Original Selections")); - useOriginalsBtn->setToolTip(tr("Ignore dependencies and proceed with objects\noriginally selected prior to opening this dialog")); - ui->buttonBox->addButton(useOriginalsBtn,QDialogButtonBox::ActionRole); - connect(ui->treeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(onItemExpanded(QTreeWidgetItem*))); + + allItem = new QTreeWidgetItem(ui->treeWidget); + allItem->setText(0, QStringLiteral("<%1>").arg(tr("All"))); + QFont font = allItem->font(0); + font.setBold(true); + allItem->setFont(0, font); + allItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled); + allItem->setCheckState(0, Qt::Checked); + + for(auto obj : initSels) + getItem(obj)->setCheckState(0, Qt::Checked); + + for(auto obj : deps) + getItem(obj)->setCheckState(0, Qt::Checked); + + auto filter = excludes; + std::sort(filter.begin(), filter.end()); + for (auto obj : deps) { + auto it = std::lower_bound(filter.begin(), filter.end(), obj); + if (it != filter.end() && *it == obj) + setItemState(obj, Qt::Unchecked); + } + onItemSelectionChanged(); + connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), - this, SLOT(onItemChanged(QTreeWidgetItem*,int))); + this, SLOT(onObjItemChanged(QTreeWidgetItem*,int))); connect(ui->depList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), - this, SLOT(onItemChanged(QTreeWidgetItem*,int))); + this, SLOT(onDepItemChanged(QTreeWidgetItem*,int))); + connect(ui->inList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), + this, SLOT(onDepItemChanged(QTreeWidgetItem*,int))); connect(ui->treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(onItemSelectionChanged())); - connect(ui->depList, SIGNAL(itemSelectionChanged()), - this, SLOT(onDepSelectionChanged())); + connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject())); - connect(useOriginalsBtn, SIGNAL(clicked()), this, SLOT(onUseOriginalsBtnClicked())); + + timer.setSingleShot(true); + connect(&timer, SIGNAL(timeout()), this, SLOT(checkItemChanged())); } /** @@ -138,249 +166,427 @@ DlgObjectSelection::~DlgObjectSelection() delete ui; } -QTreeWidgetItem *DlgObjectSelection::createItem(App::DocumentObject *obj, QTreeWidgetItem *parent) { - QTreeWidgetItem* item; - if(parent) - item = new QTreeWidgetItem(parent); - else +QTreeWidgetItem *DlgObjectSelection::getItem(App::DocumentObject *obj, + std::vector **pitems, + QTreeWidgetItem *parent) +{ + auto &items = itemMap[App::SubObjectT(obj, "")]; + if (pitems) + *pitems = &items; + QTreeWidgetItem *item; + if (!parent) { + if (items.size()) + return items[0]; item = new QTreeWidgetItem(ui->treeWidget); - auto vp = Gui::Application::Instance->getViewProvider(obj); - if(vp) item->setIcon(0, vp->getIcon()); - item->setText(0, QString::fromUtf8((obj)->Label.getValue())); - item->setData(0, Qt::UserRole, QByteArray(obj->getDocument()->getName())); - item->setData(0, Qt::UserRole+1, QByteArray(obj->getNameInDocument())); + auto vp = Base::freecad_dynamic_cast( + Gui::Application::Instance->getViewProvider(obj)); + if (vp) item->setIcon(0, vp->getIcon()); + App::SubObjectT objT(obj, ""); + item->setText(0, QString::fromUtf8((obj)->Label.getValue())); + if (std::binary_search(initSels.begin(), initSels.end(), obj)) { + QFont font = item->font(0); + font.setBold(true); + font.setItalic(true); + item->setFont(0, font); + } + item->setToolTip(0, QString::fromUtf8(objT.getObjectFullName().c_str())); + item->setData(0, Qt::UserRole, QVariant::fromValue(objT)); + item->setChildIndicatorPolicy(obj->getOutList().empty() ? + QTreeWidgetItem::DontShowIndicator : QTreeWidgetItem::ShowIndicator); + } else if (items.size()) { + item = new QTreeWidgetItem(parent); + item->setIcon(0, items[0]->icon(0)); + item->setText(0, items[0]->text(0)); + item->setFont(0, items[0]->font(0)); + item->setToolTip(0, items[0]->toolTip(0)); + item->setData(0, Qt::UserRole, items[0]->data(0, Qt::UserRole)); + item->setChildIndicatorPolicy(items[0]->childIndicatorPolicy()); + item->setCheckState(0, items[0]->checkState(0)); + } else + return nullptr; + items.push_back(item); item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled); - std::set outSet; - for(auto o : obj->getOutList()) { - if(objMap.count(o)) - outSet.insert(o); - } - if(outSet.empty()) - return item; - item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); - if(!parent) { - bool populate = false; - for(auto o : outSet) { - if(objMap[o].items.empty()) { - populate = true; - break; - } - } - if(!populate) - return item; - for(auto o : outSet) { - auto &info = objMap[o]; - info.items.push_back(createItem(o,item)); - info.items.back()->setCheckState(0,info.checkState); - } - } return item; } -class SignalBlocker { -public: - SignalBlocker(QTreeWidget *treeWidget) - :treeWidget(treeWidget) - { - treeWidget->blockSignals(true); - } - ~SignalBlocker() { - treeWidget->blockSignals(false); - } - QTreeWidget *treeWidget; -}; - -App::DocumentObject *DlgObjectSelection::objFromItem(QTreeWidgetItem *item) { - std::string name; - std::string docName; - if(item->treeWidget() == ui->treeWidget) { - docName = item->data(0,Qt::UserRole).toByteArray().constData(); - name = item->data(0,Qt::UserRole+1).toByteArray().constData(); - }else{ - docName = qPrintable(item->text(1)); - name = qPrintable(item->text(2)); - } - auto doc = App::GetApplication().getDocument(docName.c_str()); - if(!doc) return nullptr; - return doc->getObject(name.c_str()); -} - -void DlgObjectSelection::onItemExpanded(QTreeWidgetItem * item) { - if(item->childCount()) +void DlgObjectSelection::onItemExpanded(QTreeWidgetItem *item) +{ + if (item->childCount()) return; - auto obj = objFromItem(item); - if(!obj) - return; - SignalBlocker blocker(ui->treeWidget); - std::set outSet; - for(auto o : obj->getOutList()) { - if(!objMap.count(obj) || !outSet.insert(o).second) - continue; - auto &info = objMap[o]; - info.items.push_back(createItem(o,item)); - info.items.back()->setCheckState(0,info.checkState); + if (auto obj = qvariant_cast(item->data(0, Qt::UserRole)).getObject()) { + QSignalBlocker blocker(ui->treeWidget); + std::set set; + for (auto child : obj->getOutList()) { + if (child && set.insert(child).second) + getItem(child, nullptr, item); + } } } -void DlgObjectSelection::onItemChanged(QTreeWidgetItem * item, int column) { - if(column) return; - auto obj = objFromItem(item); - if(!obj) return; - auto state = item->checkState(0); - auto it = objMap.find(obj); - if(it == objMap.end() || state == it->second.checkState) - return; - SignalBlocker blocker(ui->treeWidget); - SignalBlocker blocker2(ui->depList); - auto &info = it->second; - info.checkState = state; +void DlgObjectSelection::updateAllItemState() +{ + int count = 0; + for (const auto &v : itemMap) { + auto state = v.second[0]->checkState(0); + if (state == Qt::Unchecked) { + if (count) { + allItem->setCheckState(0, Qt::PartiallyChecked); + return; + } + } else { + if (state == Qt::PartiallyChecked) { + allItem->setCheckState(0, Qt::PartiallyChecked); + return; + } + ++count; + } + } + if (count && count == (int)itemMap.size()) + allItem->setCheckState(0, Qt::Checked); +} - if(item == info.depItem) { - for(auto item : info.items) - item->setCheckState(0,state); - }else{ - info.depItem->setCheckState(0,state); - info.depItem->setText(3,state==Qt::Checked?tr("Selected"):QString()); +void DlgObjectSelection::setItemState(App::DocumentObject *obj, + Qt::CheckState state, + bool forced) +{ + std::vector *items = nullptr; + auto item = getItem(obj, &items); + if (!setCheckState(item, state, forced)) + return; + + for (size_t i=1; isize(); ++i) + setCheckState(items->at(i), state, true); + + std::vector objs = {obj}; + + if (ui->checkBoxAutoDeps->isChecked() && state == Qt::Checked) { + // If an object is newly checked, check all its dependencies + for (auto o : obj->getOutListRecursive()) { + if (!depSet.count(o) || itemChanged.count(o)) + continue; + auto itItem = itemMap.find(o); + if (itItem == itemMap.end() || itItem->second[0]->checkState(0) == state) + continue; + + for (auto i : itItem->second) + setCheckState(i, state, true); + objs.push_back(o); + } } - if(state == Qt::Unchecked) { - for(auto &v : info.outList) { - if(info.inList.count(v.first)) { - // This indicates a dependency loop. The check here is so that - // object selection still works despite of the loop + for(auto obj : objs) { + auto it = inMap.find(obj); + if (it != inMap.end()) + setCheckState(it->second, state); + + auto itDep = depMap.find(obj); + if (itDep != depMap.end()) + setCheckState(itDep->second, state); + + // If an object toggles state, we need to revisit all its in-list + // object to update the partial/full checked state. + for (auto o : obj->getInList()) { + if (!depSet.count(o) ||itemChanged.count(o)) continue; - } - if(v.second->checkState == Qt::Unchecked) + auto it = itemMap.find(o); + if (it == itemMap.end() || it->second[0]->checkState(0) == state) continue; - v.second->checkState = Qt::Unchecked; - v.second->depItem->setText(3,QString()); - v.second->depItem->setCheckState(0,Qt::Unchecked); - for(auto item : v.second->items) - item->setCheckState(0,Qt::Unchecked); - } - for(auto &v : info.inList) { - if(v.second->checkState != Qt::Checked) - continue; - v.second->checkState = Qt::PartiallyChecked; - v.second->depItem->setText(3,tr("Partial")); - v.second->depItem->setCheckState(0,Qt::PartiallyChecked); - for(auto item : v.second->items) - item->setCheckState(0,Qt::PartiallyChecked); - } - return; - } else if(state == Qt::Checked) { - for(auto &v : info.outList) { - if(info.inList.count(v.first)) { - // This indicates a dependency loop. The check here is so that - // object selection still works despite of the loop - continue; - } - if(v.second->checkState == Qt::Checked) - continue; - v.second->checkState = Qt::Checked; - v.second->depItem->setText(3,tr("Selected")); - v.second->depItem->setCheckState(0,Qt::Checked); - for(auto item : v.second->items) - item->setCheckState(0,Qt::Checked); - } - bool touched; - do { - touched = false; - for(auto &v : info.inList) { - if(v.second->checkState != Qt::PartiallyChecked) + int count = 0; + int selcount = 0; + for (auto sibling : o->getOutList()) { + if (!depSet.count(sibling)) continue; - bool partial = false; - for(auto &vv : v.second->outList) { - if(vv.second->checkState != Qt::Checked) { - partial = true; - break; - } + ++count; + auto it = itemMap.find(sibling); + if (it == itemMap.end()) + continue; + auto s = it->second[0]->checkState(0); + if (s == Qt::Unchecked) + continue; + if (it->second[0]->checkState(0) == Qt::PartiallyChecked) { + selcount = -1; + break; } - if(partial) - continue; - touched = true; - v.second->checkState = Qt::Checked; - v.second->depItem->setText(3,tr("Selected")); - v.second->depItem->setCheckState(0,Qt::Checked); - for(auto item : v.second->items) - item->setCheckState(0,Qt::Checked); + ++selcount; } - }while(touched); + auto state = it->second[0]->checkState(0); + if (state == Qt::Checked && selcount != count) + setItemState(o, Qt::PartiallyChecked, true); + else if (state == Qt::PartiallyChecked && selcount == count) + setItemState(o, Qt::Checked, true); + } } } -std::vector DlgObjectSelection::getSelections() const { - - if (returnOriginals){ - return originalSelections; - } - +std::vector DlgObjectSelection::getSelections(SelectionOptions options) const { std::vector res; - for(auto &v : objMap) { - if(v.second.checkState != Qt::Unchecked) - res.push_back(v.first); + Base::Flags flags(options); + if (!flags.testFlag(SelectionOptions::Invert)) { + for (const auto &v : itemMap) { + if (v.second[0]->checkState(0) == Qt::Unchecked) + continue; + if (auto obj = v.first.getObject()) + res.push_back(obj); + } + } else { + for (auto obj : deps) { + auto it = itemMap.find(obj); + if (it == itemMap.end() || it->second[0]->checkState(0) == Qt::Unchecked) + res.push_back(obj); + } } + if (flags.testFlag(SelectionOptions::Sort)) + std::sort(res.begin(), res.end()); return res; } +void DlgObjectSelection::onDepItemChanged(QTreeWidgetItem * depItem, int column) { + if(column) return; + QSignalBlocker blocker(ui->depList); + QSignalBlocker blocker2(ui->inList); + QSignalBlocker blocker3(ui->treeWidget); + auto state = depItem->checkState(0); + if (depItem->isSelected()) { + for (auto item : depItem->treeWidget()->selectedItems()) { + auto objT = qvariant_cast(item->data(0, Qt::UserRole)); + auto it = itemMap.find(objT); + if (it == itemMap.end()) + continue; + setCheckState(item, state); + for (auto i : it->second) + setCheckState(i, state); + itemChanged[objT] = state; + } + } else { + auto objT = qvariant_cast(depItem->data(0, Qt::UserRole)); + auto it = itemMap.find(objT); + if (it != itemMap.end()) { + itemChanged[objT] = state; + for (auto i : it->second) + setCheckState(i, state); + } + } + timer.start(10); +} + +void DlgObjectSelection::onObjItemChanged(QTreeWidgetItem * objItem, int column) { + if(column != 0) + return; + + QSignalBlocker blocker3(ui->treeWidget); + auto state = objItem->checkState(0); + if (objItem == allItem) { + if (state == Qt::PartiallyChecked) + return; + ui->treeWidget->selectionModel()->clearSelection(); + itemChanged.clear(); + timer.stop(); + onItemSelectionChanged(); + if (state == Qt::Unchecked) { + for (const auto &v : itemMap) { + for (auto i : v.second) + setCheckState(i, Qt::Unchecked); + auto it = depMap.find(v.first); + if (it != depMap.end()) + setCheckState(it->second, Qt::Unchecked); + it = inMap.find(v.first); + if (it != inMap.end()) + setCheckState(it->second, Qt::Unchecked); + } + } else { + for (auto obj : initSels) + setCheckState(getItem(obj), Qt::Checked); + for (auto obj : deps) { + setCheckState(getItem(obj), Qt::Checked); + auto it = depMap.find(obj); + if (it != depMap.end()) + setCheckState(it->second, Qt::Checked); + it = inMap.find(obj); + if (it != inMap.end()) + setCheckState(it->second, Qt::Checked); + } + } + return; + } + + if (!objItem->isSelected()) { + ui->treeWidget->selectionModel()->clearSelection(); + objItem->setSelected(true); + // We treat selected item in tree widget specially in case of checking + // items in depList or inList. To simplify logic, we change selection + // here if an unselected item has been checked. + itemChanged[qvariant_cast(objItem->data(0, Qt::UserRole))] = state; + onItemSelectionChanged(); + } + else { + for (auto item : ui->treeWidget->selectedItems()) { + setCheckState(item, state); + itemChanged[qvariant_cast(item->data(0, Qt::UserRole))] = state; + } + } + timer.start(10); +} + + +static bool getOutList(App::DocumentObject *obj, + std::set &visited, + std::vector &result) +{ + if (!visited.insert(obj).second) + return false; + + for (auto o : obj->getOutList()) { + if (getOutList(o, visited, result)) + result.push_back(o); + } + return true; +} + +void DlgObjectSelection::checkItemChanged() { + + QSignalBlocker blocker(ui->depList); + QSignalBlocker blocker2(ui->inList); + QSignalBlocker blocker3(ui->treeWidget); + + std::set unchecked; + + for (auto &v : itemChanged) { + const auto &objT = v.first; + Qt::CheckState state = v.second; + if (auto obj = objT.getObject()) { + if (state == Qt::Unchecked) { + // We'll deal with unchecked item later + if (ui->checkBoxAutoDeps->isChecked()) + unchecked.insert(obj); + } else { + // For checked item, setItemState will auto select its + // dependency + setItemState(obj, state, true); + } + } + } + + if (unchecked.size()) { + // When some item is unchecked by the user, we need to re-check the + // recursive outlist of the initially selected object, excluding all + // currently unchecked object. And then uncheck any item that does not + // appear in the returned outlist. + + for (const auto &v : itemMap) { + auto item = v.second[0]; + if (item->checkState(0) == Qt::Unchecked) { + if (auto obj = qvariant_cast(item->data(0, Qt::UserRole)).getObject()) + unchecked.insert(obj); + } + } + + auto outlist = initSels; + for (auto obj : initSels) + getOutList(obj, unchecked, outlist); + std::sort(outlist.begin(), outlist.end()); + + for (const auto &v : itemMap) { + if (v.second[0]->checkState(0) == Qt::Unchecked) + continue; + if (auto obj = v.first.getObject()) { + if (!std::binary_search(outlist.begin(), outlist.end(), obj)) + setItemState(obj, Qt::Unchecked, true); + } + } + } + + itemChanged.clear(); + updateAllItemState(); +} + +QTreeWidgetItem *DlgObjectSelection::createDepItem(QTreeWidget *parent, App::DocumentObject *obj) +{ + auto item = new QTreeWidgetItem(parent); + if (parent == ui->depList) + depMap[obj] = item; + else + inMap[obj] = item; + App::SubObjectT objT(obj); + auto vp = Gui::Application::Instance->getViewProvider(obj); + if(vp) item->setIcon(0, vp->getIcon()); + item->setData(0, Qt::UserRole, QVariant::fromValue(objT)); + item->setToolTip(0, QString::fromUtf8(objT.getObjectFullName().c_str())); + item->setText(0, QString::fromUtf8((obj)->Label.getValue())); + if (std::binary_search(initSels.begin(), initSels.end(), obj)) { + QFont font = item->font(0); + font.setBold(true); + font.setItalic(true); + item->setFont(0, font); + } + item->setText(1, QString::fromUtf8(obj->getDocument()->getName())); + item->setText(2, QString::fromUtf8(obj->getNameInDocument())); + item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled); + auto it = itemMap.find(obj); + if (it != itemMap.end()) + setCheckState(item, it->second[0]->checkState(0)); + return item; +} + void DlgObjectSelection::onItemSelectionChanged() { - SignalBlocker block2(ui->treeWidget); - SignalBlocker block(ui->depList); - QTreeWidgetItem *scroll=nullptr; - for(auto &v : objMap) { - auto &info = v.second; - auto it = sels.find(v.first); - auto selected = it==sels.end(); - for(auto item : info.items) { - if(selected == item->isSelected()) { - for(auto item : info.items) - item->setSelected(selected); - scroll = info.depItem; - info.depItem->setSelected(selected); - scroll = info.depItem; - if(!selected) - sels.erase(it); - else - sels.insert(v.first); - break; - } + ui->depList->clear(); + depMap.clear(); + ui->inList->clear(); + inMap.clear(); + + std::vector sels; + for (auto item : ui->treeWidget->selectedItems()) { + if (item == allItem) { + sels.clear(); + break; + } + auto obj = qvariant_cast(item->data(0, Qt::UserRole)).getObject(); + if (obj) + sels.push_back(obj); + } + + std::vector _deps; + if (sels.size()) { + std::sort(sels.begin(), sels.end()); + for (auto dep : App::Document::getDependencyList(sels, App::Document::DepSort)) { + if (!std::binary_search(sels.begin(), sels.end(), dep)) + _deps.push_back(dep); } } - if(scroll) - ui->depList->scrollToItem(scroll); -} -void DlgObjectSelection::onDepSelectionChanged() { - SignalBlocker block2(ui->treeWidget); - SignalBlocker block(ui->depList); - QTreeWidgetItem *scroll=nullptr; - for(auto &v : objMap) { - auto &info = v.second; - auto it = sels.find(v.first); - auto selected = it==sels.end(); - if(info.depItem->isSelected()==selected) { - for(auto item : info.items) { - scroll = item; - item->setSelected(selected); - } - if(!selected) - sels.erase(it); - else { - sels.insert(v.first); - for(auto item : info.items) { - for(auto parent=item->parent();parent;parent=parent->parent()) - parent->setExpanded(true); - } - } - } + bool enabled = ui->depList->isSortingEnabled(); + if (enabled) + ui->depList->setSortingEnabled(false); + + bool enabled2 = ui->inList->isSortingEnabled(); + if (enabled2) + ui->inList->setSortingEnabled(false); + + { + QSignalBlocker blocker(ui->depList); + auto &objs = sels.size() ? _deps : deps; + for (auto it = objs.rbegin(); it != objs.rend(); ++it) + createDepItem(ui->depList, *it); } - if(scroll) - ui->treeWidget->scrollToItem(scroll); -} -void DlgObjectSelection::onUseOriginalsBtnClicked(){ - returnOriginals = true; - QDialog::accept(); + std::set inlist; + for (auto obj : sels) + obj->getInListEx(inlist, true); + for (auto it = inlist.begin(); it != inlist.end();) { + if (!depSet.count(*it) || std::binary_search(sels.begin(), sels.end(), *it)) + it = inlist.erase(it); + else + ++it; + } + { + QSignalBlocker blocker2(ui->inList); + for (auto obj : inlist) + createDepItem(ui->inList, obj); + } + + if (enabled) + ui->depList->setSortingEnabled(true); + if (enabled2) + ui->inList->setSortingEnabled(true); } void DlgObjectSelection::accept() { @@ -391,4 +597,42 @@ void DlgObjectSelection::reject() { QDialog::reject(); } +void DlgObjectSelection::addCheckBox(QCheckBox *box) { + ui->horizontalLayout->insertWidget(0, box); +} + +void DlgObjectSelection::setMessage(const QString &msg) { + ui->label->setText(msg); +} + +void DlgObjectSelection::onAutoDeps(bool checked) +{ + hGrp->SetBool("ObjectSelectionAutoDeps", checked); + if (!checked) + return; + + QSignalBlocker blocker(ui->treeWidget); + for (auto obj : deps) { + auto it = itemMap.find(obj); + if (it == itemMap.end()) + continue; + auto item = it->second[0]; + if (item->checkState(0) == Qt::Unchecked) + continue; + Qt::CheckState state = Qt::Checked; + for (auto o : obj->getOutList()) { + auto it = itemMap.find(o); + if (it == itemMap.end()) + continue; + if (it->second[0]->checkState(0) != Qt::Checked) { + state = Qt::PartiallyChecked; + break; + } + } + for (auto i : it->second) + setCheckState(i, state); + } + onItemSelectionChanged(); +} + #include "moc_DlgObjectSelection.cpp" diff --git a/src/Gui/DlgObjectSelection.h b/src/Gui/DlgObjectSelection.h index 8c1adf4240..b1e5108dbc 100644 --- a/src/Gui/DlgObjectSelection.h +++ b/src/Gui/DlgObjectSelection.h @@ -23,52 +23,116 @@ #define GUI_DLGOBJECTSELECTION_H #include +#include +#include +#include +#include + +class QCheckBox; +class QTreeWidgetItem; +class QTreeWidget; namespace Gui { class Ui_DlgObjectSelection; + +/** Dialog for object dependency selection + */ class GuiExport DlgObjectSelection : public QDialog { Q_OBJECT public: + /** Constructor + * + * Creates a dialog for selecting the given objects and their dependent + * objects + * + * @param objs: initial objects + * @param parent: optional parent widget + * @param fl: optional window flags + */ DlgObjectSelection(const std::vector &objs, QWidget* parent = nullptr, Qt::WindowFlags fl = Qt::WindowFlags()); + + /** Constructor + * + * Creates a dialog for selecting the given objects and their dependent + * objects with exclusions + * + * @param objs: initial objects + * @param excludes: excluded objects. The objects and their dependents will + * still be in the list but unchecked. + * @param parent: optional parent widget + * @param fl: optional window flags + */ + DlgObjectSelection(const std::vector &objs, + const std::vector &excludes, + QWidget* parent = nullptr, Qt::WindowFlags fl = Qt::WindowFlags()); + + /// Destructor ~DlgObjectSelection(); - std::vector getSelections() const; + /// Options for getSelections() + enum class SelectionOptions { + /// Invert the selection, i.e. return the unselected objects + Invert = 1, + /// Sort the returned object in depending order + Sort = 2, + /// Return the unselected objects sorted in depending order + InvertSort = 3, + }; + /// Get the selected objects + std::vector getSelections(SelectionOptions options = SelectionOptions()) const; + + /// Add a user defined checkbox at the bottom of the dialog + void addCheckBox(QCheckBox *box); + + /// Override the prompt message + void setMessage(const QString &); + void accept(); void reject(); private Q_SLOTS: - void onItemExpanded(QTreeWidgetItem * item); - void onItemChanged(QTreeWidgetItem * item, int); + void onDepItemChanged(QTreeWidgetItem * item, int); + void onObjItemChanged(QTreeWidgetItem * item, int); void onItemSelectionChanged(); - void onDepSelectionChanged(); - void onUseOriginalsBtnClicked(); + void checkItemChanged(); + void onAutoDeps(bool); + void onItemExpanded(QTreeWidgetItem *item); private: - QTreeWidgetItem *createItem(App::DocumentObject *obj, QTreeWidgetItem *parent); - App::DocumentObject *objFromItem(QTreeWidgetItem *item); - QPushButton *useOriginalsBtn; - std::vector originalSelections; - bool returnOriginals = false; + QTreeWidgetItem *getItem(App::DocumentObject *obj, + std::vector **items = nullptr, + QTreeWidgetItem *parent = nullptr); + + QTreeWidgetItem *createDepItem(QTreeWidget *parent, App::DocumentObject *obj); + + void init(const std::vector &objs, + const std::vector &excludes); + + void setItemState(App::DocumentObject *obj, Qt::CheckState state, bool forced = false); + void updateAllItemState(); private: - struct Info { - std::map inList; - std::map outList; - std::vector items; - QTreeWidgetItem *depItem = nullptr; - Qt::CheckState checkState = Qt::Checked; - }; - std::map objMap; Ui_DlgObjectSelection* ui; - std::set sels; + std::vector initSels; + std::vector deps; + std::set depSet; + std::map> itemMap; + std::map depMap; + std::map inMap; + std::map itemChanged; + QTreeWidgetItem *allItem = nullptr; + + QTimer timer; + ParameterGrp::handle hGrp; }; } // namespace Gui +ENABLE_BITMASK_OPERATORS(Gui::DlgObjectSelection::SelectionOptions); #endif // GUI_DLGOBJECTSELECTION_H diff --git a/src/Gui/DlgObjectSelection.ui b/src/Gui/DlgObjectSelection.ui index 5c4ee9a40e..af4c6fdd1e 100644 --- a/src/Gui/DlgObjectSelection.ui +++ b/src/Gui/DlgObjectSelection.ui @@ -7,7 +7,7 @@ 0 0 621 - 383 + 375 @@ -23,7 +23,7 @@ - + 0 0 @@ -37,7 +37,7 @@ - + 0 @@ -54,37 +54,10 @@ 0 - - - 0 - 0 - - QAbstractItemView::ExtendedSelection - - true - - - true - - - false - - - 1 - - true - - - false - - - false - - false @@ -93,61 +66,115 @@ - - - - 1 - 0 - + + + Qt::Vertical - - QAbstractItemView::ExtendedSelection - - - false - - - true - - - 4 - - - true - - - - 1 + + + + 1 + 0 + - - - - 2 + + QAbstractItemView::ExtendedSelection - - - - 3 + + false - + + true + + + 3 + + + true + + + + 1 + + + + + 2 + + + + + 3 + + + + + + + 1 + 0 + + + + QAbstractItemView::ExtendedSelection + + + false + + + true + + + 3 + + + true + + + + 1 + + + + + 2 + + + + + 3 + + + - - - - 0 - 1 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - + + + + + Auto select depending objects + + + + + + + + 0 + 1 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + +