[crossfire] [RFC 2/3] Misc keybinding fixes and changes

Arvid Brodin arvidb at kth.se
Mon Oct 28 18:19:14 CDT 2013


* Show all keybindings (incl default ones) in both 'unbind' and the keybindings
dialog. (Should reduce confusion for new users.)

* Add "Any" key modifier type; New "Any" button in keybindings dialog. This lets
the user choose between having a key work regardless of modifier (Ctrl, Alt, etc)
key states, or to use the same key with different modifiers for different 
bindings. How this worked before ("All"/"Normal") was pretty unclear, even in the
code comments. (New feature/bug fix.)

* Consistent use of hash helper functions (keybind_insert(), keybind_remove(), 
etc). This should make the code more maintainable and reduce the risk of bugs.

* Removed possibility to rebind Ctrl, Shift, Alt, Meta modifiers. This feature
is very unusual and breaks the normal design pattern of using the designated 
keys as keyboard modifiers.

* Better usage texts (among other things they used '[' which was interpreted as 
a style tag, making parts of the usage text go missing). (Bug fix.)

* Bind only lowercase keys - we handle Shift state separately. (Bug fix.)

* Rehash keybind entry on key dialog update (fixes bug where keybinding with 
modified keyval would become unusable until restart). (Bug fix.)

* Removed -afmnr flags from 'bind' - instead use only "press the key combo you 
want" when you bind the key. This is to simplify the keybind without removing 
any real functionality.


Signed-off-by: Arvid Brodin <arvidb at kth.se>
---
 gtk-v2/glade/dialogs.glade |  28 +-
 gtk-v2/src/gtk2proto.h     |   1 +
 gtk-v2/src/keys.c          | 902 +++++++++++++++++++++------------------------
 3 files changed, 439 insertions(+), 492 deletions(-)

diff --git a/gtk-v2/glade/dialogs.glade b/gtk-v2/glade/dialogs.glade
index a8b34e6..f9e65f7 100644
--- a/gtk-v2/glade/dialogs.glade
+++ b/gtk-v2/glade/dialogs.glade
@@ -3671,8 +3671,22 @@ this is the pricing the character gets in shops.</property>
             <property name="can_focus">False</property>
             <property name="homogeneous">True</property>
             <child>
+              <widget class="GtkCheckButton" id="keybinding_checkbutton_any">
+                <property name="label" translatable="yes">Any</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="draw_indicator">True</property>
+              </widget>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
               <widget class="GtkCheckButton" id="keybinding_checkbutton_control">
-                <property name="label" translatable="yes">Run</property>
+                <property name="label" translatable="yes">Ctrl</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">False</property>
@@ -3682,12 +3696,12 @@ this is the pricing the character gets in shops.</property>
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">False</property>
-                <property name="position">0</property>
+                <property name="position">1</property>
               </packing>
             </child>
             <child>
               <widget class="GtkCheckButton" id="keybinding_checkbutton_shift">
-                <property name="label" translatable="yes">Fire</property>
+                <property name="label" translatable="yes">Shift</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">False</property>
@@ -3697,7 +3711,7 @@ this is the pricing the character gets in shops.</property>
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">False</property>
-                <property name="position">1</property>
+                <property name="position">2</property>
               </packing>
             </child>
             <child>
@@ -3712,7 +3726,7 @@ this is the pricing the character gets in shops.</property>
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">False</property>
-                <property name="position">2</property>
+                <property name="position">3</property>
               </packing>
             </child>
             <child>
@@ -3727,7 +3741,7 @@ this is the pricing the character gets in shops.</property>
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">False</property>
-                <property name="position">3</property>
+                <property name="position">4</property>
               </packing>
             </child>
             <child>
@@ -3745,7 +3759,7 @@ this is the pricing the character gets in shops.</property>
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">False</property>
-                <property name="position">4</property>
+                <property name="position">5</property>
               </packing>
             </child>
           </widget>
diff --git a/gtk-v2/src/gtk2proto.h b/gtk-v2/src/gtk2proto.h
index 2dc3d7c..26f7c6e 100644
--- a/gtk-v2/src/gtk2proto.h
+++ b/gtk-v2/src/gtk2proto.h
@@ -131,6 +131,7 @@ extern void on_keybinding_button_remove_clicked(GtkButton *button, gpointer user
 extern void on_keybinding_button_bind_clicked(GtkButton *button, gpointer user_data);
 extern void on_keybinding_button_update_clicked(GtkButton *button, gpointer user_data);
 extern void on_keybinding_button_close_clicked(GtkButton *button, gpointer user_data);
+extern void on_keybinding_checkbutton_any_clicked(GtkCheckButton *cb, gpointer user_data);
 extern gboolean keybinding_selection_func(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer userdata);
 extern void reset_keybinding_status(void);
 extern void on_keybinding_button_clear_clicked(GtkButton *button, gpointer user_data);
diff --git a/gtk-v2/src/keys.c b/gtk-v2/src/keys.c
index d44a460..fa91675 100644
--- a/gtk-v2/src/keys.c
+++ b/gtk-v2/src/keys.c
@@ -52,6 +52,7 @@ typedef int KeyCode;                    /**< Undefined type */
  * Widgets for the keybinding dialog
  */
 static GtkWidget *fire_label, *run_label, *keybinding_window,
+       *keybinding_checkbutton_any,
        *keybinding_checkbutton_control, *keybinding_checkbutton_shift,
        *keybinding_checkbutton_alt, *keybinding_checkbutton_meta,
        *keybinding_checkbutton_edit, *keybinding_entry_key,
@@ -96,19 +97,17 @@ static int cur_history_position = 0, scroll_history_position = 0;
 
 /**
  * @{
- * @name Key Entry Struct
+ * @name key_entry struct
  * A keybinding hash record structure.
  */
-typedef struct Keys {
+struct keybind {
     uint8       flags;                  /**< KEYF_* flags set for the record.*/
     sint8       direction;              /**< -1 non-direction key, else >= 0.*/
     uint32      keysym;                 /**< Key this binding record is for. */
     char        *command;               /**< Command string bound to a key. */
-    struct Keys *next;
-} Key_Entry;
-/**
- * @} EndOf Key Entry Struct
- */
+    struct keybind *next;
+};
+
 
 /***********************************************************************
  *
@@ -128,20 +127,34 @@ static uint32 firekeysym[2], runkeysym[2], commandkeysym, *bind_keysym,
 static int bind_flags = 0;
 static char bind_buf[MAX_BUF];
 
-#define KEYF_NORMAL     0x01            /**< Used in normal mode */
-#define KEYF_FIRE       0x02            /**< Used in fire mode */
-#define KEYF_RUN        0x04            /**< Used in run mode */
-#define KEYF_EDIT       0x08            /**< Line editor */
-#define KEYF_STANDARD   0x10            /**< For standard/built-in keybinds */
-#define KEYF_ALT        0x20            /**< For ALT key modifier */
-#define KEYF_META       0x40            /**< For Meta key modifier */
-#define KEYF_MODIFIERS  0x67            /**< Mask for actual keyboard
-                                         *   modifiers, not action modifiers */
+/*
+ * Key modifiers
+ *
+ * The Run, Fire, Alt and/or Meta keys can be used to qualify a key
+ * (i.e. a keybinding of the key 's' with the KEYF_RUN and KEYF_FIRE
+ * flags set will only match if both Run and Fire are held while 's' is
+ * pressed).
+ *
+ * If the user wants a key to match no matter the state of the modifier
+ * keys, the KEYF_ANY flag must be set in the binding.
+ */
+
+#define KEYF_MOD_SHIFT  (1 << 0)            /**< Used in fire mode */
+#define KEYF_MOD_CTRL   (1 << 1)            /**< Used in run mode */
+#define KEYF_MOD_ALT    (1 << 2)            /**< For ALT key modifier */
+#define KEYF_MOD_META   (1 << 3)            /**< For Meta key modifier */
+#define KEYF_MOD_MASK   (KEYF_MOD_SHIFT |   \
+                         KEYF_MOD_CTRL |    \
+                         KEYF_MOD_ALT |     \
+                         KEYF_MOD_META)
+
+#define KEYF_ANY        (1 << 4)            /**< Don't care about modifiers */
+#define KEYF_EDIT       (1 << 5)            /**< Enter command mode */
 
 extern const char *const directions[9];
 
 #define KEYHASH 257
-static Key_Entry *keys[KEYHASH];        /**< Platform independence defines that
+static struct keybind *keys[KEYHASH];   /**< Platform independence defines that
                                          *   we can't use keycodes.  instead,
                                          *   make it a hash, and set KEYHASH to
                                          *   a prime number for this purpose.
@@ -152,6 +165,41 @@ static Key_Entry *keys[KEYHASH];        /**< Platform independence defines that
  * @{
  */
 
+#define EKEYBIND_NOMEM               1
+#define EKEYBIND_TAKEN               2
+
+/**
+ * Find a keybinding for keysym.
+ *
+ * Make it possible to match a specific keysym-and-key-modifier combo
+ * (useful in game play), or to match keysym regardless of modifier.
+ *
+ * @param flags If flags has got KEYF_ANY set, the keybinding's own
+ *              flags are ignored and any match is returned.
+ *              If a keybinding matching keysym which has got KEYF_ANY
+ *              set is found, the flags param is ignored and the binding
+ *              is returned.
+ *              Otherwise, return only bindings with matching modifier
+ *              flags.
+ */
+static struct keybind *keybind_find(uint32 keysym, unsigned int flags)
+{
+    struct keybind *kb;
+
+    kb = keys[keysym % KEYHASH];
+    while (kb != NULL) {
+        if (kb->keysym == 0 || kb->keysym == keysym) {
+            if ((kb->flags & KEYF_ANY) || (flags & KEYF_ANY))
+                return kb;
+            if ((kb->flags & KEYF_MOD_MASK) == flags)
+                return kb;
+        }
+        kb = kb->next;
+    }
+
+    return NULL;
+}
+
 /**
  * Updates the keys array with the keybinding that is passed.  It allocates
  * memory for the array entry, then uses strdup_local() to allocate memory
@@ -162,40 +210,70 @@ static Key_Entry *keys[KEYHASH];        /**< Platform independence defines that
  * @param flags State that the keyboard is in.
  * @param command A command to bind to the key specified in keysym.
  */
-static void insert_key(uint32 keysym, int flags, const char *command)
+static int keybind_insert(uint32 keysym, unsigned int flags, const char *command)
 {
-    Key_Entry *newkey;
-    int i, direction = -1, slot;
+    struct keybind **next_ptr;
+    int slot;
+    int i;
+    int dir;
+
+    if (keybind_find(keysym, flags) != NULL)
+        return -EKEYBIND_TAKEN;
 
     slot = keysym % KEYHASH;
 
-    if (keys[slot] == NULL) {
-        keys[slot] = malloc(sizeof(Key_Entry));
-        keys[slot]->command = NULL;
-        keys[slot]->next = NULL;
-        newkey = keys[slot];
-    } else {
-        newkey = keys[slot];
-        while (newkey->next != NULL) {
-            newkey = newkey->next;
-        }
-        newkey->next = calloc(1, sizeof(Key_Entry));
-        newkey = newkey->next;
-    }
+    next_ptr = &keys[slot];
+    while (*next_ptr)
+        next_ptr = &(*next_ptr)->next;
+    *next_ptr = calloc(1, sizeof(**next_ptr));
+    if (*next_ptr == NULL)
+        return -EKEYBIND_NOMEM;
+
+    (*next_ptr)->keysym = keysym;
+    (*next_ptr)->flags = flags;
+    (*next_ptr)->command = strdup_local(command);
+
     /*
      * Try to find out if the command is a direction command.  If so, keep
      * track of this fact, so in fire or run mode, things work correctly.
      */
+    dir = -1;
     for (i = 0; i < 9; i++)
         if (!strcmp(command, directions[i])) {
-            direction = i;
+            dir = i;
             break;
         }
+    (*next_ptr)->direction = dir;
 
-    newkey->keysym = keysym;
-    newkey->flags = flags;
-    newkey->command = strdup_local(command);
-    newkey->direction = direction;
+    return 0;
+}
+
+static int keybind_remove(struct keybind *entry)
+{
+    struct keybind **next_ptr;
+    int slot;
+
+    slot = entry->keysym % KEYHASH;
+
+    next_ptr = &keys[slot];
+    while (*next_ptr) {
+        if (*next_ptr == entry) {
+            *next_ptr = entry->next;
+            return 0;
+        }
+        next_ptr = &(*next_ptr)->next;
+    }
+
+    /* No such key entry */
+    return -1;
+}
+
+static void keybind_free(struct keybind **entry)
+{
+    free((*entry)->command);
+    (*entry)->command = NULL;
+    free(*entry);
+    *entry = NULL;
 }
 
 /**
@@ -205,11 +283,12 @@ static void insert_key(uint32 keysym, int flags, const char *command)
  * @param line
  * @param standard Set (1) or clear (0) the KEYF_STANDARD flag for the binding.
  */
-static void parse_keybind_line(char *buf, int line, int standard)
+static void parse_keybind_line(char *buf, int line)
 {
     char *cp, *cpnext;
     uint32 keysym;
     int flags;
+    int res;
 
     /*
      * There may be a rare error case when cp is used uninitialized. So let's
@@ -245,7 +324,7 @@ static void parse_keybind_line(char *buf, int line, int standard)
                 "Line %d (%s) corrupted in keybinding file.", line, buf);
             return;
         }
-        *cp1 ++ = 0;/* Null terminate it */
+        *cp1++ = 0; /* Null terminate it */
         keysym = gdk_keyval_from_name(cp);
         /* As of now, all these keys must have keysyms */
         if (keysym == 0) {
@@ -302,12 +381,6 @@ static void parse_keybind_line(char *buf, int line, int standard)
             return;
         }
     } else {
-        if (standard) {
-            standard = KEYF_STANDARD;
-        } else {
-            standard = 0;
-        }
-
         *cpnext++ = '\0';
         keysym = gdk_keyval_from_name(buf);
         if (!keysym) {
@@ -316,7 +389,7 @@ static void parse_keybind_line(char *buf, int line, int standard)
             return;
         }
         cp = cpnext;
-        cpnext = strchr(cp,' ');
+        cpnext = strchr(cp, ' ');
         if (cpnext == NULL) {
             LOG(LOG_WARNING, "gtk-v2::parse_keybind_line",
                 "Line %d (%s) corrupted in keybinding file.", line, cp);
@@ -325,7 +398,7 @@ static void parse_keybind_line(char *buf, int line, int standard)
         *cpnext++ = '\0';
 
         cp = cpnext;
-        cpnext = strchr(cp,' ');
+        cpnext = strchr(cp, ' ');
         if (cpnext == NULL) {
             LOG(LOG_WARNING, "gtk-v2::parse_keybind_line",
                 "Line %d (%s) corrupted in keybinding file.", line, cp);
@@ -336,29 +409,25 @@ static void parse_keybind_line(char *buf, int line, int standard)
         while (*cp != '\0') {
             switch (*cp) {
             case 'A':
-                flags |= KEYF_NORMAL | KEYF_FIRE | KEYF_RUN
-                         | KEYF_META | KEYF_ALT;
+                flags |= KEYF_ANY;
                 break;
             case 'E':
                 flags |= KEYF_EDIT;
                 break;
             case 'F':
-                flags |= KEYF_FIRE;
+                flags |= KEYF_MOD_SHIFT;
                 break;
             case 'L':   /* A is used, so using L for alt */
-                flags |= KEYF_ALT;
+                flags |= KEYF_MOD_ALT;
                 break;
             case 'M':
-                flags |= KEYF_META;
+                flags |= KEYF_MOD_META;
                 break;
             case 'N':
-                flags |= KEYF_NORMAL;
+                /* Nothing to do */
                 break;
             case 'R':
-                flags |= KEYF_RUN;
-                break;
-            case 'S':
-                flags |= KEYF_STANDARD;
+                flags |= KEYF_MOD_CTRL;
                 break;
             default:
                 LOG(LOG_WARNING, "gtk-v2::parse_keybind_line",
@@ -372,10 +441,15 @@ static void parse_keybind_line(char *buf, int line, int standard)
         cpnext[strlen(cpnext) - 1] = '\0';
         if (strlen(cpnext) > (sizeof(bind_buf) - 1)) {
             cpnext[sizeof(bind_buf) - 1] = '\0';
-            LOG(LOG_WARNING, "gtk-v2::parse_keybind_line", "Command too long! Truncated.");
+            LOG(LOG_WARNING, "gtk-v2::parse_keybind_line",
+                "Command too long! Truncated.");
         }
 
-        insert_key(keysym, flags | standard, cpnext);
+        res = keybind_insert(keysym, flags, cpnext);
+        if (res == -EKEYBIND_TAKEN)
+            LOG(LOG_WARNING, "gtk-v2::parse_keybind_line",
+                "Duplicate keybinds in file; ignoring binding of command \"%s\".",
+                cpnext);
     } /* else if not special binding line */
 }
 
@@ -387,11 +461,12 @@ static void init_default_keybindings(void)
     char buf[MAX_BUF];
     int i;
 
-    LOG(LOG_DEBUG, "gtk-v2::init_default_keybindings", "Using built-in defaults");
+    LOG(LOG_DEBUG, "gtk-v2::init_default_keybindings",
+        "Using built-in defaults");
 
     for (i = 0; i < sizeof(def_keys) / sizeof(char *); i++) {
         strcpy(buf, def_keys[i]);
-        parse_keybind_line(buf, i, 1);
+        parse_keybind_line(buf, i);
     }
 }
 
@@ -490,6 +565,8 @@ void keys_init(GtkWidget *window_root)
     keybinding_window = glade_xml_get_widget(dialog_xml, "keybinding_window");
     xml_tree = glade_get_widget_tree(GTK_WIDGET(keybinding_window));
 
+    keybinding_checkbutton_any =
+        glade_xml_get_widget(xml_tree, "keybinding_checkbutton_any");
     keybinding_checkbutton_control =
         glade_xml_get_widget(xml_tree, "keybinding_checkbutton_control");
     keybinding_checkbutton_shift =
@@ -524,6 +601,9 @@ void keys_init(GtkWidget *window_root)
     g_signal_connect((gpointer) keybinding_button_bind, "clicked",
                      G_CALLBACK(on_keybinding_button_bind_clicked), NULL);
 
+    g_signal_connect((gpointer) keybinding_checkbutton_any, "clicked",
+                     G_CALLBACK(on_keybinding_checkbutton_any_clicked), NULL);
+
     widget = glade_xml_get_widget(xml_tree, "keybinding_button_clear");
     g_signal_connect((gpointer) widget, "clicked",
                      G_CALLBACK(on_keybinding_button_clear_clicked), NULL);
@@ -602,7 +682,7 @@ void keys_init(GtkWidget *window_root)
     while (fgets(buf, BIG_BUF, fp)) {
         line++;
         buf[BIG_BUF - 1] = '\0';
-        parse_keybind_line(buf, line, 0);
+        parse_keybind_line(buf, line);
     }
 
     fclose(fp);
@@ -620,7 +700,7 @@ void keys_init(GtkWidget *window_root)
  *
  * @param ks
  */
-static void parse_key_release(uint32 ks)
+static void parse_key_release(uint32 keysym)
 {
 
     /*
@@ -628,20 +708,20 @@ static void parse_key_release(uint32 ks)
      * Something smart does need to be done when the character enters a non
      * play mode with fire or run mode already set, however.
      */
-    if (ks == firekeysym[0] || ks == firekeysym[1]) {
+    if (keysym == firekeysym[0] || keysym == firekeysym[1]) {
         cpl.fire_on = 0;
         clear_fire();
         gtk_label_set(GTK_LABEL(fire_label), "    ");
-    } else if (ks == runkeysym[0] || ks == runkeysym[1]) {
+    } else if (keysym == runkeysym[0] || keysym == runkeysym[1]) {
         cpl.run_on = 0;
         if (use_config[CONFIG_ECHO])
             draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
                           "stop run");
         clear_run();
         gtk_label_set(GTK_LABEL(run_label), "   ");
-    } else if (ks == altkeysym[0] || ks == altkeysym[1]) {
+    } else if (keysym == altkeysym[0] || keysym == altkeysym[1]) {
         cpl.alt_on = 0;
-    } else if (ks == metakeysym[0] || ks == metakeysym[1]) {
+    } else if (keysym == metakeysym[0] || keysym == metakeysym[1]) {
         cpl.meta_on = 0;
     }
     /*
@@ -662,10 +742,13 @@ static void parse_key_release(uint32 ks)
  */
 static void parse_key(char key, uint32 keysym)
 {
-    Key_Entry *keyentry, *first_match = NULL;
+    struct keybind *kb;
     int present_flags = 0;
     char buf[MAX_BUF], tmpbuf[MAX_BUF];
 
+    /* We handle the Shift key separately */
+    keysym = gdk_keyval_to_lower(keysym);
+
     if (keysym == commandkeysym) {
         gtk_widget_grab_focus(GTK_WIDGET(entry_commands));
         gtk_entry_set_visibility(GTK_ENTRY(entry_commands), 1);
@@ -692,42 +775,24 @@ static void parse_key(char key, uint32 keysym)
         return;
     }
 
+    present_flags = 0;
     if (cpl.run_on) {
-        present_flags |= KEYF_RUN;
+        present_flags |= KEYF_MOD_CTRL;
     }
     if (cpl.fire_on) {
-        present_flags |= KEYF_FIRE;
+        present_flags |= KEYF_MOD_SHIFT;
     }
     if (cpl.alt_on) {
-        present_flags |= KEYF_ALT;
+        present_flags |= KEYF_MOD_ALT;
     }
     if (cpl.meta_on) {
-        present_flags |= KEYF_META;
-    }
-    if (present_flags == 0) {
-        present_flags = KEYF_NORMAL;
+        present_flags |= KEYF_MOD_META;
     }
 
-    keyentry = keys[keysym % KEYHASH];
-    while (keyentry != NULL) {
-        if ((keyentry->keysym != 0 && keyentry->keysym != keysym) ||
-                (!(keyentry->flags & present_flags))) {
-            keyentry = keyentry->next;
-            continue;
-        }
-        first_match = keyentry;
-
-        /* Try to find a prefect match */
-        if ((keyentry->flags & KEYF_MODIFIERS) != present_flags) {
-            keyentry = keyentry->next;
-            continue;
-        } else {
-            break;
-        }
-    }
-    if (first_match != NULL) {
-        if (first_match->flags & KEYF_EDIT) {
-            strcpy(cpl.input_text, first_match->command);
+    kb = keybind_find(keysym, present_flags);
+    if (kb != NULL) {
+        if (kb->flags & KEYF_EDIT) {
+            strcpy(cpl.input_text, kb->command);
             cpl.input_state = Command_Mode;
             gtk_entry_set_text(GTK_ENTRY(entry_commands), cpl.input_text);
             gtk_widget_grab_focus(GTK_WIDGET(entry_commands));
@@ -737,31 +802,33 @@ static void parse_key(char key, uint32 keysym)
         }
 
         /* Some spells (dimension door) need a valid count value */
-        cpl.count = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spinbutton_count));
+        cpl.count = gtk_spin_button_get_value_as_int(
+                                   GTK_SPIN_BUTTON(spinbutton_count));
 
-        if (first_match->direction >= 0) {
+        if (kb->direction >= 0) {
             if (cpl.fire_on) {
-                snprintf(buf, sizeof(buf), "fire %s", first_match->command);
-                fire_dir(first_match->direction);
+                snprintf(buf, sizeof(buf), "fire %s", kb->command);
+                fire_dir(kb->direction);
             } else if (cpl.run_on) {
-                run_dir(first_match->direction);
-                snprintf(buf, sizeof(buf), "run %s", first_match->command);
+                snprintf(buf, sizeof(buf), "run %s", kb->command);
+                run_dir(kb->direction);
             } else {
-                extended_command(first_match->command);
+                extended_command(kb->command);
             }
             if (use_config[CONFIG_ECHO])
                 draw_ext_info(
                     NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
-                    first_match->command);
+                    kb->command);
         } else {
             if (use_config[CONFIG_ECHO])
                 draw_ext_info(
                     NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
-                    first_match->command);
-            extended_command(first_match->command);
+                    kb->command);
+            extended_command(kb->command);
         }
         return;
     }
+
     if (key >= '0' && key <= '9') {
         cpl.count = cpl.count*10 + (key - '0');
         if (cpl.count > 100000) {
@@ -772,20 +839,20 @@ static void parse_key(char key, uint32 keysym)
     }
     tmpbuf[0] = 0;
     if (cpl.fire_on) {
-        strcat(tmpbuf, "fire+");
+        strcat(tmpbuf, "Shift + ");
     }
     if (cpl.run_on) {
-        strcat(tmpbuf, "run+");
+        strcat(tmpbuf, "Ctrl + ");
     }
     if (cpl.alt_on) {
-        strcat(tmpbuf, "alt+");
+        strcat(tmpbuf, "Alt + ");
     }
     if (cpl.meta_on) {
-        strcat(tmpbuf, "meta+");
+        strcat(tmpbuf, "Meta + ");
     }
 
     snprintf(buf, sizeof(buf),
-             "Key %s%s is not bound to any command.  Use bind to associate this keypress with a command",
+             "Key %s%s is not bound to any command. Use 'bind' to associate this keypress with a command",
              tmpbuf, keysym == NoSymbol ? "unknown" : gdk_keyval_name(keysym));
 #ifdef WIN32
     if ((65513 != keysym) && (65511 != keysym))
@@ -794,6 +861,37 @@ static void parse_key(char key, uint32 keysym)
     cpl.count = 0;
 }
 
+static void get_key_modchars(struct keybind *kb, int save_mode, char *buf)
+{
+    int bi = 0;
+
+    if (kb->flags & KEYF_ANY) {
+        buf[bi++] = 'A';
+    }
+    if (save_mode || !(kb->flags & KEYF_ANY)) {
+        if ((kb->flags & KEYF_MOD_MASK) == 0) {
+            buf[bi++] = 'N';
+        }
+        if (kb->flags & KEYF_MOD_SHIFT) {
+            buf[bi++] = 'F';
+        }
+        if (kb->flags & KEYF_MOD_CTRL) {
+            buf[bi++] = 'R';
+        }
+        if (kb->flags & KEYF_MOD_ALT) {
+            buf[bi++] = 'L';
+        }
+        if (kb->flags & KEYF_MOD_META) {
+            buf[bi++] = 'M';
+        }
+    }
+    if (kb->flags & KEYF_EDIT) {
+        buf[bi++] = 'E';
+    }
+
+    buf[bi] = '\0';
+}
+
 /**
  *
  * @param key
@@ -802,60 +900,34 @@ static void parse_key(char key, uint32 keysym)
  * information in a friendly manner.
  * @return A character string describing the key.
  */
-static char *get_key_info(Key_Entry *key, int save_mode)
+static char *get_key_info(struct keybind *kb, int save_mode)
 {
     /* bind buf is the maximum space allowed for a
-     * binded command. We will add additional datas to
-     * it so we increase by MAX_BUF*/
+     * bound command. We will add additional data to
+     * it so we increase its size by MAX_BUF*/
     static char buf[MAX_BUF + sizeof(bind_buf)];
 
     char buff[MAX_BUF];
-    int bi = 0;
 
-    if ((key->flags & KEYF_MODIFIERS) == KEYF_MODIFIERS) {
-        buff[bi++] = 'A';
-    } else {
-        if (key->flags & KEYF_NORMAL) {
-            buff[bi++] = 'N';
-        }
-        if (key->flags & KEYF_FIRE) {
-            buff[bi++] = 'F';
-        }
-        if (key->flags & KEYF_RUN) {
-            buff[bi++] = 'R';
-        }
-        if (key->flags & KEYF_ALT) {
-            buff[bi++] = 'L';
-        }
-        if (key->flags & KEYF_META) {
-            buff[bi++] = 'M';
-        }
-    }
-    if (key->flags & KEYF_EDIT) {
-        buff[bi++] = 'E';
-    }
-    if (key->flags & KEYF_STANDARD) {
-        buff[bi++] = 'S';
-    }
+    get_key_modchars(kb, save_mode, buff);
 
-    buff[bi] = '\0';
     if (save_mode) {
-        if (key->keysym == NoSymbol) {
+        if (kb->keysym == NoSymbol) {
             snprintf(buf, sizeof(buf), "(null) %i %s %s",
-                     0, buff, key->command);
+                     0, buff, kb->command);
         } else {
             snprintf(buf, sizeof(buf), "%s %i %s %s",
-                     gdk_keyval_name(key->keysym), 0,
-                     buff, key->command);
+                     gdk_keyval_name(kb->keysym),
+                     0, buff, kb->command);
         }
     } else {
-        if (key->keysym == NoSymbol) {
+        if (kb->keysym == NoSymbol) {
             snprintf(buf, sizeof(buf), "key (null) %s %s",
-                     buff, key->command);
+                     buff, kb->command);
         } else {
             snprintf(buf, sizeof(buf), "key %s %s %s",
-                     gdk_keyval_name(key->keysym),
-                     buff, key->command);
+                     gdk_keyval_name(kb->keysym),
+                     buff, kb->command);
         }
     }
     return buf;
@@ -866,10 +938,10 @@ static char *get_key_info(Key_Entry *key, int save_mode)
  *
  * @param allbindings Also shows the standard (default) keybindings.
  */
-static void show_keys(int allbindings)
+static void show_keys(void)
 {
     int i, count = 1;
-    Key_Entry *key;
+    struct keybind *kb;
     char buf[MAX_BUF];
 
     snprintf(buf, sizeof(buf), "Commandkey %s",
@@ -912,12 +984,8 @@ static void show_keys(int allbindings)
      * Perhaps we should start at 8, so that we only show 'active' keybindings?
      */
     for (i = 0; i < KEYHASH; i++) {
-        for (key = keys[i]; key != NULL; key = key->next) {
-            if (key->flags & KEYF_STANDARD && !allbindings) {
-                continue;
-            }
-
-            snprintf(buf, sizeof(buf), "%3d %s", count, get_key_info(key, 0));
+        for (kb = keys[i]; kb != NULL; kb = kb->next) {
+            snprintf(buf, sizeof(buf), "%3d %s", count, get_key_info(kb, 0));
             draw_ext_info(
                 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, buf);
             count++;
@@ -939,9 +1007,10 @@ void bind_key(char *params)
 
     if (!params) {
         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
-                      "Usage: bind [-aefmnr] {<commandline>/commandkey/firekey{1/2}"
-                      "/runkey{1/2}/altkey{1/2}/metakey{1/2}"
-                      "completekey/nextkey/prevkey}");
+"Usage: 'bind -ed <commandline>/commandkey/completekey/nextkey/prevkey'\n"
+"Where\n"
+"      -e means enter edit mode\n"
+"      -d means don't care about modifier keys (key works no matter if Ctrl/Alt etc are held)");
         return;
     }
 
@@ -958,64 +1027,6 @@ void bind_key(char *params)
         return;
     }
 
-    if (!strcmp(params, "firekey1")) {
-        bind_keysym = &firekeysym[0];
-        draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_QUERY,
-                      "Push key to bind new firekey 1.");
-        cpl.input_state = Configure_Keys;
-        return;
-    }
-    if (!strcmp(params, "firekey2")) {
-        bind_keysym = &firekeysym[1];
-        draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_QUERY,
-                      "Push key to bind new firekey 2.");
-        cpl.input_state = Configure_Keys;
-        return;
-    }
-    if (!strcmp(params, "metakey1")) {
-        bind_keysym = &metakeysym[0];
-        draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_QUERY,
-                      "Push key to bind new metakey 1.");
-        cpl.input_state = Configure_Keys;
-        return;
-    }
-    if (!strcmp(params, "metakey2")) {
-        bind_keysym = &metakeysym[1];
-        draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_QUERY,
-                      "Push key to bind new metakey 2.");
-        cpl.input_state = Configure_Keys;
-        return;
-    }
-    if (!strcmp(params, "altkey1")) {
-        bind_keysym = &altkeysym[0];
-        draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_QUERY,
-                      "Push key to bind new altkey 1.");
-        cpl.input_state = Configure_Keys;
-        return;
-    }
-    if (!strcmp(params, "altkey2")) {
-        bind_keysym = &altkeysym[1];
-        draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_QUERY,
-                      "Push key to bind new altkey 2.");
-        cpl.input_state = Configure_Keys;
-        return;
-    }
-    if (!strcmp(params, "runkey1")) {
-        bind_keysym = &runkeysym[0];
-        draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_QUERY,
-                      "Push key to bind new runkey 1.");
-        cpl.input_state = Configure_Keys;
-        return;
-    }
-
-    if (!strcmp(params, "runkey2")) {
-        bind_keysym = &runkeysym[1];
-        draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_QUERY,
-                      "Push key to bind new runkey 2.");
-        cpl.input_state = Configure_Keys;
-        return;
-    }
-
     if (!strcmp(params, "completekey")) {
         bind_keysym = &completekeysym;
         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_QUERY,
@@ -1039,31 +1050,17 @@ void bind_key(char *params)
         cpl.input_state = Configure_Keys;
         return;
     }
+    bind_keysym = NULL;
 
-    if (params[0] != '-') {
-        bind_flags = KEYF_MODIFIERS;
-    } else {
-        bind_flags = 0;
-        bind_keysym = NULL;
+    bind_flags = 0;
+    if (params[0] == '-') {
         for (params++; *params != ' '; params++)
             switch (*params) {
-            case 'a':
-                bind_flags |= KEYF_ALT;
-                break;
             case 'e':
                 bind_flags |= KEYF_EDIT;
                 break;
-            case 'f':
-                bind_flags |= KEYF_FIRE;
-                break;
-            case 'm':
-                bind_flags |= KEYF_META;
-                break;
-            case 'n':
-                bind_flags |= KEYF_NORMAL;
-                break;
-            case 'r':
-                bind_flags |= KEYF_RUN;
+            case 'd':
+                bind_flags |= KEYF_ANY;
                 break;
             case '\0':
                 draw_ext_info(
@@ -1071,18 +1068,14 @@ void bind_key(char *params)
                     "Use unbind to remove bindings.");
                 return;
             default:
-                snprintf(buf, sizeof(buf), "Unsupported or invalid bind flag: '%c'", *params);
-                draw_ext_info(
-                    NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, buf);
-                return;
+                snprintf(buf, sizeof(buf),
+                         "Unsupported or invalid bind flag: '%c'", *params);
+                draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, buf);
+                    return;
             }
         params++;
     }
 
-    if (!(bind_flags & KEYF_MODIFIERS)) {
-        bind_flags |= KEYF_MODIFIERS;
-    }
-
     if (!params[0]) {
         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
                       "Use unbind to remove bindings.");
@@ -1115,13 +1108,12 @@ void bind_key(char *params)
  *            returns when it becomes a NULL pointer.
  * @param kc
  */
-static void save_individual_key(FILE *fp, Key_Entry *key, KeyCode kc)
+static void save_individual_key(FILE *fp, struct keybind *kb, KeyCode kc)
 {
-    if (key == NULL) {
-        return;
+    while (kb) {
+        fprintf(fp, "%s\n", get_key_info(kb, 1));
+        kb = kb->next;
     }
-    fprintf(fp, "%s\n", get_key_info(key, 1));
-    save_individual_key(fp, key->next, kc);
 }
 
 /**
@@ -1235,11 +1227,15 @@ static void save_keys(void)
 static void configure_keys(uint32 keysym)
 {
     char buf[MAX_BUF];
-    Key_Entry *keyentry, *first_match = NULL;
+    struct keybind *kb;
+    int res;
+
+    /* We handle the Shift key separately */
+    keysym = gdk_keyval_to_lower(keysym);
 
     /*
      * I think that basically if we are not rebinding the special control keys
-     * (in which case bind_kesym would be set to something) we just want to
+     * (in which case bind_keysym would be set to something) we just want to
      * handle these keypresses as normal events.
      */
     if (bind_keysym == NULL) {
@@ -1262,66 +1258,47 @@ static void configure_keys(uint32 keysym)
             return;
         }
     }
-    cpl.input_state = Playing;
+
     /*
-     * Try to be clever - take into account shift/control keys being held down
-     * when binding keys - in this way, player does not have to use -f and -r
-     * flags to bind for many simple binds.
+     * Take shift/control keys into account when binding keys.
      */
-    if ((cpl.fire_on || cpl.run_on || cpl.meta_on || cpl.alt_on) &&
-            (bind_flags & KEYF_MODIFIERS) == KEYF_MODIFIERS) {
-        bind_flags &= ~KEYF_MODIFIERS;
+    if (!(bind_flags & KEYF_ANY)) {
         if (cpl.fire_on) {
-            bind_flags |= KEYF_FIRE;
+            bind_flags |= KEYF_MOD_SHIFT;
         }
         if (cpl.run_on) {
-            bind_flags |= KEYF_RUN;
+            bind_flags |= KEYF_MOD_CTRL;
         }
         if (cpl.meta_on) {
-            bind_flags |= KEYF_META;
+            bind_flags |= KEYF_MOD_META;
         }
         if (cpl.alt_on) {
-            bind_flags |= KEYF_ALT;
+            bind_flags |= KEYF_MOD_ALT;
         }
     }
 
+    /* Reset state now. We might return early if bind fails. */
+    cpl.input_state = Playing;
+
     if (bind_keysym != NULL) {
         *bind_keysym = keysym;
         bind_keysym = NULL;
     } else {
-        keyentry = keys[keysym % KEYHASH];
-        while (keyentry != NULL) {
-            if ((keyentry->keysym != 0 && keyentry->keysym != keysym) ||
-                    (!(keyentry->flags & bind_flags))) {
-                keyentry = keyentry->next;
-                continue;
-            }
-            first_match = keyentry;
-
-            /* Try to find a prefect match */
-            if ((keyentry->flags & KEYF_MODIFIERS) != bind_flags) {
-                keyentry = keyentry->next;
-                continue;
-            } else {
-                break;
-            }
-        }
-        if (first_match) {
+        res = keybind_insert(keysym, bind_flags, bind_buf);
+        if (res == -EKEYBIND_TAKEN) {
+            kb = keybind_find(keysym, bind_flags);
             snprintf(buf, sizeof(buf),
-                     "Warning: Keybind %s may conflict with new binding.",
-                     first_match->command);
+                     "Error: Key already used for command \"%s\". Use unbind first.",
+                     kb->command);
             draw_ext_info(
                 NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, buf);
+            return;
         }
-
-        insert_key(keysym, bind_flags, bind_buf);
     }
 
-    snprintf(buf, sizeof(buf), "Binded to key '%s' (%i)",
+    snprintf(buf, sizeof(buf), "Bound to key '%s' (%i)",
              keysym == NoSymbol ? "unknown" : gdk_keyval_name(keysym), keysym);
     draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_CONFIG, buf);
-    cpl.fire_on = 0;
-    cpl.run_on = 0;
     draw_message_window(0);
 
     /*
@@ -1339,13 +1316,9 @@ static void configure_keys(uint32 keysym)
 static void unbind_usage(void)
 {
     draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
-                  "Usage: unbind <entry_number> or");
-    draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
-                  "Usage: unbind [-a] [-g] to show existing bindings");
-    draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
-                  "    -a shows all (global) bindings");
+                  "Usage: 'unbind <entry_number>' or");
     draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
-                  "    -g unbinds a global binding");
+                  "Usage: 'unbind' to show existing bindings");
 }
 
 /**
@@ -1354,12 +1327,13 @@ static void unbind_usage(void)
  */
 void unbind_key(const char *params)
 {
-    int count = 0, keyentry, onkey,global = 0;
-    Key_Entry *key, *tmp;
+    int count = 0, keyentry, slot;
+    int res;
+    struct keybind *kb;
     char buf[MAX_BUF];
 
-    if (params == NULL || params[0] == '\0') {
-        show_keys(0);
+    if (params == NULL) {
+        show_keys();
         return;
     }
 
@@ -1368,71 +1342,47 @@ void unbind_key(const char *params)
         params++;
     }
 
-    if (!strcmp(params, "-a")) {
-        show_keys(1);
+    if (params[0] == '\0') {
+        show_keys();
         return;
     }
-    if (!strncmp(params, "-g", 2)) {
-        global = 1;
-        params = strchr(params,' ');
-        if (params == NULL)  {
-            unbind_usage();
-            return;
-        }
-    }
-    keyentry = atoi(params);
-    if (keyentry == 0) {
+
+    if ((keyentry = atoi(params)) == 0) {
         unbind_usage();
         return;
     }
 
-    for (onkey = 0; onkey < KEYHASH; onkey++) {
-        for (key = keys[onkey]; key; key = key->next) {
-            if (global || !(key->flags & KEYF_STANDARD)) {
-                count++;
-            }
-            /* We found the key we want to unbind */
-            if (keyentry == count) {
+    for (slot = 0; slot < KEYHASH; slot++) {
+        for (kb = keys[slot]; kb; kb = kb->next) {
+            count++;
 
-                /* If it is the first entry, it is easy */
-                if (key == keys[onkey]) {
-                    keys[onkey] = key->next;
-                    goto unbinded;
-                }
-                /*
-                 * Otherwise, we need to figure out where in the link list the
-                 * entry is.
-                 */
-                for (tmp = keys[onkey]; tmp->next != NULL; tmp = tmp->next) {
-                    if (tmp->next == key) {
-                        tmp->next = key->next;
-                        goto unbinded;
-                    }
-                }
-                LOG(LOG_ERROR, "gtk-v2::unbind_key",
-                    "found number entry, but could not find actual key");
+            if (keyentry == count) {
+                /* We found the key we want to unbind */
+                snprintf(buf, sizeof(buf), "Removing binding: %3d %s",
+                         count, get_key_info(kb, 0));
+                draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT,
+                              MSG_TYPE_CLIENT_CONFIG, buf);
+                res = keybind_remove(kb);
+                if (res < 0)
+                    LOG(LOG_ERROR, "gtk-v2::unbind_key",
+                        "found number entry, but could not find actual key");
+                keybind_free(&kb);
+                save_keys();
+                return;
             }
         }
     }
+
+    /* Not found */
     /* Makes things look better to draw the blank line */
     draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, "");
     draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
                   "Not found. Try 'unbind' with no options to find entry.");
     return;
-    /*
-     * Found. Now remove it.
-     */
-unbinded:
-    snprintf(buf, sizeof(buf), "Removed binding: %3d %s", count, get_key_info(key,0));
-
-    draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_CONFIG, buf);
-    free(key->command);
-    free(key);
-    save_keys();
 }
 
 /**
- * When the main window looses it's focus, act as if all keys have been released
+ * When the main window looses its focus, act as if all keys have been released
  */
 void focusoutfunc(GtkWidget *widget, GdkEventKey *event, GtkWidget *window)
 {
@@ -1626,63 +1576,28 @@ void keyfunc(GtkWidget *widget, GdkEventKey *event, GtkWidget *window)
 void draw_keybindings(GtkWidget *keylist)
 {
     int i, count = 1;
-    Key_Entry *key;
-    int allbindings = 0;
+    struct keybind *kb;
     char buff[MAX_BUF];
-    int bi = 0;
     char buffer[5][MAX_BUF];
     char *buffers[5];
-    gint tmprow;
 
-    gtk_clist_clear (GTK_CLIST(keylist));
+    gtk_clist_clear(GTK_CLIST(keylist));
     for (i = 0; i < KEYHASH; i++) {
-        for (key = keys[i]; key != NULL; key = key->next) {
-            if (key->flags & KEYF_STANDARD && !allbindings) {
-                continue;
-            }
-
-            bi = 0;
-
-            if ((key->flags & KEYF_MODIFIERS) == KEYF_MODIFIERS) {
-                buff[bi++] = 'A';
-            } else {
-                if (key->flags & KEYF_NORMAL) {
-                    buff[bi++] = 'N';
-                }
-                if (key->flags & KEYF_FIRE) {
-                    buff[bi++] = 'F';
-                }
-                if (key->flags & KEYF_RUN) {
-                    buff[bi++] = 'R';
-                }
-                if (key->flags & KEYF_ALT) {
-                    buff[bi++] = 'L';
-                }
-                if (key->flags & KEYF_META) {
-                    buff[bi++] = 'M';
-                }
-            }
-            if (key->flags & KEYF_EDIT) {
-                buff[bi++] = 'E';
-            }
-            if (key->flags & KEYF_STANDARD) {
-                buff[bi++] = 'S';
-            }
-
-            buff[bi] = '\0';
+        for (kb = keys[i]; kb != NULL; kb = kb->next) {
+            get_key_modchars(kb, 0, buff);
 
-            if (key->keysym != NoSymbol) {
+            if (kb->keysym != NoSymbol) {
                 snprintf(buffer[0], sizeof(buffer[0]), "%i", count);
-                snprintf(buffer[1], sizeof(buffer[1]), "%s", gdk_keyval_name(key->keysym));
+                snprintf(buffer[1], sizeof(buffer[1]), "%s", gdk_keyval_name(kb->keysym));
                 snprintf(buffer[2], sizeof(buffer[2]), "%i", i);
                 snprintf(buffer[3], sizeof(buffer[3]), "%s", buff);
-                snprintf(buffer[4], sizeof(buffer[4]), "%s", key->command);
+                snprintf(buffer[4], sizeof(buffer[4]), "%s", kb->command);
                 buffers[0] = buffer[0];
                 buffers[1] = buffer[1];
                 buffers[2] = buffer[2];
                 buffers[3] = buffer[3];
                 buffers[4] = buffer[4];
-                tmprow = gtk_clist_append(GTK_CLIST(keylist), buffers);
+                gtk_clist_append(GTK_CLIST(keylist), buffers);
             }
             count++;
         }
@@ -1736,7 +1651,7 @@ void gtk_command_history(int direction)
              */
             gtk_entry_set_text(GTK_ENTRY(entry_commands), "");
             gtk_entry_set_position(GTK_ENTRY(entry_commands), 0);
-            scroll_history_position=cur_history_position;
+            scroll_history_position = cur_history_position;
             return;
         }
     }
@@ -1746,7 +1661,7 @@ void gtk_command_history(int direction)
     }
 
     scroll_history_position=i;
-    /*  fprintf(stderr,"resetting postion to %d, data = %s\n", i, history[i]);*/
+    /*  fprintf(stderr, "resetting postion to %d, data = %s\n", i, history[i]);*/
     gtk_entry_set_text(GTK_ENTRY(entry_commands), history[i]);
     gtk_widget_grab_focus(GTK_WIDGET(entry_commands));
     gtk_editable_select_region(GTK_EDITABLE(entry_commands), 0, 0);
@@ -1855,48 +1770,49 @@ void on_entry_commands_activate(GtkEntry *entry, gpointer user_data)
  */
 void update_keybinding_list(void)
 {
-    int i, allbindings = 0;
-    Key_Entry *key;
+    int i;
+    struct keybind *kb;
     char modifier_buf[256];
     GtkTreeIter iter;
 
     gtk_list_store_clear(keybinding_store);
 
     for (i = 0; i < KEYHASH; i++) {
-        for (key = keys[i]; key != NULL; key = key->next) {
-            if (key->flags & KEYF_STANDARD && !allbindings) {
-                continue;
-            }
-
+        for (kb = keys[i]; kb != NULL; kb = kb->next) {
             modifier_buf[0] = 0;
 
-            if ((key->flags & KEYF_MODIFIERS) != KEYF_MODIFIERS) {
-                if (key->flags & KEYF_ALT) {
-                    strcat(modifier_buf, "Alt ");
+            if (kb->flags & KEYF_ANY) {
+                strcat(modifier_buf, "Any");
+            } else if ((kb->flags & KEYF_MOD_MASK) == 0) {
+                strcat(modifier_buf, "None");
+            } else {
+                if (kb->flags & KEYF_MOD_ALT) {
+                    strcat(modifier_buf, "Alt");
+                    if (kb->flags & (KEYF_MOD_SHIFT | KEYF_MOD_CTRL | KEYF_MOD_META))
+                        strcat(modifier_buf, " + ");
                 }
-                if (key->flags & KEYF_FIRE) {
-                    strcat(modifier_buf, "Fire ");
+                if (kb->flags & KEYF_MOD_SHIFT) {
+                    strcat(modifier_buf, "Shift");
+                    if (kb->flags & (KEYF_MOD_CTRL | KEYF_MOD_META))
+                        strcat(modifier_buf, " + ");
                 }
-                if (key->flags & KEYF_RUN) {
-                    strcat(modifier_buf, "Run ");
+                if (kb->flags & KEYF_MOD_CTRL) {
+                    strcat(modifier_buf, "Ctrl");
+                    if (kb->flags & KEYF_MOD_META)
+                        strcat(modifier_buf, " + ");
                 }
-                if (key->flags & KEYF_META) {
-                    strcat(modifier_buf, "Meta ");
+                if (kb->flags & KEYF_MOD_META) {
+                    strcat(modifier_buf, "Meta");
                 }
-            } else {
-                strcat(modifier_buf, "All ");
-            }
-            if (key->flags & KEYF_STANDARD) {
-                strcat(modifier_buf, "(Standard) ");
             }
             gtk_list_store_append(keybinding_store, &iter);
             gtk_list_store_set(keybinding_store, &iter,
                                KLIST_ENTRY, i,
-                               KLIST_KEY, gdk_keyval_name(key->keysym),
+                               KLIST_KEY, gdk_keyval_name(kb->keysym),
                                KLIST_MODS, modifier_buf,
-                               KLIST_EDIT, (key->flags & KEYF_EDIT) ? "Yes" : "No",
-                               KLIST_COMMAND, key->command,
-                               KLIST_KEY_ENTRY, key,
+                               KLIST_EDIT, (kb->flags & KEYF_EDIT) ? "Yes":"No",
+                               KLIST_COMMAND, kb->command,
+                               KLIST_KEY_ENTRY, kb,
                                -1);
         }
     }
@@ -1927,8 +1843,10 @@ void on_keybindings_activate(GtkMenuItem *menuitem, gpointer user_data)
  * @param user_data
  * @return TRUE (Returning TRUE prevents widget from getting this event.)
  */
-gboolean on_keybinding_entry_key_key_press_event(GtkWidget *widget,
-                                GdkEventKey *event, gpointer user_data)
+gboolean
+on_keybinding_entry_key_key_press_event(GtkWidget       *widget,
+                                        GdkEventKey     *event,
+                                        gpointer         user_data)
 {
     gtk_entry_set_text(
         GTK_ENTRY(keybinding_entry_key), gdk_keyval_name(event->keyval));
@@ -1984,52 +1902,28 @@ void on_keybinding_button_remove_clicked(GtkButton *button, gpointer user_data)
 {
     GtkTreeModel *model;
     GtkTreeIter iter;
-    Key_Entry *entry, *key, *tmp;
-    int onkey;
+    struct keybind *kb;
+    int res;
 
     if (!gtk_tree_selection_get_selected(keybinding_selection, &model, &iter)) {
         LOG(LOG_ERROR, "keys.c::on_keybinding_button_remove_clicked",
             "Function called with nothing selected\n");
         return;
     }
-    gtk_tree_model_get(model, &iter, KLIST_KEY_ENTRY, &entry, -1);
-    for (onkey = 0; onkey < KEYHASH; onkey++) {
-        for (key = keys[onkey]; key; key = key->next) {
-            if (key == entry) {
-                /*
-                 * This code is directly from unbind_key() above If it is the
-                 * first entry, it is easy
-                 */
-                if (key == keys[onkey]) {
-                    keys[onkey] = key->next;
-                    goto unbinded;
-                }
-                /*
-                 * Otherwise, we need to figure out where in the link list the
-                 * entry is.
-                 */
-                for (tmp = keys[onkey]; tmp->next != NULL; tmp = tmp->next) {
-                    if (tmp->next == key) {
-                        tmp->next = key->next;
-                        goto unbinded;
-                    }
-                }
-            }
-        }
-    }
-    LOG(LOG_ERROR, "keys.c::on_keybinding_button_remove_clicked",
-        "Unable to find matching key entry\n");
+    gtk_tree_model_get(model, &iter, KLIST_KEY_ENTRY, &kb, -1);
+    res = keybind_remove(kb);
+    if (res < 0)
+        LOG(LOG_ERROR, "keys.c::on_keybinding_button_remove_clicked",
+            "Unable to find matching key entry\n");
+    keybind_free(&kb);
 
-unbinded:
-    free(key->command);
-    free(key);
     save_keys();
     update_keybinding_list();
 }
 
 /**
- * Gets the state information from what checkboxes and other data in the window
- * and puts it in the variables passed passed.  This is used by both the update
+ * Gets the state information from checkboxes and other data in the window
+ * and puts it in the variables passed.  This is used by both the update
  * and add functions.
  *
  * @param keysym
@@ -2043,33 +1937,25 @@ static void keybinding_get_data(uint32 *keysym, uint8 *flags, const char **comma
     *flags = 0;
 
     if (gtk_toggle_button_get_active(
+                GTK_TOGGLE_BUTTON(keybinding_checkbutton_any))) {
+        *flags |= KEYF_ANY;
+    }
+    if (gtk_toggle_button_get_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_control))) {
-        *flags |= KEYF_RUN;
+        *flags |= KEYF_MOD_CTRL;
     }
-
     if (gtk_toggle_button_get_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_shift))) {
-        *flags |= KEYF_FIRE;
+        *flags |= KEYF_MOD_SHIFT;
     }
-
     if (gtk_toggle_button_get_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_alt))) {
-        *flags |= KEYF_ALT;
+        *flags |= KEYF_MOD_ALT;
     }
-
     if (gtk_toggle_button_get_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_meta))) {
-        *flags |= KEYF_META;
+        *flags |= KEYF_MOD_META;
     }
-
-    /* Set the KEYF_NORMAL flag when either none or all of the Run, Fire, Alt,
-     * Meta checkboxes are checked. This closely matches the combined existing
-     * logic in the parse_key() and get_key_info() functions.
-     */
-    if (!*flags || (*flags | KEYF_NORMAL) == KEYF_MODIFIERS) {
-        *flags |= KEYF_NORMAL;
-    }
-
     if (gtk_toggle_button_get_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_edit))) {
         *flags |= KEYF_EDIT;
@@ -2110,11 +1996,16 @@ void on_keybinding_button_bind_clicked(GtkButton *button, gpointer user_data)
     uint32 keysym;
     uint8 flags;
     const char *command;
+    int res;
 
     keybinding_get_data(&keysym, &flags, &command);
 
-    /* insert_key will do a strdup of command for us */
-    insert_key(keysym, flags, command);
+    /* keybind_insert will do a strdup of command for us */
+    res = keybind_insert(keysym, flags, command);
+    if (res == -EKEYBIND_TAKEN) {
+        // FIXME: popup message key already in use?
+        return;
+    }
 
     /*
      * I think it is more appropriate to clear the fields once the user adds
@@ -2138,21 +2029,37 @@ void on_keybinding_button_bind_clicked(GtkButton *button, gpointer user_data)
 void on_keybinding_button_update_clicked(GtkButton *button, gpointer user_data)
 {
     GtkTreeIter iter;
-    Key_Entry *entry;
+    struct keybind *kb;
     GtkTreeModel *model;
+    uint32 keysym;
+    uint8 flags;
     const char *buf;
+    int res;
 
     if (gtk_tree_selection_get_selected(keybinding_selection, &model, &iter)) {
-        gtk_tree_model_get(model, &iter, KLIST_KEY_ENTRY, &entry, -1);
+        gtk_tree_model_get(model, &iter, KLIST_KEY_ENTRY, &kb, -1);
 
-        if (!entry) {
+        if (!kb) {
             LOG(LOG_ERROR, "keys.c::on_keybinding_button_update_clicked",
                 "Unable to get key_entry structure\n");
             return;
         }
-        free(entry->command);
-        keybinding_get_data(&entry->keysym, &entry->flags, &buf);
-        entry->command = strdup_local(buf);
+
+        /* We need to rehash the binding (remove the old and add the
+         * new) since the keysym might have changed. */
+
+        keybind_remove(kb);
+
+        keybinding_get_data(&keysym, &flags, &buf);
+        res = keybind_insert(keysym, flags, buf);
+        if (res == 0) {
+            keybind_free(&kb);
+        } else {
+            /* Re-enter old binding if the update failed */
+            keybind_insert(kb->keysym, kb->flags, kb->command);
+            // FIXME: Popup dialog key in use
+        }
+
         update_keybinding_list();
         save_keys();
     } else {
@@ -2173,6 +2080,24 @@ void on_keybinding_button_close_clicked(GtkButton *button, gpointer user_data)
 }
 
 /**
+ * Deactivate the modifier checkboxes if "Any" is selected.
+ *
+ * @param button
+ * @param user_data
+ */
+void on_keybinding_checkbutton_any_clicked(GtkCheckButton *cb, gpointer user_data)
+{
+    gboolean enabled;
+
+    enabled = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb));
+
+    gtk_widget_set_sensitive(GTK_WIDGET(keybinding_checkbutton_control), enabled);
+    gtk_widget_set_sensitive(GTK_WIDGET(keybinding_checkbutton_shift), enabled);
+    gtk_widget_set_sensitive(GTK_WIDGET(keybinding_checkbutton_alt), enabled);
+    gtk_widget_set_sensitive(GTK_WIDGET(keybinding_checkbutton_meta), enabled);
+}
+
+/**
  * Called when the user clicks one of the entries in the list of keybindings
  * and places information about it into the input fields on the dialog.  This
  * allows the player to edit and update, or remove bindings.
@@ -2192,49 +2117,56 @@ gboolean keybinding_selection_func(
     gpointer          userdata)
 {
     GtkTreeIter iter;
-    Key_Entry   *entry;
+    struct keybind *kb;
 
     gtk_widget_set_sensitive(keybinding_button_remove, TRUE);
     gtk_widget_set_sensitive(keybinding_button_update, TRUE);
 
     if (gtk_tree_model_get_iter(model, &iter, path)) {
 
-        gtk_tree_model_get(model, &iter, KLIST_KEY_ENTRY, &entry, -1);
+        gtk_tree_model_get(model, &iter, KLIST_KEY_ENTRY, &kb, -1);
 
-        if (!entry) {
+        if (!kb) {
             LOG(LOG_ERROR, "keys.c::keybinding_selection_func",
                 "Unable to get key_entry structure\n");
             return FALSE;
         }
-        if (entry->flags & KEYF_RUN)
+        if (kb->flags & KEYF_ANY)
+            gtk_toggle_button_set_active(
+                GTK_TOGGLE_BUTTON(keybinding_checkbutton_any), TRUE);
+        else
+            gtk_toggle_button_set_active(
+                GTK_TOGGLE_BUTTON(keybinding_checkbutton_any), FALSE);
+
+        if (kb->flags & KEYF_MOD_CTRL)
             gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_control), TRUE);
         else
             gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_control), FALSE);
 
-        if (entry->flags & KEYF_FIRE)
+        if (kb->flags & KEYF_MOD_SHIFT)
             gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_shift), TRUE);
         else
             gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_shift), FALSE);
 
-        if (entry->flags & KEYF_ALT)
+        if (kb->flags & KEYF_MOD_ALT)
             gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_alt), TRUE);
         else
             gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_alt), FALSE);
 
-        if (entry->flags & KEYF_META)
+        if (kb->flags & KEYF_MOD_META)
             gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_meta), TRUE);
         else
             gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_meta), FALSE);
 
-        if (entry->flags & KEYF_EDIT)
+        if (kb->flags & KEYF_EDIT)
             gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_edit), TRUE);
         else
@@ -2242,9 +2174,9 @@ gboolean keybinding_selection_func(
                 GTK_TOGGLE_BUTTON(keybinding_checkbutton_edit), FALSE);
 
         gtk_entry_set_text(
-            GTK_ENTRY(keybinding_entry_key), gdk_keyval_name(entry->keysym));
+            GTK_ENTRY(keybinding_entry_key), gdk_keyval_name(kb->keysym));
         gtk_entry_set_text(
-            GTK_ENTRY(keybinding_entry_command), entry->command);
+            GTK_ENTRY(keybinding_entry_command), kb->command);
     }
     return TRUE;
 }
-- 
1.8.1.5


-- 
Arvid


More information about the crossfire mailing list