Menus and hotkeys


#1

Hey,

 

Say i have a window with an associated menu which contains some commands, like 'save' or 'save as' that i would like to trigger from keypresses like 'ctrl+s' - pretty standard. How do i go about it? I came as far as deriving my own implementation of menubarmodel which i attach to a derived DocumentWindow. Using MenuBarModel::getMenuForIndex works correctly for creating simple popup menus, and ::menuItemSelected works as expected.

However I'm unsure about how to expand to hotkeys/shortcuts. It seems you have to attach a (static) applicationcommandmanager to the model? But i dont know how to use it, basically. Even though there's documentation, it doesn't really explain the procedure / design of the system - any guidelines/hints would be appreciated.

 

Also, since applicationcommandmanager is static/global how do i attach instance data to it? 'save' commands are not related to an actual ui element, but i may have multiple windows that save's different data based on what window is active.

 

Thanks for any help


#2

Here's what i do.

I have an "App" object that holds all my app global things. Here i put the ApplicationCommandManager. Then i make an ApplicationCommandTarget which i call the Commander and also put this in the App object. It can either be told the manager or know from the global App.

 

sketch:


class Commander: public ApplicationCommandTarget
{
public:
    Commander() { _init(); }
    Commander(ApplicationCommandManager* cman) { setManager(cman); }

 void setManager(ApplicationCommandManager* cman) 
    {
        _cman = cman;
        
        //_cman.getKeyMappings()->resetToDefaultMappings();
        
        _cman->registerAllCommandsForTarget (this);
        _cman->registerAllCommandsForTarget (JUCEApplication::getInstance());
        _cman->setFirstCommandTarget(this);
    }

...
 ApplicationCommandManager*  _cman;

};

The Commander also defines all the interface functions required by an ApplicationCommandTarget. Inside, it has a set of commands that i want to use in my app. it also knows the keypresses, descriptions and all that. it uses this info to implement the required functions, including filling the ApplicationCommandInfo with the appropriate keypress info in `getCommandInfo'.

Then i have another class which is going to host the menu bar and the main content. I call this the MenuHost. It knows the Commander.

sketch:


class MenuHost:
    public Component,
    public MenuBarModel
{
public:

MenuHost() : _menuBar(this) { _init(); }

void                setCommander(Commander* c)  { _commander = c; }


void resized()
    {
        int w = getWidth();
        int h = getHeight();
        if (w > 0 && h > 0)
        {
            int mh = jmin(_controlsHeight, h);
            Rectangle<int> menuBox(0, 0, w, mh);
            _menuBar.setBounds(menuBox);
            
            if (_content)
            {
                Rectangle<int> contentBox(0, _controlsHeight, w, h - mh);
                _content->setBounds(contentBox);
            }
        }
    }


...


    int                 _controlsHeight;
    Component*          _content;
    MenuBarComponent    _menuBar;
    ...
    Commander*          _commander;

};

OK, so additionally, this MenuHost has to reference the commander when creating the menus so as to invoke the commands (that can also be invoked by keys being already published by the Commander).

The MenuHost is a MenuBarModel and is required to define various things, in particular `getMenuForIndex'. Here it must call `PopupMenu.addCommandItem', with in my case information from the Commander about the command (the CommandManager, the ID, the name)

the last thing to call is


  addKeyListener (app._cman.getKeyMappings());

in your main window (here app._cman is my app's command manager object i mentioned at the start).

So in my case the Commander object is not connected to the UI, but the MenuHost is. and it is the MenuHost that wants to bind to the command manager with its menu items.

I've missed out explaining where my actual commands are defined. I dont much like the long switch tables, so my Commander class has a set of Command objects. these hold everything about the command including to callbacks to their actions. You can either do something similar or have tables of operation somewhere.

hope this helps. i admit it's not obvious. i had to read the juce demo code before i could make it work.

 

 

 

 

 

 






#3

It seems our structure is quite similar, which is good! I managed to make some sense of it all, but i was stuck so long until i realized you have to call this:


_cman->setFirstCommandTarget(this);

Which was not obvious.. especially after one has already called a similar function with the same input:


_cman->registerAllCommandsForTarget (this); // registers commands from the target, but not the actual target?

hope this helps. i admit it's not obvious. i had to read the juce demo code before i could make it work.

Sure did! I had to read the sdk instead...

One last thing, is it a problem to have multiple applicationcommandmanagers? I find the cleanest code setup to be that the appcm is a member of your window class, so i can actually work with the window object in the perform() methods instead of it being global?


#4

 

i dont know if you can have multiple command managers. Although i put these pseudo-singletons in my App class, quite often i also pass them into things that need them rather than accessing the global. For example my Commander class is given a pointer to the ApplicationCommandManager on construction.

The global is often only there for desruction in ~App().