Keyboard shortcut assignment from script

edited June 2015 in Ruby Scripting

Matthias,

I'm trying to create ruby script to make keyboard shortcut 'profiles' or 'pre-sets' -- in other words, a user can choose from different sets of keyboard shortcuts that you may be familiar with from other layout tools. (Inasmuch as that is possible.) Rather than finding and changing each keyboard shortcut individually. This may help new KLayout users to migrate from other tools more easily.

The RBA keyboard shortcut assignment script below works, but the changes are not reflected in File > Setup > Key bindings. This may be somewhat confusing to a user since there is no other way (other than reading the code) of telling what keyboard shortcuts have been assigned to which function.

Is this just something you simply missed, or is this difficult to implement and therefore you haven't implemented it yet?

include RBA
app = Application.instance
mw = app.main_window
menu = mw.menu

# Add menu items. The keyboard shortcut works but 
# does not show up in File > Setup > Key bindings.

a = Action.new
a.title = "Item A"
a.shortcut = "Shift+A"
a.on_triggered { p "'A' triggered" }
b = Action.new
b.title = "Item B"
b.shortcut = "Shift+B"
b.on_triggered { p "'B' triggered" }

menu.insert_menu("help_menu","custom_menu","Custom")
menu.insert_item("custom_menu.end","item_a",a)
menu.insert_item("custom_menu.end","item_b",b)

# Try to remove (assign empty string) or change (assign
# new string) a keyboard shortcut. The new keyboard 
# shortcut works but only the old shortcut still shows 
# up in File > Setup > Key bindings.

menu.action("edit_menu.edit_options").shortcut = "F10" #Change from F3 to F10

David

Comments

  • edited June 2015

    Hi David,

    Maybe I can help you out with this. As you probably know, the keyboard shortcuts are stored within the klayoutrc file. The contents of this file can be accessed through the API using the RBA::Application.instance.get_config_names, RBA::Application.instance.get_config(name) and RBA::Application.instance.set_config(name, value) methods.

    I found that the keyboard shortcuts are stored under the name of key-bindings. Unfortunately the string which holds all the bindings needs to be parsed in order to access them individually, so I wrote the following script to convert the whole string into a Ruby hash:

    # get the config string
    config = RBA::Application.instance.get_config('key-bindings')
    
    # turn it into a hash
    def config.to_mapping
      in_str = false
      escaped = false
      item = key = ""
      m = {}
      each_char do |c|
        if in_str && c == "\\"
          escaped = true
          item += c
        elsif escaped && c == "'"
          escaped = false
          item += c
        elsif c == "'"
          in_str = !in_str
          item += c
        elsif !in_str && c == ':'
          key = item
          item = ''
        elsif !in_str && c == ';'
          m[key] = item
          item = ''
        else
          item += c
        end
      end
      m[key] = item
      m
    end
    mapping = config.to_mapping
    
    # access a single setting and change it
    puts mapping['edit_menu.cancel']
    mapping['edit_menu.cancel'] = 'F12'
    
    # turn the hash back into a config string
    config2 = mapping.collect{|k,v| "#{k}:#{v}"}.join(';')
    
    # activate & store the changed configuration
    RBA::Application.instance.set_config('key-bindings', config2)
    

    As I am no expert on parsing and don't know the exact rules of the key-bindings string, I might have missed some corner cases, so maybe it would be safe to make a backup of your klayoutrc file before running this script... but if you just want to save & restore a complete set of key bindings, the parsing/recombination steps aren't necessary and you can deal with the config string as a whole.

    Regards, Chris

  • edited November -1

    Chris,

    Works great! Thanks! I shoulda realized it was stored in klayoutrc. But you also gave a particularly nice way to modify it. Thanks!

    David

  • edited November -1

    Hi guys,

    I very happy to see a community at work :-)

    I wouldn't have told anything else.

    Just an explanation from my side: the configuration is actually a system of distributing key/value configuration requests superimposed over the modular architecture of the system.

    So when the configuration file is read, the "key-bindings" entry will issue a request to the menu manager which will set the shortcuts according to the value string. The RBA classes are the low-level objects involved inside the manager and hence will be reconfigured by this requests. There is not uplink, that's why any changes made to the low-level objects are not seen by the configuration system. But with the Action object you can still add shortcuts or create new actions (which is a main intended application).

    Best regards,

    Matthias

  • edited June 2015

    Quick note for future readers:

    I found I needed something like this:

    mapping['edit_menu.cancel'] = "'F12'"
    

    instead of this:

    mapping['edit_menu.cancel'] = 'F12'
    

    Otherwise, it indeed wrote to the klayoutrc file, but it got confused on some cases like Ctrl+A. i.e. it needs the quotes within the string.

  • edited November -1

    Hi Matthias, David,

    I am finding something strange here. On OSX, this works fine. But on Windows 8.1 with KLayout 24.4,

     config = RBA::Application.instance.get_config('key-bindings')
     puts config
    

    returns nothing. And the rest of the script breaks the key-bindings since they end up with a ':' character at the beginning.

    Thank you

  • edited November -1

    Hi Lukas,

    do you mean the key bindings property is empty initially? Maybe they got cleared by some event. What happens if you try to rename your configuration file from %HOME%/KLayout/klayoutrc to something else, basically resetting the configuration? I think in that case the configuration should be some non-empty string.

    And you're right, the script does not work for an empty configuration. To make it work in that case too I'd replace

     m[key] = item
    

    by

     key != "" && m[key] = item
    

    Best regards,

    Matthias

Sign In or Register to comment.