How to make parent Component dynamically catch MouseEvent instead of child components?

I’d like a parent Component to catch all MouseEvents which happen when right mouse click happens. All the rest should go to child components.

Is there an easy way to do this?

There are three ways:

  1. let the parent component catch all and call the child component, if it doesn’t want to handle it:
ParentComponent()
{
    // keep clicks for children to yourself
    setInterceptsMouseClicks (true, false);
}
// you need to implement this for all mouse events the children need
void mouseDown (const MouseEvent& event) override
{
    if (event.mods.isRightButton())
    {
        doYourThing();
        return;
    }
    for (auto* child : getChildren())
    {
        if (child->hitTest (event.x, event.y))
        {
           child->mouseDown (event->getRelativeTo (child));
           return;
        }
    }
}
  1. Add a mouse listener to every child
    This means, you need to modify every child to not react on right click

  2. have every child call the method of the parent via a back link you added on construction.
    This only works for custom components

Well, none of those qualifies for “easy way”, but it is doable.
It should be possible to create a generic event filter for option one, but that needs some thought…

1 Like

The first option looks promising. However, I got some very weird behavior which I cannot explain:

Nothing seemed to change.
The parent mouseDown() doesn’t get called at all for some reason and the child component’s mouseDown() still gets called as usual.

In other words, with the changes you suggested, nothing happened. The component(s) seem to be working exactly as before as the parent’s mouseDown() doesn’t get called for some weird reason.

I couldn’t find any other place which would set setInterceptsMouseClicks() for this parent component, so I doubt it’s that what could be causing this issue now.

In my current parent Component the child components cover the whole parent surface area completely. Still this behavior should not happen if I understand correctly how the
setInterceptsMouseClicks(true, false) should make things behave.

Is the parent derrived from juce::Component?
Maybe you can add a breakpoint in setInterceptsMouseClicks or in mouseDownInternal to see, why it doesn’t respect the flag to skip the children…

The parent is derived from another class which is derived from a Component.

I tried making a hole in the parent component so I can actually click it and added the following lines to the parent’s mouseDown():

        bool first, second;
        getInterceptsMouseClicks(first, second);

The results I got were:

first == true
second == false

So the flags are correct.
I’ll see if I can get some answers by following your debugging suggestion by placing those break points.

And another one to check: being a child is not defined by being a class member, but rather where you called addAndMakeVisible. I have seen things like:

thirdparty.addAndMakeVisible (child);

This way the event would skip this and be passed from thirdparty directly to the child…

All components seem to be added the following way:

addAndMakeVisible(&childComponent);

So they’re added by / into the same component which owns them. So no third party bugs seem to be present. At least I couldn’t find one.

mouseDownInternal() seems to be related only to treeview or something and isn’t called when mouse is clicked anywhere in the application. So no point debugging that.

EDIT:
Ah, you must be referring to internalMouseDown() which does call mouseDown(). Debugging now.

Oh sorry, typed from memory, should have opened the sources :blush:

The below function(s) seems to be the ones JUCE uses to solve which components are under the mouse cursor at any given time.

detail::ComponentHelpers::hitTest eventually calls Component::hitTest() which I’ve listed below.

Component* Component::getComponentAt (Point<float> position)
{
    if (flags.visibleFlag && detail::ComponentHelpers::hitTest (*this, position))
    {
        for (int i = childComponentList.size(); --i >= 0;)
        {
            auto* child = childComponentList.getUnchecked (i);

            child = child->getComponentAt (detail::ComponentHelpers::convertFromParentSpace (*child, position));

            if (child != nullptr)
                return child;
        }

        return this;
    }

    return nullptr;
}


bool Component::hitTest (int x, int y)
{
    if (! flags.ignoresMouseClicksFlag)
        return true;

    if (flags.allowChildMouseClicksFlag)
    {
        for (int i = childComponentList.size(); --i >= 0;)
        {
            auto& child = *childComponentList.getUnchecked (i);

            if (child.isVisible()
                 && detail::ComponentHelpers::hitTest (child, detail::ComponentHelpers::convertFromParentSpace (child, Point<int> (x, y).toFloat())))
                return true;
        }
    }

    return false;
}

As far as I can tell, the core of the issue is the following:

Component* Component::getComponentAt (Point<float> position)

If the component has been set to intercept the mouse clicks, then also all its child components will always be tested, regardless of if the component has been set to not to do that.

In other words, the hierarchical test to find the child nodes that touch the mouse cursor does NOT stop when component has been marked to NOT allow child nodes to be tested IF/WHEN that same node has been marked to react to mouse clicks.

Could someone else also confirm my logical analysis if this is true?
If it is, then this sounds like JUCE has a bug.

I’m not sure but maybe the easiest way is to process the events normally in the child, and if they are right click events call the parent events with getParentComponent()->mouseDown(event); (you may have to adjust the relative coordinates of the event if the position is needed)

I wonder if there would be an easy way to somehow put another invisible Component on top of the actual component, which I want to have that special behavior with right mouse button. Then that invisible component would have a pointer to that actual component and would forward the mouse messages to the component as needed.

Actually the code in the first answer to this thread might do the trick if the Components were used the way I described here. I’ll try this combination and see what happens.

Well, the idea was good, but it didn’t work very well in practice:

Some of the ComboBoxes inside those child components don’t react for mouse clicks.
Also rotary sliders don’t like it when mouse cursor goes outside the area of the covering Component. Meaning that the slider rotates only as long as the mouse cursor (which is invisible at that time) stays inside the covering component. Once it goes outside, the knob/slider stops rotating.

I’ll go the most obvious way then, which was suggeseted here already, and just implement the functionality to each of the child components.

You might be able to fix that setting

event.source.enableUnboundedMouseMovement (true, false);

But it’s a rabbit hole. You also interfere with the sequence of mouseEnter/mouseExit and such, so I expect more edge cases arising.

Indeed. That’s why I’ll just bite the bullet and implement the right mouse button handling into the child objects instead of trying to get it working on some on-top-of-Component.