Skip to content

Commit

Permalink
Gui: Add support for selecting multiple objects without keyboard
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hyarion authored and wwmayer committed Feb 3, 2021
1 parent d73ccbd commit 05c61e3
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/Gui/DlgPropertyLink.cpp
Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions src/Gui/DlgSettingsSelection.cpp
Expand Up @@ -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()
Expand All @@ -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)
Expand Down
25 changes: 16 additions & 9 deletions src/Gui/DlgSettingsSelection.ui
Expand Up @@ -14,13 +14,6 @@
<string>Selection</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="checkBoxAutoSwitch">
<property name="text">
<string>Auto switch to the 3D view containing the selected item</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="checkBoxAutoExpand">
<property name="text">
Expand All @@ -42,19 +35,33 @@
</property>
</widget>
</item>
<item row="4" column="0">
<item row="5" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>274</height>
<height>245</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="checkBoxAutoSwitch">
<property name="text">
<string>Auto switch to the 3D view containing the selected item</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="checkBoxSelectionCheckBoxes">
<property name="text">
<string>Add checkboxes for selection in document tree</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
Expand Down
68 changes: 64 additions & 4 deletions src/Gui/Tree.cpp
Expand Up @@ -139,6 +139,11 @@ void TreeParams::onSyncSelectionChanged() {
TreeWidget::scrollItemToTop();
}

void TreeParams::onCheckBoxesSelectionChanged()
{
TreeWidget::instance()->synchronizeSelectionCheckBoxes();
}

void TreeParams::onDocumentModeChanged() {
App::GetApplication().setActiveDocument(App::GetApplication().getActiveDocument());
}
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -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<DocumentObjectItem*>(*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);
Expand Down Expand Up @@ -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);
Expand All @@ -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<DocumentObjectItem*>(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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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)) {
Expand All @@ -4242,6 +4290,7 @@ void DocumentItem::selectItems(SelectionReason reason) {
}
item->selected = 1;
item->setSelected(true);
item->setCheckState(true);
}
END_FOREACH_ITEM;

Expand Down Expand Up @@ -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;
}

Expand All @@ -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());
Expand Down Expand Up @@ -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());
}
Expand Down
6 changes: 6 additions & 0 deletions src/Gui/Tree.h
Expand Up @@ -129,6 +129,8 @@ class TreeWidget : public QTreeWidget, public SelectionObserver
void startItemSearch(QLineEdit*);
void itemSearch(const QString &text, bool select);

void synchronizeSelectionCheckBoxes();

protected:
/// Observer message from the Selection
void onSelectionChanged(const SelectionChanges& msg) override;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -436,6 +439,8 @@ class DocumentObjectItem : public QTreeWidgetItem
TreeWidget *getTree() const;

private:
void setCheckState(bool checked);

QBrush bgBrush;
DocumentItem *myOwner;
DocumentObjectDataPtr myData;
Expand Down Expand Up @@ -521,6 +526,7 @@ class GuiExport TreeParams : public ParameterGrp::ObserverType {

#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) \
Expand Down

0 comments on commit 05c61e3

Please sign in to comment.