Detecting Audio Plugin Editor Window Movement

Hello Everyone,

I am trying to detect movement of the audio plugin editor window, but without much success. I am hoping that someone can give me a hint, or confirm there is an issue in the framework.

I am on JUCE v4.3.1 at the moment, but I would convince my people to upgrade to latest if that would solve this problem. Currently working on macOS Sierra 10.12.4 and need for this to work eventually on Win32.

As I said, I need to detect when the audio plugin editor is moved by the user. (The reason is that we are integrating a chromium embedded framework browser window for the plugin editor, and as was pointed out in the docs for ComponentMovementWatcher (https://www.juce.com/doc/classComponentMovementWatcher#details), I need to keep my custom window in the right place, just like JUCE’s own WebBrowserComponent.)

Besides ComponentMovementWatcher docs, I have also seen this post (ComponentMovementWatcher class is not working as expected), where near the end of the discussion, Jules says “The only thing it [the ComponentMovementWatcher] watches is the target component, and it will only tell you when that component is moved relative to either the screen (if it is itself a window) or the top-level comp, if it’s embedded at some arbitrary depth within a window.” I want to know when the window itself moves relative to the screen. Simple enough.

I realized after some experimentation that you can’t just create the ComponentMovementWatcher too early, because the hierarchy of Components might not be fully built, and the ultimate parent target Component (the window) of the ComponentMovementWatcher might not be in the Component hierarchy yet (to pass into the ComponentMovementWatcher(Component *component) constructor). My solution was to make my browser component a ComponentListener, and create my ComponentMovementWatcher in componentParentHierarchyChanged(). The last time the function is called, everything ought to be built, and the call to browserComponent.getTopLevelComponent() should return the top level window:

class TopWindowMovementWatcher;

class WebBrowserComponent :  public Component, public ComponentListener
{
    . . .
    void componentParentHierarchyChanged( Component & component )
    {
        topWindowMovementWatcher = new TopWindowMovementWatcher( *this );
    }
    
    ScopedPointer<TopWindowMovementWatcher> topWindowMovementWatcher;
};

class TopWindowMovementWatcher : public ComponentMovementWatcher
{
    TopWindowMovementWatcher( WebBrowserComponent& browserComponent )
        : ComponentMovementWatcher( browserComponent.getTopLevelComponent() ),
          webBrowserComponent( browserComponent )
    {
    }

    . . .
    // From ComponentMovementWatcher
    void componentMovedOrResized( bool wasMoved, bool wasResized ) override;
    {
        Rectangle<int> bounds( webBrowserComponent.getScreenBounds() );
        . . .
        std::cerr << "typeid(*getComponent()).name(): " << typeid(*getComponent()).name() << std::endl;
    }

    WebBrowserComponent & webBrowserComponent;
};

The problem is that the componentMovedOrResized() function gets called only a few times at the time the plugin editor is created, and never again even when I move the editor on screen. Note that the top level window just doesn’t ever seem to appear, as my diagnostic messages show:

. . .
typeid(*getComponent()).name(): 15MyMainComponent
typeid(*getComponent()).name(): 28MypluginAudioProcessorEditor
typeid(*getComponent()).name(): N6JuceAU16EditorCompHolderE
typeid(*getComponent()).name(): N6JuceAU16EditorCompHolderE
typeid(*getComponent()).name(): N6JuceAU16EditorCompHolderE
typeid(*getComponent()).name(): N6JuceAU16EditorCompHolderE

A search in the JUCE codebase shows that the internal class EditorCompHolder is a Component and not any sort of window:

class EditorCompHolder  : public Component
{. . .}

And the EditorCompHolder is added to the desktop in the same JUCE source file:

static NSView* createViewFor (AudioProcessor* filter, JuceAU* au, AudioProcessorEditor* const editor)
{
    EditorCompHolder* editorCompHolder = new EditorCompHolder (editor);
    . . .
    editorCompHolder->addToDesktop (0, (void*) view);
    . . .
}

(These last lines I don’t completely understand.)

But in the end, the EditorCompHolder never moves relative to its parent (something on the “desktop”, I guess), and I don’t get componentMovedOrResized() messages.

So do I have a fundamental misunderstanding of how this is supposed to work: you register a ComponentMovementWatcher to listen for movement of its target relative to the screen (if the target is a window) or the top level Component (if the target is not a window) by passing that target to the ComponentMovementWatcher constructor? Or is there something not quite correct in the JUCE implementation?

Thanks for any help,

Isaac

Full disclosure: I haven’t read your post thoroughly, but the plug-in window is owned by the host… so you wouldn’t get a notification if it’s moved.

Rail

1 Like

Hi Rail,

That would explain it.

My work around at this point is to set up a Thread that periodically checks if the position of the editor window has changed, and if it has, move my browser window to the correct location.

I already have a loop like that in order to integrate the event loop of the chromium embedded framework (cef) into the JUCE event loop: never enter the cef loop, just call the cef non-blocking event handling function periodically from the JUCE event Thread. Yes, this is polling, but this is how you have to do it. And for integrating cef, this works pretty good. It doesn’t use up too many machine cycles and it is safe if you properly call std::condition_variable::wait_for() in the loop and adjust the polling frequency carefully.

So I stick another call to do my editor window position change check into this same callback function (for polling cef event handling), and what I get is a not very smooth move: the browser part of the window lags behind the title bar when I drag the title bar. I can’t go with this other than as an experiment. It makes me look like a clown.

Window moves (as well as resizes) are pretty fundamental. Is there any way to get any sort of callback as the plugin editor window is moved? Any info would be appreciated even if it takes separate implementations for macOS and Win32.

Thanks.

Isaac

If there would be a way to get this information (especially while live-moving the window) then JUCE would have a lot less bugs to worry about (see this thread for example). I don’t think there is an easy cross-platform way to do this. See this bug for example:

Has there been any advances in the engine that can service this? I need the same thing, I too have implemented a timer callback that checks the screen bounds but I don’t like it, it’s not reactive enough. I see the standalone window is a document window, when I move the editor window I see Component::sendMovedResizedMessages run from the resizable window that the standalone filter window has, if I could just add a listener to that I’d be fine. But I can’t get my hands on it. The editor object doesn’t know about it, the editor doesn’t even have a peer, or parent component.

I made an engine edit, I sent through the context standalone window to the editor, now my editor objects can listen to the movements of the outermost window. Works really well.

If you are only interested in the standalone, you can call the following code from your editor component, to find the parent DocumentWindow that contains it:

juce::DocumentWindow* standaloneWindow = findParentComponentOfClass <juce::DocumentWindow> ();

but probably you cannot call it immediately from the constructor because in the constructor your editor will not have a parent yet.

I suggest you to call it in the body of the parentHierarchyChanged() callback, that’s most likely the correct place that will receive the notification when your editor gets added to the window that will hold it.

Thank you, that works very well.

Would it be different for VST? Same type of thing? Will VST have a document window too? I’ve wrapped this code up in JUCE_STANDALONE_APPLICATION just to be sure for the meantime.

Unfortunately not: for actual plug-in formats that are hosted in a DAW, your plug-in editor is the root of the JUCE Components hierarchy and it shouldn’t be expected to have a parent JUCE Component.

It may have a heavyweight peer component provided by the DAW, but I strongly advice against trying to use that for anything, because it’s very implementation dependent: any DAW can have it set up its own way and furthermore it can change with any new version of the same DAW too, so you are basically relying on undefined behaviour. Definitely not future proof.

You’re right, I just tested in VST using the simple audio plugin. There are lots of doc windows being constructed and destructing but by the time the editor is moving, it’s a different component that is sending the componentMovedOrResized messages, and yet there is still a component sending messages, I’d love to be able to grab that, whatever it is.

For now I have adaquate componentMovedOrResized being sent in standalone, then reliance on the not so good timercallback in vst 3. Not ideal but it works and that’s the maint hing. Thanks.