Skip to content

Commit

Permalink
TreeView: add preselect on mouse over item
Browse files Browse the repository at this point in the history
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
  • Loading branch information
realthunder committed Mar 23, 2018
1 parent 8156ff2 commit 381e79b
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 145 deletions.
95 changes: 45 additions & 50 deletions src/Gui/Selection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
# include <QStatusBar>
#endif

#include <boost/algorithm/string/predicate.hpp>

/// Here the FreeCAD includes sorted by Base,App,Gui......
#include "Application.h"
#include "Document.h"
Expand Down Expand Up @@ -630,8 +632,16 @@ void SelectionSingleton::slotSelectionChanged(const SelectionChanges& msg) {

bool SelectionSingleton::setPreselect(const char* pDocName, const char* pObjectName, const char* pSubName, float x, float y, float z, bool signal)
{
if (DocName != "")
if(!pDocName || !pObjectName) {
rmvPreselect();
return false;
}
if(!pSubName) pSubName = "";

if(DocName==pDocName && FeatName==pObjectName && SubName==pSubName)
return true;

rmvPreselect();

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

if(signal) {
Chng.Type = SelectionChanges::SetPreselect;
Notify(Chng);
signalSelectionChanged(Chng);
}

// allows the preselection
return true;
}
Expand Down Expand Up @@ -717,8 +733,7 @@ void SelectionSingleton::rmvPreselect()
if (DocName == "")
return;

SelectionChanges Chng(SelectionChanges::RmvPreselect,
DocName,FeatName,SubName);
SelectionChanges Chng(SelectionChanges::RmvPreselect,DocName,FeatName,SubName);

// reset the current preselection
CurrentPreselection = SelectionChanges();
Expand Down Expand Up @@ -943,6 +958,13 @@ bool SelectionSingleton::updateSelection(bool show, const char* pDocName,
{
if(!pDocName || !pObjectName)
return false;
if(!pSubName)
pSubName = "";
if(show && DocName==pDocName && FeatName==pObjectName && SubName==pSubName) {
SelectionChanges Chng(SelectionChanges::SetPreselectSignal,DocName,FeatName,SubName);
Notify(Chng);
signalSelectionChanged(Chng);
}
if (!isSelected(pDocName, pObjectName, pSubName))
return false;
auto pDoc = getDocument(pDocName);
Expand Down Expand Up @@ -1216,7 +1238,7 @@ void SelectionSingleton::clearCompleteSelection()
}

bool SelectionSingleton::isSelected(const char* pDocName,
const char* pObjectName, const char* pSubName, bool resolve) const
const char* pObjectName, const char* pSubName, int resolve) const
{
if(!pObjectName) return false;
auto pDoc = getDocument(pDocName);
Expand All @@ -1226,7 +1248,7 @@ bool SelectionSingleton::isSelected(const char* pDocName,
return isSelected(pDoc->getObject(pObjectName),pSubName,resolve);
}

bool SelectionSingleton::isSelected(App::DocumentObject* pObject, const char* pSubName, bool resolve) const
bool SelectionSingleton::isSelected(App::DocumentObject* pObject, const char* pSubName, int resolve) const
{
if(!pObject || !pObject->getNameInDocument() || !pObject->getDocument())
return false;
Expand All @@ -1239,20 +1261,24 @@ bool SelectionSingleton::isSelected(App::DocumentObject* pObject, const char* pS
if(!pResolvedObject)
return false;
std::string subname;
if(pSubName) {
if(element && elementName.first.size()) {
std::string prefix;
if(pSubName && element) {
prefix = std::string(pSubName, element-pSubName);
if(elementName.first.size()) {
// make sure the selected sub name is a new style if available
subname = std::string(pSubName,element-pSubName) + elementName.first;
subname = prefix + elementName.first;
pSubName = subname.c_str();
}
}
for (auto &sel : _SelList) {
if (sel.DocName==pDocName && sel.FeatName==pObjectName) {
if(!pSubName || sel.SubName==pSubName)
return true;
if(resolve>1 && boost::starts_with(sel.SubName,prefix))
return true;
}
}
if(resolve) {
if(resolve==1) {
for(auto &sel : _SelList) {
if(sel.pResolvedObject != pResolvedObject)
continue;
Expand Down Expand Up @@ -1497,49 +1523,18 @@ PyObject *SelectionSingleton::sUpdateSelection(PyObject * /*self*/, PyObject *ar
PyObject *show;
PyObject *object;
char* subname=0;
if (PyArg_ParseTuple(args, "OO!|s", &show,&(App::DocumentObjectPy::Type),&object,&subname)) {
App::DocumentObjectPy* docObjPy = static_cast<App::DocumentObjectPy*>(object);
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
if (!docObj || !docObj->getNameInDocument()) {
PyErr_SetString(Base::BaseExceptionFreeCADError, "Cannot check invalid object");
return NULL;
}

Selection().updateSelection(PyObject_IsTrue(show),
docObj->getDocument()->getName(), docObj->getNameInDocument(), subname);
Py_Return;
}

PyErr_Clear();
PyObject *sequence;
if (PyArg_ParseTuple(args, "O!O", &(App::DocumentObjectPy::Type),&object,&sequence)) {
App::DocumentObjectPy* docObjPy = static_cast<App::DocumentObjectPy*>(object);
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
if (!docObj || !docObj->getNameInDocument()) {
PyErr_SetString(Base::BaseExceptionFreeCADError, "Cannot check invalid object");
return NULL;
}

try {
if (PyTuple_Check(sequence) || PyList_Check(sequence)) {
Py::Sequence list(sequence);
for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
std::string subname = static_cast<std::string>(Py::String(*it));
Selection().updateSelection(docObj->getDocument()->getName(),
docObj->getNameInDocument(),
subname.c_str());
}

Py_Return;
}
}
catch (const Py::Exception&) {
// do nothing here
}
if(!PyArg_ParseTuple(args, "OO!|s", &show,&(App::DocumentObjectPy::Type),&object,&subname))
return 0;
App::DocumentObjectPy* docObjPy = static_cast<App::DocumentObjectPy*>(object);
App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr();
if (!docObj || !docObj->getNameInDocument()) {
PyErr_SetString(Base::BaseExceptionFreeCADError, "Cannot check invalid object");
return NULL;
}

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


Expand Down
4 changes: 2 additions & 2 deletions src/Gui/Selection.h
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,9 @@ class GuiExport SelectionSingleton : public Base::Subject<const SelectionChanges
void clearCompleteSelection();
/// Check if selected
bool isSelected(const char* pDocName, const char* pObjectName=0,
const char* pSubName=0, bool resolve=true) const;
const char* pSubName=0, int resolve=1) const;
/// Check if selected
bool isSelected(App::DocumentObject*, const char* pSubName=0, bool resolve=true) const;
bool isSelected(App::DocumentObject*, const char* pSubName=0, int resolve=1) const;

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

Expand Down
1 change: 1 addition & 0 deletions src/Gui/SoFCUnifiedSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ bool SoFCUnifiedSelection::setHighlight(SoFullPath *path, const SoDetail *det,
if(!highlighted) {
currenthighlight->unref();
currenthighlight = 0;
Selection().rmvPreselect();
}
this->touch();
}
Expand Down
88 changes: 77 additions & 11 deletions src/Gui/Tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,16 @@ FC_LOG_LEVEL_INIT("Tree",false,true,true);

using namespace Gui;

#define TREEVIEW_PARAM "User parameter:BaseApp/Preferences/TreeView"
#define GET_TREEVIEW_PARAM(_name) \
ParameterGrp::handle _name = App::GetApplication().GetParameterGroupByPath(TREEVIEW_PARAM)

/////////////////////////////////////////////////////////////////////////////////

QPixmap* TreeWidget::documentPixmap = 0;
const int TreeWidget::DocumentType = 1000;
const int TreeWidget::ObjectType = 1001;


/* TRANSLATOR Gui::TreeWidget */
TreeWidget::TreeWidget(const char *name, QWidget* parent)
: QTreeWidget(parent), SelectionObserver(false,false), contextItem(0)
Expand All @@ -87,17 +92,19 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent)
this->setDropIndicatorShown(false);
this->setRootIsDecorated(false);

#define GET_TREEVIEW_PARAM(_name) \
ParameterGrp::handle _name = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView")

GET_TREEVIEW_PARAM(hGrp);
bool sync = hGrp->GetBool("SyncSelection",true);
this->syncSelectionAction = new QAction(this);
this->syncSelectionAction->setCheckable(true);
this->syncSelectionAction->setChecked(sync);
this->syncSelectionAction->setChecked(hGrp->GetBool("SyncSelection",true));
connect(this->syncSelectionAction, SIGNAL(triggered()),
this, SLOT(onSyncSelection()));

this->preSelectionAction = new QAction(this);
this->preSelectionAction->setCheckable(true);
this->preSelectionAction->setChecked(hGrp->GetBool("PreSelection",true));
connect(this->preSelectionAction, SIGNAL(triggered()),
this, SLOT(onPreSelection()));

this->syncViewAction = new QAction(this);
this->syncViewAction->setCheckable(true);
this->syncViewAction->setChecked(hGrp->GetBool("SyncView",false));
Expand Down Expand Up @@ -168,6 +175,9 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent)
this->setMouseTracking(true); // needed for itemEntered() to work
#endif

this->preselectTimer = new QTimer(this);
this->preselectTimer->setSingleShot(true);

this->statusTimer = new QTimer(this);
this->statusTimer->setSingleShot(false);

Expand All @@ -181,6 +191,9 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent)
this, SLOT(onItemExpanded(QTreeWidgetItem*)));
connect(this, SIGNAL(itemSelectionChanged()),
this, SLOT(onItemSelectionChanged()));
connect(this->preselectTimer, SIGNAL(timeout()),
this, SLOT(onPreSelectTimer()));
preselectTime.start();

setupText();
_updateStatus();
Expand Down Expand Up @@ -232,8 +245,12 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e)
contextMenu.addSeparator();
topact = actions.front();
}
contextMenu.insertAction(topact,this->syncSelectionAction);
contextMenu.insertAction(topact,this->syncViewAction);
QMenu optionsMenu;
optionsMenu.setTitle(tr("Tree view options"));
optionsMenu.addAction(this->preSelectionAction);
optionsMenu.addAction(this->syncSelectionAction);
optionsMenu.addAction(this->syncViewAction);
contextMenu.insertMenu(topact,&optionsMenu);
contextMenu.insertSeparator(topact);

// get the current item
Expand Down Expand Up @@ -1101,11 +1118,51 @@ void TreeWidget::onItemEntered(QTreeWidgetItem * item)
{
// object item selected
if (item && item->type() == TreeWidget::ObjectType) {
DocumentObjectItem* obj = static_cast<DocumentObjectItem*>(item);
obj->displayStatusInfo();
DocumentObjectItem* objItem = static_cast<DocumentObjectItem*>(item);
objItem->displayStatusInfo();

if(preSelectionAction->isChecked()) {
if(preselectTime.elapsed() < 700)
onPreSelectTimer();
else{
preselectTimer->start(500);
Selection().rmvPreselect();
}
}
} else if(preSelectionAction->isChecked())
Selection().rmvPreselect();
}

void TreeWidget::leaveEvent(QEvent *) {
if(preSelectionAction->isChecked()) {
preselectTimer->stop();
Selection().rmvPreselect();
}
}

void TreeWidget::onPreSelectTimer() {
if(!preSelectionAction->isChecked())
return;
auto item = itemAt(viewport()->mapFromGlobal(QCursor::pos()));
if(!item || item->type()!=TreeWidget::ObjectType)
return;

FC_LOG("preselect timer");
preselectTime.restart();
DocumentObjectItem* objItem = static_cast<DocumentObjectItem*>(item);
auto vp = objItem->object();
auto obj = vp->getObject();
std::ostringstream ss;
App::DocumentObject *parent = 0;
objItem->getSubName(ss,parent);
if(parent)
ss << obj->getNameInDocument() << '.';
else
parent = obj;
Selection().setPreselect(parent->getDocument()->getName(),parent->getNameInDocument(),
ss.str().c_str(),0,0,0,true);
}

void TreeWidget::onItemCollapsed(QTreeWidgetItem * item)
{
// object item collapsed
Expand Down Expand Up @@ -1142,6 +1199,9 @@ void TreeWidget::setupText() {
this->headerItem()->setText(0, tr("Labels & Attributes"));
this->rootItem->setText(0, tr("Application"));

this->preSelectionAction->setText(tr("Pre-selection"));
this->preSelectionAction->setStatusTip(tr("Preselect the object in 3D view when mouse over the tree item"));

this->syncSelectionAction->setText(tr("Sync selection"));
this->syncSelectionAction->setStatusTip(tr("Auto expand item when selected in 3D view"));

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

void TreeWidget::onPreSelection() {
GET_TREEVIEW_PARAM(hGrp);
hGrp->SetBool("PreSelection",preSelectionAction->isChecked());
}


void TreeWidget::onSyncView() {
GET_TREEVIEW_PARAM(hGrp);
hGrp->SetBool("SyncView",syncViewAction->isChecked());
Expand Down Expand Up @@ -1287,7 +1353,7 @@ TreeDockWidget::TreeDockWidget(Gui::Document* pcDocument,QWidget *parent)
setWindowTitle(tr("Tree view"));
this->treeWidget = new TreeWidget("TreeView",this);
this->treeWidget->setRootIsDecorated(false);
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
GET_TREEVIEW_PARAM(hGrp);
this->treeWidget->setIndentation(hGrp->GetInt("Indentation", this->treeWidget->indentation()));

QGridLayout* pLayout = new QGridLayout(this);
Expand Down
7 changes: 7 additions & 0 deletions src/Gui/Tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#define GUI_TREE_H

#include <QTreeWidget>
#include <QTime>

#include <App/Document.h>
#include <App/Application.h>
Expand Down Expand Up @@ -119,6 +120,7 @@ class TreeWidget : public QTreeWidget, public SelectionObserver
protected:
void showEvent(QShowEvent *) override;
void hideEvent(QHideEvent *) override;
void leaveEvent(QEvent *) override;
void _updateStatus(bool delay=false);

protected Q_SLOTS:
Expand All @@ -130,6 +132,8 @@ protected Q_SLOTS:
void onSkipRecompute(bool on);
void onMarkRecompute();
void onSyncSelection();
void onPreSelection();
void onPreSelectTimer();
void onSyncView();
void onShowHidden();
void onHideInTree();
Expand Down Expand Up @@ -159,6 +163,7 @@ private Q_SLOTS:
QAction* finishEditingAction;
QAction* skipRecomputeAction;
QAction* markRecomputeAction;
QAction* preSelectionAction;
QAction* syncSelectionAction;
QAction* syncViewAction;
QAction* showHiddenAction;
Expand All @@ -168,6 +173,8 @@ private Q_SLOTS:
DocumentItem *currentDocItem;
QTreeWidgetItem* rootItem;
QTimer* statusTimer;
QTimer* preselectTimer;
QTime preselectTime;
static QPixmap* documentPixmap;
std::map<const Gui::Document*,DocumentItem*> DocumentMap;
bool fromOutside;
Expand Down
Loading

0 comments on commit 381e79b

Please sign in to comment.