Tim MB
Tim MB

Reputation: 4521

How can I selectively make QWidget accept focus from a mouse click?

I'm creating an application that provides a visual representation of nodes and lines connecting them together. Both nodes and lines are represented by custom QWidgets, WidgetNode and WidgetLine, say.

I've implemented WidgetLine as a transparent widget that is large enough to contain the start and end point of the line, and a custom function to draw the line itself.

I would like it so that if the user clicks on or right next to the line then the WidgetLine receives focus, but if they click further away from the line (but still over the rectangular area covered by WidgetLine's geometry) then the click is completely ignored by WidgetLine and passed on to the widget below.

I first tried doing this with a custom focusInEvent() function on WidgetLine, but found the mouse clicks weren't propagating below. I then tried setting the focus policy to Qt::NoFocus and using a custom mousePressEvent() using setFocus() to manually set the focus when appropriate, but the mouse events are still not being propagated to widgets above even when I call ignore() on them.

Finally, I've tried installing an event filter to reject mouse events, with this event filter function

bool WidgetLineFilter::eventFilter(QObject* object, QEvent* event)
{
    assert(object == mCord);
    if (event->type() == QEvent::MouseButtonPress)
    {
        QMouseEvent* e = dynamic_cast<QMouseEvent*>(event);
        assert(e);
        if (e)
        {
            QPoint mouseRelativeToParent = mCord->mapToParent(e->pos());
            // calculate distance of mouse click to patch cord
            QLineF line(mCord->line());
            float distance = distanceFromPointToLine(QVector2D(line.p1()), QVector2D(line.p2()), QVector2D(mouseRelativeToParent));
            qDebug() << distance;
            const float distanceThreshold = 2.f;
            if (distance < distanceThreshold)
            {
                qDebug() << "consuming mouse click for focus";
                mCord->setFocus(Qt::MouseFocusReason);
                return true;
            }
            else{
                qDebug() << "mousepressevent too far for focus";
                return QObject::eventFilter(object, event);
            }
        }
    }
    return false;
}

But this still does not propagate the mouse events to the parent on the "mousepressevent too far for focus" case. I've also tried returning false and true from here, and calling ignore on e but the widgets below are not receiving the click.

(NB the above approaches do work in the sense that WidgetLine only gets focus at the right time, it's just the widgets below that aren't receiving the press events when it doesn't get focus.)

Any ideas on how to fix this?

Upvotes: 2

Views: 2675

Answers (2)

Tim MB
Tim MB

Reputation: 4521

In the end, I created an event filter to selectively intercept mouse events and installed it on the base window and recursively on every single child widget of the base window (installing it on new child widgets when they are created). This filter calls the base window with each mouse press event which then iterates through each WidgetLine, testing if they should be selected by this mouse press and setting focus on them if they should. If they all test false then the filter releases the event, otherwise the filter consumes it.

WidgetLine's are then set to be transparent for mouse events with

setAttribute(Qt::WA_TransparentForMouseEvents);

It's messier than it ought to be to achieve this but does the trick.

Upvotes: 1

bvs
bvs

Reputation: 350

Store the mouse pos in a global var. Have all of your widgets have enter/leave events like the following and use that to check what widget you're in / near when you run the func.

void QGLWidget::enterEvent(QEvent *)
{
    setFocus();
}

void QGLWidget::leaveEvent(QEvent *)
{
    clearFocus();
}

Upvotes: 1

Related Questions