1
/****************************************************************************
2
* Copyright (c) 2018 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
****************************************************************************/
23
#include "PreCompiled.h"
25
# include <QPushButton>
26
# include <QTreeWidget>
30
#include <App/Document.h>
31
#include <App/DocumentObject.h>
33
#include "DlgObjectSelection.h"
34
#include "ui_DlgObjectSelection.h"
35
#include "Application.h"
36
#include "MainWindow.h"
37
#include "ViewProviderDocumentObject.h"
39
#include "ViewParams.h"
41
FC_LOG_LEVEL_INIT("Gui",true,true)
45
/* TRANSLATOR Gui::DlgObjectSelection */
47
DlgObjectSelection::DlgObjectSelection(
48
const std::vector<App::DocumentObject*> &objs, QWidget* parent, Qt::WindowFlags fl)
54
DlgObjectSelection::DlgObjectSelection(
55
const std::vector<App::DocumentObject*> &objs,
56
const std::vector<App::DocumentObject*> &excludes,
64
static bool inline setCheckState(QTreeWidgetItem *item, Qt::CheckState state, bool forced=true)
67
if (item->isSelected()) {
68
if (state == Qt::Unchecked || item->checkState(0) == Qt::Unchecked)
71
if (item->checkState(0) == state)
74
// auto objT = qvariant_cast<App::SubObjectT>(item->data(0, Qt::UserRole));
75
// FC_MSG(objT.getObjectFullName() << (state == Qt::Unchecked ? " unchecked" :
76
// (state == Qt::Checked ? " checked" : " partial")));
77
item->setCheckState(0, state);
81
void DlgObjectSelection::init(const std::vector<App::DocumentObject*> &objs,
82
const std::vector<App::DocumentObject*> &excludes)
86
std::sort(initSels.begin(), initSels.end());
88
deps = App::Document::getDependencyList(objs, App::Document::DepSort);
89
depSet.insert(deps.begin(), deps.end());
91
ui = new Ui_DlgObjectSelection;
94
hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General");
95
ui->checkBoxAutoDeps->setChecked(hGrp->GetBool("ObjectSelectionAutoDeps", true));
96
connect(ui->checkBoxAutoDeps, &QCheckBox::toggled, this, &DlgObjectSelection::onAutoDeps);
98
ui->checkBoxShowDeps->setChecked(hGrp->GetBool("ObjectSelectionShowDeps", false));
99
QObject::connect(ui->checkBoxShowDeps, &QCheckBox::toggled,
100
[this](bool checked) {
101
hGrp->SetBool("ObjectSelectionShowDeps", checked);
104
QMetaObject::invokeMethod(this, "onShowDeps", Qt::QueuedConnection);
106
// make sure to show a horizontal scrollbar if needed
107
ui->depList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
108
ui->depList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
109
ui->depList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
110
ui->inList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
111
ui->inList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
112
ui->inList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
113
ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
115
ui->depList->headerItem()->setText(0, tr("Depending on"));
116
ui->depList->headerItem()->setText(1, tr("Document"));
117
ui->depList->headerItem()->setText(2, tr("Name"));
119
ui->inList->headerItem()->setText(0, tr("Depended by"));
120
ui->inList->headerItem()->setText(1, tr("Document"));
121
ui->inList->headerItem()->setText(2, tr("Name"));
123
ui->treeWidget->headerItem()->setText(0, tr("Selections"));
124
ui->treeWidget->header()->setStretchLastSection(false);
126
connect(ui->treeWidget, &QTreeWidget::itemExpanded,
127
this, &DlgObjectSelection::onItemExpanded);
129
allItem = new QTreeWidgetItem(ui->treeWidget);
130
allItem->setText(0, QStringLiteral("<%1>").arg(tr("All")));
131
QFont font = allItem->font(0);
133
allItem->setFont(0, font);
134
allItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled);
135
allItem->setCheckState(0, Qt::Checked);
137
for(auto obj : initSels)
138
getItem(obj)->setCheckState(0, Qt::Checked);
141
getItem(obj)->setCheckState(0, Qt::Checked);
143
auto filter = excludes;
144
std::sort(filter.begin(), filter.end());
145
for (auto obj : deps) {
146
auto it = std::lower_bound(filter.begin(), filter.end(), obj);
147
if (it != filter.end() && *it == obj)
148
setItemState(obj, Qt::Unchecked);
150
onItemSelectionChanged();
153
* create useOriginalsBtn and add to the button box
154
* tried adding to .ui file, but could never get the
155
* formatting exactly the way I wanted it. -- <TheMarkster>
157
useOriginalsBtn = new QPushButton(tr("&Use Original Selections"));
158
useOriginalsBtn->setToolTip(tr("Ignore dependencies and proceed with objects\noriginally selected prior to opening this dialog"));
159
ui->buttonBox->addButton(useOriginalsBtn, QDialogButtonBox::ResetRole);
161
connect(ui->treeWidget, &QTreeWidget::itemChanged, this, &DlgObjectSelection::onObjItemChanged);
162
connect(ui->depList, &QTreeWidget::itemChanged, this, &DlgObjectSelection::onDepItemChanged);
163
connect(ui->inList, &QTreeWidget::itemChanged, this, &DlgObjectSelection::onDepItemChanged);
164
connect(ui->treeWidget, &QTreeWidget::itemSelectionChanged,
165
this, &DlgObjectSelection::onItemSelectionChanged);
166
connect(useOriginalsBtn, &QPushButton::clicked,
167
this, &DlgObjectSelection::onUseOriginalsBtnClicked);
169
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &DlgObjectSelection::accept);
170
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &DlgObjectSelection::reject);
172
timer.setSingleShot(true);
173
connect(&timer, &QTimer::timeout, this, &DlgObjectSelection::checkItemChanged);
177
* Destroys the object and frees any allocated resources
179
DlgObjectSelection::~DlgObjectSelection()
181
// no need to delete child widgets, Qt does it all for us
185
QTreeWidgetItem *DlgObjectSelection::getItem(App::DocumentObject *obj,
186
std::vector<QTreeWidgetItem*> **pitems,
187
QTreeWidgetItem *parent)
189
auto &items = itemMap[App::SubObjectT(obj, "")];
192
QTreeWidgetItem *item;
196
item = new QTreeWidgetItem(ui->treeWidget);
197
auto vp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
198
Gui::Application::Instance->getViewProvider(obj));
199
if (vp) item->setIcon(0, vp->getIcon());
200
App::SubObjectT objT(obj, "");
201
item->setText(0, QString::fromUtf8((obj)->Label.getValue()));
202
if (std::binary_search(initSels.begin(), initSels.end(), obj)) {
203
QFont font = item->font(0);
205
font.setItalic(true);
206
item->setFont(0, font);
208
item->setToolTip(0, QString::fromUtf8(objT.getObjectFullName().c_str()));
209
item->setData(0, Qt::UserRole, QVariant::fromValue(objT));
210
item->setChildIndicatorPolicy(obj->getOutList().empty() ?
211
QTreeWidgetItem::DontShowIndicator : QTreeWidgetItem::ShowIndicator);
212
} else if (!items.empty()) {
213
item = new QTreeWidgetItem(parent);
214
item->setIcon(0, items[0]->icon(0));
215
item->setText(0, items[0]->text(0));
216
item->setFont(0, items[0]->font(0));
217
item->setToolTip(0, items[0]->toolTip(0));
218
item->setData(0, Qt::UserRole, items[0]->data(0, Qt::UserRole));
219
item->setChildIndicatorPolicy(items[0]->childIndicatorPolicy());
220
item->setCheckState(0, items[0]->checkState(0));
223
items.push_back(item);
224
item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled);
228
void DlgObjectSelection::onItemExpanded(QTreeWidgetItem *item)
230
if (item->childCount())
232
if (auto obj = qvariant_cast<App::SubObjectT>(item->data(0, Qt::UserRole)).getObject()) {
233
QSignalBlocker blocker(ui->treeWidget);
234
std::set<App::DocumentObject*> set;
235
for (auto child : obj->getOutList()) {
236
if (child && set.insert(child).second)
237
getItem(child, nullptr, item);
242
void DlgObjectSelection::updateAllItemState()
245
for (const auto &v : itemMap) {
246
auto state = v.second[0]->checkState(0);
247
if (state == Qt::Unchecked) {
249
allItem->setCheckState(0, Qt::PartiallyChecked);
253
if (state == Qt::PartiallyChecked) {
254
allItem->setCheckState(0, Qt::PartiallyChecked);
260
if (count && count == (int)itemMap.size())
261
allItem->setCheckState(0, Qt::Checked);
263
allItem->setCheckState(0, Qt::Unchecked);
266
void DlgObjectSelection::setItemState(App::DocumentObject *obj,
267
Qt::CheckState state,
270
std::vector<QTreeWidgetItem*> *items = nullptr;
271
auto item = getItem(obj, &items);
272
if (!setCheckState(item, state, forced))
275
for (size_t i=1; i<items->size(); ++i)
276
setCheckState(items->at(i), state, true);
278
std::vector<App::DocumentObject*> objs = {obj};
280
if (ui->checkBoxAutoDeps->isChecked() && state == Qt::Checked) {
281
// If an object is newly checked, check all its dependencies
282
for (auto o : obj->getOutListRecursive()) {
283
if (!depSet.count(o) || itemChanged.count(o))
285
auto itItem = itemMap.find(o);
286
if (itItem == itemMap.end() || itItem->second[0]->checkState(0) == state)
289
for (auto i : itItem->second)
290
setCheckState(i, state, true);
295
for(auto obj : objs) {
296
auto it = inMap.find(obj);
297
if (it != inMap.end())
298
setCheckState(it->second, state);
300
auto itDep = depMap.find(obj);
301
if (itDep != depMap.end())
302
setCheckState(itDep->second, state);
304
// If an object toggles state, we need to revisit all its in-list
305
// object to update the partial/full checked state.
306
for (auto o : obj->getInList()) {
307
if (!depSet.count(o) ||itemChanged.count(o))
309
auto it = itemMap.find(o);
310
if (it == itemMap.end() || it->second[0]->checkState(0) == state)
314
for (auto sibling : o->getOutList()) {
315
if (!depSet.count(sibling))
318
auto it = itemMap.find(sibling);
319
if (it == itemMap.end())
321
auto s = it->second[0]->checkState(0);
322
if (s == Qt::Unchecked)
324
if (it->second[0]->checkState(0) == Qt::PartiallyChecked) {
330
auto state = it->second[0]->checkState(0);
331
if (state == Qt::Checked && selcount != count)
332
setItemState(o, Qt::PartiallyChecked, true);
333
else if (state == Qt::PartiallyChecked && selcount == count)
334
setItemState(o, Qt::Checked, true);
339
std::vector<App::DocumentObject*> DlgObjectSelection::getSelections(SelectionOptions options) const {
344
std::vector<App::DocumentObject*> res;
345
Base::Flags<SelectionOptions> flags(options);
346
if (!flags.testFlag(SelectionOptions::Invert)) {
347
for (const auto &v : itemMap) {
348
if (v.second[0]->checkState(0) == Qt::Unchecked)
350
if (auto obj = v.first.getObject())
354
for (auto obj : deps) {
355
auto it = itemMap.find(obj);
356
if (it == itemMap.end() || it->second[0]->checkState(0) == Qt::Unchecked)
360
if (flags.testFlag(SelectionOptions::Sort))
361
std::sort(res.begin(), res.end());
365
void DlgObjectSelection::onDepItemChanged(QTreeWidgetItem * depItem, int column) {
368
QSignalBlocker blocker(ui->depList);
369
QSignalBlocker blocker2(ui->inList);
370
QSignalBlocker blocker3(ui->treeWidget);
371
auto state = depItem->checkState(0);
372
if (depItem->isSelected()) {
373
const auto items = depItem->treeWidget()->selectedItems();
374
for (auto item : items) {
375
auto objT = qvariant_cast<App::SubObjectT>(item->data(0, Qt::UserRole));
376
auto it = itemMap.find(objT);
377
if (it == itemMap.end())
379
setCheckState(item, state);
380
for (auto i : it->second)
381
setCheckState(i, state);
382
itemChanged[objT] = state;
385
auto objT = qvariant_cast<App::SubObjectT>(depItem->data(0, Qt::UserRole));
386
auto it = itemMap.find(objT);
387
if (it != itemMap.end()) {
388
itemChanged[objT] = state;
389
for (auto i : it->second)
390
setCheckState(i, state);
396
void DlgObjectSelection::onObjItemChanged(QTreeWidgetItem * objItem, int column) {
400
QSignalBlocker blocker3(ui->treeWidget);
401
auto state = objItem->checkState(0);
402
if (objItem == allItem) {
403
if (state == Qt::PartiallyChecked)
405
ui->treeWidget->selectionModel()->clearSelection();
408
onItemSelectionChanged();
409
if (state == Qt::Unchecked) {
410
for (const auto &v : itemMap) {
411
for (auto i : v.second)
412
setCheckState(i, Qt::Unchecked);
413
auto it = depMap.find(v.first);
414
if (it != depMap.end())
415
setCheckState(it->second, Qt::Unchecked);
416
it = inMap.find(v.first);
417
if (it != inMap.end())
418
setCheckState(it->second, Qt::Unchecked);
421
for (auto obj : initSels)
422
setCheckState(getItem(obj), Qt::Checked);
423
for (auto obj : deps) {
424
setCheckState(getItem(obj), Qt::Checked);
425
auto it = depMap.find(obj);
426
if (it != depMap.end())
427
setCheckState(it->second, Qt::Checked);
428
it = inMap.find(obj);
429
if (it != inMap.end())
430
setCheckState(it->second, Qt::Checked);
436
if (!objItem->isSelected()) {
437
ui->treeWidget->selectionModel()->clearSelection();
438
objItem->setSelected(true);
439
// We treat selected item in tree widget specially in case of checking
440
// items in depList or inList. To simplify logic, we change selection
441
// here if an unselected item has been checked.
442
itemChanged[qvariant_cast<App::SubObjectT>(objItem->data(0, Qt::UserRole))] = state;
443
onItemSelectionChanged();
446
const auto items = ui->treeWidget->selectedItems();
447
for (auto item : items) {
448
setCheckState(item, state);
449
itemChanged[qvariant_cast<App::SubObjectT>(item->data(0, Qt::UserRole))] = state;
456
static bool getOutList(App::DocumentObject *obj,
457
std::set<App::DocumentObject*> &visited,
458
std::vector<App::DocumentObject*> &result)
460
if (!visited.insert(obj).second)
463
for (auto o : obj->getOutList()) {
464
if (getOutList(o, visited, result))
470
void DlgObjectSelection::checkItemChanged() {
472
QSignalBlocker blocker(ui->depList);
473
QSignalBlocker blocker2(ui->inList);
474
QSignalBlocker blocker3(ui->treeWidget);
476
std::set<App::DocumentObject*> unchecked;
478
for (const auto &v : itemChanged) {
479
const auto &objT = v.first;
480
Qt::CheckState state = v.second;
481
if (auto obj = objT.getObject()) {
482
if (state == Qt::Unchecked) {
483
// We'll deal with unchecked item later
484
if (ui->checkBoxAutoDeps->isChecked())
485
unchecked.insert(obj);
487
// For checked item, setItemState will auto select its
489
setItemState(obj, state, true);
494
if (!unchecked.empty()) {
495
// When some item is unchecked by the user, we need to re-check the
496
// recursive outlist of the initially selected object, excluding all
497
// currently unchecked object. And then uncheck any item that does not
498
// appear in the returned outlist.
500
for (const auto &v : itemMap) {
501
auto item = v.second[0];
502
if (item->checkState(0) == Qt::Unchecked) {
503
if (auto obj = qvariant_cast<App::SubObjectT>(item->data(0, Qt::UserRole)).getObject())
504
unchecked.insert(obj);
508
auto outlist = initSels;
509
for (auto obj : initSels)
510
getOutList(obj, unchecked, outlist);
511
std::sort(outlist.begin(), outlist.end());
513
for (const auto &v : itemMap) {
514
if (itemChanged.count(v.first) == 0 && v.second[0]->checkState(0) == Qt::Unchecked)
516
if (auto obj = v.first.getObject()) {
517
if (!std::binary_search(outlist.begin(), outlist.end(), obj))
518
setItemState(obj, Qt::Unchecked, true);
524
updateAllItemState();
527
QTreeWidgetItem *DlgObjectSelection::createDepItem(QTreeWidget *parent, App::DocumentObject *obj)
529
auto item = new QTreeWidgetItem(parent);
530
if (parent == ui->depList)
534
App::SubObjectT objT(obj);
535
auto vp = Gui::Application::Instance->getViewProvider(obj);
536
if(vp) item->setIcon(0, vp->getIcon());
537
item->setData(0, Qt::UserRole, QVariant::fromValue(objT));
538
item->setToolTip(0, QString::fromUtf8(objT.getObjectFullName().c_str()));
539
item->setText(0, QString::fromUtf8((obj)->Label.getValue()));
540
if (std::binary_search(initSels.begin(), initSels.end(), obj)) {
541
QFont font = item->font(0);
543
font.setItalic(true);
544
item->setFont(0, font);
546
item->setText(1, QString::fromUtf8(obj->getDocument()->getName()));
547
item->setText(2, QString::fromUtf8(obj->getNameInDocument()));
548
item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled);
549
auto it = itemMap.find(obj);
550
if (it != itemMap.end())
551
setCheckState(item, it->second[0]->checkState(0));
555
void DlgObjectSelection::onItemSelectionChanged() {
556
ui->depList->clear();
561
std::vector<App::DocumentObject *> sels;
562
const auto items = ui->treeWidget->selectedItems();
563
for (auto item : items) {
564
if (item == allItem) {
568
auto obj = qvariant_cast<App::SubObjectT>(item->data(0, Qt::UserRole)).getObject();
573
std::vector<App::DocumentObject*> _deps;
575
std::sort(sels.begin(), sels.end());
576
for (auto dep : App::Document::getDependencyList(sels, App::Document::DepSort)) {
577
if (!std::binary_search(sels.begin(), sels.end(), dep))
578
_deps.push_back(dep);
582
bool enabled = ui->depList->isSortingEnabled();
584
ui->depList->setSortingEnabled(false);
586
bool enabled2 = ui->inList->isSortingEnabled();
588
ui->inList->setSortingEnabled(false);
591
QSignalBlocker blocker(ui->depList);
592
auto &objs = !sels.empty() ? _deps : deps;
593
for (auto it = objs.rbegin(); it != objs.rend(); ++it)
594
createDepItem(ui->depList, *it);
597
std::set<App::DocumentObject*> inlist;
598
for (auto obj : sels)
599
obj->getInListEx(inlist, true);
600
for (auto it = inlist.begin(); it != inlist.end();) {
601
if (!depSet.count(*it) || std::binary_search(sels.begin(), sels.end(), *it))
602
it = inlist.erase(it);
607
QSignalBlocker blocker2(ui->inList);
608
for (auto obj : inlist)
609
createDepItem(ui->inList, obj);
613
ui->depList->setSortingEnabled(true);
615
ui->inList->setSortingEnabled(true);
618
void DlgObjectSelection::onUseOriginalsBtnClicked() {
619
returnOriginals = true;
623
void DlgObjectSelection::accept() {
627
void DlgObjectSelection::reject() {
631
void DlgObjectSelection::addCheckBox(QCheckBox *box) {
632
ui->horizontalLayout->insertWidget(0, box);
635
void DlgObjectSelection::setMessage(const QString &msg) {
636
ui->label->setText(msg);
639
void DlgObjectSelection::onAutoDeps(bool checked)
641
hGrp->SetBool("ObjectSelectionAutoDeps", checked);
645
QSignalBlocker blocker(ui->treeWidget);
646
for (auto obj : deps) {
647
auto it = itemMap.find(obj);
648
if (it == itemMap.end())
650
auto item = it->second[0];
651
if (item->checkState(0) == Qt::Unchecked)
653
Qt::CheckState state = Qt::Checked;
654
for (auto o : obj->getOutList()) {
655
auto it = itemMap.find(o);
656
if (it == itemMap.end())
658
if (it->second[0]->checkState(0) != Qt::Checked) {
659
state = Qt::PartiallyChecked;
663
for (auto i : it->second)
664
setCheckState(i, state);
666
onItemSelectionChanged();
669
void DlgObjectSelection::onShowDeps()
671
bool checked = ui->checkBoxShowDeps->isChecked();
672
auto sizes = ui->vsplitter->sizes();
673
if (!checked && sizes[1] > 0)
675
else if (checked && (sizes[0] == 0 || sizes[1] == 0))
676
sizes[0] = sizes[1] = this->width()/2;
679
ui->vsplitter->setSizes(sizes);
682
#include "moc_DlgObjectSelection.cpp"