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?

1 Like

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?