Gui: refactor Object Selection Dialog

It is originally used for dependency selection when copying object.
Modified to improve auto dependency selection, and for use by Link
configuration object setup, which also involves dependency selection.
This commit is contained in:
Zheng, Lei
2022-03-15 21:33:07 +08:00
committed by Chris Hennes
parent c6d87e69b5
commit 5947f91b17
5 changed files with 788 additions and 370 deletions

View File

@@ -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<DocumentObjectT&>(*this) = other;
subname.clear();
return *this;
}
SubObjectT &SubObjectT::operator=(const DocumentObject *other)
{
static_cast<DocumentObjectT&>(*this) = other;
subname.clear();
return *this;
}
bool SubObjectT::operator==(const SubObjectT &other) const {
return static_cast<const DocumentObjectT&>(*this) == other
&& subname == other.subname;
@@ -398,6 +419,43 @@ std::vector<App::DocumentObject*> 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()

View File

@@ -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;

View File

@@ -24,6 +24,7 @@
#ifndef _PreComp_
# include <QPushButton>
# include <QTreeWidget>
# include <QCheckBox>
#endif
#include <App/Document.h>
@@ -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<App::DocumentObject*> &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<App::DocumentObject*> &objs,
const std::vector<App::DocumentObject*> &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<App::SubObjectT>(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<App::DocumentObject*> &objs,
const std::vector<App::DocumentObject*> &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. -- <TheMarkster>
*/
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<QTreeWidgetItem*> **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<ViewProviderDocumentObject>(
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<App::DocumentObject *> 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<App::DocumentObject *> 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<App::SubObjectT>(item->data(0, Qt::UserRole)).getObject()) {
QSignalBlocker blocker(ui->treeWidget);
std::set<App::DocumentObject*> 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<QTreeWidgetItem*> *items = nullptr;
auto item = getItem(obj, &items);
if (!setCheckState(item, state, forced))
return;
for (size_t i=1; i<items->size(); ++i)
setCheckState(items->at(i), state, true);
std::vector<App::DocumentObject*> 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<App::DocumentObject*> DlgObjectSelection::getSelections() const {
if (returnOriginals){
return originalSelections;
}
std::vector<App::DocumentObject*> DlgObjectSelection::getSelections(SelectionOptions options) const {
std::vector<App::DocumentObject*> res;
for(auto &v : objMap) {
if(v.second.checkState != Qt::Unchecked)
res.push_back(v.first);
Base::Flags<SelectionOptions> 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<App::SubObjectT>(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<App::SubObjectT>(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<App::SubObjectT>(objItem->data(0, Qt::UserRole))] = state;
onItemSelectionChanged();
}
else {
for (auto item : ui->treeWidget->selectedItems()) {
setCheckState(item, state);
itemChanged[qvariant_cast<App::SubObjectT>(item->data(0, Qt::UserRole))] = state;
}
}
timer.start(10);
}
static bool getOutList(App::DocumentObject *obj,
std::set<App::DocumentObject*> &visited,
std::vector<App::DocumentObject*> &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<App::DocumentObject*> 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<App::SubObjectT>(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<App::DocumentObject *> sels;
for (auto item : ui->treeWidget->selectedItems()) {
if (item == allItem) {
sels.clear();
break;
}
auto obj = qvariant_cast<App::SubObjectT>(item->data(0, Qt::UserRole)).getObject();
if (obj)
sels.push_back(obj);
}
std::vector<App::DocumentObject*> _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<App::DocumentObject*> 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"

View File

@@ -23,52 +23,116 @@
#define GUI_DLGOBJECTSELECTION_H
#include <QDialog>
#include <QTimer>
#include <App/DocumentObserver.h>
#include <Base/Parameter.h>
#include <Base/Bitmask.h>
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<App::DocumentObject*> &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<App::DocumentObject*> &objs,
const std::vector<App::DocumentObject*> &excludes,
QWidget* parent = nullptr, Qt::WindowFlags fl = Qt::WindowFlags());
/// Destructor
~DlgObjectSelection();
std::vector<App::DocumentObject*> 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<App::DocumentObject*> 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<App::DocumentObject*> originalSelections;
bool returnOriginals = false;
QTreeWidgetItem *getItem(App::DocumentObject *obj,
std::vector<QTreeWidgetItem*> **items = nullptr,
QTreeWidgetItem *parent = nullptr);
QTreeWidgetItem *createDepItem(QTreeWidget *parent, App::DocumentObject *obj);
void init(const std::vector<App::DocumentObject*> &objs,
const std::vector<App::DocumentObject*> &excludes);
void setItemState(App::DocumentObject *obj, Qt::CheckState state, bool forced = false);
void updateAllItemState();
private:
struct Info {
std::map<App::DocumentObject *, Info*> inList;
std::map<App::DocumentObject *, Info*> outList;
std::vector<QTreeWidgetItem*> items;
QTreeWidgetItem *depItem = nullptr;
Qt::CheckState checkState = Qt::Checked;
};
std::map<App::DocumentObject *,Info> objMap;
Ui_DlgObjectSelection* ui;
std::set<App::DocumentObject*> sels;
std::vector<App::DocumentObject*> initSels;
std::vector<App::DocumentObject*> deps;
std::set<App::DocumentObject*> depSet;
std::map<App::SubObjectT, std::vector<QTreeWidgetItem*>> itemMap;
std::map<App::SubObjectT, QTreeWidgetItem*> depMap;
std::map<App::SubObjectT, QTreeWidgetItem*> inMap;
std::map<App::SubObjectT, Qt::CheckState> itemChanged;
QTreeWidgetItem *allItem = nullptr;
QTimer timer;
ParameterGrp::handle hGrp;
};
} // namespace Gui
ENABLE_BITMASK_OPERATORS(Gui::DlgObjectSelection::SelectionOptions);
#endif // GUI_DLGOBJECTSELECTION_H

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>621</width>
<height>383</height>
<height>375</height>
</rect>
</property>
<property name="windowTitle">
@@ -23,7 +23,7 @@
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@@ -37,7 +37,7 @@
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter">
<widget class="QSplitter" name="vsplitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@@ -54,37 +54,10 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="rootIsDecorated">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="headerHidden">
<bool>false</bool>
</property>
<property name="columnCount">
<number>1</number>
</property>
<attribute name="headerVisible">
<bool>true</bool>
</attribute>
<attribute name="headerCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="headerShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
<column>
@@ -93,61 +66,115 @@
</property>
</column>
</widget>
<widget class="QTreeWidget" name="depList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="columnCount">
<number>4</number>
</property>
<attribute name="headerShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
<widget class="QTreeWidget" name="depList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</column>
<column>
<property name="text">
<string notr="true">2</string>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</column>
<column>
<property name="text">
<string notr="true">3</string>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
</column>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="columnCount">
<number>3</number>
</property>
<attribute name="headerShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
<column>
<property name="text">
<string notr="true">2</string>
</property>
</column>
<column>
<property name="text">
<string notr="true">3</string>
</property>
</column>
</widget>
<widget class="QTreeWidget" name="inList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="columnCount">
<number>3</number>
</property>
<attribute name="headerShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
<column>
<property name="text">
<string notr="true">2</string>
</property>
</column>
<column>
<property name="text">
<string notr="true">3</string>
</property>
</column>
</widget>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="checkBoxAutoDeps">
<property name="text">
<string>Auto select depending objects</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>