home | tech | misc | code | bookmarks (broken) | contact | README


FreeCAD Development Notes

Last update to this page was in 2019-04-13.

Tips for debugging

Qt's qDebug() << "foo\n"; 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:

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. The documentation of this class is here.

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...