From 67f21d663de97ca5139d0f4f6071ba241f456a48 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Sat, 2 Jan 2021 20:31:41 +0100 Subject: [PATCH] Gui: Add support for selecting multiple objects without keyboard Adds checkboxes to the document tree which makes it possible to select multiple document objects on devices without physical keyboard. These checkboxes are opt-in and can be enabled/disabled from: Edit -> Preferences -> Display -> Navigation -> Selection Forum thread for feature the request: https://forum.freecadweb.org/viewtopic.php?f=34&t=53065 We need to iterate the tree to add/remove the selection boxes when the enable-setting has been changed. The size for the first column in the tree is adjusted to fit both name and optional checkbox. --- src/Gui/DlgPropertyLink.cpp | 2 +- src/Gui/DlgSettingsSelection.cpp | 2 + src/Gui/DlgSettingsSelection.ui | 25 +++++++----- src/Gui/Tree.cpp | 68 ++++++++++++++++++++++++++++++-- src/Gui/Tree.h | 6 +++ 5 files changed, 89 insertions(+), 14 deletions(-) diff --git a/src/Gui/DlgPropertyLink.cpp b/src/Gui/DlgPropertyLink.cpp index 6fb8f2c1f0..ff99b918ed 100644 --- a/src/Gui/DlgPropertyLink.cpp +++ b/src/Gui/DlgPropertyLink.cpp @@ -897,7 +897,7 @@ QTreeWidgetItem *DlgPropertyLink::createItem( if(allowSubObject) { item->setChildIndicatorPolicy(obj->getLinkedObject(true)->getOutList().size()? QTreeWidgetItem::ShowIndicator:QTreeWidgetItem::DontShowIndicator); - item->setFlags(item->flags() | Qt::ItemIsEditable); + item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); } const char *typeName = obj->getTypeId().getName(); diff --git a/src/Gui/DlgSettingsSelection.cpp b/src/Gui/DlgSettingsSelection.cpp index ec798f80b2..aff8d341d2 100644 --- a/src/Gui/DlgSettingsSelection.cpp +++ b/src/Gui/DlgSettingsSelection.cpp @@ -52,6 +52,7 @@ void DlgSettingsSelection::saveSettings() handle->SetBool("SyncSelection", ui->checkBoxAutoExpand->isChecked()); handle->SetBool("PreSelection", ui->checkBoxPreselect->isChecked()); handle->SetBool("RecordSelection", ui->checkBoxRecord->isChecked()); + handle->SetBool("CheckBoxesSelection", ui->checkBoxSelectionCheckBoxes->isChecked()); } void DlgSettingsSelection::loadSettings() @@ -61,6 +62,7 @@ void DlgSettingsSelection::loadSettings() ui->checkBoxAutoExpand->setChecked(handle->GetBool("SyncSelection")); ui->checkBoxPreselect->setChecked(handle->GetBool("PreSelection")); ui->checkBoxRecord->setChecked(handle->GetBool("RecordSelection")); + ui->checkBoxSelectionCheckBoxes->setChecked(handle->GetBool("CheckBoxesSelection")); } void DlgSettingsSelection::changeEvent(QEvent *e) diff --git a/src/Gui/DlgSettingsSelection.ui b/src/Gui/DlgSettingsSelection.ui index 02799e5312..b4ec70372f 100644 --- a/src/Gui/DlgSettingsSelection.ui +++ b/src/Gui/DlgSettingsSelection.ui @@ -14,13 +14,6 @@ Selection - - - - Auto switch to the 3D view containing the selected item - - - @@ -42,7 +35,7 @@ - + Qt::Vertical @@ -50,11 +43,25 @@ 20 - 274 + 245 + + + + Auto switch to the 3D view containing the selected item + + + + + + + Add checkboxes for selection in document tree + + + diff --git a/src/Gui/Tree.cpp b/src/Gui/Tree.cpp index 222625d279..d2557d3dc1 100644 --- a/src/Gui/Tree.cpp +++ b/src/Gui/Tree.cpp @@ -139,6 +139,11 @@ void TreeParams::onSyncSelectionChanged() { TreeWidget::scrollItemToTop(); } +void TreeParams::onCheckBoxesSelectionChanged() +{ + TreeWidget::instance()->synchronizeSelectionCheckBoxes(); +} + void TreeParams::onDocumentModeChanged() { App::GetApplication().setActiveDocument(App::GetApplication().getActiveDocument()); } @@ -549,6 +554,8 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent) this, SLOT(onItemExpanded(QTreeWidgetItem*))); connect(this, SIGNAL(itemSelectionChanged()), this, SLOT(onItemSelectionChanged())); + connect(this, SIGNAL(itemChanged(QTreeWidgetItem*, int)), + this, SLOT(onItemChanged(QTreeWidgetItem*, int))); connect(this->preselectTimer, SIGNAL(timeout()), this, SLOT(onPreSelectTimer())); connect(this->selectTimer, SIGNAL(timeout()), @@ -2800,6 +2807,33 @@ void TreeWidget::onItemSelectionChanged () this->blockConnection(lock); } +static bool isSelectionCheckBoxesEnabled() { + return TreeParams::Instance()->CheckBoxesSelection(); +} + +void TreeWidget::synchronizeSelectionCheckBoxes() { + const bool useCheckBoxes = isSelectionCheckBoxesEnabled(); + for (QTreeWidgetItemIterator it(this); *it; ++it) { + if (const auto item = dynamic_cast(*it)) { + if (useCheckBoxes) + item->QTreeWidgetItem::setCheckState(0, item->isSelected() ? Qt::Checked : Qt::Unchecked); + else + item->setData(0, Qt::CheckStateRole, QVariant()); + } + } + resizeColumnToContents(0); +} + +void TreeWidget::onItemChanged(QTreeWidgetItem *item, int column) { + if (column == 0 && isSelectionCheckBoxesEnabled()) { + bool selected = item->isSelected(); + bool checked = item->checkState(0) == Qt::Checked; + if (checked != selected) { + item->setSelected(checked); + } + } +} + void TreeWidget::onSelectTimer() { _updateStatus(false); @@ -3923,6 +3957,7 @@ void DocumentItem::clearSelection(DocumentObjectItem *exclude) item->selected = 0; item->mySubs.clear(); item->setSelected(false); + item->setCheckState(false); } END_FOREACH_ITEM; treeWidget()->blockSignals(ok); @@ -3933,8 +3968,10 @@ void DocumentItem::updateSelection(QTreeWidgetItem *ti, bool unselect) { auto child = ti->child(i); if(child && child->type()==TreeWidget::ObjectType) { auto childItem = static_cast(child); - if(unselect) + if (unselect) { childItem->setSelected(false); + childItem->setCheckState(false); + } updateItemSelection(childItem); if(unselect && childItem->isGroup()) { // If the child item being force unselected by its group parent @@ -3952,8 +3989,17 @@ void DocumentItem::updateSelection(QTreeWidgetItem *ti, bool unselect) { void DocumentItem::updateItemSelection(DocumentObjectItem *item) { bool selected = item->isSelected(); - if((selected && item->selected>0) || (!selected && !item->selected)) + bool checked = item->checkState(0) == Qt::Checked; + + if(selected && !checked) + item->setCheckState(true); + + if(!selected && checked) + item->setCheckState(false); + + if((selected && item->selected>0) || (!selected && !item->selected)) { return; + } if(item->selected != -1) item->mySubs.clear(); item->selected = selected; @@ -4026,6 +4072,7 @@ void DocumentItem::updateItemSelection(DocumentObjectItem *item) { if(!Gui::Selection().addSelection(docname,objname,subname.c_str())) { item->selected = 0; item->setSelected(false); + item->setCheckState(false); return; } } @@ -4221,6 +4268,7 @@ void DocumentItem::selectItems(SelectionReason reason) { item->selected = 0; item->mySubs.clear(); item->setSelected(false); + item->setCheckState(false); }else if(item->selected) { if(sync) { if(item->selected==2 && showItem(item,false,reason==SR_FORCE_EXPAND)) { @@ -4242,6 +4290,7 @@ void DocumentItem::selectItems(SelectionReason reason) { } item->selected = 1; item->setSelected(true); + item->setCheckState(true); } END_FOREACH_ITEM; @@ -4338,8 +4387,10 @@ bool DocumentItem::showItem(DocumentObjectItem *item, bool select, bool force) { }else parent->setExpanded(true); - if(select) + if(select) { item->setSelected(true); + item->setCheckState(true); + } return true; } @@ -4366,7 +4417,9 @@ DocumentObjectItem::DocumentObjectItem(DocumentItem *ownerDocItem, DocumentObjec : QTreeWidgetItem(TreeWidget::ObjectType) , myOwner(ownerDocItem), myData(data), previousStatus(-1),selected(0),populated(false) { - setFlags(flags()|Qt::ItemIsEditable); + setFlags(flags() | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); + setCheckState(false); + myData->items.insert(this); ++countItems; TREE_LOG("Create item: " << countItems << ", " << object()->getObject()->getFullName()); @@ -4948,6 +5001,13 @@ App::DocumentObject *DocumentObjectItem::getRelativeParent( return 0; } +void DocumentObjectItem::setCheckState(bool checked) { + if (isSelectionCheckBoxesEnabled()) + QTreeWidgetItem::setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); + else + setData(0, Qt::CheckStateRole, QVariant()); +} + DocumentItem *DocumentObjectItem::getParentDocument() const { return getTree()->getDocumentItem(object()->getDocument()); } diff --git a/src/Gui/Tree.h b/src/Gui/Tree.h index 8f9b2a5a76..aa9e4cc9e2 100644 --- a/src/Gui/Tree.h +++ b/src/Gui/Tree.h @@ -129,6 +129,8 @@ public: void startItemSearch(QLineEdit*); void itemSearch(const QString &text, bool select); + void synchronizeSelectionCheckBoxes(); + protected: /// Observer message from the Selection void onSelectionChanged(const SelectionChanges& msg) override; @@ -176,6 +178,7 @@ protected Q_SLOTS: private Q_SLOTS: void onItemSelectionChanged(void); + void onItemChanged(QTreeWidgetItem*, int); void onItemEntered(QTreeWidgetItem * item); void onItemCollapsed(QTreeWidgetItem * item); void onItemExpanded(QTreeWidgetItem * item); @@ -436,6 +439,8 @@ public: TreeWidget *getTree() const; private: + void setCheckState(bool checked); + QBrush bgBrush; DocumentItem *myOwner; DocumentObjectDataPtr myData; @@ -521,6 +526,7 @@ public: #define FC_TREEPARAM_DEFS \ FC_TREEPARAM_DEF2(SyncSelection,bool,Bool,true) \ + FC_TREEPARAM_DEF2(CheckBoxesSelection,bool,Bool,false) \ FC_TREEPARAM_DEF(SyncView,bool,Bool,true) \ FC_TREEPARAM_DEF(PreSelection,bool,Bool,true) \ FC_TREEPARAM_DEF(SyncPlacement,bool,Bool,false) \