ApplicationCommandTarget & TreeView - can't get my TreeViewItems to ever get their perform() invoked

Hi!

I have an application with several TreeViews in which I want to implement proper use of ApplicationCommandTargets.

In my main component I have the following:


    class MyWindow : public DocumentWindow {
    public:
        MyWindow(String commandLine) :
            DocumentWindow(...)
        {
            m_ContentComponent = std::make_unique<MainComponent>(this);

            m_ContentComponent->getApplicationCommandManager()
                .registerAllCommandsForTarget(JUCEApplication::getInstance());

            addKeyListener(m_ContentComponent->getApplicationCommandManager().getKeyMappings());
        }

        ~MyWindow() {
        }
        //==============================================================================
        void closeButtonPressed() {
            // When the user presses the close button, we'll tell the app to quit. This
            // window will be deleted by our shutdown() method.
            JUCEApplication::getInstance()->systemRequestedQuit();
        }

        MainComponent* getMainComponent() {
            return m_ContentComponent.get();
        }

    private:
        std::unique_ptr<MainComponent> m_ContentComponent;

        JUCE_LEAK_DETECTOR(MyWindow);
    };


    class MyApplication : public JUCEApplication,
        AsyncUpdater {
    public:
        MyApplication()
            : m_MyWindow() {}

        ~MyApplication() = default;

        void initialise(const String& commandLine) override {
               m_MyWindow = std::make_unique<MyWindow>(commandLine);
        }

        ApplicationCommandTarget* getNextCommandTarget() override {
            return  nullptr;
        }

        void getAllCommands(Array <CommandID>& commands) override {
            JUCEApplication::getAllCommands(commands);

            const CommandID ids[] = {
                CommandIDs::newProject,
                CommandIDs::saveDocument,
                CommandIDs::saveDocumentAs,
                CommandIDs::newProject,
                CommandIDs::open,
                CommandIDs::togglePlayback,
                CommandIDs::deleteSelectionSet,
                CommandIDs::undo,
                CommandIDs::redo,
                CommandIDs::cut,
                CommandIDs::copy,
    // CommandIDs::paste, This shouldn't handle paste
                CommandIDs::duplicate
            };

            commands.addArray(ids, numElementsInArray(ids));
        }

        void getCommandInfo(CommandID commandID, ApplicationCommandInfo& result) override {
            switch (commandID)
            {
            case CommandIDs::newProject:
                result.setInfo("New Project...", "Creates a new project", CommandCategories::general, 0);
                result.defaultKeypresses.add(KeyPress('n', ModifierKeys::commandModifier, 0));
                break;
            case CommandIDs::open:
                result.setInfo("Open...", "Opens a Jucer project", CommandCategories::general, 0);
                result.defaultKeypresses.add(KeyPress('o', ModifierKeys::commandModifier, 0));
                break;
            case CommandIDs::saveDocument:
                result.setInfo("Save", "Saves document", CommandCategories::general, 0);
                result.defaultKeypresses.add(KeyPress('s', ModifierKeys::ctrlModifier, 0));
                break;
            case CommandIDs::saveDocumentAs:
                result.setInfo("Save As", "Saves document as", CommandCategories::general, 0);
                break;
            case CommandIDs::undo:
                result.setInfo("Undo", "Undo", CommandCategories::editing, 0);
                result.defaultKeypresses.add(KeyPress('z', ModifierKeys::ctrlModifier, 0));
                break;
            case CommandIDs::redo:
                result.setInfo("Redo", "Redo", CommandCategories::editing, 0);
                result.defaultKeypresses.add(KeyPress('y', ModifierKeys::ctrlModifier, 0));
                break;
            case CommandIDs::cut:
                result.setInfo("Cut", "Cut", CommandCategories::editing, 0);
                result.defaultKeypresses.add(KeyPress('x', ModifierKeys::ctrlModifier, 0));
                break;
            case CommandIDs::copy:
                result.setInfo("Copy", "Copy", CommandCategories::editing, 0);
                result.defaultKeypresses.add(KeyPress('c', ModifierKeys::ctrlModifier, 0));
                break;
                /*case CommandIDs::paste:
                    result.setInfo("Paste", "Paste", CommandCategories::editing, 0);
                    result.defaultKeypresses.add(KeyPress('v', ModifierKeys::ctrlModifier, 0));
                    break;*/
            default:
                JUCEApplication::getCommandInfo(commandID, result);
                break;
            }
        }

        bool perform(const InvocationInfo& info) override {
            switch (info.commandID) {
            case CommandIDs::newProject:
                break;
            case CommandIDs::open:
                break;
            case CommandIDs::saveDocument:
                save();
                break;
            case CommandIDs::undo:
                undo();
                break;
            case CommandIDs::redo:
                redo();
                break;
            case CommandIDs::cut:
                cut();
                break;
            case CommandIDs::copy:
                copy();
                break;
            default:
                return JUCEApplication::perform(info);
            }

            return true;
        }

        void save() {}
        void undo() {}
        void redo() {}
        void cut() {}
        void copy() {}

        void paste() {
            // This shouldn't handle paste.
            jassertfalse;
        }

    private:
        std::unique_ptr<MyWindow> m_MyWindow;

        JUCE_LEAK_DETECTOR(MyApplication);
    };

    // This macro creates the application's main() function..
    START_JUCE_APPLICATION(MyApplication)

Then I have a TreeViewItem base class as follows:


class TIBase : public ThreeViewItem,
    public ApplicationCommandTarget
{
public:
    TIBase(Controller* c) {
        // This fetches the ApplicationCommandManager of the application
        r_Controller->getOwner()->getApplicationCommandManager().registerAllCommandsForTarget(this);
    }

    ~TIBase(); // ...
        

    bool isInterestedInDragSource(const DragAndDropTarget::SourceDetails& dragSourceDetails) override;

    ApplicationCommandTarget* getNextCommandTarget() override {
        return findFirstTargetParentComponent();
    }

    void getAllCommands(juce::Array <CommandID>& commands) override {
        const CommandID ids[] = {
            CommandIDs::paste
        };

        commands.addArray(ids, numElementsInArray(ids));
    }

    void getCommandInfo(CommandID commandID, ApplicationCommandInfo& result) override {
        switch (commandID) {
        case CommandIDs::paste:
            result.setInfo("Paste", "Paste", CommandCategories::editing, 0);
            result.defaultKeypresses.add(KeyPress('v', ModifierKeys::ctrlModifier, 0));
            break;
        }
    }

    bool perform(const InvocationInfo& info) override {
        if (info.commandID == CommandIDs::paste) {
            // This is never reached
            // yourComponent's->handleCommandMessage(CommandIDs::paste);
            return true;
        }

        return false;
    }

protected:
    JUCE_LEAK_DETECTOR(TIBase);
};

From this TIBase I derive the TreeViewItems of the TreeView.

All the above are the “important” parts of the code, of course the above wouldn’t compile as is, I cut many bits out for brevity.

My issue is, with the above, the TIBase perform method for the paste command is never invoked - when I click the empty space in the TreeView, or select any of the TreeViewItems, and pres “Ctrl+V”, the only method ever invoked is MyApplication::getNextCommandTarget() override { return nullptr; }.

Callstack:

|>|TWO.exe!MediatorApplication::getNextCommandTarget() Line 306|C++|
|---|---|---|
| |TWO.exe!juce::ApplicationCommandTarget::getTargetForCommand(const int commandID) Line 98|C++|
| |TWO.exe!juce::ApplicationCommandManager::getTargetForCommand(int commandID, juce::ApplicationCommandInfo & upToDateInfo) Line 226|C++|
| |TWO.exe!juce::KeyPressMappingSet::keyPressed(const juce::KeyPress & key, juce::Component * originatingComponent) Line 329|C++|
| |TWO.exe!juce::ComponentPeer::handleKeyPress(const juce::KeyPress & keyInfo) Line 195|C++|
| |TWO.exe!juce::ComponentPeer::handleKeyPress(int keyCode, unsigned int textCharacter) Line 177|C++|
| |TWO.exe!juce::HWNDComponentPeer::doKeyChar(int key, const __int64 flags) Line 3153|C++|
| |TWO.exe!juce::HWNDComponentPeer::peerWindowProc(HWND__ * h, unsigned int message, unsigned __int64 wParam, __int64 lParam) Line 3627|C++|
| |TWO.exe!juce::HWNDComponentPeer::windowProc(HWND__ * h, unsigned int message, unsigned __int64 wParam, __int64 lParam) Line 3456|C++|

What do I need to do to get the methods in TIBase to be invoked for “paste” to be handled there?
Navigating the treeview with the keyboard arrow buttons does work, so I am assuming it has “focus”…

The other commands, e.g. cut /copy, are invoked correctly, in MyApplication::perform() {...}.

For now I’ve done it for only one of the TreeViews, but eventually I want all the different treeviews to be able to distinctly handle commands, e.g. each one having its own cut/copy/paste, depending on what component was last clicked by the user. Do I need to do any work to track the “focus”, or is the plumbing for that behind the scenes in JUCE’s TreeViews?

Thanks!

Reading through the JUCE codebase I realize that even if I call registerAllCommandsForTarget(this); in the constructor of TIBase, the ApplicationCommandManager doesn’t keep track of the target instances through the “this” pointer, and, since no command target would return TIBase from its getNextCommantTarget() override, it will never be reached.

But then how is this CommandTarget system supposed to be used? Is it up to the end-developer to track current selection, and setting “setFirstCommandTarget” to match?

OR is this tracked from KeyboardFocus in some way I haven’t found?

addKeyListener(…) seems to have something to do with it, but that can only be called from Components, which doesn’t map straightforwardly to the model of JUCE’s TreeView - the root, or “empty space”, in a TreeView, is not a component per se.