Keybord Bindings

Programmers discuss here anything related to FreeOrion programming. Primarily for the developers to discuss.

Moderator: Committer

Message
Author
User avatar
strooka
Space Kraken
Posts: 165
Joined: Tue Jun 23, 2009 5:34 pm
Location: Bielefeld, Germany

Keybord Bindings

#1 Post by strooka »

hi all, i'm going to start to make a menu for the keyboard bindings.

i'll implement it as a submenu in the options menu.

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 12772
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: Keybord Bindings

#2 Post by Geoff the Medio »

strooka wrote:hi all, i'm going to start to make a menu for the keyboard bindings.

i'll implement it as a submenu in the options menu.
Before making a menu, it would be better to make a mechanism that will be used to implement configurable keyboard bindings.

Edit: I had some discussions with tzlaine on this subject several months ago. I'll paste some of it here:
> Is it possible to convert back and forth between a std::string and
> GG::Key using a string representation nicer than the enum entries'
> names?

No. Below it doesn't sound like that's what you want exactly, though.
Ctrl-X is actually not a GG::Key -- it's a GG::Key and a GG::KeyMod.
- Hide quoted text -

> I'm thinking about making a customizable hotkey system, and
> want to be able to store hotkeys in the config.xml file, and need to
> convert them to and from human-readable and editable text to do so.
>
> I'm thinking that the entries in config.xml would look something like this:
>
> <hotkeys>
> <galaxy-map>
> <command-name> alt g </command-name>
> <other-name> ctrl F10 </other-name>
> </galaxy-map>
> <production>
> <command-name> ctrl alt shift meta q </command-name>
> <other-name> ctrl spacebar </other-name>
> </production>
> </hotkeys>
>
> where <galaxy-map> and <production> are screens on which the hotkeys
> are valid, and <command-name> are specific in-game actions that
> hotkeys can activate, and the text like " ctrl x " indicates the key
> combination that is the hotkey. Something like this should be easy to
> edit in the text file, and store and retreive programatically after
> setting the hotkey in an options screen GUI.
>
> The alt, ctrl, shift and meta symbols would be flags that are detected
> by the parser and used to set boolean variables in the hotkey object,
> but g, x, q and w, spacebar or F10 need to be converted to and from
> GG::Key.
>
> Based on the loaded hotkeys, appropriate accelerators and signals
> would be set up and connected to in-game response functions by a
> centralized hotkey manager.
>
> In Base.h, GG::Key is added to a GG_ENUM_MAP, but I'm guessing the
> string representation will be awkward and unobvious for users, like
> GGK_F10 and GGK_PLUS instead of just F10 and + (respectively).
>
> I suppose GGK_LESS will work better in an XML file than < though
> "lessthan" would be better.
>
> And now that I think of it, are there any keyboard layouts that have a
> pure < key that isn't shift and . or similar?
>
> If the user holds shift and presses the . and < key, which GG::Key is
> generated? . + shift (as a ModKey) or just < ?

I think a key binding manager/UI is in order, I just don't think you
should waste your time with the "user-editable" XML file. We'd be
better off with a much more usable GUI. If done properly, this allows
us to avoid 1) the great difficulty many users have with writing
well-formed XML 2) the obscurity of the configuration options (in an
file instead of in the UI) and 3) the string-to-key-combination
parsing and what-happens-with-shift-and-CapsLock problems -- most such
UIs ask the user to pick a key binding, and then hit some keys to
indicate the new binding. We can just directly record the GG::Key and
GG::KeyMods the user presses.
>> Is it possible to convert back and forth between a std::string and
>> GG::Key using a string representation nicer than the enum entries'
>> names?
>
> No. Below it doesn't sound like that's what you want exactly, though.
> Ctrl-X is actually not a GG::Key -- it's a GG::Key and a GG::KeyMod.

We'll probably need to write a custom GG::Key to std::string converter
anyway, for use in the GUI. It's fine to say that we don't need to
worry about human readability and editability of entries in
config.xml, but we can't be showing users GGK_RIGHTBRACKET and similar
in the in-GUI list of hotkeys that have been set.

> I think a key binding manager/UI is in order, I just don't think you
> should waste your time with the "user-editable" XML file. We'd be
> better off with a much more usable GUI. If done properly, this allows
> us to avoid 1) the great difficulty many users have with writing
> well-formed XML 2) the obscurity of the configuration options (in an
> file instead of in the UI) and 3) the string-to-key-combination
> parsing and what-happens-with-shift-and-CapsLock problems -- most such
> UIs ask the user to pick a key binding, and then hit some keys to
> indicate the new binding. We can just directly record the GG::Key and
> GG::KeyMods the user presses.

I was planning to make a GUI as well, but thought it would be nice if
the config.xml entries were clear and easy-to-edit as well.

...

The simplest way to do customizable hotkeys seems to me to be to just
call to the options db whenever connecting accelerator signals and
setting or disabling keyboard accelerators. This would just replace
the hard-coded references to GGK_whatever to a call to
GetOptionsDB().Get<Hotkey>("UI.hotkey.particular-screen.command-name")

A problem with this is that if the hotkeys are changed while FO is
running, the changed accelerators will need to be removed and re-set
and all the signals reconnected. Removing an accelerator apparently
doesn't disconnect its signal, so all those signal connections would
need to be stored as well, in each class that defines a hotkey and
connects signals related to it.

A hotkey manager could do the accelerator upkeep internally, and do
something like providing public access to signals for a particular GUI
command (that is always the same signal and doesn't need to be
reconnected outside the hotkey manager), rather than a particular
keypress. I'm not sure how best to design the manager's interface
though; I have two ideas:

1) expose signals, indexed by a name given when a hotkey is
registered. For example, in MapWnd, we might do something like:

GetHotkeyManager().RegisterHotkey("map-zoom-in",
default_keypress_for_this_command);

where "map-zoom-in" is just a name used later to get the signal that
this hotkey keypress would fire:

GG::Connect(GetHotkeyManager().HotkeySignal("map-zoom-in"),
&MapWnd::ZoomIn, this);

...

2) pass a void (*FunctionPointer)() when registering a hotkey. For example...

GetHotkeyManager().RegisterHotkeyCommand("map-zoom-in",
default_keypress, &MapWnd::ZoomIn, this);

(for now I'm ignoring issues of member function pointers and how to
declare such a function in a way that would let the this pointer be
accepted in a type-safe way)

...

In either case, the hotkey manager would then register an option with
the options db to determine which keypress to use, with the default
keypress given by the RegisterHotkey parameter.

...

Additionally, what do you think about having a hotkey scope parameter
when registering hotkeys? The scope would be a string passed when
registering the hotkey...

GetHotkeyManager().RegisterHotkeySomehow("specific-command-name",
"command-scope", etc...);

If the scope is "" then the hotkey is always active, but if the scope
is some other string, the hotkey will only function (execute its
command or fire its signal) if

GetHotkeyManager().EnableHotkeyScope("some-scope");

has been called. Hotkeys could be similarly disabled like so:

GetHotkeyManager().DisableHotkeyScope("some-other-scope");

This would be useful when switching between production, design,
mapwnd, research, etc. where we might have commands that we don't
want to be activated unless the player is looking at the appropriate
screen.
>>> Is it possible to convert back and forth between a std::string and
>>> GG::Key using a string representation nicer than the enum entries'
>>> names?
>>
>> No. Below it doesn't sound like that's what you want exactly, though.
>> Ctrl-X is actually not a GG::Key -- it's a GG::Key and a GG::KeyMod.
>
> We'll probably need to write a custom GG::Key to std::string converter
> anyway, for use in the GUI. It's fine to say that we don't need to
> worry about human readability and editability of entries in
> config.xml, but we can't be showing users GGK_RIGHTBRACKET and similar
> in the in-GUI list of hotkeys that have been set.

No, but why wouldn't we show "]" instead of "GGK_RIGHTBRACKET"?

>> I think a key binding manager/UI is in order, I just don't think you
>> should waste your time with the "user-editable" XML file. We'd be
>> better off with a much more usable GUI. If done properly, this allows
>> us to avoid 1) the great difficulty many users have with writing
>> well-formed XML 2) the obscurity of the configuration options (in an
>> file instead of in the UI) and 3) the string-to-key-combination
>> parsing and what-happens-with-shift-and-CapsLock problems -- most such
>> UIs ask the user to pick a key binding, and then hit some keys to
>> indicate the new binding. We can just directly record the GG::Key and
>> GG::KeyMods the user presses.
>
> I was planning to make a GUI as well, but thought it would be nice if
> the config.xml entries were clear and easy-to-edit as well.

I really think that if we have a suitable GUI, saying "screw the
editable file format" is probably a good idea. Too many chefs, etc...

> The simplest way to do customizable hotkeys seems to me to be to just
> call to the options db whenever connecting accelerator signals and
> setting or disabling keyboard accelerators. This would just replace
> the hard-coded references to GGK_whatever to a call to
> GetOptionsDB().Get<Hotkey>("UI.hotkey.particular-screen.command-name")

Maybe... but what does the type HotKey look like?

> A problem with this is that if the hotkeys are changed while FO is
> running, the changed accelerators will need to be removed and re-set
> and all the signals reconnected. Removing an accelerator apparently
> doesn't disconnect its signal, so all those signal connections would
> need to be stored as well, in each class that defines a hotkey and
> connects signals related to it.

[snip]

I think this is a non-issue. If an accelerator is removed, it will
never fire its associated signal, and so it doesn't matter what is
connected to it or not. Am I missing something?

> Additionally, what do you think about having a hotkey scope parameter
> when registering hotkeys? The scope would be a string passed when
> registering the hotkey...
>
> GetHotkeyManager().RegisterHotkeySomehow("specific-command-name",
> "command-scope", etc...);
>
> If the scope is "" then the hotkey is always active, but if the scope
> is some other string, the hotkey will only function (execute its
> command or fire its signal) if
>
> GetHotkeyManager().EnableHotkeyScope("some-scope");
>
> has been called. Hotkeys could be similarly disabled like so:
>
> GetHotkeyManager().DisableHotkeyScope("some-other-scope");
>
> This would be useful when switching between production, design,
> mapwnd, research, etc. where we might have commands that we don't
> want to be activated unless the player is looking at the appropriate
> screen.

I think this is a good idea in principle, but I'd rather use an
enumeration instead of strings (i.e. enum KeyContext {MAP, COMBAT,
PRODUCTION, ...};). As for how to manage the registering and
deregistering, or connecting and disconnecting, we should put such
transitions into the HumanClientFSM. It already has all the mechanism
in place to do many of these transitions, and adding new states is
relatively easy. Otherwise, we'll be partially reinventing this
particular wheel inside HotkeyManager.
>> We'll probably need to write a custom GG::Key to std::string converter
>> anyway, for use in the GUI. It's fine to say that we don't need to
>> worry about human readability and editability of entries in
>> config.xml, but we can't be showing users GGK_RIGHTBRACKET and similar
>> in the in-GUI list of hotkeys that have been set.
>
> No, but why wouldn't we show "]" instead of "GGK_RIGHTBRACKET"?

Being able to do that is why we'd need a GG::Key to string converter.
(I realize "keyboard symbols have been cleverly chosen to map to
ASCII" but that doesn't help for keys without a recognizable ASCII
representation.)

>> The simplest way to do customizable hotkeys seems to me to be to just
>> call to the options db whenever connecting accelerator signals and
>> setting or disabling keyboard accelerators. This would just replace
>> the hard-coded references to GGK_whatever to a call to
>> GetOptionsDB().Get<Hotkey>("UI.hotkey.particular-screen.command-name")
>
> Maybe... but what does the type HotKey look like?

something like this:

struct Hotkey {
Hotkey();
Hotkey(GG::Key key, bool ctrl, bool alt, bool shift, bool meta);

GG::Key key; //!< GG code for key that was pressed
bool ctrl; //!< Is CTRL key pressed to activate hotkey?
bool alt; //!< ALT key?
bool shift; //!< SHIFT key?
bool meta; //!< META key?
};

or this:

struct Hotkey {
Hotkey();
Hotkey(GG::Key key_, GG::Flags<GG::ModKey> modkeys_);

GG::Key key; //!< GG code for key that was pressed
GG::Flags<GG::ModKey> modkeys; //!< GG modkey (alt, shift, ctrl,
meta) held while key was pressed
};

Depending on which is easier to use internally in a hotkey manager and
if GG::Flags<GG::ModKey> is easy to serialize. I'm not sure how the
different ModKeys for (for example) left and right CTRL or both/either
CTRL keys work, so initially preferred the bool version.

>> A problem with this is that if the hotkeys are changed while FO is
>> running, the changed accelerators will need to be removed and re-set
>> and all the signals reconnected. Removing an accelerator apparently
>> doesn't disconnect its signal, so all those signal connections would
>> need to be stored as well, in each class that defines a hotkey and
>> connects signals related to it.
>
> [snip]
>
> I think this is a non-issue. If an accelerator is removed, it will
> never fire its associated signal, and so it doesn't matter what is
> connected to it or not. Am I missing something?

If zoom-in is mapped to z, and then the user remaps z to open
sidepanel, if the z accelerator signal hasn't been disconnected,
pressing z will do both actions.

We can't just tell users to restart after changing hotkeys, because
the above could occur if an class that connects an accelerator signal
is initialized while the program is running. (I would guess that we
can't be sure that such signal connections won't happen more than once
without a lot of extra work to ensure it...)

> ... for how to manage the registering and
> deregistering, or connecting and disconnecting, we should put such
> transitions into the HumanClientFSM. It already has all the mechanism
> in place to do many of these transitions, and adding new states is
> relatively easy. Otherwise, we'll be partially reinventing this
> particular wheel inside HotkeyManager.

If I understand correctly, you want to have an SFM state for the
production screen, the research screen, the main mapwnd, and similar
in-game screens.

I'm a bit reluctant to do this, as it seems like it would limit how
the UI can work. If research and production are separate states, then
we can never modify the UI to have both screens open (as movable /
resizable windows).

It's also a bit limiting, as we couldn't have different hotkeys
enabled when (for example) the sidepanel is open or closed without
making that a separate FSM state, or similarly for the SitRep or even
the chat box. If each of those is a separate state, then transitions
from any of them to Research, for example, should revert back to the
map with the same set of chat / sidepanel / sitrep open, meaning the
"leave research" transition would have multiple subsequent states
depending on the state previous, or several states previous (if, for
example, you had the sitrep open, opened production, then switched to
research, and then closed research to go back to the map).

It also seems easier to have hotkey registration be done from within
the code that the hotkey command would use. By that, I mean we'd put
code to create / register a hotkey to zoom the map in MapWnd.cpp,
essentially where the accelerator signal connections are now, and not
in a separate bit of FSM code. I'm not sure what you meant above by
having the registering and deregistering in the transitions, but it
sounds like it would remove them from where they're relevant.
>> ... for how to manage the registering and
>> deregistering, or connecting and disconnecting, we should put such
>> transitions into the HumanClientFSM. It already has all the mechanism
>> in place to do many of these transitions, and adding new states is
>> relatively easy. Otherwise, we'll be partially reinventing this
>> particular wheel inside HotkeyManager.
>
> If I understand correctly, you want to have an SFM state for the
> production screen, the research screen, the main mapwnd, and similar
> in-game screens.
>
> I'm a bit reluctant to do this, as it seems like it would limit how
> the UI can work. If research and production are separate states, then
> we can never modify the UI to have both screens open (as movable /
> resizable windows).

Again, a reasonable limitation IMO. Let's not try to overengineer this.

> It's also a bit limiting, as we couldn't have different hotkeys
> enabled when (for example) the sidepanel is open or closed without
> making that a separate FSM state,

I think that's bad UI design anyway. The key bindings for the map
shouldn't change because the SidePanel opens, or because a fleet
window is open.

> or similarly for the SitRep or even
> the chat box. If each of those is a separate state, then transitions
> from any of them to Research, for example, should revert back to the
> map with the same set of chat / sidepanel / sitrep open, meaning the
> "leave research" transition would have multiple subsequent states
> depending on the state previous, or several states previous (if, for
> example, you had the sitrep open, opened production, then switched to
> research, and then closed research to go back to the map).

This is why I suggest using the FSM. It will handle all such messy
details for us. It uses RAII to set up and tear down each state, and
a transition between states Foo and Bar calls all destructors for Foo
and then all its enclosing states (but only those that do not also
enclose Bar), then the constructor for all enclosing states of Bar,
then Bar. If each state simply has a SetAccels() and an UnsetAccels()
function, everything "just works".

> It also seems easier to have hotkey registration be done from within
> the code that the hotkey command would use. By that, I mean we'd put
> code to create / register a hotkey to zoom the map in MapWnd.cpp,
> essentially where the accelerator signal connections are now, and not
> in a separate bit of FSM code. I'm not sure what you meant above by
> having the registering and deregistering in the transitions, but it
> sounds like it would remove them from where they're relevant.

Not necessarily. We could place the code inside functions defined
proximate to the use of the keys, and just call the transition
functions from the FSM.
>> Being able to do that is why we'd need a GG::Key to string converter.
>> (I realize "keyboard symbols have been cleverly chosen to map to
>> ASCII" but that doesn't help for keys without a recognizable ASCII
>> representation.)
>
> That seems like a reasonable limitation to me. Can you come up with a
> compelling use case for non-ASCII characters?

We already have hotkeys that use the enter / return keys. PgUp, PgDn,
End, Insert, Delete, Home and all the function keys don't have
single-character ASCII representations... or at least ones that I'm
aware of that are easily recognized.
> We already have hotkeys that use the enter / return keys. PgUp, PgDn,
> End, Insert, Delete, Home and all the function keys don't have
> single-character ASCII representations... or at least ones that I'm
> aware of that are easily recognized.

Ok, so special-case those few exceptions and then direct-convert via
static_cast<char>(x) for all other x.

User avatar
strooka
Space Kraken
Posts: 165
Joined: Tue Jun 23, 2009 5:34 pm
Location: Bielefeld, Germany

Re: Keybord Bindings

#3 Post by strooka »

ok, guys, here are my first results:
i've added the modified files to this post.

i've created a class called HotKeyManager in OptionsWnd.h :

Code: Select all

//by strooka
class HotKeyManager {
public:
    HotKeyManager() {
	s_HKManager = this;
    }

       enum KeyboardEnum { //names for the strings in the vector KeyboardStrings
	   EN_NO_KEY,
           EN_FIRST,
           EN_BACKSPACE,
           EN_TAB,
           EN_CLEAR,
           EN_RETURN,
           EN_PAUSE,
           EN_ESCAPE,
           EN_SPACE,
           EN_EXCLAIM,
           EN_QUOTEDBL,
           EN_HASH,
           EN_DOLLAR,
           EN_AMPERSAND,
           EN_QUOTE,
           EN_LEFTPAREN,
           EN_RIGHTPAREN,
           EN_ASTERISK,
           EN_PLUS,
           EN_COMMA,
           EN_MINUS,
           EN_PERIOD,
           EN_SLASH,
           EN_0,
           EN_1,
           EN_2,
           EN_3,
           EN_4,
           EN_5,
           EN_6,
           EN_7,
           EN_8,
           EN_9,
           EN_COLON,
           EN_SEMICOLON,
           EN_LESS,
           EN_EQUALS,
           EN_GREATER,
           EN_QUESTION,
           EN_AT,
           EN_A,
           EN_B,
           EN_C,
           EN_D,
           EN_E,
           EN_F,
           EN_G,
           EN_H,
           EN_I,
           EN_J,
           EN_K,
           EN_L,
           EN_M,
           EN_N,
           EN_O,
           EN_P,
           EN_Q,
           EN_R,
           EN_S,
           EN_T,
           EN_U,
           EN_V,
           EN_W,
           EN_X,
           EN_Y,
           EN_Z,
           EN_LEFTBRACKET,
           EN_BACKSLASH,
           EN_RIGHTBRACKET,
           EN_CARET,
           EN_UNDERSCORE,
           EN_BACKQUOTE,
           EN_a,
           EN_b,
           EN_c,
           EN_d,
           EN_e,
           EN_f,
           EN_g,
           EN_h,
           EN_i,
           EN_j,
           EN_k,
           EN_l,
           EN_m,
           EN_n,
           EN_o,
           EN_p,
           EN_q,
           EN_r,
           EN_s,
           EN_t,
           EN_u,
           EN_v,
           EN_w,
           EN_x,
           EN_y,
           EN_z,
           EN_DELETE,
           EN_KP0,
           EN_KP1,
           EN_KP2,
           EN_KP3,
           EN_KP4,
           EN_KP5,
           EN_KP6,
           EN_KP7,
           EN_KP8,
           EN_KP9,
           EN_KP_PERIOD,
           EN_KP_DIVIDE,
           EN_KP_MULITPLY,
           EN_KP_MINUS,
           EN_KP_PLUS,
           EN_KP_ENTER,
           EN_KP_EQUALS,
           EN_UP,
           EN_DOWN,
           EN_RIGHT,
           EN_LEFT,
           EN_INSERT,
           EN_HOME,
           EN_END,
           EN_PAGEUP,
           EN_PAGEDOWN,
           EN_F1,
           EN_F2,
           EN_F3,
           EN_F4,
           EN_F5,
           EN_F6,
           EN_F7,
           EN_F8,
           EN_F9,
           EN_F10,
           EN_F11,
           EN_F12,
           EN_F13,
           EN_F14,
           EN_F15,
           EN_NUMLOCK,
           EN_CAPSLOCK,
           EN_SCROLLOCK,
           EN_RSHIFT,
           EN_LSHIFT,
           EN_RCTRL,
           EN_LCTRL,
           EN_RALT,
           EN_LALT,
           EN_RMETA,
           EN_LMETA,
           EN_LSUPER,
           EN_RSUPER,
           EN_MODE,
           EN_COMPOSE,
           EN_HELP,
           EN_PRINT,
           EN_SYSREQ,
           EN_BREAK,
           EN_MENU,
           EN_POWER,
           EN_EURO,
           EN_UNDO,
           EN_LAST
       };

       struct KeyboardStrings : std::vector<std::string> {
           KeyboardStrings();
       }KBStr;
 
           enum Commands { //some sample commands - exept the first, which means no command
               NO_CMD,
               command_a,
               command_b,
               command_c,
               command_d
           };

           KeyboardEnum ConvertKeyToEnum(GG::Key key, GG::Flags<GG::ModKey> mod_keys); 

           //!< Template for following window handler structs
           struct CommandVector {
               void Add(KeyboardEnum Enum, Commands Cmd) {
 		   m_enum.push_back(Enum);
  		   m_cmd.push_back(Cmd);
               }
               std::string operator[](Commands Cmd);
               HotKeyManager::Commands operator[](KeyboardEnum kbenum);

	       std::vector<KeyboardEnum> m_enum;
               std::vector<Commands> m_cmd;
   
               class KeyBinding {
               public:
                  KeyBinding(std::string section, std::string name);
                 ~KeyBinding();

                 KeyboardEnum GetEnum();

                 private:
                    std::string m_keybindingstr;
                    std::string m_kbsection;
                    std::string m_kbname;
               };
               std::vector<KeyBinding> m_keybindings;
	   };
           //!<the window handler structs - simply derive from CommandVector and Define your Commands in init Table
           struct ResearchWndCommands : public CommandVector {
               void InitTable();
           }RsrchWndCmd;

	   struct ChatWndCommands : public CommandVector {
               void InitTable();
           }ChatWndCmd;

       private:
           GG::Key m_key; //!< GG code for key that was pressed
           bool m_ctrl; //!< Is CTRL key pressed to activate hotkey? 
           bool m_alt; //!< ALT key?
           bool m_shift; //!< SHIFT key?
           bool m_meta; //!< META key?
public:
   static HotKeyManager* GetHKM(){return s_HKManager;}
   static HotKeyManager* s_HKManager;
};
//
in the optionswnd dlg a dlg is added to modify the key bindings:

Code: Select all

    //by strooka
    HotKeyManager::GetHKM()->RsrchWndCmd.InitTable();

    BeginPage(UserString("OPTIONS_PAGE_KEY_BINDINGS"));
    BeginSection(UserString("OPTIONS_KEYS_RESEARCH_WND"));
    KeyOption(HotKeyManager::command_a,      "command a"/*UserString("OPTIONS_RESEARCH_WND_COMMAND_A")*/, HotKeyManager::GetHKM()->RsrchWndCmd);
    KeyOption(HotKeyManager::command_b,      "command b"/*UserString("OPTIONS_RESEARCH_WND_COMMAND_B")*/, HotKeyManager::GetHKM()->RsrchWndCmd);
    EndSection();
    EndPage();
    //
there are just sample commands yet, and saving does not work, for some reason the KeyBindings file contents are deleted, i'm debugging it right now.

the window key handler funcs can be easily modified to :

Code: Select all

//by strooka
void ResearchWnd::KeyPress(GG::Key key, boost::uint32_t key_code_point, GG::Flags<GG::ModKey> mod_keys) {

    HotKeyManager::Commands Cmd;
    //call hotkeymanagers window handler objects and extract command from key
    Cmd = HotKeyManager::GetHKM()->RsrchWndCmd[HotKeyManager::GetHKM()->ConvertKeyToEnum(key, mod_keys)];
    switch (Cmd) {
    case HotKeyManager::command_a: { //switch commands - a command is a enum that implies a function
        int i = 0; //do something
    }
    }
}
//
keys are handled now by synonyms - the commands. and they stand for a key.

since there is for every window a different commandvector object is created, same keys can occour in different windows.
Attachments
HotKeyManager.tgz
the modified files
(17.19 KiB) Downloaded 65 times
Last edited by Geoff the Medio on Tue Jan 19, 2010 8:28 pm, edited 1 time in total.
Reason: added code tags

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 12772
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: Keybord Bindings

#4 Post by Geoff the Medio »

If posting preformatted text such as code, please wrap it in code forum tags.

Why do you define KeyboardEnum when a similar and more complete set of keyboard enumerations in GG::Key and
GG::KeyMods?

Why are ResearchWndCommands and ChatWndCommands and the like new struct types derived from CommandVector, rather than objects of type CommandVector?

Edit: Also, modifying KeyPress functions isn't a good way to implement hotkeys. This would require every window that would respond to a KeyPress to have focus, which would mean basically *every* GUI class would need an overridden KeyPress, which is impractical. Instead, connecting keyboard accelerator signals to functions that implement the appropriate function should be used. See MapWnd::ConnectKeyboardAcceleratorSignals() for examples of doing this, and the quoted discussion above for some issues related to it.

class HotKeyManager should probably be in its own set of header and source files, rather than part of OptionsWnd, as it's an independent mechanism that could / should work without a GUI with which to set the hotkeys.

Hotkey commands probably don't need to be enumerated in the HotkeyManager... That would make using the HotkeyManager rather awkward... It should be sufficient to have some function in some UI class that does a command, and just have the HotkeyManager connect a signal to call that function. A way to add more Hotkey-usable commands should be available at runtime, similar to OptionsDB::Add for adding configurable options.

There also doesn't need to be a separate file for storing hotkeys... Instead, individual hotkey assignments should be stored in the OptionsDB.

Also, you appear to be planning to store keypresses and commands in parallel std::vector and relying on the two to have matching indices to decide which command to execute when a particular keypress is detected. This is awkward and potentially error-prone; it'd be better to combine keypresses and commands into a single data structure, or at least have a std::multimap<keypress, command> or somesuch, to simplify the association between keypresses and commands.

User avatar
strooka
Space Kraken
Posts: 165
Joined: Tue Jun 23, 2009 5:34 pm
Location: Bielefeld, Germany

Re: Keybord Bindings

#5 Post by strooka »

do you define KeyboardEnum when a similar and more complete set of keyboard enumerations in GG::Key and
GG::KeyMods?
because i didnt find the definition of it yet. I'll Look for it.
Why are ResearchWndCommands and ChatWndCommands and the like new struct types derived from CommandVector, rather than objects of type CommandVector?
because there was a discussion about having the same keys defined in different Windows.

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 12772
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: Keybord Bindings

#6 Post by Geoff the Medio »

strooka wrote:because there was a discussion about having the same keys defined in different Windows.
That doesn't require defining a new struct (ie. a new class) for each window. It should be possible to have several instances of a common CommandVector-like class that accomplishes this.

For example, to do something like

Code: Select all

int apples = 5, pears = 2, peaches = 16;
I don't need to do

Code: Select all

typedef int num_apples_t;
typedef int num_pears_t;
typedef int num_peaches_t;

num_apples_t apples = 5;
num_pears_t pears = 2;
num_peaches_t peaches = 16;

User avatar
strooka
Space Kraken
Posts: 165
Joined: Tue Jun 23, 2009 5:34 pm
Location: Bielefeld, Germany

Re: Keybord Bindings

#7 Post by strooka »

Geoff the Medio wrote:
strooka wrote:because there was a discussion about having the same keys defined in different Windows.
That doesn't require defining a new struct (ie. a new class) for each window. It should be possible to have several instances of a common CommandVector-like class that accomplishes this.

For example, to do something like

Code: Select all

int apples = 5, pears = 2, peaches = 16;
I don't need to do

Code: Select all

typedef int num_apples_t;
typedef int num_pears_t;
typedef int num_peaches_t;

num_apples_t apples = 5;
num_pears_t pears = 2;

num_peaches_t peaches = 16;
every struct has an inittable function where the commands are associated with the Key values. Different Windows have different command/Key combinations. Also the inittable func Must Be rewritten.

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 12772
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: Keybord Bindings

#8 Post by Geoff the Medio »

strooka wrote:
do you define KeyboardEnum when a similar and more complete set of keyboard enumerations in GG::Key and GG::KeyMods?
because i didnt find the definition of it yet. I'll Look for it.
See GG/GG/Base.h
every struct has an inittable function where the commands are associated with the Key values. Different Windows have different command/Key combinations. Also the inittable func Must Be rewritten.
It would probably be better to do hotkey command creation using a mechanism like OptionsDB::Add is used for adding configurable options, which is used in the various AddOptions functions that are passed to the RegisterOptions function in various source files. This function might be called HotkeyManager::AddCommand.

In this manner, the various commands would be created in the source files relevant to those commands, much like the options are created near where they're relevant.

The HotkeyManager::AddCommand function would probably take an enum for the UI state in which the command should be available such as production mode, research mode, map mode, etc., and would internally take care of connecting and disconnecting hotkeys from those commands when the game UI state changed.

Storing which hotkey was connected to which command in each UI state should be done in the OptionsDB. The HotkeyManager would check with the OptionsDB when setting hotkeys for a particular UI screen to see which key should be set to activate the commands for that UI state. tzlaine suggested using a fininte state machine to ensure all setting and un-setting of hotkey signal connections is done properly when switching between UI states.

User avatar
strooka
Space Kraken
Posts: 165
Joined: Tue Jun 23, 2009 5:34 pm
Location: Bielefeld, Germany

Re: Keybord Bindings

#9 Post by strooka »

ok, here is a firs preview what i've changed...

I ve created a Class in a new file...

Code: Select all

class HotKeyManager {
public:
    HotKeyManager() {
	s_HKManager = this;
        HotKey::InitKeyboardStrings();
    }

    struct HotKey {
        HotKey(std::string str);
        HotKey(GG::Key key);
        HotKey(GG::Key key, GG::Flags<GG::ModKey> mod_keys);
        bool operator < (const HotKey& b) const { return m_key < b.m_key; }

        static std::vector<std::string>* GetKBStr() { return &m_vecKeynames; }
        static void InitKeyboardStrings();
        static std::vector<std::string> m_vecKeynames;
        operator std::string() const;

        GG::Key m_key; //!< GG code for key that was pressed
        bool m_ctrl; //!< Is CTRL key pressed to activate hotkey? 
        bool m_alt; //!< ALT key?
        bool m_shift; //!< SHIFT key?
        bool m_meta; //!< META key?
        GG::Flags<GG::ModKey> m_mod_keys;
    };

    struct Counter {
        Counter() { ++s_counter; }
        static int s_counter;
    };

    template< class WndType >
    struct Handler : public Counter {
        Handler(bool (WndType::*pt2HandlerFunc)()){ m_pt2HandlerFunc = pt2HandlerFunc; m_id = s_counter; }
        bool operator < (const Handler<WndType>& b) const { return m_id < b.m_id; }

        int m_id;
        bool (WndType::*m_pt2HandlerFunc)();
    };

    //!< Template for following window handler structs
    template< class WndType >
    struct CommandVector {
        CommandVector(WndType *pWnd ) { m_pWnd = pWnd; }
        void InitOption(const std::string& option_name, Handler& handler);
        void SaveOption(const std::string& option_name, const std::string& option_val);

        std::map< const Handler<WndType>, HotKey > m_commands;
        WndType *m_pWnd; 
    };

    template < class WndType >
    struct SetOptionFunctor
    {
        SetOptionFunctor(const std::string& option_name, const CommandVector<WndType>& vec, CUIEdit* edit = 0, OptionsWnd::StringValidator string_validator = 0) :
            m_option_name(option_name), m_edit(edit), m_string_validator(string_validator), m_pCommandVector(&vec) { assert(bool(m_edit) == bool(m_string_validator));}

        void operator()(const std::string& option_val) { m_pCommandVector->SaveOption(m_option_name, const std::string& option_val); }

        CUIEdit* m_edit;
        OptionsWnd::StringValidator m_string_validator;
        const std::string m_option_name;
        CommandVector<WndType> *m_pCommandVector;
    };

public:
   static HotKeyManager* GetHKM(){return s_HKManager;}
   static HotKeyManager* s_HKManager;
};
in Map Wnd it is initialized like this...

Code: Select all

void MapWnd::ConnectKeyboardAcceleratorSignals()
{

//by strooka
    m_MapWndCmd.InitOption("UI.HotKey.MapWnd.command-a", Handler(&DoIt));
    m_MapWndCmd.InitOption("UI.HotKey.MapWnd.command-b", Handler(&DoIt2));
//
Attachments
HotKeyManager.tgz
the new files
(54.17 KiB) Downloaded 65 times

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 12772
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: Keybord Bindings

#10 Post by Geoff the Medio »

Why do you store bools for each of the mod keys and a GG::Flags<GG::ModKey> in the HotKey struct? Just storing the GG::Flags<GG::ModKey> would be sufficient.

You should probably be using classes rather than structs in most cases, particularly if there's a bunch of internal class data that should be private, and the data structure is doing more than just holding a few related variables together.

Use initializer lists in constructors when possible.

Is there any reason for KeyBindings.* to be in util rather than UI? It seems pretty natural to put keyboard command binding under User Interface.

There's no reason to have the confusing operator std::string() in HotKey. Add a clearly-named KeyString() function. Get rid of m_vecKeynames, and instead have a big switch block to pick the correct string to return to represent keys. See GG::KeypadKeyToPrintable in GG/Base.cpp for reference. The returned key string should be looked up using UserString() if it's not a simple letter or number. So it's fine to return the hard-coded string "b" but things like GG::GGK_PLUS should be handled by looking up something in the stringtable, with the name of the key written out, rather than using "+".

It would also be good to return "CTRL + b" rather than just "b", so the KeyString function should check how many mod keys were pressed. Based on that number, one of several UserString entries shoud be used to show the required number of keys. Don't just assume it's ok to append modkey names, separated by +, and then add the main keystring at the end, as other languages might not work this way. Instead, there should be stringtable entries like

Code: Select all

KEY_STRING_ONE_KEY
%1%

KEY_STRING_TWO_KEYS
%1% + %2%

KEY_STRING_THREE_KEYS
%1% + %2% + %3%
and probably similar for four or five keys, since there could be up to four mod keys pressed simultaneously to defined a hotkey command.

The KeyString function would use the correct stringtable entry for the number of modkeys that were pressed for the hotkey, and substitute in "CTRL", "ALT", "SHIFT" and/or "META" (also looked up in a stringtable) as necessary to the UserString, and then substitute in the appropriate actual key string.

HotKey::operator < should be modified to properly handle cases where hotkeys have the same main keypress, but have different modkeys pressed, which should not be "equal" hotkeys. Probably it should also separate based on the command to be issue, likely by sorting based on function pointer value or equiavalent, in cases where the same keypresses are used for multiple hotkey commands. (Even if this is disallowed in the UI)

GetHKM() should return a reference, not a pointer

Is HotKeyManager::MapWndCommands::InitTable() supposed to be there? It's not decleared anywhere, but is defined in KeyBindings.cpp. What is its purpose? Adding MapWnd commands should be done by calling a function from within MapWnd.cpp.

I'm not sure what exactly is going on with Counter, Handler and CommandVector, but you should not need to use templates for different Wnd classes.

There are a few alternatives...

You could take boost::function<void ()> instead of class member function pointers.

You could use boost::bind to make a non-member function out of a member function (see various spots in MapWnd::MapWnd()).

However, the best method would probably be to have a HotKeyManager::AddCommand function that would be called from various UI class source files similar to OptionsDB::Add. This would take a string command name that might as well be formatted like, and used internally as the name for, an OptionsDB entry for that command's hotkey binding. When AddCommand is run, it does internal bookkeeping so that later in a UI class, one could do

GetHotKeyManager().CommandSignal("UI.hotkeys.command-name")

which would return a reference to a boost::signal that could be connected to the actual command-executing function.

For example, in MapWnd, you'd have at the top of the file in an anonymous namespace, some code that gets called during initialization which contains

Code: Select all

GetHotKeyManager().AddCommand("UI.hotkeys.galaxy-map-zoom-in", UserString("UI_COMMAND_MAP_ZOOM_IN"), UI_MODE_GALAXY_MAP, std::pair<GG::Key, GG::Flags<GG::ModKey> >(GG::GGK_z, GG::MOD_KEY_CTRL) )
which specifies:
* the command and option internal name, used to lookup the associated hotkey signal (see below) and to store the bound hotkey in the options DB
* the user-visible command name, used to name the hotkey in the options window while setting hotkeys
* a "UI mode" name enum or string, which would indicate what UI mode (on map, in research, in combat, etc.) for which the hotkey command would be available / active. When switching between UI modes, the HotKeyManager would be informed, and would only issue hotkey signals when in the appropriate mode for which they were defined.
* the default key press to activate the command, which should be an optional argument with default value of no key, meaning there would be no keypress combination bound to the command by default, although it would be made available for binding on the options screen

In MapWnd::Init or MapWnd::MapWnd, you'd do

Code: Select all

GG::Connect(GetHotKeyManager().CommandSignal("UI.hotkeys.galaxy-map-zoom-in"), &MapWnd::KeyboardZoomIn, this);
where CommandSignal() would return the appropriate boost::signal object, which would be activated when the HotKeyManager catches the appropriate keypress signals, which would cause the connected function (in this case KeyboardZoomIn) to be called, implementing the hotkey's effect.

User avatar
strooka
Space Kraken
Posts: 165
Joined: Tue Jun 23, 2009 5:34 pm
Location: Bielefeld, Germany

Re: Keybord Bindings

#11 Post by strooka »

Geoff the Medio wrote:Why do you store bools for each of the mod keys and a GG::Flags<GG::ModKey> in the HotKey struct? Just storing the GG::Flags<GG::ModKey> would be sufficient.
Yah, i've Found that out too, today :)
You should probably be using classes rather than structs in most cases, particularly if there's a bunch of internal class data that should be private, and the data structure is doing more than just holding a few related variables together.
When i'm done with it , i could change it, but in this stadium just defining GetXYZ funcs would make just much writing work.

Use initializer lists in constructors when possible.
what do you mean? Can you give an example pls?
Is there any reason for KeyBindings.* to be in util rather than UI? It seems pretty natural to put keyboard command binding under User Interface.
I thought because it is a Class which isnt derived from GG::Wnd


There's no reason to have the confusing operator std::string() in HotKey. Add a clearly-named KeyString() function. Get rid of m_vecKeynames, and instead have a big switch block to pick the correct string to return to represent keys. See GG::KeypadKeyToPrintable in GG/Base.cpp for reference. The returned key string should be looked up using UserString() if it's not a simple letter or number. So it's fine to return the hard-coded string "b" but things like GG::GGK_PLUS should be handled by looking up something in the stringtable, with the name of the key written out, rather than using "+".
Ok, i didnt find à solution yet for Key combinations
It would also be good to return "CTRL + b" rather than just "b", so the KeyString function should check how many mod keys were pressed. Based on that number, one of several UserString entries shoud be used to show the required number of keys. Don't just assume it's ok to append modkey names, separated by +, and then add the main keystring at the end, as other languages might not work this way. Instead, there should be stringtable entries like

Code: Select all

KEY_STRING_ONE_KEY
%1%

KEY_STRING_TWO_KEYS
%1% + %2%

KEY_STRING_THREE_KEYS
%1% + %2% + %3%
and probably similar for four or five keys, since there could be up to four mod keys pressed simultaneously to defined a hotkey command.
Sorry, i don't Know what you mean, how and where should i Place the Key combinations, since they shall Be hardcoded?
I could define in UserString a compare func which searches in the Input String for Parts which Match the Meta keys
HotKey::operator < should be modified to properly handle cases where hotkeys have the same main keypress, but have different modkeys pressed, which should not be "equal" hotkeys. Probably it should also separate based on the command to be issue, likely by sorting based on function pointer value or equiavalent, in cases where the same keypresses are used for multiple hotkey commands. (Even if this is disallowed in the UI)
i think that isnt important i can delete the operator since the Map is sorted by the handler class
Is HotKeyManager::MapWndCommands::InitTable() supposed to be there? It's not decleared anywhere, but is defined in KeyBindings.cpp. What is its purpose? Adding MapWnd commands should be done by calling a function from within MapWnd.cpp.
It is obsolete since InitOption overtakes its function, which i could rename to AddCommand, this is what initializes the Map and connects with the approitate handler function.
I'm not sure what exactly is going on with Counter, Handler and CommandVector, but you should not need to use templates for different Wnd classes.
Counter is à Class that gives a possibility to get an id for each Object so that they can Be enumerated and sorted.
Handler Stores a window function Pointer, theese functions are called when Handling à Key event. Since there are different funcs in each Class i'll have to use an Template, cause the Type of the function has à different Class Type.
I have à Single Class now that Stores the Key/handler combinations, maybe i should rename it to KeyHandlerMap

There are a few alternatives...

You could take boost::function<void ()> instead of class member function pointers.

You could use boost::bind to make a non-member function out of a member function (see various spots in MapWnd::MapWnd()).

However, the best method would probably be to have a HotKeyManager::AddCommand function that would be called from various UI class source files similar to OptionsDB::Add. This would take a string command name that might as well be formatted like, and used internally as the name for, an OptionsDB entry for that command's hotkey binding. When AddCommand is run, it does internal bookkeeping so that later in a UI class, one could do

GetHotKeyManager().CommandSignal("UI.hotkeys.command-name")

which would return a reference to a boost::signal that could be connected to the actual command-executing function.

For example, in MapWnd, you'd have at the top of the file in an anonymous namespace, some code that gets called during initialization which contains

Code: Select all

GetHotKeyManager().AddCommand("UI.hotkeys.galaxy-map-zoom-in", UserString("UI_COMMAND_MAP_ZOOM_IN"), UI_MODE_GALAXY_MAP, std::pair<GG::Key, GG::Flags<GG::ModKey> >(GG::GGK_z, GG::MOD_KEY_CTRL) )
which specifies:
* the command and option internal name, used to lookup the associated hotkey signal (see below) and to store the bound hotkey in the options DB
* the user-visible command name, used to name the hotkey in the options window while setting hotkeys
* a "UI mode" name enum or string, which would indicate what UI mode (on map, in research, in combat, etc.) for which the hotkey command would be available / active. When switching between UI modes, the HotKeyManager would be informed, and would only issue hotkey signals when in the appropriate mode for which they were defined.
* the default key press to activate the command, which should be an optional argument with default value of no key, meaning there would be no keypress combination bound to the command by default, although it would be made available for binding on the options screen

In MapWnd::Init or MapWnd::MapWnd, you'd do

Code: Select all

GG::Connect(GetHotKeyManager().CommandSignal("UI.hotkeys.galaxy-map-zoom-in"), &MapWnd::KeyboardZoomIn, this);
where CommandSignal() would return the appropriate boost::signal object, which would be activated when the HotKeyManager catches the appropriate keypress signals, which would cause the connected function (in this case KeyboardZoomIn) to be called, implementing the hotkey's effect.

I think i have delivered already à good solution, because my solution is Class based.
In MapWnd, One defines a CommandVector (a Map) and connects commands to functions through

Code: Select all

    m_MapWndCmd.InitOption("UI.HotKey.MapWnd.command-a", Handler(&DoIt));
DoIt is a function that is being connected to.
I thought there were no additional changes needed cause internally i use the same GG::Connect func for the signals. The HotKey Object is only used for displaying the Key in the OptionsWnd dlg and for changing the signals.

Changing the signals is directly Done in SaveOption which is called when a focusChanged Signal in the Input window comes. I think this is neccessary because of Error Handling.

There are Lots of compiling errors still now, it should Be just à preview

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 12772
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: Keybord Bindings

#12 Post by Geoff the Medio »

strooka wrote:
Use initializer lists in constructors when possible.
what do you mean? Can you give an example pls?

Code: Select all

SetOptionFunctor(const std::string& option_name, const CommandVector<WndType>& vec, CUIEdit* edit = 0, OptionsWnd::StringValidator string_validator = 0) :
            m_option_name(option_name), m_edit(edit), m_string_validator(string_validator), m_pCommandVector(&vec) { assert(bool(m_edit) == bool(m_string_validator));}
You used an initializer list here but didn't in the Handler constructor.

Code: Select all

Handler(bool (WndType::*pt2HandlerFunc)()){ m_pt2HandlerFunc = pt2HandlerFunc; m_id = s_counter; }
Is there any reason for KeyBindings.* to be in util rather than UI? It seems pretty natural to put keyboard command binding under User Interface.
I thought because it is a Class which isnt derived from GG::Wnd
While most UI-related clases are in-game "windows", not all of them are, and it's not an important distinction for directory sorting purposes.

It would also be good to return "CTRL + b" rather than just "b", so the KeyString function should check how many mod keys were pressed. Based on that number, one of several UserString entries shoud be used to show the required number of keys. Don't just assume it's ok to append modkey names, separated by +, and then add the main keystring at the end, as other languages might not work this way. Instead, there should be stringtable entries like

Code: Select all

KEY_STRING_ONE_KEY
%1%

KEY_STRING_TWO_KEYS
%1% + %2%

KEY_STRING_THREE_KEYS
%1% + %2% + %3%
and probably similar for four or five keys, since there could be up to four mod keys pressed simultaneously to defined a hotkey command.
Sorry, i don't Know what you mean, how and where should i Place the Key combinations, since they shall Be hardcoded?
I could define in UserString a compare func which searches in the Input String for Parts which Match the Meta keys
Above I suggested adding a KeyString function instead of returning an std::string from operator(). Then, I suggested making KeyString return not just the main key press (eg. "b") but instead return a string that fully describes the key combination (main key and one or more meta keys) to activate the command.

If the hotkey bound to a command was just GGK_b without any meta keys, you'd look up the text representation of GGK_b ("b") with a switch statement, and then substitute it into UserString("KEY_STRING_ONE_KEY").

If the hotkey command was GGK_PLUS while CTRL and SHIFT were held down, you'd look up GGK_PLUS, CTRL and SHIFT text representations, all of which should involve UserString lookups (ie. not just "+" for GGK_PLUS). Then since there are three keys used in combination for the command, you'd substitute them all into UserString("KEY_STRING_THREE_KEYS").
HotKey::operator < should be modified to properly handle cases where hotkeys have the same main keypress, but have different modkeys pressed, which should not be "equal" hotkeys. Probably it should also separate based on the command to be issue, likely by sorting based on function pointer value or equiavalent, in cases where the same keypresses are used for multiple hotkey commands. (Even if this is disallowed in the UI)
i think that isnt important i can delete the operator since the Map is sorted by the handler class
You shouldn't need the Handler class...
Handler Stores a window function Pointer, theese functions are called when Handling à Key event. Since there are different funcs in each Class i'll have to use an Template, cause the Type of the function has à different Class Type.
Storing a class-specific function pointers is a very awkward way of handling this sort of thing. You'd be much better using one of my suggestions in the previous post to get free function pointers. I'm not sure how you plan to set things up, but if you're assuming that all possible hotkey-bindable commands while in "map mode" are going to be MapWnd member function pointers, you probably shouldn't assume that.
I think i have delivered already à good solution, because my solution is Class based.
What do you mean by "Class based" ?
In MapWnd, One defines a CommandVector (a Map) and connects commands to functions through

Code: Select all

    m_MapWndCmd.InitOption("UI.HotKey.MapWnd.command-a", Handler(&DoIt));
DoIt is a function that is being connected to.
Having to add a CommandVector to various UI classes' definitions is rather awkward. A global HotKeyManager class shouldn't require modifying UI classes definitions to use it, and it should be possible to add hotkey commands at file scope (eg. in MapWnd.cpp) without needing a member variable in one or more UI classes to store them, since the HotKeyManager could do so.

The use of Handler is confusing in this example as well... Avoiding member function pointers would seem a better solution than that.

Your method also seems to have issues if a UI object is destructed and recreated, which would leave dangling pointers in your CommandVector to non-existant objects. My suggestions involving connecting signals within UI class initialzation would be better, as the UI classes are trackable objects, meaning the signals automatically clean up if the associated UI class object goes away. As well, if a new UI class object is created, it would automatically re-connect all the command signals it needs.

User avatar
strooka
Space Kraken
Posts: 165
Joined: Tue Jun 23, 2009 5:34 pm
Location: Bielefeld, Germany

Re: Keybord Bindings

#13 Post by strooka »

Ok, thx for the adds. I'll change it to the Way As you mentioned.

User avatar
strooka
Space Kraken
Posts: 165
Joined: Tue Jun 23, 2009 5:34 pm
Location: Bielefeld, Germany

Re: Keybord Bindings

#14 Post by strooka »

here are my newest changes:

Code: Select all

// -*- C++ -*-
//by strooka
#ifndef _HotKeyManager_h_
#define _HotKeyManager_h_

#include <../GG/GG/Base.h>

#include "../UI/MapWnd.h"
#include "../UI/ChatWnd.h"
#include "../util/Directories.h"
#include "../util/OptionsDB.h"



class HotKeyManager;

/////////////////////////////////////////////
// Free Functions
/////////////////////////////////////////////
typedef void (*HotKeyManagerDBFn)(HotKeyManager&);///< the function signature for functions that add Functions to the HotKeyManager (void (HotKeyManager&))
typedef HotKeyManagerFn boost::function<void ()>; ///< the function signature for functions to be added to the database

bool RegisterCommands(HotKeyManagerDBFn function);

/** returns the single instance of the class */
HotKeyManager& GetHotKeyManager();


class HotKeyManager : private OptionsDB
{
public:
    /** \name Signal Types */ //@{
    typedef boost::signal<void ()>                     CommandChangedSignalType; ///< emitted when an command has changed
    typedef boost::signal<void (const std::string&)>   CommandAddedSignalType;   ///< emitted when an command is added
    typedef boost::signal<void (const std::string&)>   CommandRemovedSignalType; ///< emitted when an command is removed
    //@}

    HotKey   Get(const std::string& name) const 
    {
        std::map<std::string, Command>::const_iterator it = m_commands.find(name);
        if (it == m_commands.end())
            throw std::runtime_error("HotKeyManager::Get() : Attempted to get nonexistent option \"" + name + "\".");
        return it->second.value1;
    }

    HotKeyManagerDBFn   Get(const std::string& name) const 
    {
        std::map<std::string, Command>::const_iterator it = m_commands.find(name);
        if (it == m_commands.end())
            throw std::runtime_error("HotKeyManager::Get() : Attempted to get nonexistent option \"" + name + "\".");
        return it->second.value2;
    }

    HotKeyManagerDBFn   GetDefaultHotKey(const std::string& name) const
    {
        std::map<std::string, Command>::const_iterator it = m_commands.find(name);
        if (it == m_commands.end())
            throw std::runtime_error("HotKeyManager::GetDefault() : Attempted to get nonexistent option \"" + name + "\".");
        return it->second.default_value;
    }

    /** returns the string representation of the value of the command \a name.*/
    std::string GetValueString(const std::string& option_name) const;

    /** returns the string representation of the default value of the
      * command \a name.*/
    std::string GetDefaultValueString(const std::string& option_name) const;

    /** returns the description string for command \a command_name, or throws
      * std::runtime_error if no such Command exists. */
    const std::string&  GetDescription(const std::string& option_name) const;
    /** writes a usage message to \a os */
    void        GetUsage(std::ostream& os, const std::string& command_line = "") const;

    /** returns the contents of the DB as an XMLDoc. */
    XMLDoc      GetXML() const;

    /** the option changed signal object for the given option */
    CommandChangedSignalType&    CommandChangedSignal(const std::string& option);

    mutable CommandAddedSignalType   CommandAddedSignal;   ///< the option added signal object for this DB
    mutable CommandRemovedSignalType CommandRemovedSignal; ///< the change removed signal object for this DB

    void        Add(char short_name = static_cast<char>(0), const std::string& name, const                           std::string& description, HotKey default_value, HotKeyManagerFn function, GG::CUIWnd pWnd, bool storable = true)
    {
        if (m_commands.find(name) != m_commands.end())
            throw std::runtime_error("HotKeyManager::Add() : Command " + name + " was specified twice.");
        m_commands[name] = Command(short_name, name, default_value, function, default_value, description, storable);
        GG::Connect(GG::GUI::GetGUI()->AcceleratorSignal(default_value.GetKey(), default_value.GetFlags()), function, pWnd));

        CommandAddedSignal(name);
    }

    /** removes an Command */
    void        Remove(const std::string& name);

    /** sets the value1 (the HotKey) of command \a name to \a value */
    void        Set(const std::string& name, const HotKey& value = 0, HotKeyManagerFn function = 0, GG::CUIWnd pWnd)
    {
        std::map<std::string, Command>::iterator it = m_commands.find(name);
        if (it == m_commands.end())
            throw std::runtime_error("HotKeyManager::Set() : Attempted to set nonexistent option \"" + name + "\".");
        if(value != 0)
            it->second.value1 = value;
        if(function != 0)
            it->second.value2 = function;
        if(vallue != 0 || function != 0)
            GG::Connect(GG::GUI::GetGUI()->AcceleratorSignal(it->second.value1.GetKey(), it->second.value1.GetFlags()), it->second.value2, pWnd));
        (*it->second.command_changed_sig_ptr)();
    }

    /** fills some or all of the options of the DB from values stored in
      * XMLDoc \a doc */
    void        SetFromXML(const XMLDoc& doc);

    class HotKey 
    {
    public:
        HotKey(std::string str);
        HotKey( GG::Key key = GG::GGK_UNKNOWN, GG::Flags<GG::ModKey> mod_keys = GG::Flags<GG::ModKey>() );
        bool operator < (const HotKey& b) const;

        static std::string KeyString();
        bool GetCtrl() {     return mod_keys & (GG::MOD_KEY_CTRL); }//!< Is CTRL key pressed to activate hotkey? 
        bool GetAlt() { return mod_keys & (GG::MOD_KEY_ALT); } //!< ALT key?
        bool GetShift() { return mod_keys & (GG::MOD_KEY_LSHIFT | GG::MOD_KEY_RSHIFT); } //!< SHIFT key?
        bool GetMeta() { mod_keys & (GG::MOD_KEY_META); } //!< META key?
        GG::Key GetKey() { return m_key; }
        GG::Flags<GG::ModKey> GetFlags() { return m_mod_keys; }
    private:
        GG::Key m_key; //!< GG code for key that was pressed
        GG::Flags<GG::ModKey> m_mod_keys;
    };

    struct SetOptionFunctor
    {
        SetOptionFunctor(const std::string& option_name, CUIEdit* edit = 0) :
            m_option_name(option_name), m_edit(edit) { assert(bool(m_edit) == bool(m_string_validator));}

        void operator()(const std::string& command_val) { GetHotKeyManager().Set(m_option_name, const std::string& option_val); }

        CUIEdit* m_edit;
        const std::string m_command_name;
    };
private:
    struct Command
    {
        Command();
        Command(char short_name_, const std::string& name_, const                                            HotKeyManager::HotKey& value_1, HotKeyManagerFn value_2,
                const HotKeyManager::HotKey& default_value_, const std::string& description_, bool storable_);

        void            SetFromString(const std::string& str);
        std::string     ValueToString() const;
        std::string     DefaultValueToString() const;

        std::string     name;           ///< the name of the command
        char            short_name;     ///< the one character abbreviation of the command
        HotKey          value1;          ///< the value of the command
        HotKeyManagerFn value2;          ///< the fn value of the command
        HotKey          default_value;  ///< the default value of the command
        std::string     description;    ///< a desription of the command
        bool            storable;       ///< whether this command can be stored in an XML config file for use across multiple runs

        mutable boost::shared_ptr<boost::signal<void ()> > command_changed_sig_ptr;

        static std::map<char, std::string> short_names;   ///< the master list of abbreviated option names, and their corresponding long-form names
    };

    HotKeyManager();

    void        SetFromXMLRecursive(const XMLElement& elem, const std::string& section_name);

    std::map<std::string, Command>   m_commands;

    friend HotKeyManager& GetHotKeyManager();

    static HotKeyManager* s_HKManager;
};
//

#endif // _HotKeyManager_h_

in Map Wnd:

Code: Select all

//by strooka
    void AddCommands(HotKeyManager& hkm) {
        hkm.AddCommand("UI.HotKey.MapWnd.command-a", "a sample command"/*"OPTIONS_DB_MAP_WND_COMMAND_A"*/, HotKey("a"),  &MapWnd::DoIt, ??? );
        hkm.AddCommand("UI.HotKey.MapWnd.command-b", "a second sample command"/*"OPTIONS_DB_MAP_WND_COMMAND_B"*/, HotKey(GG::GGK_b),  &MapWnd::DoIt2,??? );

    }
    bool temp_bool = RegisterCommands(&AddCommands);
//

how do i get the pointer to the window?

All this could probably be reduced to

Code: Select all

AddOptions(...)
...
db.Add("UI.Hotkey.command-xyz", std::pair<Hotkey(GG::GGK_xyz), &HotKeyManager::some_function>);
...
and in Hot Key Manager there could Be à centralized Key Handling defined. I mean all handler functions could Be defined there.
Attachments
HotKeyManager.tgz
the files
(44.04 KiB) Downloaded 58 times

User avatar
strooka
Space Kraken
Posts: 165
Joined: Tue Jun 23, 2009 5:34 pm
Location: Bielefeld, Germany

Re: Keybord Bindings

#15 Post by strooka »

I've thought about getting the Pointer to the window- in the constructor i could Register the window in HotKeyHandler and reference to it. This would afford à New Vector of Windows. But the GG::Connect function would Be inline. Maybe i should make à function that is called in the constructors of the Windows gets the this Pointer and connects all funcs belonging to that window.

Post Reply