Created at:
Modified at:
FreeCAD Development Notes
Last update to this page was in 2019-03-06.
Tips for debugging
Qt's qDebug() << "foon";
just doesn't work here. FreeCAD guys have done
a different thing. Instead, you should use::
/* This comes at the top, if there is any error */
#include <PreCompiled.h>
#include <Base/Console.h>
/* this comes where you want the debug output */
Base::Console().Warning("%s %d\n", "foo", 5);
Source code explained (at least a small part of it)
Trying to solve issue 3786 (Ability to cycle between selectable objects (using a keyboard shortcut)
Trying to solve issue 3786 (Ability to cycle between selectable objects
(using a keyboard shortcut), we see that most (if not all?) mouse events for
the workspace area (I don't know if this is the correct name) are in
src/Gui/Quarter/Mouse.cpp
. We can see they here:
Issue 3786 (Ability to cycle between selectable objects (using a keyboard shortcut)
File src/Gui/Quarter/Mouse.cpp
, lines 203 through 219::
switch (event->button()) {
case Qt::LeftButton:
this->mousebutton->setButton(SoMouseButtonEvent::BUTTON1);
break;
case Qt::RightButton:
this->mousebutton->setButton(SoMouseButtonEvent::BUTTON2);
break;
case Qt::MidButton:
this->mousebutton->setButton(SoMouseButtonEvent::BUTTON3);
break;
default:
this->mousebutton->setButton(SoMouseButtonEvent::ANY);
SoDebugError::postInfo("Mouse::mouseButtonEvent",
"Unhandled ButtonState = %x", event->button());
break;
}
return this->mousebutton;
This is returned to Mouse::translateEvent(QEvent * event)
in the same
file:
File src/Gui/Quarter/Mouse.cpp
, lines 112 through 136::
/*! Translates from QMouseEvents to SoLocation2Events and
SoMouseButtonEvents
*/
const SoEvent *
Mouse::translateEvent(QEvent * event)
{
switch (event->type()) {
case QEvent::MouseMove:
return PRIVATE(this)->mouseMoveEvent((QMouseEvent *) event);
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
// a dblclick event comes in a series of press, release, dblclick,
// release, so we can simply treat it as an ordinary press event.
// -mortene.
case QEvent::MouseButtonDblClick:
return PRIVATE(this)->mouseButtonEvent((QMouseEvent *) event);
case QEvent::Wheel:
return PRIVATE(this)->mouseWheelEvent((QWheelEvent *) event);
case QEvent::Resize:
PRIVATE(this)->resizeEvent((QResizeEvent *) event);
return NULL;
default:
return NULL;
}
}
Who calls this? A quick grep tell us is, in the same directory,
EventFilter.cpp
, in EventFilter::eventFilter(QObject * obj, QEvent *
qevent)
:
File src/Gui/Quarter/EventFilter.cpp
, lines 169 through 176::
// translate QEvent into SoEvent and see if it is handled by scene
// graph
foreach(InputDevice * device, PRIVATE(this)->devices) {
const SoEvent * soevent = device->translateEvent(qevent);
if (soevent && PRIVATE(this)->quarterwidget->processSoEvent(soevent)) {
return true;
}
}
So, we believe the event is passed to SoQt (a Coin3D_ interface to Qt_).
Let's investigate what the function call within the if
means. PRIVATE
macro is defined here:
File src/Gui/Quarter/EventFilter.cpp
, lines 90 through 90::
#define PRIVATE(obj) obj->pimpl
And defined here:
File src/Gui/Quarter/EventFilter.cpp
, lines 99 through 103::
PRIVATE(this) = new EventFilterP;
QuarterWidget* quarter = dynamic_cast<QuarterWidget *>(parent);
PRIVATE(this)->quarterwidget = quarter;
assert(PRIVATE(this)->quarterwidget);
QuarterWidget
is a big class defined in QuarterWidget.cpp
file. Next
now locate the processSoEvent()
method.
File src/Gui/Quarter/QuarterWidget.cpp
, lines 1023 through 1036::
/*!
Passes an event to the eventmanager.
\param[in] event to pass
\retval Returns true if the event was successfully processed
*/
bool
QuarterWidget::processSoEvent(const SoEvent * event)
{
return
event &&
PRIVATE(this)->soeventmanager &&
PRIVATE(this)->soeventmanager->processEvent(event);
}
soeventmanager
is defined here:
File src/Gui/Quarter/QuarterWidget.cpp
, lines 277 through 277::
PRIVATE(this)->soeventmanager = new SoEventManager;
SoEventManager
is a class of Coin3D.
Coin: SoEventManager Class Reference
Codes above are low level: they work in a layer very near Coin3D. Selection works in a higher level of abstraction...
In the Gui
directory, we have a Selection.cpp
file, with a
_onSelectionChanged()
function.
File src/Gui/Selection.cpp
, lines 89 through 102::
void SelectionObserver::_onSelectionChanged(const SelectionChanges& msg) {
try {
if (blockSelection)
return;
onSelectionChanged(msg);
} catch (Base::Exception &e) {
e.ReportException();
FC_ERR("Unhandled Base::Exception caught in selection observer: ");
} catch (std::exception &e) {
FC_ERR("Unhandled std::exception caught in selection observer: " << e.what());
} catch (...) {
FC_ERR("Unhandled unknown exception caught in selection observer");
}
}
This method is called whenever we have a mouse event on the workspace. Then,
onSelectionChanged(msg)
is called. What is it? It is not declared in
Selection.cpp
. The SelectionObserver
class definition in the
Selection.h
file has:
File src/Gui/Selection.h
, lines 144 through 145::
virtual void onSelectionChanged(const SelectionChanges& msg) = 0;
void _onSelectionChanged(const SelectionChanges& msg);
As we just saw, _onSelectionChanged()
is defined, but
onSelectionChanged()
is a virtual method that should be implemented by
derived classes. Who implements it? Let's see::
$ grep 'class.*:.*SelectionObserver' * 2>/dev/null
PropertyView.h:class PropertyView : public QWidget, public Gui::SelectionObserver
Selection.h:class GuiExport SelectionObserverPython : public SelectionObserver
Tree.h:class TreeWidget : public QTreeWidget, public SelectionObserver
Hmmm, should it be the TreeWidget
implemented in Tree.cpp
? Indeed,
we have it:
File src/Gui/Tree.cpp
, lines 958 through 1031::
void TreeWidget::onSelectionChanged(const SelectionChanges& msg)
{
switch (msg.Type)
{
case SelectionChanges::AddSelection:
{
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
std::map<const Gui::Document*, DocumentItem*>::iterator it;
it = DocumentMap.find(pDoc);
bool lock = this->blockConnection(true);
if (it!= DocumentMap.end())
it->second->setObjectSelected(msg.pObjectName,true);
this->blockConnection(lock);
} break;
case SelectionChanges::RmvSelection:
{
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
std::map<const Gui::Document*, DocumentItem*>::iterator it;
it = DocumentMap.find(pDoc);
bool lock = this->blockConnection(true);
if (it!= DocumentMap.end())
it->second->setObjectSelected(msg.pObjectName,false);
this->blockConnection(lock);
} break;
case SelectionChanges::SetSelection:
{
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
std::map<const Gui::Document*, DocumentItem*>::iterator it;
it = DocumentMap.find(pDoc);
// we get notified from the selection and must only update the selection on the tree,
// thus no need to notify again the selection. See also onItemSelectionChanged().
if (it != DocumentMap.end()) {
bool lock = this->blockConnection(true);
it->second->selectItems();
this->blockConnection(lock);
}
} break;
case SelectionChanges::ClrSelection:
{
// clears the complete selection
if (strcmp(msg.pDocName,"") == 0) {
this->clearSelection ();
}
else {
// clears the selection of the given document
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
std::map<const Gui::Document*, DocumentItem*>::iterator it;
it = DocumentMap.find(pDoc);
if (it != DocumentMap.end()) {
it->second->clearSelection();
}
}
this->update();
} break;
case SelectionChanges::SetPreselect:
{
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
std::map<const Gui::Document*, DocumentItem*>::iterator it;
it = DocumentMap.find(pDoc);
if (it!= DocumentMap.end())
it->second->setObjectHighlighted(msg.pObjectName,true);
} break;
case SelectionChanges::RmvPreselect:
{
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
std::map<const Gui::Document*, DocumentItem*>::iterator it;
it = DocumentMap.find(pDoc);
if (it!= DocumentMap.end())
it->second->setObjectHighlighted(msg.pObjectName,false);
} break;
default:
break;
}
}
VoilĂ ! Here there probably is what we are looking for. After some investigaion we discovered the *Preselect events happen when mouse hovers on an item in the workspace while the *Selection ones happen when we click over them.
Actually, this seems to be the code that does the actual selection but, as we
just saw, this is TreeWidget
so the events that call
onSelectionChanged()
must be called by someone else. As we can see, a
msg
variable is passed to this method. Who passes it? Let's first see
its definition, again in the Selection.h
file:
File src/Gui/Selection.h
, lines 54 through 93::
/** Transport the changes of the Selection
* This class transports closer information what was changed in the
* selection. It's an optional information and not all commands set this
* information. If not set all observer of the selection assume a full change
* and update everything (e.g 3D view). This is not a very good idea if, e.g. only
* a small parameter has changed. Therefore one can use this class and make the
* update of the document much faster!
* @see Base::Observer
*/
class GuiExport SelectionChanges
{
public:
enum MsgType {
AddSelection,
RmvSelection,
SetSelection,
ClrSelection,
SetPreselect,
RmvPreselect
};
SelectionChanges()
: Type(ClrSelection)
, pDocName(0)
, pObjectName(0)
, pSubName(0)
, pTypeName(0)
, x(0),y(0),z(0)
{
}
MsgType Type;
const char* pDocName;
const char* pObjectName;
const char* pSubName;
const char* pTypeName;
float x;
float y;
float z;
};
So, what if we look what files uses the SelectionChanges
class? Grep
helps us again and, besides other files we see it is used in
View3DInventorViewer.cpp
:
File src/Gui/View3DInventorViewer.cpp
, lines 628 through 645::
/// @cond DOXERR
void View3DInventorViewer::OnChange(Gui::SelectionSingleton::SubjectType& rCaller,
Gui::SelectionSingleton::MessageType Reason)
{
Q_UNUSED(rCaller);
if (Reason.Type == SelectionChanges::AddSelection ||
Reason.Type == SelectionChanges::RmvSelection ||
Reason.Type == SelectionChanges::SetSelection ||
Reason.Type == SelectionChanges::ClrSelection) {
SoFCSelectionAction cAct(Reason);
cAct.apply(pcViewProviderRoot);
}
else if (Reason.Type == SelectionChanges::RmvPreselect) {
SoFCHighlightAction cAct(Reason);
cAct.apply(pcViewProviderRoot);
}
}
/// @endcond
OK, we are near to where the selection magic happens, but this is not the
right method yet. It seems to be called after the selection was done (i.e.,
after the Observer have told us of the state change). Looking in the same
file we see we have the pcViewProviderRoot
object, initialized here:
File src/Gui/View3DInventorViewer.cpp
, lines 434 through 441::
// NOTE: For every mouse click event the SoFCUnifiedSelection searches for the picked
// point which causes a certain slow-down because for all objects the primitives
// must be created. Using an SoSeparator avoids this drawback.
selectionRoot = new Gui::SoFCUnifiedSelection();
selectionRoot->applySettings();
#endif
// set the ViewProvider root node
pcViewProviderRoot = selectionRoot;
Both are pointers to the same object of type SoFCUnifiedSelection
,
implemented in SoFCUnifiedSelection.cpp
. SoFCUnifiedSelection
defines
the complex and important method handleEvent()
there seems to be what we
are looking for.
Indeed, we have this:
Fine src/Gui/SoFCUnifiedSelection.cpp
, lines 561 through 567::
// mouse press events for (de)selection
else if (event->isOfType(SoMouseButtonEvent::getClassTypeId()) &&
selectionMode.getValue() == SoFCUnifiedSelection::ON) {
const SoMouseButtonEvent* e = static_cast<const SoMouseButtonEvent *>(event);
if (SoMouseButtonEvent::isButtonReleaseEvent(e,SoMouseButtonEvent::BUTTON1)) {
// check to see if the mouse is over a geometry...
const SoPickedPoint * pp = this->getPickedPoint(action);
Let's take a look on the getPickedPoint()
method:
Fine src/Gui/SoFCUnifiedSelection.cpp
, lines 305 through 333::
const SoPickedPoint*
SoFCUnifiedSelection::getPickedPoint(SoHandleEventAction* action) const
{
// To identify the picking of lines in a concave area we have to
// get all intersection points. If we have two or more intersection
// points where the first is of a face and the second of a line with
// almost similar coordinates we use the second point, instead.
const SoPickedPointList & points = action->getPickedPointList();
if (points.getLength() == 0)
return 0;
else if (points.getLength() == 1)
return points[0];
const SoPickedPoint* picked = points[0];
int picked_prio = getPriority(picked);
const SbVec3f& picked_pt = picked->getPoint();
for (int i=1; i<points.getLength();i++) {
const SoPickedPoint* cur = points[i];
int cur_prio = getPriority(cur);
const SbVec3f& cur_pt = cur->getPoint();
if ((cur_prio > picked_prio) && picked_pt.equals(cur_pt, 0.01f)) {
picked = cur;
picked_prio = cur_prio;
}
}
return picked;
}
Hmmm, indeed, my print debugging indicates that points
is an array that
has the coordinates of the objects clicked (or hovered) by mouse. If there
are two or more objects (like two faces of a cube), it returns the one that
has the biggest priority. What defines a priority is both the type and the
position. So, for example, if there are two faces, it will return the most
visible one. The priority between different types of objects is defined on
the getPriority()
method:
File src/Gui/SoFCUnifiedSelection.cpp
, lines 295 through 303::
int SoFCUnifiedSelection::getPriority(const SoPickedPoint* p)
{
const SoDetail* detail = p->getDetail();
if (!detail) return 0;
if (detail->isOfType(SoFaceDetail::getClassTypeId())) return 1;
if (detail->isOfType(SoLineDetail::getClassTypeId())) return 2;
if (detail->isOfType(SoPointDetail::getClassTypeId())) return 3;
return 0;
}
To be continued...