2011-01-13

Creating a global control in OpenLayers for specific key combinations

This is largely a follow-up to a previous post I made discussing how to effectively capture right clicks on an OpenLayers map object.  That had been related to another objective that I had, which was to have a global control that always responds to the right click in order to display a basic context menu on the map.  It's easy enough to get this working on the right-click, with all other controls working using the left click event - but what about Mac users, or people suffering from some kind of right-click impairment?  This needs a modifier key to go along with the left-click, but then there needs to be some way to prevent other controls from being triggered at the same time.

In OpenLayers, there is a common property that can be passed to handlers/controls, called keyMask.  It is used in a bitwise comparison in the checkModifiers method of the OpenLayers.Handler class.  The main issue I had was that it currently only works to allow a given control to be restricted to a specified key combination.  It also does not allow you to have a single control that responds to different keys.  Rather, any event keys added to the keyMask property of a control/handler must ALL be present in order for checkModifers to return true.  However, here's the situation I wanted:

  1. Global control (context menu): triggers on right click, or left-click + alt, or left-click + ctrl (this allows users with a one-button mouse to still access the menu), and
  2. All other controls: trigger on any left-click event that excludes the alt or ctrl keys.
So to accomplish what I needed, I added a bit of code the checkModifiers function in the OpenLayers.Handler class to make it also check for a keyIMask property (i.e., an inverse key mask...though maybe that's not the best name for it).  Where the current keyMask must match all keys included in the event in order for checkModifiers function to return true, if the keyIMask matches any keys included in the event, then checkModifiers returns false.  Here's what it looks like:

checkModifiers: function (evt) {
if(this.keyMask == null && this.keyIMask == null) {
return true;
}
/* calculate the keyboard modifier mask for this event */
var keyModifiers =
(evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) |
(evt.ctrlKey  ? OpenLayers.Handler.MOD_CTRL  : 0) |
(evt.altKey   ? OpenLayers.Handler.MOD_ALT   : 0);
/* if it differs from the handler object's key mask,
bail out of the event handler */
return ((this.keyMask == null || keyModifiers == this.keyMask)
&& (this.keyIMask == null || (keyModifiers & this.keyIMask) == 0));
}

With the Handler modified in this way, I can tell any control to not respond if any key in an event matches keyIMask.  In my global control, it internally checks if the event is either right-click, or if it's a left-click with either the ctrlKey or altKey present.  All of my other controls are set with the property "keyIMask = OpenLayers.Handler.MOD_CTRL | OpenLayers.Handler.MOD_ALT;", which prevents controls from being triggered when the menu is displayed, and still leaves room for MOD_SHIFT to be used either as a keyMask, or to internally modify the behaviour of control's events (e.g., the freehand toggle in the OpenLayers.Handler.Path class).