FreeCAD / src /Gui /Dialogs /DlgObjectSelection.cpp
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* Copyright (c) 2018 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
****************************************************************************/
#include <QPushButton>
#include <QTreeWidget>
#include <QCheckBox>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include "Dialogs/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)
using namespace Gui;
/* TRANSLATOR Gui::DlgObjectSelection */
DlgObjectSelection::DlgObjectSelection(
const std::vector<App::DocumentObject*>& objs,
QWidget* parent,
Qt::WindowFlags fl
)
: QDialog(parent, fl)
{
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, &QCheckBox::toggled, this, &DlgObjectSelection::onAutoDeps);
ui->checkBoxShowDeps->setChecked(hGrp->GetBool("ObjectSelectionShowDeps", false));
QObject::connect(ui->checkBoxShowDeps, &QCheckBox::toggled, [this](bool checked) {
hGrp->SetBool("ObjectSelectionShowDeps", checked);
onShowDeps();
});
QMetaObject::invokeMethod(this, "onShowDeps", Qt::QueuedConnection);
// 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->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->headerItem()->setText(0, tr("Depending on"));
ui->depList->headerItem()->setText(1, tr("Document"));
ui->depList->headerItem()->setText(2, tr("Name"));
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);
connect(ui->treeWidget, &QTreeWidget::itemExpanded, this, &DlgObjectSelection::onItemExpanded);
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();
/**
* 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 Selection"));
useOriginalsBtn->setToolTip(
tr("Ignore dependencies and proceed with the objects\noriginally selected prior to opening "
"this dialog")
);
ui->buttonBox->addButton(useOriginalsBtn, QDialogButtonBox::ResetRole);
connect(ui->treeWidget, &QTreeWidget::itemChanged, this, &DlgObjectSelection::onObjItemChanged);
connect(ui->depList, &QTreeWidget::itemChanged, this, &DlgObjectSelection::onDepItemChanged);
connect(ui->inList, &QTreeWidget::itemChanged, this, &DlgObjectSelection::onDepItemChanged);
connect(
ui->treeWidget,
&QTreeWidget::itemSelectionChanged,
this,
&DlgObjectSelection::onItemSelectionChanged
);
connect(useOriginalsBtn, &QPushButton::clicked, this, &DlgObjectSelection::onUseOriginalsBtnClicked);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &DlgObjectSelection::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &DlgObjectSelection::reject);
timer.setSingleShot(true);
connect(&timer, &QTimer::timeout, this, &DlgObjectSelection::checkItemChanged);
}
/**
* Destroys the object and frees any allocated resources
*/
DlgObjectSelection::~DlgObjectSelection()
{
// no need to delete child widgets, Qt does it all for us
delete ui;
}
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.empty()) {
return items[0];
}
item = new QTreeWidgetItem(ui->treeWidget);
auto vp = freecad_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.empty()) {
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);
return item;
}
void DlgObjectSelection::onItemExpanded(QTreeWidgetItem* item)
{
if (item->childCount()) {
return;
}
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::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);
}
else if (!count) {
allItem->setCheckState(0, Qt::Unchecked);
}
}
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.contains(o) || itemChanged.contains(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);
}
}
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.contains(o) || itemChanged.contains(o)) {
continue;
}
auto it = itemMap.find(o);
if (it == itemMap.end() || it->second[0]->checkState(0) == state) {
continue;
}
int count = 0;
int selcount = 0;
for (auto sibling : o->getOutList()) {
if (!depSet.contains(sibling)) {
continue;
}
++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;
}
++selcount;
}
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(SelectionOptions options) const
{
if (returnOriginals) {
return initSels;
}
std::vector<App::DocumentObject*> res;
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()) {
const auto items = depItem->treeWidget()->selectedItems();
for (auto item : items) {
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 {
const auto items = ui->treeWidget->selectedItems();
for (auto item : items) {
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 (const 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.empty()) {
// 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 (!itemChanged.contains(v.first) && 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()
{
ui->depList->clear();
depMap.clear();
ui->inList->clear();
inMap.clear();
std::vector<App::DocumentObject*> sels;
const auto items = ui->treeWidget->selectedItems();
for (auto item : items) {
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.empty()) {
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);
}
}
}
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.empty() ? _deps : deps;
for (auto it = objs.rbegin(); it != objs.rend(); ++it) {
createDepItem(ui->depList, *it);
}
}
std::set<App::DocumentObject*> inlist;
for (auto obj : sels) {
obj->getInListEx(inlist, true);
}
for (auto it = inlist.begin(); it != inlist.end();) {
if (!depSet.contains(*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::onUseOriginalsBtnClicked()
{
returnOriginals = true;
QDialog::accept();
}
void DlgObjectSelection::accept()
{
QDialog::accept();
}
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();
}
void DlgObjectSelection::onShowDeps()
{
bool checked = ui->checkBoxShowDeps->isChecked();
auto sizes = ui->vsplitter->sizes();
if (!checked && sizes[1] > 0) {
sizes[1] = 0;
}
else if (checked && (sizes[0] == 0 || sizes[1] == 0)) {
sizes[0] = sizes[1] = this->width() / 2;
}
else {
return;
}
ui->vsplitter->setSizes(sizes);
}
#include "moc_DlgObjectSelection.cpp"