Skip to content

Commit 381e79b

Browse files
committed
TreeView: add preselect on mouse over item
This behavior is enabled by default. This option can be turned on/off through tree view context menu 'Tree view options'->'Pre-selection'. The parameter is stored at User parameter:BaseApp/Preferences/TreeView/Preselect This tree view preselection will automatcially add object to 3D view On-Top display group, to make it easy for the user to see it
1 parent 8156ff2 commit 381e79b

File tree

11 files changed

+398
-145
lines changed

11 files changed

+398
-145
lines changed

src/Gui/Selection.cpp

Lines changed: 45 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
# include <QStatusBar>
3434
#endif
3535

36+
#include <boost/algorithm/string/predicate.hpp>
37+
3638
/// Here the FreeCAD includes sorted by Base,App,Gui......
3739
#include "Application.h"
3840
#include "Document.h"
@@ -630,8 +632,16 @@ void SelectionSingleton::slotSelectionChanged(const SelectionChanges& msg) {
630632

631633
bool SelectionSingleton::setPreselect(const char* pDocName, const char* pObjectName, const char* pSubName, float x, float y, float z, bool signal)
632634
{
633-
if (DocName != "")
635+
if(!pDocName || !pObjectName) {
634636
rmvPreselect();
637+
return false;
638+
}
639+
if(!pSubName) pSubName = "";
640+
641+
if(DocName==pDocName && FeatName==pObjectName && SubName==pSubName)
642+
return true;
643+
644+
rmvPreselect();
635645

636646
if (ActiveGate && !signal) {
637647
App::Document* pDoc = getDocument(pDocName);
@@ -688,6 +698,12 @@ bool SelectionSingleton::setPreselect(const char* pDocName, const char* pObjectN
688698
Notify(Chng);
689699
signalSelectionChanged(Chng);
690700

701+
if(signal) {
702+
Chng.Type = SelectionChanges::SetPreselect;
703+
Notify(Chng);
704+
signalSelectionChanged(Chng);
705+
}
706+
691707
// allows the preselection
692708
return true;
693709
}
@@ -717,8 +733,7 @@ void SelectionSingleton::rmvPreselect()
717733
if (DocName == "")
718734
return;
719735

720-
SelectionChanges Chng(SelectionChanges::RmvPreselect,
721-
DocName,FeatName,SubName);
736+
SelectionChanges Chng(SelectionChanges::RmvPreselect,DocName,FeatName,SubName);
722737

723738
// reset the current preselection
724739
CurrentPreselection = SelectionChanges();
@@ -943,6 +958,13 @@ bool SelectionSingleton::updateSelection(bool show, const char* pDocName,
943958
{
944959
if(!pDocName || !pObjectName)
945960
return false;
961+
if(!pSubName)
962+
pSubName = "";
963+
if(show && DocName==pDocName && FeatName==pObjectName && SubName==pSubName) {
964+
SelectionChanges Chng(SelectionChanges::SetPreselectSignal,DocName,FeatName,SubName);
965+
Notify(Chng);
966+
signalSelectionChanged(Chng);
967+
}
946968
if (!isSelected(pDocName, pObjectName, pSubName))
947969
return false;
948970
auto pDoc = getDocument(pDocName);
@@ -1216,7 +1238,7 @@ void SelectionSingleton::clearCompleteSelection()
12161238
}
12171239

12181240
bool SelectionSingleton::isSelected(const char* pDocName,
1219-
const char* pObjectName, const char* pSubName, bool resolve) const
1241+
const char* pObjectName, const char* pSubName, int resolve) const
12201242
{
12211243
if(!pObjectName) return false;
12221244
auto pDoc = getDocument(pDocName);
@@ -1226,7 +1248,7 @@ bool SelectionSingleton::isSelected(const char* pDocName,
12261248
return isSelected(pDoc->getObject(pObjectName),pSubName,resolve);
12271249
}
12281250

1229-
bool SelectionSingleton::isSelected(App::DocumentObject* pObject, const char* pSubName, bool resolve) const
1251+
bool SelectionSingleton::isSelected(App::DocumentObject* pObject, const char* pSubName, int resolve) const
12301252
{
12311253
if(!pObject || !pObject->getNameInDocument() || !pObject->getDocument())
12321254
return false;
@@ -1239,20 +1261,24 @@ bool SelectionSingleton::isSelected(App::DocumentObject* pObject, const char* pS
12391261
if(!pResolvedObject)
12401262
return false;
12411263
std::string subname;
1242-
if(pSubName) {
1243-
if(element && elementName.first.size()) {
1264+
std::string prefix;
1265+
if(pSubName && element) {
1266+
prefix = std::string(pSubName, element-pSubName);
1267+
if(elementName.first.size()) {
12441268
// make sure the selected sub name is a new style if available
1245-
subname = std::string(pSubName,element-pSubName) + elementName.first;
1269+
subname = prefix + elementName.first;
12461270
pSubName = subname.c_str();
12471271
}
12481272
}
12491273
for (auto &sel : _SelList) {
12501274
if (sel.DocName==pDocName && sel.FeatName==pObjectName) {
12511275
if(!pSubName || sel.SubName==pSubName)
12521276
return true;
1277+
if(resolve>1 && boost::starts_with(sel.SubName,prefix))
1278+
return true;
12531279
}
12541280
}
1255-
if(resolve) {
1281+
if(resolve==1) {
12561282
for(auto &sel : _SelList) {
12571283
if(sel.pResolvedObject != pResolvedObject)
12581284
continue;
@@ -1497,49 +1523,18 @@ PyObject *SelectionSingleton::sUpdateSelection(PyObject * /*self*/, PyObject *ar
14971523
PyObject *show;
14981524
PyObject *object;
14991525
char* subname=0;
1500-
if (PyArg_ParseTuple(args, "OO!|s", &show,&(App::DocumentObjectPy::Type),&object,&subname)) {
1501-
App::DocumentObjectPy* docObjPy = static_cast<App::DocumentObjectPy*>(object);
1502-
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
1503-
if (!docObj || !docObj->getNameInDocument()) {
1504-
PyErr_SetString(Base::BaseExceptionFreeCADError, "Cannot check invalid object");
1505-
return NULL;
1506-
}
1507-
1508-
Selection().updateSelection(PyObject_IsTrue(show),
1509-
docObj->getDocument()->getName(), docObj->getNameInDocument(), subname);
1510-
Py_Return;
1511-
}
1512-
1513-
PyErr_Clear();
1514-
PyObject *sequence;
1515-
if (PyArg_ParseTuple(args, "O!O", &(App::DocumentObjectPy::Type),&object,&sequence)) {
1516-
App::DocumentObjectPy* docObjPy = static_cast<App::DocumentObjectPy*>(object);
1517-
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
1518-
if (!docObj || !docObj->getNameInDocument()) {
1519-
PyErr_SetString(Base::BaseExceptionFreeCADError, "Cannot check invalid object");
1520-
return NULL;
1521-
}
1522-
1523-
try {
1524-
if (PyTuple_Check(sequence) || PyList_Check(sequence)) {
1525-
Py::Sequence list(sequence);
1526-
for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
1527-
std::string subname = static_cast<std::string>(Py::String(*it));
1528-
Selection().updateSelection(docObj->getDocument()->getName(),
1529-
docObj->getNameInDocument(),
1530-
subname.c_str());
1531-
}
1532-
1533-
Py_Return;
1534-
}
1535-
}
1536-
catch (const Py::Exception&) {
1537-
// do nothing here
1538-
}
1526+
if(!PyArg_ParseTuple(args, "OO!|s", &show,&(App::DocumentObjectPy::Type),&object,&subname))
1527+
return 0;
1528+
App::DocumentObjectPy* docObjPy = static_cast<App::DocumentObjectPy*>(object);
1529+
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
1530+
if (!docObj || !docObj->getNameInDocument()) {
1531+
PyErr_SetString(Base::BaseExceptionFreeCADError, "Cannot check invalid object");
1532+
return NULL;
15391533
}
15401534

1541-
PyErr_SetString(PyExc_ValueError, "type must be 'DocumentObject[,subname[,x,y,z]]' or 'DocumentObject, list or tuple of subnames'");
1542-
return 0;
1535+
Selection().updateSelection(PyObject_IsTrue(show),
1536+
docObj->getDocument()->getName(), docObj->getNameInDocument(), subname);
1537+
Py_Return;
15431538
}
15441539

15451540

src/Gui/Selection.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,9 @@ class GuiExport SelectionSingleton : public Base::Subject<const SelectionChanges
307307
void clearCompleteSelection();
308308
/// Check if selected
309309
bool isSelected(const char* pDocName, const char* pObjectName=0,
310-
const char* pSubName=0, bool resolve=true) const;
310+
const char* pSubName=0, int resolve=1) const;
311311
/// Check if selected
312-
bool isSelected(App::DocumentObject*, const char* pSubName=0, bool resolve=true) const;
312+
bool isSelected(App::DocumentObject*, const char* pSubName=0, int resolve=1) const;
313313

314314
const char *getSelectedElement(App::DocumentObject*, const char* pSubName) const;
315315

src/Gui/SoFCUnifiedSelection.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ bool SoFCUnifiedSelection::setHighlight(SoFullPath *path, const SoDetail *det,
478478
if(!highlighted) {
479479
currenthighlight->unref();
480480
currenthighlight = 0;
481+
Selection().rmvPreselect();
481482
}
482483
this->touch();
483484
}

src/Gui/Tree.cpp

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,16 @@ FC_LOG_LEVEL_INIT("Tree",false,true,true);
7171

7272
using namespace Gui;
7373

74+
#define TREEVIEW_PARAM "User parameter:BaseApp/Preferences/TreeView"
75+
#define GET_TREEVIEW_PARAM(_name) \
76+
ParameterGrp::handle _name = App::GetApplication().GetParameterGroupByPath(TREEVIEW_PARAM)
77+
78+
/////////////////////////////////////////////////////////////////////////////////
79+
7480
QPixmap* TreeWidget::documentPixmap = 0;
7581
const int TreeWidget::DocumentType = 1000;
7682
const int TreeWidget::ObjectType = 1001;
7783

78-
7984
/* TRANSLATOR Gui::TreeWidget */
8085
TreeWidget::TreeWidget(const char *name, QWidget* parent)
8186
: QTreeWidget(parent), SelectionObserver(false,false), contextItem(0)
@@ -87,17 +92,19 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent)
8792
this->setDropIndicatorShown(false);
8893
this->setRootIsDecorated(false);
8994

90-
#define GET_TREEVIEW_PARAM(_name) \
91-
ParameterGrp::handle _name = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView")
92-
9395
GET_TREEVIEW_PARAM(hGrp);
94-
bool sync = hGrp->GetBool("SyncSelection",true);
9596
this->syncSelectionAction = new QAction(this);
9697
this->syncSelectionAction->setCheckable(true);
97-
this->syncSelectionAction->setChecked(sync);
98+
this->syncSelectionAction->setChecked(hGrp->GetBool("SyncSelection",true));
9899
connect(this->syncSelectionAction, SIGNAL(triggered()),
99100
this, SLOT(onSyncSelection()));
100101

102+
this->preSelectionAction = new QAction(this);
103+
this->preSelectionAction->setCheckable(true);
104+
this->preSelectionAction->setChecked(hGrp->GetBool("PreSelection",true));
105+
connect(this->preSelectionAction, SIGNAL(triggered()),
106+
this, SLOT(onPreSelection()));
107+
101108
this->syncViewAction = new QAction(this);
102109
this->syncViewAction->setCheckable(true);
103110
this->syncViewAction->setChecked(hGrp->GetBool("SyncView",false));
@@ -168,6 +175,9 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent)
168175
this->setMouseTracking(true); // needed for itemEntered() to work
169176
#endif
170177

178+
this->preselectTimer = new QTimer(this);
179+
this->preselectTimer->setSingleShot(true);
180+
171181
this->statusTimer = new QTimer(this);
172182
this->statusTimer->setSingleShot(false);
173183

@@ -181,6 +191,9 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent)
181191
this, SLOT(onItemExpanded(QTreeWidgetItem*)));
182192
connect(this, SIGNAL(itemSelectionChanged()),
183193
this, SLOT(onItemSelectionChanged()));
194+
connect(this->preselectTimer, SIGNAL(timeout()),
195+
this, SLOT(onPreSelectTimer()));
196+
preselectTime.start();
184197

185198
setupText();
186199
_updateStatus();
@@ -232,8 +245,12 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e)
232245
contextMenu.addSeparator();
233246
topact = actions.front();
234247
}
235-
contextMenu.insertAction(topact,this->syncSelectionAction);
236-
contextMenu.insertAction(topact,this->syncViewAction);
248+
QMenu optionsMenu;
249+
optionsMenu.setTitle(tr("Tree view options"));
250+
optionsMenu.addAction(this->preSelectionAction);
251+
optionsMenu.addAction(this->syncSelectionAction);
252+
optionsMenu.addAction(this->syncViewAction);
253+
contextMenu.insertMenu(topact,&optionsMenu);
237254
contextMenu.insertSeparator(topact);
238255

239256
// get the current item
@@ -1101,11 +1118,51 @@ void TreeWidget::onItemEntered(QTreeWidgetItem * item)
11011118
{
11021119
// object item selected
11031120
if (item && item->type() == TreeWidget::ObjectType) {
1104-
DocumentObjectItem* obj = static_cast<DocumentObjectItem*>(item);
1105-
obj->displayStatusInfo();
1121+
DocumentObjectItem* objItem = static_cast<DocumentObjectItem*>(item);
1122+
objItem->displayStatusInfo();
1123+
1124+
if(preSelectionAction->isChecked()) {
1125+
if(preselectTime.elapsed() < 700)
1126+
onPreSelectTimer();
1127+
else{
1128+
preselectTimer->start(500);
1129+
Selection().rmvPreselect();
1130+
}
1131+
}
1132+
} else if(preSelectionAction->isChecked())
1133+
Selection().rmvPreselect();
1134+
}
1135+
1136+
void TreeWidget::leaveEvent(QEvent *) {
1137+
if(preSelectionAction->isChecked()) {
1138+
preselectTimer->stop();
1139+
Selection().rmvPreselect();
11061140
}
11071141
}
11081142

1143+
void TreeWidget::onPreSelectTimer() {
1144+
if(!preSelectionAction->isChecked())
1145+
return;
1146+
auto item = itemAt(viewport()->mapFromGlobal(QCursor::pos()));
1147+
if(!item || item->type()!=TreeWidget::ObjectType)
1148+
return;
1149+
1150+
FC_LOG("preselect timer");
1151+
preselectTime.restart();
1152+
DocumentObjectItem* objItem = static_cast<DocumentObjectItem*>(item);
1153+
auto vp = objItem->object();
1154+
auto obj = vp->getObject();
1155+
std::ostringstream ss;
1156+
App::DocumentObject *parent = 0;
1157+
objItem->getSubName(ss,parent);
1158+
if(parent)
1159+
ss << obj->getNameInDocument() << '.';
1160+
else
1161+
parent = obj;
1162+
Selection().setPreselect(parent->getDocument()->getName(),parent->getNameInDocument(),
1163+
ss.str().c_str(),0,0,0,true);
1164+
}
1165+
11091166
void TreeWidget::onItemCollapsed(QTreeWidgetItem * item)
11101167
{
11111168
// object item collapsed
@@ -1142,6 +1199,9 @@ void TreeWidget::setupText() {
11421199
this->headerItem()->setText(0, tr("Labels & Attributes"));
11431200
this->rootItem->setText(0, tr("Application"));
11441201

1202+
this->preSelectionAction->setText(tr("Pre-selection"));
1203+
this->preSelectionAction->setStatusTip(tr("Preselect the object in 3D view when mouse over the tree item"));
1204+
11451205
this->syncSelectionAction->setText(tr("Sync selection"));
11461206
this->syncSelectionAction->setStatusTip(tr("Auto expand item when selected in 3D view"));
11471207

@@ -1175,6 +1235,12 @@ void TreeWidget::onSyncSelection() {
11751235
hGrp->SetBool("SyncSelection",syncSelectionAction->isChecked());
11761236
}
11771237

1238+
void TreeWidget::onPreSelection() {
1239+
GET_TREEVIEW_PARAM(hGrp);
1240+
hGrp->SetBool("PreSelection",preSelectionAction->isChecked());
1241+
}
1242+
1243+
11781244
void TreeWidget::onSyncView() {
11791245
GET_TREEVIEW_PARAM(hGrp);
11801246
hGrp->SetBool("SyncView",syncViewAction->isChecked());
@@ -1287,7 +1353,7 @@ TreeDockWidget::TreeDockWidget(Gui::Document* pcDocument,QWidget *parent)
12871353
setWindowTitle(tr("Tree view"));
12881354
this->treeWidget = new TreeWidget("TreeView",this);
12891355
this->treeWidget->setRootIsDecorated(false);
1290-
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
1356+
GET_TREEVIEW_PARAM(hGrp);
12911357
this->treeWidget->setIndentation(hGrp->GetInt("Indentation", this->treeWidget->indentation()));
12921358

12931359
QGridLayout* pLayout = new QGridLayout(this);

src/Gui/Tree.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#define GUI_TREE_H
2626

2727
#include <QTreeWidget>
28+
#include <QTime>
2829

2930
#include <App/Document.h>
3031
#include <App/Application.h>
@@ -119,6 +120,7 @@ class TreeWidget : public QTreeWidget, public SelectionObserver
119120
protected:
120121
void showEvent(QShowEvent *) override;
121122
void hideEvent(QHideEvent *) override;
123+
void leaveEvent(QEvent *) override;
122124
void _updateStatus(bool delay=false);
123125

124126
protected Q_SLOTS:
@@ -130,6 +132,8 @@ protected Q_SLOTS:
130132
void onSkipRecompute(bool on);
131133
void onMarkRecompute();
132134
void onSyncSelection();
135+
void onPreSelection();
136+
void onPreSelectTimer();
133137
void onSyncView();
134138
void onShowHidden();
135139
void onHideInTree();
@@ -159,6 +163,7 @@ private Q_SLOTS:
159163
QAction* finishEditingAction;
160164
QAction* skipRecomputeAction;
161165
QAction* markRecomputeAction;
166+
QAction* preSelectionAction;
162167
QAction* syncSelectionAction;
163168
QAction* syncViewAction;
164169
QAction* showHiddenAction;
@@ -168,6 +173,8 @@ private Q_SLOTS:
168173
DocumentItem *currentDocItem;
169174
QTreeWidgetItem* rootItem;
170175
QTimer* statusTimer;
176+
QTimer* preselectTimer;
177+
QTime preselectTime;
171178
static QPixmap* documentPixmap;
172179
std::map<const Gui::Document*,DocumentItem*> DocumentMap;
173180
bool fromOutside;

0 commit comments

Comments
 (0)