Could you tell me why you don't (often) use ApplicationCommandManager?

Recently, I’m finally getting what ApplicationCommandManager is and am trying to learn to use it.
Maybe it’s a fuzzy question, but I want to ask about the history and the use case about this class.

A series of Command classes including ApplicationCommandManager seems to assume a software which have command line mode, like Projucer, but I think it is also very useful for a software which has no command line but only GUI, because it helps to organize software design and even my mind.
To make commands means simulating a “interaction” between a software and the users. So I notice that it’s very important in both sotware design and implementation.
But, there is few samples which uses ApplicationCommandManager. Even in DemoRunner, it appears only in KeyMappingDemo, whose main purpose is for keyboad shortcut management only.
Seeing Github blame, it was made 13 years ago, and though there were
updates, it lacks post C++11 functions and great JUCE benefits in these days.
For example, adding lambda callback to ApplicationCommandInfo class will make it easy to attach to Component class. Instead of using enum, using Identifier class will be much helpful when I use ValueTree to manage parameters.

…Yes, I know it’s not easy to accomplish. I’m sorry… But can I ask questions about its concept.
I have three questions.

1, When you find any misunderstand in what I wrote above, please tell me.
2, About the future of ApplicationCommandManager (and other command classes)…Are there any plans to update it?
3, If there isn’t any plan to update, could you tell me the reason? Planning commands is wrong? I’m assuming the possibilities that softwares had to be driven by commandline, but now it’s no use to do(I’m newbie and don’t know the process or discussion at that time!)
Could you let me know the process about not (often) using ApplicationCommandManager class?

P.S.
My main question is above, but let me explain why I asked such above, to make questions more clearer and to make it easy to discuss.
I want to make a software which has GUI mode, and also command line mode.
The main purpose to allow command line mode is to make it easy to batch process.
Fully controllable from command line as GUI does is very important.
But I notice it’s hard to manage commands, models, and views at once.
When I want to add a parameter, I have to add Identifier, make chachedValue, refer to ValueTree, add command enum, add enum to getAllCommands(), getCommandInfo(), and perform(), make Component, and make a callback…Oh, I forgot to implement command line switch.
Of course, I don’t want silver bullet, but it seems there’s better way, isn’t there?

2 Likes

No answer…
Maybe I asked too silly question and it made no sense.
So can I ask a different question?
I’ll try to make things more concretely.

Here’s my code that is simplified as possible as I can.
I mainly refer to Projucer(MyApplication) and Tracktion Engine examples’ EditViewState(MyAppEngine) class.
And I omit codes about command line mode to make it more easy to understand. Please forget about command line mode.
I hope that it shows my intention.

//================MyAppEngine.h================================

namespace IDs
{
#define DECLARE_ID(name)  const juce::Identifier name (#name);
    DECLARE_ID(MyAppParameters)
    DECLARE_ID(MyToggleButtonState)
#undef DECLARE_ID
}

class MyAppEngine 
{
public:
    MyAppEngine()
    {
        MyToggleButtonState.referTo(params, IDs::MyToggleButtonState, &undo, false);
    }

    ValueTree params{ "MyAppParams" };
    CachedValue<bool> MyToggleButtonState;
    UndoManager undo;
}

//================MyApplication.h================================

class MyApplication : public JUCEApplication
{
public:
    MyApplication() = default;

    static MyApplication& getApp();

    /*...inherited member functions from JUCEApplication...*/

    enum MyCommandIDs
    {
        Open = 1,
        Save,
        Undo,
        Redo,
        ChangeMyToggleButtonState
    };
    std::unique_ptr<ApplicationCommandManager> commandManager;
    std::unique_ptr<MyAppEngine> engine;
private:
    std::unique_ptr<DocumentWindow> mainWindow;
};


//================Maincomponent.h================================

class Maincomponent : public juce::Component
                    , public ApplicationCommandTarget
                    , public ValueTree::Listener
{
    Maincomponent(MyAppEngine& engine);
private:
    MyAppEngine& engine;
    ToggleButton MyToggleButton;
    /*...inherited member functions...*/
}

//================Maincomponent.cpp================================

Maincomponent::Maincomponent()
    :engine(*MyApplication::getApp().engine)
{
    MyToggleButton.onClick = [this] { engine.MyToggleButtonState = !engine.MyToggleButtonState.get();;};
}

void MainComponent::valueTreePropertyChanged(ValueTree& , const Identifier& id)
{
    if(id == IDs::MyToggleButtonState)
    {
        MyToggleButton.setToggleState(engine.MyToggleButtonState.get(), dontsendNotification);
    }
}

ApplicationCommandTarget* MainComponent::getNextCommandTarget() { return nullptr; }

void MainComponent::getAllCommands(Array<CommandID>& commands)
{
    Array<CommandID> ids
    {
        MyApplication::Open                      ,
        MyApplication::Save                      ,
        MyApplication::Undo                      ,
        MyApplication::Redo                      ,
        MyApplication::ChangeMyToggleButtonState ,
    };
    commands.addArray(ids);
}

void MainComponent::getCommandInfo(CommandID commandID, ApplicationCommandInfo& result)
{
    switch (commandID)
    {
    case MyApplication::Open:
    {
        result.setInfo("Open", Open_Script, "File", 0);
        auto k = KeyPress::createFromDescription("ctrl + o");
        result.addDefaultKeypress(k.getKeyCode(), k.getModifiers());
        break;
    }
    case MyApplication::Save:
    {
        result.setInfo("Save", Save_Script, "File", 0);
        auto k = KeyPress::createFromDescription("ctrl + Shift + s");
        result.addDefaultKeypress(k.getKeyCode(), k.getModifiers());
        break;
    }
    case MyApplication::Undo:
    {
        result.setInfo("Undo", Undo_Script, "Edit", 0);
        auto k = KeyPress::createFromDescription("ctrl + z");
        result.addDefaultKeypress(k.getKeyCode(), k.getModifiers());
        break;
    }
    case MyApplication::Redo:
    {
        result.setInfo("Redo", Redo_Script, "Edit", 0);
        auto k = KeyPress::createFromDescription("ctrl + y");
        result.addDefaultKeypress(k.getKeyCode(), k.getModifiers());
        break;
    }
    case MyApplication::ChangeMyToggleButtonState:
    {
        result.setInfo("ChangeMyToggleButtonState", ChangeMyToggleButtonState_Script, "Edit", 0);
        auto k = KeyPress::createFromDescription("ctrl + 0");
        result.addDefaultKeypress(k.getKeyCode(), k.getModifiers());
        break;
    }
    default:
        break;
}

//I often use Utils namespace to  static functions 
bool MainComponent::perform(const InvocationInfo& info)
{
    switch (info.commandID)
    {
    case MyApplication::Open:
        Utils::OpenProject(engine);
        break;
    case MyApplication::SaveAsPreset:
        Utils::save(engine);
        break;
    case MyApplication::Undo:
        if(engine.undo.canUndo()) 
            engine.undo.undo();
        break;
    case MyApplication::Redo:
        if(engine.undo.canRedo())
            engine.undo.redo();
        break;
    case MyApplication::ChangeMyToggleButtonState:
        engine.MyToggleButtonState = engine.MyToggleButtonState.get();
        break;
    default:
        return false;
    }
    return true;
}


//================MyApp.cpp================================
void MyApplication::initialise(const juce::String&)
{
    engine = std::make_unique<MyAppEngine>();
    commandManager = std::make_unique<ApplicationCommandManager>();
    commandManager->registerAllCommandsForTarget(this);
    {
        //register first so that all widgets can receive command description.
        MainComponent comp;
        commandManager->registerAllCommandsForTarget(&comp);
        //destruct temporary MainComponent
    }

    mainWindow = std::make_unique<MainWindow>();//mainWindow is content owning MainComponent at constructor.
    mainWindow->grabKeyboardFocus();
    mainWindow->addKeyListener(commandManager->getKeyMappings());
}

My problem is,

  1. I have to manage both enum(MyApplication::ChangeMyToggleButtonState) and Identifier( IDs::MyToggleButtonState) at the same time.
  2. I have to write engine.MyToggleButtonState = !engine.MyToggleButtonState.get(); twice, at MyToggleButton.onClick and at bool MainComponent::perform(const InvocationInfo& info)

1 is about enum vs Identifier. Of course both are not the same. But my suggestion is, how about managing ApplicationCommandManager with Identifier? I think it’s sensible question, but I’m simply curious if there is any problem to change enum to Identifier.

2 is about invoking command manager to run a command. I couldn’t find a way, something like commandManager.invoke(MyApplication::ChangeMyToggleButtonState) then I can manage “ChangeMyToggleButtonState” command in one place.
Or, if there is a std::function in ApplicationCommandInfo and InvocationInfo it would be more neat. Is it a bad design?

1 Like

First thing I noticed here is that you should be using Button::setClickingTogglesState to set up your button as a toggle.

Next up is using the command system appropriately with buttons by directly associating a command ID with that particular button. You do this via Button::setCommandToTrigger.

From there you can remove all of your custom button state changing code entirely. The command manager will handle the button state.

I don’t think the JUCE website provides a tutorial on setting up and using command managers, so that could be a worthwhile request!

3 Likes

Hi! Thanks for your reply. I feel much appreciated.

Yes, I also found Button::setClickingTogglesState and Button::setCommandToTrigger .
I’m now on new project and I’m now using these method. I think it will be the solution, though I can’t say it’s perfectly working till I finish it.
So my “2” problem has been (almost) solved. Thank you @jrlanglois!
Then, I now have another question. Is there any examples which show me how to use ApplicationCommandManager for the parameters which should be handled by Sliders or ComboBoxes?
In my project currently, like that…

//===============Slider====================
//a member attribute whose class is almost singleton and statically callable,
//like MyApp   : JUCEApplication
//and has function static MyApp& getApp();
Array<Component*> commandFocusableWidgets;
Array<int>       commandFocusableWidgetsKey;

//public Member Function
void setWidgetFocus(int command)
{
   int index = -1;
   for (const auto& k : commandFocusableWidgetsKey)
   {
       if (k == command)
       {
           index = commandFocusableWidgetsKey.indexOf(k);
           break;
       }
   }

   //You forgot to add a focusable widget!
   jassert(index != -1);
   commandFocusableWidgets[index]->grabKeyboardFocus();
}

void addCommandFocusable(int command, Component* target)
{
   if (!commandFocusableWidgets.contains(target))
   {
       commandFocusableWidgets.insert(command, target);
       commandFocusableWidgetsKey.insert(command, command);
   }
}
//===============ComboBox=================
//let's think a ComboBox which changes bit depth.
//a member function in some class which is inherited by ApplicationCommandTarget.
void setNextBitDepth()
{
    auto& dep = app.engine->AudioBitDepth;
    dep = dep == 24
        ? 16
        : 24;
}
//=============implementation===============
bool perform(const InvocationInfo& info)override
{
   switch (info.commandID)
    {
    case MyEnum::SliderCommand:
        if(!MyApp::getApp().isHeadless())
           MyApp::getApp().setWidgetFocus(MyEnum::SliderCommand);
        return true;
    case MyEnum::ComboBoxCommand:
        setNextBitDepth();
        return true;
     //...
}

And, I would like to ask JUCErs “1” question again. Taking this discussion, and if there’s no performance difference between numeric type and juce::Identifier, I would suggest using CommandID = Identifier;. Taking backward compability, how about making a macro to switch using CommandID?
It’s just what came up my mind. Any comment, thoughts or telling me my misunderstands would much be appreciated, because it will be much help for my learning programming!
Thanks.

P.S.

I don’t think the JUCE website provides a tutorial on setting up and using command managers, so that could be a worthwhile request!

OK! But I don’t know how to request a tutorial. Where should I request? And What is the best way to make it progress?

There aren’t any examples in the codebase to my knowledge, but it sounds to me that you’re thinking about the problem in a weird way.

You can have other objects/functions listen for changes to your ComboBox and Slider (via their Listener child classes, via a Value, via a lambda function - depending on the class). When you get a listener callback, you can then trigger whatever command you need - whether this triggered command
is based on the value of the ComboBox or Slider is up to you entirely.

I’m simply suggesting making a separate post on the forum requesting a JUCE tutorial be added that covers setting up and using the ApplicationCommandManager.

it sounds to me that you’re thinking about the problem in a weird way.

Yes, I think as you said too. But ApplicationCommand classes also provide keyboard shortcuts for each commands. I want to know how JUCErs use it for the Sliders and ComboBoxes. I’m using it for keyboard focusing(Slider, which has editable TextBox) and change choice(ComboBox). Sorry for the unclear question!

Well, in this case, the issue is that the Component needs focus to get any keyboard (or any other kind of) input.

If your goal is to go from a command to achieve something against any Component, be it a Slider or what have you, then intercept that command appropriately and apply some effect to that particular Component.

Does that make more sense?

Thanks to your advice, I notice I was little obsessed to apply Commands for each Component.Sorry, I did not completely understand your previous advice! :crazy_face:
Then I should think GUI design and Commands design more separately.
Some of the Component and Commands happen to match, but they must not be one-to-one correspondence.
Now after I come to the understand, I wonder why I was so obsessed :grin:
Thanks!

I made a feature(tutorial) request post!

2 Likes

To suggest a reply to the OP: The reason not many of these exist is likely that an application would need to reach a relatively high level of complexity before it became the best choice. Good suggestion on a tutorial! I have an example somewhere, I’ll try to find it: but it sounds like you’ve solved the basics anyway!

2 Likes

Thank you for your reply!
I’m glad to hear that I’m not on the wrong way.

I’m really curious what you mention “an application would need to reach a relatively high level of complexity before it became the best choice”.
Sorry, I’m newbie and don’t have any ideas what it means.
Could you explain it deeper if you have any time?
Hope you find your example. Thanks :smile:

I dug out the example but it’s very old JUCE code with breaking changes… (it’s possibly from JUCE 3 days!) I’ll have a play with modernising it for recent JUCE.

My comment complexity was along lines of thinking about a simple single window application and that the ApplicationCommandManager is probably overkill, even if you have something like cut/copy/paste commands. But if your app has multiple windows and (say) cut/copy/paste has a different meaning in different windows or when different areas of windows are focused (or the app is in a different state). Then the command manager (for example) helps direct these commands and have them picked up where the action is required.

1 Like

Thank you very much for really interesting point , and I can see now with your explanation.
So, ApplicationCommandManager is used not only for the shortcuts but also for the each different commands on each different GUI by e.g. using if statement with InvocationInfo::originatingComponent on ApplicationCommandTarget::perform()?