Cubase & keyboard input


#1

Hi there,

when running a Juce-plugin in Cubase (e.g. the demo plugin), Cubase only passes keys that don’t have any Cubase functionality. So neither the num-pad nor enter/backspace work in TextEdit fields or editable slider labels. (Cubase 5.5, latest git from Juce and Windows Vista/7).
This also seems to be only a problem with Cubase & Juce. A VSTGUI-demo with an added textinput worked in Cubase. Other sequencers don’t show that problem at all.

Is there any switch etc. I overlooked in Juce to get real keyboard focus in Cubase?

Chris


#2

This is not a prerogative of Cubase: other hosts, even on mac, behave strangely with keyboard input in plug-ins. The solution I came up with, is to determine what host has loaded the plug-in and, for those that require it, replace the regular TextEditor with a equivalent one, inherited from TextEditor, but that performs an addToDesktop call when it gains the focus, and then adds itself back into its parent component with an addChildComponent when it loses its focus


#3

Thanks for the response.
Why don’t you always use the modified TextEdit? Also, to use this for a slider I think it would be nessecary to completely rewrite the Slider component since the Textedit is used only inside the class. Is that correct?

Chris


#4

as for the first question: there are hosts, on the contrary, that lose some characters if the texteditor does not belong to the plug-in window. This is odd enough, but it is the behaviour I experienced and didn’t bother to look further because of deadlines approaching.

As for the second question, yes, this basically needs a partial rewrite / inheritation of those components that use a texteditor internally, unless JUCE does not provide a callback that informs you that such editor is about to be opened, or calls a factory method for a TextEditor to use instead of actually creating its own one internally.


#5

Didn’t you post some code about this a few weeks ago? I can’t seem to find it with the search function…


#6

This is the code I use for it.

I kinda implemented it in an amalgamated fashion, so you find the class definition and then the implementation of the methods and functions, too… if you are going to use this in your code, probably you will need to spread the different part of it among header and source files

/********* Start of inlined file: oj_BestTextEditor.h *********/
#ifndef __OJ_BESTTEXTEDITOR_H__
#define __OJ_BESTTEXTEDITOR_H__

class HackedTextEditor1 :
    public TextEditor
{
// ---------- members
    bool spippoling;    // Didn't know how to call this flag... if you come up with a meaningful name, let me know. This has no meaning
    Component* storedParentComponent;
    Rectangle <int> storedBoundsInParentComponent;

public:
// ---------- constructors
    HackedTextEditor1 (const String& componentName = String::empty, const tchar passwordCharacter = 0);
    virtual ~HackedTextEditor1 ();

// ---------- callbacks
    virtual void focusGained (FocusChangeType cause);
    virtual void focusLost (FocusChangeType cause);

private:
// ---------- not implemented
    /** @internal */
    HackedTextEditor1 (const HackedTextEditor1&);
    /** @internal */
    const HackedTextEditor1& operator= (const HackedTextEditor1&);

};

TextEditor* oj_createBestTextEditorForPlugin (const String& componentName = String::empty, const tchar passwordCharacter = 0) throw ();
TextEditor* oj_createBestTextEditor (const String& componentName = String::empty, const tchar passwordCharacter = 0) throw ();

#endif   // __OJ_BESTTEXTEDITOR_H__
/********* End of inlined file: oj_BestTextEditor.h *********/

/**
Same parameters of TextEditor(...)
*/
HackedTextEditor1::HackedTextEditor1 (const String& componentName, const tchar passwordCharacter) :
    TextEditor (componentName, passwordCharacter),
    spippoling (false)
{
}

HackedTextEditor1::~HackedTextEditor1()
{
}

void HackedTextEditor1::focusGained (FocusChangeType cause)
{
    if(spippoling)
        return;

    TextEditor::focusGained(cause);

    if(!isOnDesktop())
    {
        storedParentComponent = getParentComponent();
        jassert(storedParentComponent != 0);
        storedBoundsInParentComponent = getBounds();

        spippoling = true;
        addToDesktop(0);
        grabKeyboardFocus();
        spippoling = false;
    }
}

void HackedTextEditor1::focusLost (FocusChangeType cause)
{
    if(spippoling)
        return;

    if(isOnDesktop())
    {
        jassert (storedParentComponent != 0);

        spippoling = true;
        storedParentComponent->addChildComponent(this);
        setBounds(storedBoundsInParentComponent);
        spippoling = false;
    }

    TextEditor::focusLost(cause);
}

TextEditor* oj_createBestTextEditorForPlugin (const String& componentName, const tchar passwordCharacter) throw ()
{
	static PluginHostType hostType;

#if JUCE_MAC

    return new TextEditor (componentName, passwordCharacter);

#elif JUCE_WINDOWS

	if (hostType.isTracktion() || hostType.isSonar())
		return new TextEditor (componentName, passwordCharacter);

	return new HackedTextEditor1 (componentName, passwordCharacter);

#else
#error Unsupported OS
#endif

}

TextEditor* oj_createBestTextEditor (const String& componentName, const tchar passwordCharacter) throw ()
{
#if JucePlugin_Build_Standalone
    return new TextEditor (componentName, passwordCharacter);
#else
    return oj_createBestTextEditorForPlugin (componentName, passwordCharacter);
#endif
}

Basically, calling oj_createBestTextEditor with the same parameters as the TextEditor constructor, will result in an appropriate TextEditor being created for you, provided that you correctly set the JucePlugin_Build_Standalone.

If you are using this in a plug-in only project, you can use oj_createBestTextEditorForPlugin directly.


#7

Thanks!


#8

Thanks. That should help a lot.

Chris


#9

I just implemented the hacked TextEdit in a new Label class by simply reimplementing createEditorComponent() with the changed TextEdit component. I had some problems with the component apearing somewhere else on screen, but solved that with a setBounds() in editorShown() (though I can’t figure out, why the component changed position).

It nonetheless isn’t possible to edit the label. The edit field looses keyboard focus as soon as it is shown (caret disappears, but text stays highlighted). I tried to trace where the focus is lost, but without success. This is the same with the TextEdit field being somewhere else (e.g. without a new editorShown()).
Any suggestions what I missed or how this could be fixed?

Thanks in advance,
Chris


#10

I don’t have integrated it into the editable label. I was dealing with that case by adding a mouseListener to the label to intercept the double click, then opening the hacked texteditor “by hand”

I remember having done some experiments with the label though, but I don’t have the code handy now…


#11

Thanks Yfede for the tip.
The double click method works great for Label (only tested on Ableton Live)

Kevin


#12

This is a beautiful hack! and it works miracles!

I augmented the class to listen to componentVisibilityChanged of the storedParentComponent while the text editor has focus. The handler simply calls Component::unfocusAllComponents() if the original parent is hidden and we have focus. That moves the text editor from the desktop back to the, now hidden, parent.

Thank you so much for sharing this piece of code!