MouseListener Traverser?


#1

I there a way to specify, in which order components are called on a MouseDown event?

I have a parent component, which contains a slider. In the parent component I am calling slider.addMouseListener(this, true) so that the parent can listen to the mouse events of its child, the slider. Works fine.

The problem:
The MouseDown of the slider is called first and only afterwards the MouseDown of the parent component is called.
I would like it to be the other way round. I.e. the mouse handler should first call MouseDown on the parent and afterwards MouseDown on the child.

Is there a solution, whithout subclassing Slider?


#2

To have it the other way around you may have to intercept mouse clicks and pass them manually:

Parent::Parent()
{
    ...
    setInterceptsMouseClicks(true, false); // no clicks on children
}

void Parent::mouseDown(const MouseEvent &e)
{
    Component *target = getComponentAt(e.getPosition()

    if (target == nullptr)
        return;

    if (target != this)
        target->mouseDown(e);
    else
        // your parent's mouse handling code
}

Note that this is basically how JUCE handles mouse events anyway, by traversing the children to find the target child component (based on z-order). What’s your use case for intercepting the event in the parent first?


#3

Thanks for the fast reply.
I’ll try that.

Here is my somewhat lengthy explanation as to why I asked the question:
My use case is Undo functionality. And I am getting problems with undo. Thats the reason for the post.
I have four instrument panels. And each instrument panels has sliders. If you click on an instrument panel, you activate the instrument. So thats an undoable action. Changing the value of the slider is another undoable action.
Now imagine you decide to drag a slider, without the instrument being activated.
Then this happens: First slider MouseDown is called and the slider changes its value, which of course also goes into the UndoManager. Next thing that happens: the MouseDown event is upcasted to the parent panel. That activates the instrument panel, which, again is sent to the UndoManager. But the user is still dragging the slider. Which means, that further slider value updates are made, which again go to the UndoManager.

So what is the problem here? The “activate instrument” action is coming during the slider drag. In other words: first the slider is dragged a very small amount, then the instrument is activated and then the slider is dragged some more.

Now if you press undo, this happens: First most of the slider movement is undone. Second time you press undo, the “activate instrument panel” is undone.
And now the problem comes: If you press undo a third time, it will once again change the slider a little bit. That is the little bit the slider was able to move, before the initial call to “activate panel” was made.
So this is three undos, all in all.

But I just want two undos. Because that is a nicer user experience:
One undo which changes all of the slider value. And another undo, which changes the instrument panel activation. This can be acomplished by making sure, that the “activate instrument” is called, before changing any slider value.

Hope my explaning makes sense? :slight_smile:


#4

Ah, gotcha!

In that case maybe the slider lambdas be easier to use:

// assuming we're in a parent function
slider.onDragStart = [this]() { /* handle instrument change if it's not already the active one */ };
slider.onDragEnd   = [this]() { /* update the value given by slider.getValue() as an undoable action */ };

Slider drags begin on mouse down and end on mouse up

Note that they also apply to other events (double clicks, mouse wheels, textbox entry, inc/dec buttons if the slider uses those, etc.) in the order of “drag starts, value updates, drag ends” so you’d have to coalesce those value changes differently


#5

Yes, I was thinking about using DragStart and DragEnd instead.

But the problem is automation. The sliders are volume sliders. And I want to make them automateable, I think. In other words: sliding them can be recorded by the host. Therefore I am using the onValueChange lambda and not the onDragStart / onDragEnd.


#6

If you’re using AudioProcessorValueTreeState for parameters and its SliderAttachments for the GUI stuff it will handle automation start/end for you. It inherits Slider::Listener and overrides sliderDragStarted() and sliderDragEnded(), which is another option to use for your initial case

https://docs.juce.com/master/classSlider_1_1Listener.html


#7

OK, I did not know the AudioProcessorValueTreeState does that.

Still, I am not quite sure, if it helps solve my problem?


#8

I’m just referring to automation gestures with the AudioProcessorValueTreeState

You can set the onDragStart and onDragEnd lambdas to handle the instrument change and undoable action specifics. Alternatively you can make the parent component a Slider::Listener and handle those actions inside listener callbacks instead. Either approach won’t interfere with automation if you’re using AudioProcessorValueTreeState and its SliderAttachment class

If you aren’t using AudioProcessorValueTreeState you can still write the AudioProcessorParameter::beginChangeGesture() and AudioProcessorParameter::endChangeGesture() calls in the slider lambdas or in listener callbacks anyway, and it will give you the same behavior


#9

OK, interesting.
Thanks for the explanation. I’ll take a look.