Detecting Modifier Key when touching Slider/RotaryKnob

I need to be able to do something special in “sliderValueChanged” when touching, that is clicking or adjusting a slider (actually a rotary knob), and at the same time either holding down the “Ctrl” key or right-clicking the mouse button. I need to do this, on many rotary knobs, and each action although related, is unique to each knob.

I had hoped I could simply add some modifier key detection code inside my below “sliderValueChanged”.

   void MutineerAudioProcessorEditor::sliderValueChanged (Slider* slider)
    {
    	String id = slider->getComponentID ();

        // Detect modifier key here


    }

There is a static variable ModifierKeys::currentModifiers that you can check at any time:

if (juce::ModifierKeys::currentModifiers.isCtrlDown())
    // ...
2 Likes

Thanks a bunch! What I am doing is instead of a large LFO matrix window, I want to be able to set/unset a knob (parameter) as an LFO destination with a special click.

I see, in that case I would probably rather override mouseDown of the slider, reason being that you can act without needing to change the slider value. Just don’t forget to call the mouseDown of the super class…

1 Like

Sounds reasonable, but I am not sure how to override mouseDown of the slider. On another related subject, I just remembered that Ctrl is used to fine adjust sliders, but not rotary knobs which I could really find helpful in my synth.

Ok I think I got it.

I already had; void mouseDown (const MouseEvent& event) override;

So I add a mouse Listener to my rotary knob.

Then I test for modifier key in mouseDown on the specific knob;

void MutineerAudioProcessorEditor::mouseDown (const MouseEvent& event)
{
	if (event.eventComponent == &myKnob)
      {
        if (juce::ModifierKeys::currentModifiers.isCtrlDown ()) setLFOTarget(&myKnob);
      }
}

And then of course in sliderValueChanged I also need to check if the same modifier key was pressed so to avoid changing the slider value.

There’s a better way:

class MySlider : public juce::Slider
{
public:
    void mouseDown (const juce::MouseEvent& event) override
    {
        if (juce::ModifierKeys::currentModifiers.isCtrlDown())
            toggleYourLfo()
        else
            juce::Slider::mouseDown (event); 
    }
private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MySlider)
};

This way the mouseDown will not reach the slider, if ctrl was pressed… I think the subsequent mouseDrag should have no effect then… haven’t tested it

1 Like

Ok thanks a bunch I will test it within the next few minutes :wink:

Ok my knowledge on C++ is clearly lacking here. I am confused about where to put the code you presented. If I put it in a “MySlider.h” file, and then in my PluginEditor.h include “MySlider.h” and then in same file do MySlider mySlider, I get a compiler error “no appropriate default constructor available”

I imagine I could just put the code in PluginEditor.h somewhere, where I also have my custom lookandfeel class, so it would work with all my knobs from any component file.

Ok, I left that out for brevity.
But I can add it here:

class MySlider : public juce::Slider
{
public:
    MySlider() = default;

    MySlider (const juce::String &componentName) : juce::Slider (componentName) {}

    MySlider (juce::Slider::SliderStyle style, juce::Slider::TextEntryBoxPosition textBoxPosition) 
    : juce::Slider (style, textBoxPosition) {}

    void mouseDown (const juce::MouseEvent& event) override
    {
        if (juce::ModifierKeys::currentModifiers.isCtrlDown())
            toggleYourLfo()
        else
            juce::Slider::mouseDown (event); 
    }
private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MySlider)
};

Hope that helps

1 Like

Ok thanks I’ll give that a try.

Seems to be working like a charm and you’re right holding down Ctrl while dragging the slider(knob) does not trigger sliderValueChanged.

You’re a life, well program saver, thanks!

1 Like

So for anyone needing to use a modifier key/mouse click, and a pop-up window(component) close to a slider/knob, similar to the tooltip, here is how I am doing it with great help from @daniel

Note that I ended up implementing a mouse right-click instead of ctrl key with regular mouse click, and in the case of Windows, the mouse right-click will automatically be a mouse left-click if you set you Windows to “Primary mouse key” as left.

MySlider.h

#include <JuceHeader.h>

#include "Common.h"
#include "ModuleLFOSpecifics.h"

class MySlider : public juce::Slider {
public:
    MySlider () = default;

    MySlider (const juce::String& componentName) : juce::Slider (componentName) {}

    MySlider (juce::Slider::SliderStyle style, juce::Slider::TextEntryBoxPosition 
textBoxPosition)
        : juce::Slider (style, textBoxPosition) {}

    void mouseDown (const juce::MouseEvent& event) override
    {
        if (juce::ModifierKeys::currentModifiers.isRightButtonDown ())
        {
            String id = Slider::getComponentID ();

            int module;

            if (Slider::getProperties ().contains ("Section"))
                module = int (Slider::getProperties ()["Module"]);
            else
                module = currentModule;

            auto& desktop = Desktop::getInstance ();
            auto mouseSource = desktop.getMainMouseSource ();

            auto mousePos = mouseSource.getScreenPosition ();
            int mouseX = roundToInt (mousePos.getX ());
            int mouseY = roundToInt (mousePos.getY ());
            
            auto window = getTopLevelComponent ();
            int appX = window->getScreenBounds ().getX();
            int appY = window->getScreenBounds ().getY ();

            lfoTargetComponent.setLFOTargetStatus (id, module, mouseX, mouseY, appX, appY);
        }
        else
        {
            juce::Slider::mouseDown (event);
        }
    }

private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MySlider)

Then in my “lfoTargetComponen” below, and note the variables “appWidth” and “appHeight” is set in PluginEditor’s resized method;

void LFOTargetComponent::setLFOTargetStatus (String id, int module, int mouseX, int mouseY, int appX, int appY)
{
    targetModule = module;
    targetSlider = id;

    // Move component position close to calling knob's position
    int x = mouseX - appX - getWidth() / 2;
    int y = mouseY - appY + knobSize / 2;
    
    // Adjust x position if part or all exceeds app
    if (x < 0)
        x = sectionMargin;
    else if (x + getWidth() > appWidth)
        x = appWidth - getWidth() - sectionMargin;

    // Adjust y position if part or all exceeds app
    if (y < 0)
        y = sectionMargin;
    else if (y + getHeight () > appHeight)
        y = appHeight - getHeight () - sectionMargin;


    setBounds (x, y, getWidth(), getHeight ());
    setVisible (true);
    repaint ();
    ...
    ...
}

Note even though my Component is positioning itself fine, even if you have multiple monitors, and no matter which monitor the plugin is on, if the Component is visible while you resize the entire plugin, although it will resize the Component, it won’t be repositioned correctly, but then again doing that is not very likely.

To see this in action, check out my synthesizer work in progress here at about 4 minutes in where I start assigning several LFO’s to filters - https://youtu.be/ncXcUyMZdI0

Ok I am reopening this issue here. Since I implemented your suggestion, it has worked great. Now however I have restructured my synth, which worked great as a single instance, and replaced global variables to classes and using references and pointers. And doing so I here stumbled upon something that I can’t seem to wrap my head around. As a matter of fact this object is the last thing to be unglobalized :slight_smile:

In the above code posted, previously if the modifier key is pressed, my “toggleYourLfo()” is invoked. My method residing in a separate Class component file is called “lfoTargetComponent.setLFOTargetStatus (id, module, mouseX, mouseY, appX, appY);”

The MySlider class I am using to declare rotary knobs, instead of normal Slider, in about 45 component classes (modules as in 8 of each; tone generator, envelope, effect, LFO, filter, plus a few more), components which are all created and used in PluginEditor.

Problem is my “lfoTargetComponent” which actually also handles envelope destinations so it should be renamed, anyways it needs to access a bunch of shared synth parameters (envelope and LFO). They were all global before, but have now been separated into classes, and I pass a reference of the needed object to “lfoTargetComponent” in PluginEditor’s constructor. All working great.

My problem arises when trying to change the “lfoTargetComponent” object from global, to well non-global.

Right now I have a large SynthParameters class. A public object of that is created in PluginProcessor which also need much of these parameters for effects. When voices are created in PluginProcessor, a reference is passed to SynthVoice. Also via the “processor” reference I can then also use it in PluginEditor, where as I wrote in the constructor first thing pass the reference to some effect components, and “lfoTargetComponent”.

So my question is where would I create an object of “LFOTargetComponent”? PluginProcessor or PluginEditor? But the for me hard question is how do I let MySlider know which object to invoke?

Hope my issue makes any sense.

Anything called “Component” should most likely live in the editor.

I’m not sure I completely understand the second part of the question, but this talk of “invoking” certain action on certain components sounds reminiscent of juce’s ApplicationCommandTarget and ApplicationCommandManager, perhaps looking through those classes could help.

You’re right, since this is a component only needed when I set envelope and LFO targets, it is not needed in PluginProcessor.

Problem is how do I let my MySlider class, which is used individually in about 45 components, each on multiple knobs, know to all use the same setEnvelopeAndLFOTarget and the synth Data it has a reference to, when I right-click a knob.

I’ll take take a look at the classes you mentioned, thanks.

Well, is there any common element that is passed to all of these components – like for example, a reference to your process, or a reference to some state object?

Just put the thing you need in there.

Yes there is something I pass to all those components, but would you mind clarifying for me what you mean by “thing”.

Below is an example of an effect module, which I pass a few references to in my PluginEditor’s constructor, meaning before addChildComponent. But note that module, like most do NOT need access to all my synthVoice parameters.

#pragma once

#include <JuceHeader.h>

#include "ModuleGeneral.h"
#include "SectionGeneral.h"

#include "MySlider.h"

//==============================================================================
/*
*/
class ModuleBitcrusher  : public juce::Component
{
public:
    ModuleBitcrusher();
    ~ModuleBitcrusher() override;

    void setModuleGeneralPtr (ModuleGeneral* ptr);
    void setSectionGeneralPtr (SectionGeneral* ptr);

    void paint (juce::Graphics&) override;
    void resized() override;

    // Buttons
    TextButton statusButton;
    ImageButton lockButton;

    //Knobs
    MySlider levelKnob, reductionKnob;
    Slider typeKnob;

private:
    ModuleGeneral* moduleGeneralPtr = NULL;

    SectionGeneral* sectionGeneralPtr = NULL;
   ...

As you see two of the three knobs are using MySlider, instead of Slider to declare the knobs. And here is exactly how MySlider class (file) looks;

#pragma once

#include <JuceHeader.h>

#include "ModuleLFOSpecifics.h"

//==============================================================================
class MySlider : public juce::Slider {
public:
    MySlider () = default;

    MySlider (const juce::String& componentName) : juce::Slider (componentName) {}

    MySlider (juce::Slider::SliderStyle style, juce::Slider::
              TextEntryBoxPosition textBoxPosition)
              : juce::Slider (style, textBoxPosition) {}

    void mouseDown (const juce::MouseEvent& event) override
    {
        if (juce::ModifierKeys::currentModifiers.isRightButtonDown ())
        {
            String id = Slider::getComponentID ();

            int module;

            // Module knob
            if (Slider::getProperties ().contains ("Section"))
                module = int (Slider::getProperties ()["Module"]);

            // Detail knob
            else
                module = -1;

            auto& desktop = Desktop::getInstance ();
            auto mouseSource = desktop.getMainMouseSource ();

            auto mousePos = mouseSource.getScreenPosition ();
            int mouseX = roundToInt (mousePos.getX ());
            int mouseY = roundToInt (mousePos.getY ());
            
            auto window = getTopLevelComponent ();
            int appX = window->getScreenBounds ().getX();
            int appY = window->getScreenBounds ().getY ();

            lfoTargetComponent.setLFOTargetStatus (id, module, mouseX, mouseY, appX, appY);
        }
        else
            juce::Slider::mouseDown (event);
    }

private:

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MySlider)
};

The “lfoTargetComponent” is defined globally, yes horrible I know which why I am trying to change it, in the “ModuleLFOSpecifics.h” file. Anyways the “lfoTargetComponent” is passed a reference to all my synthVoice parameters also in PluginEditor’s constructor, before addChildComponent.

If you need to access lfoTargetComponent in each of these other components, then you basically have two options:

Option 1
Make the lfoTargetComponent a public member of the processor, or your state (either would work). Then, from inside your components, do processor.lfoTargetComponent (or state.lfoTargetComponent, if you choose to put it in the state).

Option 2
Make each of these components take a lfoTargetComponent& in its constructor.

I tried option one yesterday, however although my PluginEditor knew “processor”, my 45 components seemed not to.