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!
