VST Plugin Still Not Getting Keystrokes

68 posts / 0 new
Last post
JuceProspector
Offline
Last seen: 1 year 3 months ago
Joined: 20 Sep 2010 - 22:09
VST Plugin Still Not Getting Keystrokes

This is an old but apparently still unsolved problem. In certain VST Hosts -- e.g. Cubase on Windows -- Juce labels and text editors do not receive keystrokes because the keystrokes are intercepted by the host sequencer. With some other dev toolkits -- e.g. VSTGUI -- this is not a problem. This implies that there is a solution that could be incorporated in Juce. This has been discussed in various threads in this forum for a number of years but apparently the problem persists. What is the status?

red_muze
Offline
Last seen: 1 week 5 days ago
Joined: 31 May 2008 - 21:20
Re: VST Plugin Still Not Getting Keystrokes

there was some kind of solution that was mentioned on the closed thread, but i am not sure if it works or not, that had to do with adding the Cubase key strokes to the vst wrapper. did anyone find a defect with this solution?

Simple Actions -> C0mPL3X R34Kt10NZ
www.synthschool.com

JuceProspector
Offline
Last seen: 1 year 3 months ago
Joined: 20 Sep 2010 - 22:09
Re: VST Plugin Still Not Getting Keystrokes

The Cubase keywords are easily changed (added to or deleted) by the Cubase user so adding a list in the VST Wrapper would not be a very robust solution. I believe the keyboard focus issue needs to be resolved at the OS level. It already works on Mac OSX and VSTGUI is able to capture focus on Windows so there must be a solution.

Jakob
Offline
Last seen: 1 month 1 week ago
Joined: 14 Jun 2010 - 18:45
Re: VST Plugin Still Not Getting Keystrokes

Yeah, this kept us busy for several work days to solve. This is the solution we came up with, do not know if this is the optimal solution, but it works for us. Also thanks a lot for all the posts in http://www.rawmaterialsoftware.com/viewtopic.php?f=8&t=1662&start=30

Some extra code for Sonar, as Sonar passes keystrokes to the plugins.

First of all, in your , we added this:

  // Request keyboard focus here, so that text fields do not get them automatically.
  setWantsKeyboardFocus(true);

Our :

#if _WIN32
class ModalTextEditor: public juce::TextEditor, public juce::TextEditorListener
{

public:
	explicit ModalTextEditor();

	virtual ~ModalTextEditor();
 
public:
	void setTextColour
	( const juce::Colour& colour );
 
	void setFocusedTextColour
	( const juce::Colour& colour );
	
	virtual void focusGained
	( juce::Component::FocusChangeType cause );

protected:
	void updateTextColour();
	
	virtual void focusLost
	( juce::Component::FocusChangeType cause );

	//-- TextEditorListener
	void textEditorTextChanged
	( TextEditor& editor ) { };

	void textEditorReturnKeyPressed
	( TextEditor& editor );
	
	void textEditorEscapeKeyPressed
	( TextEditor& editor );
	
	void textEditorFocusLost
	( TextEditor& editor ) { };

private:
	juce::Colour m_textColour;
	juce::Colour m_focusedTextColour;
	juce::Component* m_oldParent;
	juce::Rectangle<int> m_oldBounds;

	bool m_isSonar;

	juce::String m_textBeforeEdit;
};

//-----------------------------------------------------------------------------------------
#else

class ModalTextEditor: public juce::TextEditor, public juce::TextEditorListener
{
public:
	ModalTextEditor();
	
	virtual ~ModalTextEditor();

public:
	void setTextColour
	( const juce::Colour& colour ) { };
	
	void setFocusedTextColour
	( const juce::Colour& colour ) { };

protected:
	void mouseDown
	( const juce::MouseEvent& e );

	//-- TextEditorListener-Methoden
	void textEditorTextChanged
	( juce::TextEditor& editor ) { };
	
	void textEditorReturnKeyPressed
	( juce::TextEditor& editor );

	void textEditorEscapeKeyPressed
	( juce::TextEditor& editor );
	
	void textEditorFocusLost
	( juce::TextEditor& editor ) { };

private:
	juce::String m_textBeforeEdit;
};

#endif

Our :

#if _WIN32
//==============================================================================
// Windows
//==============================================================================
ModalTextEditor::ModalTextEditor()
: juce::TextEditor("ModalTextEditor")
, m_oldParent(0)
, m_isSonar(false)
, m_textBeforeEdit("")
{
	setPopupMenuEnabled(false);
	setOpaque(true);
	addListener(this);

	// Set <m_isSonar>.
	const juce::String hostPath(juce::File::getSpecialLocation(juce::File::hostApplicationPath).getFullPathName());
	const juce::String hostFilename(juce::File(hostPath).getFileName());
	m_isSonar = hostFilename.containsIgnoreCase("SONAR");
};

//------------------------------------------------------------------------------
ModalTextEditor::~ModalTextEditor()
{
};

//------------------------------------------------------------------------------
void ModalTextEditor::focusGained
( juce::Component::FocusChangeType cause )
{
	if(m_isSonar)
	{
		juce::TextEditor::focusGained(cause);
	}
	else
	{
		if(!isOnDesktop())
		{
			m_oldBounds = getBounds();
			m_oldParent = getParentComponent();
			m_textBeforeEdit = getText();
			addToDesktop(juce::ComponentPeer::windowIsTemporary);
			updateTextColour();
			grabKeyboardFocus();
		}
	
		repaint();
	}
};

//------------------------------------------------------------------------------
void ModalTextEditor::focusLost
( juce::Component::FocusChangeType cause )
{
	if(!m_isSonar)
	{
		if(isOnDesktop() && m_oldParent != 0)
		{
			m_oldParent->addChildComponent(this);
			updateTextColour();
			setBounds(m_oldBounds);
			setHighlightedRegion(juce::Range<int>(0, 0));
		}
		
		repaint();
	}

	if(isCurrentlyModal())
	{
		exitModalState(0);
	}

	juce::TextEditor::focusLost(cause);
};

//------------------------------------------------------------------------------
void ModalTextEditor::textEditorReturnKeyPressed
( juce::TextEditor& editor )
{
	focusLost(focusChangedDirectly);
	moveKeyboardFocusToSibling(true);
	repaint();
};

//------------------------------------------------------------------------------
void ModalTextEditor::textEditorEscapeKeyPressed
( juce::TextEditor& editor )
{
	editor.setText(m_textBeforeEdit);

	focusLost(focusChangedDirectly);
	moveKeyboardFocusToSibling(true);
	repaint();
};

#else
//==============================================================================
// MAC
//==============================================================================
ModalTextEditor::ModalTextEditor()
: juce::TextEditor("ModalTextEditor")
, m_textBeforeEdit("")
{
	addListener(this);
};
	
//------------------------------------------------------------------------------
ModalTextEditor::~ModalTextEditor()
{
};
	
//------------------------------------------------------------------------------
void ModalTextEditor::mouseDown
( const juce::MouseEvent& e )
{
	juce::TextEditor::mouseDown(e);
	m_textBeforeEdit = getText();
	grabKeyboardFocus();
};
	
//------------------------------------------------------------------------------
void ModalTextEditor::textEditorReturnKeyPressed
( juce::TextEditor& editor )
{
	moveKeyboardFocusToSibling(true);
};
	
//------------------------------------------------------------------------------
void ModalTextEditor::textEditorEscapeKeyPressed
( juce::TextEditor& editor )
{
	moveKeyboardFocusToSibling(true);
	editor.setText(m_textBeforeEdit);
};
	
//------------------------------------------------------------------------------
#endif
JuceProspector
Offline
Last seen: 1 year 3 months ago
Joined: 20 Sep 2010 - 22:09
Re: VST Plugin Still Not Getting Keystrokes

Thanks so much for this. I will test it ASAP. I'm hoping that if this approach works then Jules will include it in the main release. There are a number of classes that either inherit from or aggregate TextEditor -- e.g. Label and Slider via Label -- so it becomes kind of a mess to reintegrate these kind of changes on every new Juce release.

steffen
Offline
Last seen: 6 months 2 weeks ago
Joined: 4 May 2010 - 11:52
Re: VST Plugin Still Not Getting Keystrokes

to avoid subclassing, we placed all the stuff required for keyboard focus in a combined MouseListener/TextEditorListener.

When creating the PluginEditor, the host type is checked, and the listeners are added to all TextEditors in the component hierarchy. What I like about that approach is that you don't change the functionality if the same components are used in a standalone application. What I don't like is that you need to manually add it for all components created *after* initialization of the PluginEditor (unless they are in a modal dialog which seems to work fine).

Here's our implementation of such a listener. There's an ugly work around because textEditorFocusLost was called twice (at least in ableton live on win) and for some reason the editor couldn't be removed from the desktop in the first call.

    ///////////////////////////////////////////////////////////////////////////
    class KeyboardFocusWorkaroundListener : public MouseListener,
                                            public TextEditorListener
    {
    public:

        KeyboardFocusWorkaroundListener(TextEditor* pTextEditor)
            : m_pTextEditor(pTextEditor),
              m_bRemoveWhenLosingFocus(false),
              m_pOriginalParent(0),
              m_bIsOnDesktop(false)
        {

        }

        void mouseDown(const MouseEvent& e)
        {   
            addEditorToDesktop();
        }

        void mouseExit(const MouseEvent e)
        {
 
        }

        virtual void textEditorTextChanged (TextEditor& editor)       { }
        virtual void textEditorReturnKeyPressed (TextEditor& editor)
        { 
            removeEditorFromDesktop();
        }
        virtual void textEditorEscapeKeyPressed (TextEditor& editor)
        { 
            removeEditorFromDesktop();
        }
        virtual void textEditorFocusLost (TextEditor& editor) 
        { 
            if (m_bRemoveWhenLosingFocus)
            {
                removeEditorFromDesktop();
            }
            else
            {
                m_bRemoveWhenLosingFocus = true;
            }
        }

        juce_UseDebuggingNewOperator

    protected:

        void addEditorToDesktop()
        {
            if (m_bIsOnDesktop) return;

            m_originalBounds = m_pTextEditor->getBounds();
            m_pOriginalParent = m_pTextEditor->getParentComponent();
            
            m_bRemoveWhenLosingFocus = false;

            m_pTextEditor->addToDesktop(ComponentPeer::windowIsTemporary);
            m_pTextEditor->grabKeyboardFocus();

            m_bIsOnDesktop = true;
        }

        void removeEditorFromDesktop()
        {
            if (!m_bIsOnDesktop) return;

            m_pTextEditor->removeFromDesktop();

            if (m_pOriginalParent != 0)
            {
                m_pOriginalParent->addChildComponent(m_pTextEditor);
            }

            m_pTextEditor->setBounds(m_originalBounds);

            m_bIsOnDesktop = false;
        }

    private:

        bool m_bIsOnDesktop;
        bool m_bRemoveWhenLosingFocus;

        Rectangle<int> m_originalBounds;
        Component* m_pOriginalParent;

        TextEditor* m_pTextEditor;

    };

If you're worried about subclassing, it might be worth going this way (but Jakob's is probably better tested, we didn't test much with Sonar). If you come up with something that combines both approaches, I'd be happy to test it in ableton live on win/osx.

RiphRaph
Offline
Last seen: 6 months 3 weeks ago
Joined: 12 Oct 2010 - 10:11
Re: VST Plugin Still Not Getting Keystrokes

Hi all,
Based on the code zamrate wrote few years ago (see an older thread about this problem), I wrote a class called MySliderLabel that I use to edit the value of my slider.
TheText Editor is actually a modal window so it REALLY gets the focus on the keyboard and loses it as soon as the user presses enter, escape or clicks elsewhere.
edit() is called from the ShowTextBox function of my slider.
Hope this will help some of you.
Cheers
Raph

(with a little update of the code)

MySliderLabel.h:

 
#ifndef __MYSLIDERLABEL_HEADER__
#define __MYSLIDERLABEL_HEADER__

#include "MySlider.h"
class MySlider;

class MySliderTextEditor : public TextEditor
{
public:

// if a mouse down event happens, exit the modal text editor window
	bool canModalEventBeSentToComponent (const Component* comp)
	{
		if (comp->isMouseButtonDownAnywhere())
		{
			exitModalState(0);
		}

		return false;
	}
};



class MySliderLabel : public Label
{
public:
   MySliderLabel();
   ~MySliderLabel();

   void setText(const String &text)
   {
      mText = text;
      repaint();
   }

   const String& getText(){return mText;}
   void edit();
   bool isBeingEdited(){return mbLabelIsEdited;};

  
private:   
	String		mText;
	MySlider*	       mpSlider;
	bool			mbLabelIsEdited;

    void  textEditorReturnKeyPressed (TextEditor &editor)
   {
      editor.exitModalState(0);
   }

    void  textEditorEscapeKeyPressed (TextEditor &editor)
   {
      editor.exitModalState(0);
   }

    void  textEditorFocusLost (TextEditor &editor)
   {
      editor.exitModalState(0);
   }

   void mouseUp (const MouseEvent& e);

};

#endif

MySliderLabel.cpp:

// MySliderLabel gets the focus on the sliderLabel

#include "MySliderLabel.h"

MySliderLabel :: MySliderLabel(Slider* slider)
{
  setRepaintsOnMouseActivity(true);	
   mpSlider = (MySlider*) slider;
   mpSlider->setSliderLabelPtr(this);

  mbLabelIsEdited = false;
}

MySliderLabel :: ~MySliderLabel()
{
}

// Caledl from MySlider::ShowTextEditor: pops up a modal text editor 
void MySliderLabel :: edit()
{
	mbLabelIsEdited = true;

	MySliderTextEditor* ed = new MySliderTextEditor();

	ed->setColour(TextEditor::backgroundColourId,Colours::white);
	ed->setColour(TextEditor::textColourId,Colours::black);

	ed->setColour(TextEditor::highlightColourId,Colours::black);
	ed->setColour(TextEditor::highlightedTextColourId,Colours::white);

	ed->setBounds(0, 0, getWidth(), getHeight());
	ed->setFont(Font ("Futura", 11, Font::bold));

	ed->setText(getText(),false);
	Range<int>  range(0, getText().length()); 
	ed->setHighlightedRegion(range);

	addAndMakeVisible(ed);
	ed->addListener(this);

	ed->runModalLoop();

	setText(ed->getText());

	deleteAndZero(ed);

	mbLabelIsEdited = false;
}

void MySliderLabel :: mouseUp (const MouseEvent& e)
{ 
	if (e.mouseWasClicked()
     && contains (e.getPosition())
     && ! e.mods.isPopupMenu()) 
	{
		mpSlider->showTextBox();
	}
}

MyLabel.cpp file:

#include "MyLookAndFeel.h"
#include "MySlider.h"

//==============================================================================
MySlider:: MySlider(const String& componentName)
               : Slider        (componentName)
{

	mpSliderLabel = NULL;
	setLookAndFeel (MyLookAndFeel::getInstance());
	setBufferedToImage(true);

}
//==============================================================================
MySlider:: ~MySlider()
{
}

//==============================================================================
void MySlider::showTextBox()
{
    if (mpSliderLabel)
      {
		const String suffix = getTextValueSuffix();
		mpSliderLabel->setText(getTextFromValue(getValue()).removeCharacters(suffix));
		mpSliderLabel->edit(); 
		setValue(getValueFromText(mpSliderLabel->getText()));	  
	}
}

MySlider.h:


#ifndef __MY_SLIDER_HEADER__
#define __MY_SLIDER_HEADER__

#include "../JuceLibraryCode/JuceHeader.h"
#include "MySliderLabel.h"

class MySliderLabel;
class MySlider:	public Slider
{
public:
	MySlider(const String& componentName);
	~MySlider();

	void showTextBox();

	void setSliderLabelPtr(MySliderLabel* ptr) {mpSliderLabel = ptr;};

private: 
	MySliderLabel* mpSliderLabel;
};

#endif

And filnally the lookAndFeel file:
MyLookAndFeel.h:

#ifndef _MY_LOOKANDFEEL_HEADER_
#define _MY_LOOKANDFEEL_HEADER_

#include "../JuceLibraryCode/JuceHeader.h"
// ==============================================================================

class MyLookAndFeel: public LookAndFeel
{
public:
	static MyLookAndFeel* getInstance();

private:
	Label* createSliderTextBox (Slider& slider); 
};

#endif

MyLookAndFeel.hcpp

#include "MyLookAndFeel.h"
#include "MySliderLabel.h"

//----------------------------------------------------------------------// 
MyLookAndFeel* MyLookAndFeel::getInstance()
{
	static  MyLookAndFeel myLookAndFeel; 
	return &myLookAndFeel;
}

// 
//==============================================================================

Label* MyLookAndFeel::createSliderTextBox(Slider& slider)
{
	MySliderLabel* l = new MySliderLabel(&slider);

    l->setJustificationType (Justification::centred);

    l->setColour (Label::textColourId, slider.findColour (Slider::textBoxTextColourId));

    l->setColour (Label::backgroundColourId,
                  (slider.getSliderStyle() == Slider::LinearBar) ? Colours::transparentBlack
                                                                 : slider.findColour (Slider::textBoxBackgroundColourId));
    l->setColour (Label::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId));

    l->setColour (TextEditor::textColourId, slider.findColour (Slider::textBoxTextColourId));

    l->setColour (TextEditor::backgroundColourId,
                  slider.findColour (Slider::textBoxBackgroundColourId)
                        .withAlpha (slider.getSliderStyle() == Slider::LinearBar ? 0.7f : 1.0f));

    l->setColour (TextEditor::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId));

    return l;
}
200gaga
Offline
Last seen: 4 months 2 weeks ago
Joined: 17 Aug 2011 - 15:41
Re: VST Plugin Still Not Getting Keystrokes

I can't find ShowTextBox in Label class.Did you write it on your own?

zamrate
Offline
Last seen: 1 year 2 months ago
Joined: 24 Sep 2007 - 17:33
Re: VST Plugin Still Not Getting Keystrokes

It's rather unbelievable that this question comes up ever again, when I've posted a solution, which worked, but for mysterious reasons never made its way into JUCE. Link: http://www.rawmaterialsoftware.com/viewtopic.php?f=8&t=1662&hilit=keystrokes&start=60 Check out the bottom of page 4 (last code I posted).

Shlomi
Offline
Last seen: 6 months 2 weeks ago
Joined: 28 Dec 2010 - 13:06
Re: VST Plugin Still Not Getting Keystrokes

Hi Zamrate.

I really like your solution cause it's not intrusive like other solutions suggested here (like adding components to the desktop upon focus gain).
Though I can't get the control modifier working in your solution, so I can't use it for now.

What I did was to add the entire component of my application to the desktop on focus gain/ mouse enter.

steffen
Offline
Last seen: 6 months 2 weeks ago
Joined: 4 May 2010 - 11:52
Re: VST Plugin Still Not Getting Keystrokes

zamrate wrote:
It's rather unbelievable that this question comes up ever again, when I've posted a solution, which worked, but for mysterious reasons never made its way into JUCE. Link: viewtopic.php?f=8&t=1662&hilit=keystrokes&start=60 Check out the bottom of page 4 (last code I posted).

I just tried your patch, and keyboard input indeed works perfectly in Live 8.2.1, but there is still trouble in Cubase 6 (32bit).

Many keys work well (numbers, letters, space, backspace, cursors, shift) but others don't: commas and the period key and the whole number block (which is weird because it looks like its keys are explicitly mapped in your code).

I'd like to get rid of the focus/desktop workarounds, but being able to enter decimal separators is an absolute must for those text fields.

Any ideas wants going wrong here? Maybe it makes a difference that I am using a German keyboard layout?

Pages