The Accidental Rubyist

invalid byte sequence in UTF-8

Trapping multiple keys for application

leave a comment »

This could help you if you require to map multiple keys like emacs (C-x C-d) or C-x q etc to methods. My first find was manveru’s “VER” which (I presume) means Vi Emacs in ruby.
I am using his keyboard.rb to trap keys – helpful in escape combinations and meta-key trapping.

However, his excellent keymapper was not what i required in my app, and it was too hard for a newbie like me to understand. What is more, I felt i needed key mapper objects in my application.

So I wrote a smaller mapper which takes care of modes, key combinations and regexes. It will work with both keys given as strings, as well as numeric keys. (As i wrote it, I did get back into manveru’s code and use some of it, though).

A full-fledged example using mapping defined as strings may be seen in lib/rbcurse/sqlpopup.rb.
Here is an tiny example with mappings as integers (as returned by window.getch).

Mapping methods to keys

Define a mapper object, giving self as handle. Methods to be called are in self.
(The syntax for mapping is identical to manveru’s in case you ever want to switch)


    @mapper = Mapper.new(self)

view refers to the object passed to constructor of Mapper, here it is self
:normal is the normal state of my application, any name can do. I have defined @mode = :normal in constructor.


    @mapper.let :normal do
      map(?\C-x, ?\C-d){ view.down }
      map(?\C-x, ?\C-u){ view.up }
      map(?\C-x, ?q){ view.stop }
      map(?\C-x, ?\C-s){ view.do_search_ask }
      map(?\C-x, ?\C-x){ view.do_select }

Above, we mapped some double key combinations to methods in self. To keep things simple, no more that 2 combinations may be defined.

In the next line, we switch to another mode named “:cx”.


      map(?\C-s){ view.mode = :cx }

Some single key combinations are shown. Note that a method symbol may be passed too. Of course, a single control key may be mapped, and so can a regular expression.


      map(?q) { view.stop }
      map(?j) { :down }
      map(?k) { view.up }
      map(KEY_DOWN) { view.down }
      map(KEY_UP) { view.up }

      map(?\C-e) { view.do_clear_selection }
      map(?\M-e) { view.do_clear_selection }  # using alt/meta/escape
      map(?\M-E) { view.do_something_else }   # case sensitive

      map(/^([[:print:]])$/){ view.show("Got printable: #{@arg}              ")
      }

We spoke of a mode named :cx. Whatever that is, imagine that once you are in that mode, you can only pres c, a, r and q. Pressing “i” returns you to normal. In reality, this could be linked to an object which only accepts certain keys (say a list).


    @mapper.let :cx do
      map(?c) { view.show(:c) }
      map(?a) { view.show(:a) }
      map(?r) { view.show(:r) }
      map(?q) { view.stop }
      map(?i) { view.mode = :normal }

You may (ofcourse) pass your own object/s while mapping and have them passed back.


      map(?r, @form) {|form| form.do_something(:r) }

Handling keys

After mapping keys, we can give control to the key-trapping program.

This example uses a modification of manveru’s Keyboard, to return integers and not strings. (I am just trying this out to see it’s benefits). In line 2, I allow the mapper to directly take calls.


    VER::Keyboard2.focus = self
    @keyhandler = @mapper     # i don't want a separate handler, this will do

IIRC, manveru’s VER, has a separate handler which then calls the mapper.

The keyboard programs calls press whenever a key is pressed, we pass it to the mapper to handle it. The mapper knows that a “C-x” will be followed by another key, so it takes care of combinations.


  def press(key)
      @keyhandler.press(key)
      break if stopping?
      ... exception handling etc

A couple more dependencies (check samples if interested)
Method or attribute window() that gives the keyboard program the window so as to call getch().
Accessor mode, for getting and changing mode.


 # keyboard will stop polling and return control if this returns a false.
  def stopping?
    @stop
  end

  # called by mapper if unknown key pressed
  def info(message)
    @message = message
  end

There you have it, with minimal fuss, a means to trap a large number of combinations, and map them to methods.

One advantage of binding keys is that bindings can be modified by users. Also, one may query the map object to display all mapped keys to the user. That, of course, looks better if string bindings are used as against integer bindings.

Please check keytest.rb and sqlpopup.rb for running examples. Also, see manveru’s github project for the handler and mapper he has written.

Advertisements

Written by totalrecall

November 26, 2008 at 2:42 pm

Posted in ncurses, ruby

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: