#1594 Key event support added

jessevdam Thu 28 Jul 2011

I added support for key events. There is some extra code to direct the key events to the last selected "focused" widget, did not add tabbing yet. Since a canvas is not focusable in html 5.

Added support for most of the keycode in chrome and firefox (should also include IE), but tested it on chrome and firefox.

Added also charCode to it, although it does not take from the browser and is according default us keyboard.

Test the keyCode and charCode on both. The - in firefox is a keypad-. So shift- is not charcode _. Firefox under Ubuntu does not handle \' \^ \` \~ \" well because these are "escapee" characters.

Here is the code

//////////////////////////////////////////////////////////////////////////
// EventListeners
//////////////////////////////////////////////////////////////////////////

//Some elements are no focusable so then we create our focussing system 
var focusWidget = null;
var keyDownHandler = function(e)
{
  var walker = focusWidget
  while(walker != null)
  {
    if(walker.m_keyDownListener != null)
    {
      walker.m_keyDownListener(e);
      break;
    }
    walker = walker.parentNode;
  }
}

var keyUpHandler = function(e)
{
  var walker = focusWidget
  while(walker != null)
  {
    if(walker.m_keyUpListener != null)
    {
      walker.m_keyUpListener(e);
      break;
    }
    walker = walker.parentNode;
  } 
}

var mouseHandler = function(e)
{
  focusWidget = e.target
}

this.document.addEventListener("keydown", keyDownHandler, false);
this.document.addEventListener("keyup", keyUpHandler, false);
this.document.addEventListener("mouseup", mouseHandler, true);

fan.fwt.WidgetPeer.prototype.attachEventListener = function(self, type, evtId, listeners)
{
  var peer = this;
  var funcMouse = function(e)
  {
    // find pos relative to widget
    var dis  = peer.posOnWindow(self);
    var mx   = e.clientX - dis.m_x;
    var my   = e.clientY - dis.m_y;

    // make sure to rel against window root
    var win = self.window();
    if (win != null && win.peer.root != null)
    {
      mx -= win.peer.root.offsetLeft;
      my -= win.peer.root.offsetTop;
    }

    // cache event type
    var isClickEvent = evtId == fan.fwt.EventId.m_mouseDown ||
                       evtId == fan.fwt.EventId.m_mouseUp;
    var isWheelEvent = evtId == fan.fwt.EventId.m_mouseWheel;

    // create fwt::Event and invoke handler
    var evt = fan.fwt.Event.make();
    evt.m_id     = evtId;
    evt.m_pos    = fan.gfx.Point.make(mx, my);
    evt.m_widget = self;
    evt.m_key    = fan.fwt.WidgetPeer.toKey(e);
    if(evt.m_key)
      evt.m_keyChar = fan.fwt.WidgetPeer.keyToCharCode(evt.m_key)

    if (isClickEvent)
    {
      evt.m_button = e.button + 1;
      evt.m_count  = fan.fwt.WidgetPeer.processMouseClicks(peer, evt);
    }
    if (isWheelEvent)
    {
      evt.m_button = 1;  // always set to middle button?
      evt.m_delta = fan.fwt.WidgetPeer.toWheelDelta(e);
    }

    // invoke handlers
    var list = listeners.list();
    for (var i=0; i<list.m_size; i++)
    {
      list.get(i).call(evt);
      if (evt.m_consumed) break;
    }

    // prevent bubbling
    e.stopPropagation();
    return false;
  }

  // special handler for firefox
  if (type == "mousewheel" && fan.fwt.DesktopPeer.$isFirefox) type = "DOMMouseScroll";

  if (evtId == fan.fwt.EventId.m_keyDown)
  {
    this.elem.m_keyDownListener = funcMouse;
  }
  else if(evtId == fan.fwt.EventId.m_keyUp)
  {  
    this.elem.m_keyUpListener = funcMouse;
  }
  else
  {
    // attach event handler
    this.elem.addEventListener(type, funcMouse, false);
  }  
}

fan.fwt.WidgetPeer.processMouseClicks = function(peer, e)
{
  // init mouse clicks if not defined
  if (peer.mouseClicks == null)
  {
    peer.mouseClicks = {
      last: new Date().getTime(),
      pos:  e.m_pos,
      cur:  1
    };
    return peer.mouseClicks.cur;
  }

  // only process on mousedown
  if (e.m_id != fan.fwt.EventId.m_mouseDown)
    return peer.mouseClicks.cur;

  // verify pos and frequency
  var now  = new Date().getTime();
  var diff = now - peer.mouseClicks.last;
  if (diff < 600 && peer.mouseClicks.pos.equals(e.m_pos))
  {
    // increment click count
    peer.mouseClicks.cur++;
  }
  else
  {
    // reset handler
    peer.mouseClicks.pos = e.m_pos;
    peer.mouseClicks.cur = 1;
  }

  // update ts and return result
  peer.mouseClicks.last = now;
  return peer.mouseClicks.cur;
}

fan.fwt.WidgetPeer.toWheelDelta = function(e)
{
  var wx = 0;
  var wy = 0;

  if (e.wheelDeltaX != null)
  {
    // WebKit
    wx = -e.wheelDeltaX;
    wy = -e.wheelDeltaY;

    // Safari
    if (wx % 120 == 0) wx = wx / 40;
    if (wy % 120 == 0) wy = wy / 40;
  }
  else if (e.wheelDelta != null)
  {
    // IE
    wy = -e.wheelDelta;
    if (wy % 120 == 0) wy = wy / 40;
  }
  else if (e.detail != null)
  {
    // Firefox
    wx = e.axis == 1 ? e.detail : 0;
    wy = e.axis == 2 ? e.detail : 0;
  }

  // make sure we have ints and return
  wx = wx > 0 ? Math.ceil(wx) : Math.floor(wx);
  wy = wy > 0 ? Math.ceil(wy) : Math.floor(wy);
  return fan.gfx.Point.make(wx, wy);
}

fan.fwt.WidgetPeer.toKey = function(event)
{
  // find primary key
  var key = null;
  if (event.keyCode != null && event.keyCode > 0)
    key = fan.fwt.WidgetPeer.keyCodeToKey(event.keyCode);

  if (event.shiftKey)   key = key==null ? fan.fwt.Key.m_shift :  key.plus(fan.fwt.Key.m_shift);
  if (event.altKey)     key = key==null ? fan.fwt.Key.m_alt   : key.plus(fan.fwt.Key.m_alt);
  if (event.ctrlKey)    key = key==null ? fan.fwt.Key.m_ctrl  : key.plus(fan.fwt.Key.m_ctrl);
  // TODO FIXIT
  //if (event.commandKey) key = key.plus(Key.command);
  return key; 
}

fan.fwt.WidgetPeer.keyCodeToKey = function(keyCode)
{
  //Codes for most common browsers taken from http://unixpapa.com/js/key.html
  switch (keyCode)
  {
    case 38: return fan.fwt.Key.m_up;
    case 40: return fan.fwt.Key.m_down;
    case 37: return fan.fwt.Key.m_left;
    case 39: return fan.fwt.Key.m_right;
    case 46: return fan.fwt.Key.m_$delete;
    case 33: return fan.fwt.Key.m_pageUp;
    case 34: return fan.fwt.Key.m_pageDown;
    case 36: return fan.fwt.Key.m_home;
    case 35: return fan.fwt.Key.m_end;
    case 45: return fan.fwt.Key.m_insert;
    case 112: return fan.fwt.Key.m_f1;
    case 113: return fan.fwt.Key.m_f2;
    case 114: return fan.fwt.Key.m_f3;
    case 115: return fan.fwt.Key.m_f4;
    case 116: return fan.fwt.Key.m_f5;
    case 117: return fan.fwt.Key.m_f6;
    case 118: return fan.fwt.Key.m_f7;
    case 119: return fan.fwt.Key.m_f8;
    case 120: return fan.fwt.Key.m_f9;
    case 121: return fan.fwt.Key.m_f10;
    case 122: return fan.fwt.Key.m_f11;
    case 123: return fan.fwt.Key.m_f12;
    case 106: return fan.fwt.Key.m_keypadMult;
    case 107: return fan.fwt.Key.m_keypadPlus;
    case 109: return fan.fwt.Key.m_keypadMinus;
    case 110: return fan.fwt.Key.m_keypadDot;
    case 111: return fan.fwt.Key.m_keypadDiv;
    case 96: return fan.fwt.Key.m_keypad0;
    case 97: return fan.fwt.Key.m_keypad1;
    case 98: return fan.fwt.Key.m_keypad2;
    case 99: return fan.fwt.Key.m_keypad3;
    case 100: return fan.fwt.Key.m_keypad4;
    case 101: return fan.fwt.Key.m_keypad5;
    case 102: return fan.fwt.Key.m_keypad6;
    case 103: return fan.fwt.Key.m_keypad7;
    case 104: return fan.fwt.Key.m_keypad8;
    case 105: return fan.fwt.Key.m_keypad9;
    case 106: return fan.fwt.Key.m_keypadEqual;
    //case 13: return fan.fwt.Key.m_keypadEnter; same as enter
    case 20: return fan.fwt.Key.m_capsLock;
    case 144: return fan.fwt.Key.m_numLock;
    case 145: return fan.fwt.Key.m_scrollLock;
    case 19: return fan.fwt.Key.m_pause;
   // case 46: return fan.fwt.Key.m_printScreen; cant get it on my computer
    case 18: return fan.fwt.Key.m_alt; 
    case 16: return fan.fwt.Key.m_shift;
    case 17: return fan.fwt.Key.m_ctrl;
    case 91: return fan.fwt.Key.m_command; //the windows key
    case 59: case 186: return fan.fwt.Key.fromMask(59); //;: 
    case 61: case 187: return fan.fwt.Key.fromMask(61); //=+ 
    case 188: return fan.fwt.Key.fromMask(44); //,< 
    case 189: return fan.fwt.Key.fromMask(45); //-_ //in firefox it will be keypad.minus
    case 190: return fan.fwt.Key.fromMask(46); //.> 
    case 191: return fan.fwt.Key.fromMask(47); ///? 
    case 192: return fan.fwt.Key.fromMask(96); //`~ 
    case 219: return fan.fwt.Key.fromMask(91); //[{
    case 220: return fan.fwt.Key.fromMask(92); //\| 
    case 221: return fan.fwt.Key.fromMask(93); //]} 
    case 222: return fan.fwt.Key.fromMask(39); //'" 

    default: return fan.fwt.Key.fromMask(keyCode);
  }
}

fan.fwt.WidgetPeer.keyToCharCode = function(key)
{
  var mask = key.m_mask & ~0x470000;
  if(mask >= 65 && mask <= 90)
  {
    if(!key.isShift())
      return mask + 32;
    return mask;
  }
  if(key.isShift())
  {
    //Will do according us keyboard
    switch(mask)
    {
      case fan.fwt.Key.m_num1.m_mask: return 33;// !
      case fan.fwt.Key.m_num2.m_mask: return 64;// @
      case fan.fwt.Key.m_num3.m_mask: return 35;// #
      case fan.fwt.Key.m_num4.m_mask: return 36;// $
      case fan.fwt.Key.m_num5.m_mask: return 37;// %
      case fan.fwt.Key.m_num6.m_mask: return 94;// ^
      case fan.fwt.Key.m_num7.m_mask: return 38;// &
      case fan.fwt.Key.m_num8.m_mask: return 42;// *
      case fan.fwt.Key.m_num9.m_mask: return 40;// (
      case fan.fwt.Key.m_num0.m_mask: return 41;// )
      case 59: return 58;  // ;: 
      case 61: return 43;  // =+ 
      case 44: return 60;  // ,< 
      case 45: return 95;  // -_ 
      case 46: return 62;  // .> 
      case 47: return 63;  // /? 
      case 96: return 126; // `~ 
      case 91: return 123; // [{
      case 92: return 124; // \| 
      case 93: return 125; // ]} 
      case 39: return 34;  // '" 
      case fan.fwt.Key.m_keypadMult.m_mask: return 42; // *
      case fan.fwt.Key.m_keypadPlus.m_mask: return 43; // +
      case fan.fwt.Key.m_keypadMinus.m_mask: return 45;// -
      case fan.fwt.Key.m_keypadDot.m_mask: return 127; //Java implemetation does this also, is this what we want
      case fan.fwt.Key.m_keypadDiv.m_mask: return 47;  // /
      case fan.fwt.Key.m_keypad0.m_mask:
      case fan.fwt.Key.m_keypad1.m_mask:
      case fan.fwt.Key.m_keypad2.m_mask: 
      case fan.fwt.Key.m_keypad3.m_mask: 
      case fan.fwt.Key.m_keypad4.m_mask: 
      case fan.fwt.Key.m_keypad5.m_mask: 
      case fan.fwt.Key.m_keypad6.m_mask: 
      case fan.fwt.Key.m_keypad7.m_mask: 
      case fan.fwt.Key.m_keypad8.m_mask: 
      case fan.fwt.Key.m_keypad9.m_mask: return 0; //Java implemetation does this also, is this what we want
      case fan.fwt.Key.m_keypadEqual.m_mask: return 61;// =
      default:
    }
  }
  else
  {
    switch(key.m_mask)
    {
      case fan.fwt.Key.m_num1.m_mask:
      case fan.fwt.Key.m_num2.m_mask:
      case fan.fwt.Key.m_num3.m_mask:
      case fan.fwt.Key.m_num4.m_mask:
      case fan.fwt.Key.m_num5.m_mask:
      case fan.fwt.Key.m_num6.m_mask:
      case fan.fwt.Key.m_num7.m_mask:
      case fan.fwt.Key.m_num8.m_mask:
      case fan.fwt.Key.m_num9.m_mask:
      case fan.fwt.Key.m_num0.m_mask:
      case 59:  // ;: 
      case 61:  // =+ 
      case 44:  //,< 
      case 45:  //-_ 
      case 46:  //.> 
      case 47:  ///? 
      case 96:  //`~ 
      case 91:  //[{
      case 92:  //\| 
      case 93:  //]} 
      case 39:  //'"   
 return mask;
      case fan.fwt.Key.m_keypadMult.m_mask: return 42; // *
      case fan.fwt.Key.m_keypadPlus.m_mask: return 43; // +
      case fan.fwt.Key.m_keypadMinus.m_mask: return 45;// -
      case fan.fwt.Key.m_keypadDot.m_mask: return 46;  // .
      case fan.fwt.Key.m_keypadDiv.m_mask: return 47;  // /
      case fan.fwt.Key.m_keypad0.m_mask: return fan.fwt.Key.m_num0.m_mask;// 0
      case fan.fwt.Key.m_keypad1.m_mask: return fan.fwt.Key.m_num1.m_mask;// 1
      case fan.fwt.Key.m_keypad2.m_mask: return fan.fwt.Key.m_num2.m_mask;// 2
      case fan.fwt.Key.m_keypad3.m_mask: return fan.fwt.Key.m_num3.m_mask;// 3
      case fan.fwt.Key.m_keypad4.m_mask: return fan.fwt.Key.m_num4.m_mask;// 4
      case fan.fwt.Key.m_keypad5.m_mask: return fan.fwt.Key.m_num5.m_mask;// 5
      case fan.fwt.Key.m_keypad6.m_mask: return fan.fwt.Key.m_num6.m_mask;// 6
      case fan.fwt.Key.m_keypad7.m_mask: return fan.fwt.Key.m_num7.m_mask;// 7
      case fan.fwt.Key.m_keypad8.m_mask: return fan.fwt.Key.m_num8.m_mask;// 8
      case fan.fwt.Key.m_keypad9.m_mask: return fan.fwt.Key.m_num9.m_mask;// 9
      case fan.fwt.Key.m_keypadEqual.m_mask: return 61;// =
      default:
    }
  }
  return 0;
}

EDIT: small bug fix this.document.addEventListener("mouseup", mouseHandler, false->true);

jessevdam Thu 28 Jul 2011

After doing this fix I got my app, which I initially made to run on the desktop running fine in the chrome browser :) and in firefox (but that is really slow).

It is small application to visualize networks.

But i think it is really big achievement to so easaly "convert" a normal app to a web app.

andy Thu 28 Jul 2011

Woah thats a lot of code ;)

The key eventing should already be functional if you're working off tip. Its pretty straightforward - if any key listeners are attached to a widget - then his peer.elem configures tabIndex which makes that element a focusable node. See the changeset.

I'll take a look at pulling the key codes in when I get back from the beach week after next. Thanks for posting those.

jessevdam Thu 28 Jul 2011

Thanks for the tabIndex thing. I did not knew that. I checked it and my app is now working in firefox, chrome and in safari.

Login or Signup to reply.