Browse Source

Merge pull request #507 from rcsimm/helios-sunit

Refactor HLListWidget setupKeyBindings
Nicolas Petton 11 years ago
parent
commit
10c80fe677
6 changed files with 753 additions and 256 deletions
  1. 41 101
      js/Helios-Core.deploy.js
  2. 55 105
      js/Helios-Core.js
  3. 231 0
      js/Helios-KeyBindings.deploy.js
  4. 307 0
      js/Helios-KeyBindings.js
  5. 21 50
      st/Helios-Core.st
  6. 98 0
      st/Helios-KeyBindings.st

+ 41 - 101
js/Helios-Core.deploy.js

@@ -1153,6 +1153,20 @@ return self}, function($ctx1) {$ctx1.fill(self,"alert:",{aString:aString},smallt
 messageSends: ["alert:"]}),
 smalltalk.HLWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "bindKeyDown:up:",
+fn: function (keyDownBlock,keyUpBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(self._wrapper())._asJQuery();
+_st($1)._keydown_(keyDownBlock);
+$2=_st($1)._keyup_(keyUpBlock);
+return self}, function($ctx1) {$ctx1.fill(self,"bindKeyDown:up:",{keyDownBlock:keyDownBlock,keyUpBlock:keyUpBlock},smalltalk.HLWidget)})},
+messageSends: ["keydown:", "asJQuery", "wrapper", "keyup:"]}),
+smalltalk.HLWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "canHaveFocus",
@@ -1321,6 +1335,20 @@ return $1;
 messageSends: ["tabClass", "class"]}),
 smalltalk.HLWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "unbindKeyDownUp",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(self._wrapper())._asJQuery();
+_st($1)._unbind_("keydown");
+$2=_st($1)._unbind_("keyup");
+return self}, function($ctx1) {$ctx1.fill(self,"unbindKeyDownUp",{},smalltalk.HLWidget)})},
+messageSends: ["unbind:", "asJQuery", "wrapper"]}),
+smalltalk.HLWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "unregister",
@@ -1457,11 +1485,11 @@ return smalltalk.withContext(function($ctx1) {
 var $1;
 $1=_st(_st(self._wrapper())._notNil())._and_((function(){
 return smalltalk.withContext(function($ctx2) {
-return _st(_st(self._wrapper())._asJQuery())._is_(":focus");
+return _st(_st(self._wrapper())._asJQuery())._hasClass_(self._focusClass());
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"hasFocus",{},smalltalk.HLFocusableWidget)})},
-messageSends: ["and:", "is:", "asJQuery", "wrapper", "notNil"]}),
+messageSends: ["and:", "hasClass:", "focusClass", "asJQuery", "wrapper", "notNil"]}),
 smalltalk.HLFocusableWidget);
 
 smalltalk.addMethod(
@@ -1895,109 +1923,21 @@ smalltalk.method({
 selector: "setupKeyBindings",
 fn: function (){
 var self=this;
-var active,interval,delay,repeatInterval;
+function $HLRepeatingKeyBindingHandler(){return smalltalk.HLRepeatingKeyBindingHandler||(typeof HLRepeatingKeyBindingHandler=="undefined"?nil:HLRepeatingKeyBindingHandler)}
 return smalltalk.withContext(function($ctx1) { 
-var $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11;
-active=false;
-repeatInterval=(70);
-_st(_st(self._wrapper())._asJQuery())._unbind_("keydown");
-_st(_st(self._wrapper())._asJQuery())._keydown_((function(e){
+var $1,$2;
+$1=_st($HLRepeatingKeyBindingHandler())._forWidget_(self);
+_st($1)._whileKeyPressed_do_((38),(function(){
 return smalltalk.withContext(function($ctx2) {
-$1=_st(_st(_st(e)._which()).__eq((38)))._and_((function(){
-return smalltalk.withContext(function($ctx3) {
-return _st(active).__eq(false);
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
-if(smalltalk.assert($1)){
-active=true;
-active;
-self._activatePreviousListItem();
-delay=_st((function(){
-return smalltalk.withContext(function($ctx3) {
-interval=_st((function(){
-return smalltalk.withContext(function($ctx4) {
-$2=_st(_st(self._wrapper())._asJQuery())._hasClass_(self._focusClass());
-if(smalltalk.assert($2)){
 return self._activatePreviousListItem();
-} else {
-active=false;
-active;
-$3=interval;
-if(($receiver = $3) == nil || $receiver == undefined){
-$3;
-} else {
-_st(interval)._clearInterval();
-};
-$4=delay;
-if(($receiver = $4) == nil || $receiver == undefined){
-return $4;
-} else {
-return _st(delay)._clearTimeout();
-};
-};
-}, function($ctx4) {$ctx4.fillBlock({},$ctx3)})}))._valueWithInterval_(repeatInterval);
-return interval;
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}))._valueWithTimeout_((300));
-delay;
-};
-$5=_st(_st(_st(e)._which()).__eq((40)))._and_((function(){
-return smalltalk.withContext(function($ctx3) {
-return _st(active).__eq(false);
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
-if(smalltalk.assert($5)){
-active=true;
-active;
-self._activateNextListItem();
-delay=_st((function(){
-return smalltalk.withContext(function($ctx3) {
-interval=_st((function(){
-return smalltalk.withContext(function($ctx4) {
-$6=_st(_st(self._wrapper())._asJQuery())._hasClass_(self._focusClass());
-if(smalltalk.assert($6)){
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+_st($1)._whileKeyPressed_do_((40),(function(){
+return smalltalk.withContext(function($ctx2) {
 return self._activateNextListItem();
-} else {
-active=false;
-active;
-$7=interval;
-if(($receiver = $7) == nil || $receiver == undefined){
-$7;
-} else {
-_st(interval)._clearInterval();
-};
-$8=delay;
-if(($receiver = $8) == nil || $receiver == undefined){
-return $8;
-} else {
-return _st(delay)._clearTimeout();
-};
-};
-}, function($ctx4) {$ctx4.fillBlock({},$ctx3)})}))._valueWithInterval_(repeatInterval);
-return interval;
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}))._valueWithTimeout_((300));
-return delay;
-};
-}, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}));
-_st(_st(self._wrapper())._asJQuery())._keyup_((function(e){
-return smalltalk.withContext(function($ctx2) {
-$9=active;
-if(smalltalk.assert($9)){
-active=false;
-active;
-$10=interval;
-if(($receiver = $10) == nil || $receiver == undefined){
-$10;
-} else {
-_st(interval)._clearInterval();
-};
-$11=delay;
-if(($receiver = $11) == nil || $receiver == undefined){
-return $11;
-} else {
-return _st(delay)._clearTimeout();
-};
-};
-}, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}));
-return self}, function($ctx1) {$ctx1.fill(self,"setupKeyBindings",{active:active,interval:interval,delay:delay,repeatInterval:repeatInterval},smalltalk.HLListWidget)})},
-messageSends: ["unbind:", "asJQuery", "wrapper", "keydown:", "ifTrue:", "activatePreviousListItem", "valueWithTimeout:", "valueWithInterval:", "ifTrue:ifFalse:", "ifNotNil:", "clearInterval", "clearTimeout", "hasClass:", "focusClass", "and:", "=", "which", "activateNextListItem", "keyup:"]}),
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+$2=_st($1)._rebindKeys();
+return self}, function($ctx1) {$ctx1.fill(self,"setupKeyBindings",{},smalltalk.HLListWidget)})},
+messageSends: ["whileKeyPressed:do:", "activatePreviousListItem", "forWidget:", "activateNextListItem", "rebindKeys"]}),
 smalltalk.HLListWidget);
 
 

+ 55 - 105
js/Helios-Core.js

@@ -1513,6 +1513,25 @@ referencedClasses: []
 }),
 smalltalk.HLWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "bindKeyDown:up:",
+category: 'keybindings',
+fn: function (keyDownBlock,keyUpBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(self._wrapper())._asJQuery();
+_st($1)._keydown_(keyDownBlock);
+$2=_st($1)._keyup_(keyUpBlock);
+return self}, function($ctx1) {$ctx1.fill(self,"bindKeyDown:up:",{keyDownBlock:keyDownBlock,keyUpBlock:keyUpBlock},smalltalk.HLWidget)})},
+args: ["keyDownBlock", "keyUpBlock"],
+source: "bindKeyDown: keyDownBlock up: keyUpBlock\x0a\x09self wrapper asJQuery\x0a\x09\x09keydown: keyDownBlock;\x0a\x09\x09keyup: keyUpBlock ",
+messageSends: ["keydown:", "asJQuery", "wrapper", "keyup:"],
+referencedClasses: []
+}),
+smalltalk.HLWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "canHaveFocus",
@@ -1746,6 +1765,25 @@ referencedClasses: []
 }),
 smalltalk.HLWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "unbindKeyDownUp",
+category: 'keybindings',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(self._wrapper())._asJQuery();
+_st($1)._unbind_("keydown");
+$2=_st($1)._unbind_("keyup");
+return self}, function($ctx1) {$ctx1.fill(self,"unbindKeyDownUp",{},smalltalk.HLWidget)})},
+args: [],
+source: "unbindKeyDownUp\x0a\x09self wrapper asJQuery\x0a\x09\x09unbind: 'keydown';\x0a\x09\x09unbind: 'keyup'",
+messageSends: ["unbind:", "asJQuery", "wrapper"],
+referencedClasses: []
+}),
+smalltalk.HLWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "unregister",
@@ -1932,20 +1970,20 @@ smalltalk.HLFocusableWidget);
 smalltalk.addMethod(
 smalltalk.method({
 selector: "hasFocus",
-category: 'events',
+category: 'testing',
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 var $1;
 $1=_st(_st(self._wrapper())._notNil())._and_((function(){
 return smalltalk.withContext(function($ctx2) {
-return _st(_st(self._wrapper())._asJQuery())._is_(":focus");
+return _st(_st(self._wrapper())._asJQuery())._hasClass_(self._focusClass());
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"hasFocus",{},smalltalk.HLFocusableWidget)})},
 args: [],
-source: "hasFocus\x0a\x09^ self wrapper notNil and: [ self wrapper asJQuery is: ':focus' ]",
-messageSends: ["and:", "is:", "asJQuery", "wrapper", "notNil"],
+source: "hasFocus\x0a\x09^ self wrapper notNil and: [ self wrapper asJQuery hasClass: self focusClass ]",
+messageSends: ["and:", "hasClass:", "focusClass", "asJQuery", "wrapper", "notNil"],
 referencedClasses: []
 }),
 smalltalk.HLFocusableWidget);
@@ -2512,112 +2550,24 @@ selector: "setupKeyBindings",
 category: 'events',
 fn: function (){
 var self=this;
-var active,interval,delay,repeatInterval;
+function $HLRepeatingKeyBindingHandler(){return smalltalk.HLRepeatingKeyBindingHandler||(typeof HLRepeatingKeyBindingHandler=="undefined"?nil:HLRepeatingKeyBindingHandler)}
 return smalltalk.withContext(function($ctx1) { 
-var $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11;
-active=false;
-repeatInterval=(70);
-_st(_st(self._wrapper())._asJQuery())._unbind_("keydown");
-_st(_st(self._wrapper())._asJQuery())._keydown_((function(e){
+var $1,$2;
+$1=_st($HLRepeatingKeyBindingHandler())._forWidget_(self);
+_st($1)._whileKeyPressed_do_((38),(function(){
 return smalltalk.withContext(function($ctx2) {
-$1=_st(_st(_st(e)._which()).__eq((38)))._and_((function(){
-return smalltalk.withContext(function($ctx3) {
-return _st(active).__eq(false);
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
-if(smalltalk.assert($1)){
-active=true;
-active;
-self._activatePreviousListItem();
-delay=_st((function(){
-return smalltalk.withContext(function($ctx3) {
-interval=_st((function(){
-return smalltalk.withContext(function($ctx4) {
-$2=_st(_st(self._wrapper())._asJQuery())._hasClass_(self._focusClass());
-if(smalltalk.assert($2)){
 return self._activatePreviousListItem();
-} else {
-active=false;
-active;
-$3=interval;
-if(($receiver = $3) == nil || $receiver == undefined){
-$3;
-} else {
-_st(interval)._clearInterval();
-};
-$4=delay;
-if(($receiver = $4) == nil || $receiver == undefined){
-return $4;
-} else {
-return _st(delay)._clearTimeout();
-};
-};
-}, function($ctx4) {$ctx4.fillBlock({},$ctx3)})}))._valueWithInterval_(repeatInterval);
-return interval;
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}))._valueWithTimeout_((300));
-delay;
-};
-$5=_st(_st(_st(e)._which()).__eq((40)))._and_((function(){
-return smalltalk.withContext(function($ctx3) {
-return _st(active).__eq(false);
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
-if(smalltalk.assert($5)){
-active=true;
-active;
-self._activateNextListItem();
-delay=_st((function(){
-return smalltalk.withContext(function($ctx3) {
-interval=_st((function(){
-return smalltalk.withContext(function($ctx4) {
-$6=_st(_st(self._wrapper())._asJQuery())._hasClass_(self._focusClass());
-if(smalltalk.assert($6)){
-return self._activateNextListItem();
-} else {
-active=false;
-active;
-$7=interval;
-if(($receiver = $7) == nil || $receiver == undefined){
-$7;
-} else {
-_st(interval)._clearInterval();
-};
-$8=delay;
-if(($receiver = $8) == nil || $receiver == undefined){
-return $8;
-} else {
-return _st(delay)._clearTimeout();
-};
-};
-}, function($ctx4) {$ctx4.fillBlock({},$ctx3)})}))._valueWithInterval_(repeatInterval);
-return interval;
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}))._valueWithTimeout_((300));
-return delay;
-};
-}, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}));
-_st(_st(self._wrapper())._asJQuery())._keyup_((function(e){
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+_st($1)._whileKeyPressed_do_((40),(function(){
 return smalltalk.withContext(function($ctx2) {
-$9=active;
-if(smalltalk.assert($9)){
-active=false;
-active;
-$10=interval;
-if(($receiver = $10) == nil || $receiver == undefined){
-$10;
-} else {
-_st(interval)._clearInterval();
-};
-$11=delay;
-if(($receiver = $11) == nil || $receiver == undefined){
-return $11;
-} else {
-return _st(delay)._clearTimeout();
-};
-};
-}, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}));
-return self}, function($ctx1) {$ctx1.fill(self,"setupKeyBindings",{active:active,interval:interval,delay:delay,repeatInterval:repeatInterval},smalltalk.HLListWidget)})},
+return self._activateNextListItem();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+$2=_st($1)._rebindKeys();
+return self}, function($ctx1) {$ctx1.fill(self,"setupKeyBindings",{},smalltalk.HLListWidget)})},
 args: [],
-source: "setupKeyBindings\x0a\x09\x22TODO: refactor this!\x22\x0a\x09\x0a\x09| active interval delay repeatInterval |\x0a\x09\x0a\x09active := false.\x0a\x09repeatInterval := 70.\x0a\x09self wrapper asJQuery unbind: 'keydown'.\x0a\x0a\x09self wrapper asJQuery keydown: [ :e |\x0a\x09\x09\x0a        (e which = 38 and: [ active = false ]) ifTrue: [ \x0a\x09\x09\x09active := true.\x0a\x09\x09\x09self activatePreviousListItem.\x0a        \x09delay := [\x0a\x09\x09\x09\x09interval := [\x0a\x09\x09\x09\x09\x09(self wrapper asJQuery hasClass: self focusClass)\x0a\x09\x09\x09\x09\x09\x09ifTrue: [\x0a\x09\x09\x09\x09\x09\x09\x09self activatePreviousListItem ]\x0a\x09\x09\x09\x09\x09\x09ifFalse: [\x0a\x09\x09\x09\x09\x09\x09\x09active := false.\x0a\x09\x09\x09\x09\x09\x09\x09interval ifNotNil: [ interval clearInterval ].\x0a\x09\x09\x09\x09\x09\x09\x09delay ifNotNil: [ delay clearTimeout] ] ]\x0a\x09\x09\x09\x09\x09valueWithInterval: repeatInterval ]\x0a\x09\x09\x09\x09\x09\x09valueWithTimeout: 300 ].\x0a\x09\x09\x09\x0a      \x09(e which = 40 and: [ active = false ]) ifTrue: [\x0a            active := true.\x0a\x09\x09\x09self activateNextListItem.\x0a        \x09delay := [\x0a\x09\x09\x09\x09interval := [ \x0a\x09\x09\x09\x09\x09(self wrapper asJQuery hasClass: self focusClass)\x0a\x09\x09\x09\x09\x09\x09ifTrue: [\x0a\x09\x09\x09\x09\x09\x09\x09self activateNextListItem ]\x0a\x09\x09\x09\x09\x09\x09ifFalse: [\x0a\x09\x09\x09\x09\x09\x09\x09active := false.\x0a\x09\x09\x09\x09\x09\x09\x09interval ifNotNil: [ interval clearInterval ].\x0a\x09\x09\x09\x09\x09\x09\x09delay ifNotNil: [ delay clearTimeout] ] ]\x0a\x09\x09\x09\x09\x09valueWithInterval: repeatInterval ]\x0a\x09\x09\x09\x09\x09\x09valueWithTimeout: 300 ] ].\x0a\x09\x0a\x09self wrapper asJQuery keyup: [ :e |\x0a\x09\x09active ifTrue: [\x0a\x09\x09\x09active := false.\x0a\x09\x09\x09interval ifNotNil: [ interval clearInterval ].\x0a\x09\x09\x09delay ifNotNil: [ delay clearTimeout] ] ]",
-messageSends: ["unbind:", "asJQuery", "wrapper", "keydown:", "ifTrue:", "activatePreviousListItem", "valueWithTimeout:", "valueWithInterval:", "ifTrue:ifFalse:", "ifNotNil:", "clearInterval", "clearTimeout", "hasClass:", "focusClass", "and:", "=", "which", "activateNextListItem", "keyup:"],
-referencedClasses: []
+source: "setupKeyBindings \x0a\x09(HLRepeatingKeyBindingHandler forWidget: self)\x0a\x09\x09whileKeyPressed: 38 do: [ self activatePreviousListItem ];\x0a\x09\x09whileKeyPressed: 40 do: [ self activateNextListItem ];\x0a\x09\x09rebindKeys",
+messageSends: ["whileKeyPressed:do:", "activatePreviousListItem", "forWidget:", "activateNextListItem", "rebindKeys"],
+referencedClasses: ["HLRepeatingKeyBindingHandler"]
 }),
 smalltalk.HLListWidget);
 

+ 231 - 0
js/Helios-KeyBindings.deploy.js

@@ -1457,3 +1457,234 @@ messageSends: ["keyBinder:", "new", "yourself"]}),
 smalltalk.HLKeyBinderHelper.klass);
 
 
+smalltalk.addClass('HLRepeatingKeyBindingHandler', smalltalk.Object, ['repeatInterval', 'delay', 'interval', 'keyBindings', 'widget', 'isKeyCurrentlyPressed'], 'Helios-KeyBindings');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "bindKeys",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(self["@widget"])._bindKeyDown_up_((function(e){
+return smalltalk.withContext(function($ctx2) {
+return self._handleKeyDown_(e);
+}, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}),(function(e){
+return smalltalk.withContext(function($ctx2) {
+return self._handleKeyUp_(e);
+}, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"bindKeys",{},smalltalk.HLRepeatingKeyBindingHandler)})},
+messageSends: ["bindKeyDown:up:", "handleKeyDown:", "handleKeyUp:"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "delayBeforeStartingRepeatWithAction:",
+fn: function (action){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st((function(){
+return smalltalk.withContext(function($ctx2) {
+self["@interval"]=self._startRepeatingAction_(action);
+return self["@interval"];
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._valueWithTimeout_((300));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"delayBeforeStartingRepeatWithAction:",{action:action},smalltalk.HLRepeatingKeyBinderForWidget)})},
+messageSends: ["valueWithTimeout:", "startRepeatingAction:"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "handleKeyDown:",
+fn: function (e){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(self["@keyBindings"])._keysAndValuesDo_((function(key,action){
+return smalltalk.withContext(function($ctx2) {
+return self._ifKey_wasPressedIn_thenDo_(key,e,action);
+}, function($ctx2) {$ctx2.fillBlock({key:key,action:action},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"handleKeyDown:",{e:e},smalltalk.HLRepeatingKeyBindingHandler)})},
+messageSends: ["keysAndValuesDo:", "ifKey:wasPressedIn:thenDo:"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "handleKeyUp",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+self["@isKeyCurrentlyPressed"]=false;
+$1=self["@interval"];
+if(($receiver = $1) == nil || $receiver == undefined){
+$1;
+} else {
+_st(self["@interval"])._clearInterval();
+};
+$2=self["@delay"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$2;
+} else {
+_st(self["@delay"])._clearTimeout();
+};
+return self}, function($ctx1) {$ctx1.fill(self,"handleKeyUp",{},smalltalk.HLRepeatingKeyBindingHandler)})},
+messageSends: ["ifNotNil:", "clearInterval", "clearTimeout"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "handleKeyUp:",
+fn: function (e){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@isKeyCurrentlyPressed"];
+if(smalltalk.assert($1)){
+self._handleKeyUp();
+};
+return self}, function($ctx1) {$ctx1.fill(self,"handleKeyUp:",{e:e},smalltalk.HLRepeatingKeyBindingHandler)})},
+messageSends: ["ifTrue:", "handleKeyUp"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "ifKey:wasPressedIn:thenDo:",
+fn: function (key,e,action){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st(_st(e)._which()).__eq(key))._and_((function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(self["@isKeyCurrentlyPressed"]).__eq(false);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+if(smalltalk.assert($1)){
+self._whileTheKeyIsPressedDo_(action);
+};
+return self}, function($ctx1) {$ctx1.fill(self,"ifKey:wasPressedIn:thenDo:",{key:key,e:e,action:action},smalltalk.HLRepeatingKeyBinderForWidget)})},
+messageSends: ["ifTrue:", "whileTheKeyIsPressedDo:", "and:", "=", "which"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "initialize",
+fn: function (){
+var self=this;
+function $Dictionary(){return smalltalk.Dictionary||(typeof Dictionary=="undefined"?nil:Dictionary)}
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.Object.fn.prototype._initialize.apply(_st(self), []);
+self["@keyBindings"]=_st($Dictionary())._new();
+self["@isKeyCurrentlyPressed"]=false;
+self["@repeatInterval"]=(70);
+return self}, function($ctx1) {$ctx1.fill(self,"initialize",{},smalltalk.HLRepeatingKeyBindingHandler)})},
+messageSends: ["initialize", "new"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "rebindKeys",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=self;
+_st($1)._unbindKeys();
+$2=_st($1)._bindKeys();
+return self}, function($ctx1) {$ctx1.fill(self,"rebindKeys",{},smalltalk.HLRepeatingKeyBinderForWidget)})},
+messageSends: ["unbindKeys", "bindKeys"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "repeatInterval:",
+fn: function (aMillisecondIntegerValue){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@repeatInterval"]=aMillisecondIntegerValue;
+return self}, function($ctx1) {$ctx1.fill(self,"repeatInterval:",{aMillisecondIntegerValue:aMillisecondIntegerValue},smalltalk.HLRepeatingKeyBinderForWidget)})},
+messageSends: []}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "startRepeatingAction:",
+fn: function (action){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$1=_st((function(){
+return smalltalk.withContext(function($ctx2) {
+$2=_st(self["@widget"])._hasFocus();
+if(smalltalk.assert($2)){
+return _st(action)._value();
+} else {
+return self._handleKeyUp();
+};
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._valueWithInterval_(self["@repeatInterval"]);
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"startRepeatingAction:",{action:action},smalltalk.HLRepeatingKeyBindingHandler)})},
+messageSends: ["valueWithInterval:", "ifTrue:ifFalse:", "value", "handleKeyUp", "hasFocus"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "unbindKeys",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(self["@widget"])._unbindKeyDownUp();
+return self}, function($ctx1) {$ctx1.fill(self,"unbindKeys",{},smalltalk.HLRepeatingKeyBinderForWidget)})},
+messageSends: ["unbindKeyDownUp"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "whileKeyPressed:do:",
+fn: function (aKey,aBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(self["@keyBindings"])._at_put_(aKey,aBlock);
+return self}, function($ctx1) {$ctx1.fill(self,"whileKeyPressed:do:",{aKey:aKey,aBlock:aBlock},smalltalk.HLRepeatingKeyBindingHandler)})},
+messageSends: ["at:put:"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "whileTheKeyIsPressedDo:",
+fn: function (action){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@isKeyCurrentlyPressed"]=true;
+_st(action)._value();
+self["@delay"]=self._delayBeforeStartingRepeatWithAction_(action);
+return self}, function($ctx1) {$ctx1.fill(self,"whileTheKeyIsPressedDo:",{action:action},smalltalk.HLRepeatingKeyBinderForWidget)})},
+messageSends: ["value", "delayBeforeStartingRepeatWithAction:"]}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "widget:",
+fn: function (aWidget){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@widget"]=aWidget;
+return self}, function($ctx1) {$ctx1.fill(self,"widget:",{aWidget:aWidget},smalltalk.HLRepeatingKeyBinderForWidget)})},
+messageSends: []}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "forWidget:",
+fn: function (aWidget){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+$2=self._new();
+_st($2)._widget_(aWidget);
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"forWidget:",{aWidget:aWidget},smalltalk.HLRepeatingKeyBindingHandler.klass)})},
+messageSends: ["widget:", "new", "yourself"]}),
+smalltalk.HLRepeatingKeyBindingHandler.klass);
+
+

+ 307 - 0
js/Helios-KeyBindings.js

@@ -1922,3 +1922,310 @@ referencedClasses: []
 smalltalk.HLKeyBinderHelper.klass);
 
 
+smalltalk.addClass('HLRepeatingKeyBindingHandler', smalltalk.Object, ['repeatInterval', 'delay', 'interval', 'keyBindings', 'widget', 'isKeyCurrentlyPressed'], 'Helios-KeyBindings');
+smalltalk.HLRepeatingKeyBindingHandler.comment="##Usage\x0a\x0a    (HLRepeatingKeyBindingHandler forWidget: aWidget)\x0a        whileKeyPressed: keyCode do: [xxxx];\x0a        whileKeyPressed: anotherKey do: [yyy];\x0a        rebind\x0a\x0aPerforms an action on a key press, waits for 300 ms and then preforms the action every repeatInterval ms until the button is released";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "bindKeys",
+category: 'actions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(self["@widget"])._bindKeyDown_up_((function(e){
+return smalltalk.withContext(function($ctx2) {
+return self._handleKeyDown_(e);
+}, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}),(function(e){
+return smalltalk.withContext(function($ctx2) {
+return self._handleKeyUp_(e);
+}, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"bindKeys",{},smalltalk.HLRepeatingKeyBindingHandler)})},
+args: [],
+source: "bindKeys\x0a\x09widget bindKeyDown: [ :e | self handleKeyDown: e ] up: [ :e | self handleKeyUp: e ]",
+messageSends: ["bindKeyDown:up:", "handleKeyDown:", "handleKeyUp:"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "delayBeforeStartingRepeatWithAction:",
+category: 'actions',
+fn: function (action){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st((function(){
+return smalltalk.withContext(function($ctx2) {
+self["@interval"]=self._startRepeatingAction_(action);
+return self["@interval"];
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._valueWithTimeout_((300));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"delayBeforeStartingRepeatWithAction:",{action:action},smalltalk.HLRepeatingKeyBinderForWidget)})},
+args: ["action"],
+source: "delayBeforeStartingRepeatWithAction: action\x0a\x09^ [ interval := self startRepeatingAction: action ] valueWithTimeout: 300",
+messageSends: ["valueWithTimeout:", "startRepeatingAction:"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "handleKeyDown:",
+category: 'events-processing',
+fn: function (e){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(self["@keyBindings"])._keysAndValuesDo_((function(key,action){
+return smalltalk.withContext(function($ctx2) {
+return self._ifKey_wasPressedIn_thenDo_(key,e,action);
+}, function($ctx2) {$ctx2.fillBlock({key:key,action:action},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"handleKeyDown:",{e:e},smalltalk.HLRepeatingKeyBindingHandler)})},
+args: ["e"],
+source: "handleKeyDown: e\x0a\x09 keyBindings keysAndValuesDo: [ :key :action | \x0a\x09\x09self ifKey: key wasPressedIn: e thenDo: action ]",
+messageSends: ["keysAndValuesDo:", "ifKey:wasPressedIn:thenDo:"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "handleKeyUp",
+category: 'actions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+self["@isKeyCurrentlyPressed"]=false;
+$1=self["@interval"];
+if(($receiver = $1) == nil || $receiver == undefined){
+$1;
+} else {
+_st(self["@interval"])._clearInterval();
+};
+$2=self["@delay"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$2;
+} else {
+_st(self["@delay"])._clearTimeout();
+};
+return self}, function($ctx1) {$ctx1.fill(self,"handleKeyUp",{},smalltalk.HLRepeatingKeyBindingHandler)})},
+args: [],
+source: "handleKeyUp\x0a\x09isKeyCurrentlyPressed := false.\x0a\x09interval ifNotNil: [ interval clearInterval ].\x0a\x09delay ifNotNil: [ delay clearTimeout ]",
+messageSends: ["ifNotNil:", "clearInterval", "clearTimeout"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "handleKeyUp:",
+category: 'events-processing',
+fn: function (e){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@isKeyCurrentlyPressed"];
+if(smalltalk.assert($1)){
+self._handleKeyUp();
+};
+return self}, function($ctx1) {$ctx1.fill(self,"handleKeyUp:",{e:e},smalltalk.HLRepeatingKeyBindingHandler)})},
+args: ["e"],
+source: "handleKeyUp: e\x0a\x09isKeyCurrentlyPressed\x0a\x09\x09ifTrue: [ self handleKeyUp ] ",
+messageSends: ["ifTrue:", "handleKeyUp"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "ifKey:wasPressedIn:thenDo:",
+category: 'events-processing',
+fn: function (key,e,action){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st(_st(e)._which()).__eq(key))._and_((function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(self["@isKeyCurrentlyPressed"]).__eq(false);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+if(smalltalk.assert($1)){
+self._whileTheKeyIsPressedDo_(action);
+};
+return self}, function($ctx1) {$ctx1.fill(self,"ifKey:wasPressedIn:thenDo:",{key:key,e:e,action:action},smalltalk.HLRepeatingKeyBinderForWidget)})},
+args: ["key", "e", "action"],
+source: "ifKey: key wasPressedIn: e thenDo: action\x0a\x09(e which = key and: [ isKeyCurrentlyPressed = false ])\x0a\x09\x09ifTrue: [  self whileTheKeyIsPressedDo: action ]",
+messageSends: ["ifTrue:", "whileTheKeyIsPressedDo:", "and:", "=", "which"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "initialize",
+category: 'initialization',
+fn: function (){
+var self=this;
+function $Dictionary(){return smalltalk.Dictionary||(typeof Dictionary=="undefined"?nil:Dictionary)}
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.Object.fn.prototype._initialize.apply(_st(self), []);
+self["@keyBindings"]=_st($Dictionary())._new();
+self["@isKeyCurrentlyPressed"]=false;
+self["@repeatInterval"]=(70);
+return self}, function($ctx1) {$ctx1.fill(self,"initialize",{},smalltalk.HLRepeatingKeyBindingHandler)})},
+args: [],
+source: "initialize \x0a\x09super initialize.\x0a\x09keyBindings := Dictionary new.\x0a\x09isKeyCurrentlyPressed := false.\x0a\x09repeatInterval := 70.",
+messageSends: ["initialize", "new"],
+referencedClasses: ["Dictionary"]
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "rebindKeys",
+category: 'actions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=self;
+_st($1)._unbindKeys();
+$2=_st($1)._bindKeys();
+return self}, function($ctx1) {$ctx1.fill(self,"rebindKeys",{},smalltalk.HLRepeatingKeyBinderForWidget)})},
+args: [],
+source: "rebindKeys\x0a\x09self unbindKeys;\x0a\x09\x09bindKeys",
+messageSends: ["unbindKeys", "bindKeys"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "repeatInterval:",
+category: 'accessing',
+fn: function (aMillisecondIntegerValue){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@repeatInterval"]=aMillisecondIntegerValue;
+return self}, function($ctx1) {$ctx1.fill(self,"repeatInterval:",{aMillisecondIntegerValue:aMillisecondIntegerValue},smalltalk.HLRepeatingKeyBinderForWidget)})},
+args: ["aMillisecondIntegerValue"],
+source: "repeatInterval: aMillisecondIntegerValue \x0a\x09repeatInterval := aMillisecondIntegerValue",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "startRepeatingAction:",
+category: 'actions',
+fn: function (action){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$1=_st((function(){
+return smalltalk.withContext(function($ctx2) {
+$2=_st(self["@widget"])._hasFocus();
+if(smalltalk.assert($2)){
+return _st(action)._value();
+} else {
+return self._handleKeyUp();
+};
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._valueWithInterval_(self["@repeatInterval"]);
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"startRepeatingAction:",{action:action},smalltalk.HLRepeatingKeyBindingHandler)})},
+args: ["action"],
+source: "startRepeatingAction: action\x0a\x09^ [ (widget hasFocus)\x0a\x09\x09ifTrue: [ action value ]\x0a\x09\x09ifFalse: [ self handleKeyUp ] ] valueWithInterval: repeatInterval",
+messageSends: ["valueWithInterval:", "ifTrue:ifFalse:", "value", "handleKeyUp", "hasFocus"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "unbindKeys",
+category: 'actions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(self["@widget"])._unbindKeyDownUp();
+return self}, function($ctx1) {$ctx1.fill(self,"unbindKeys",{},smalltalk.HLRepeatingKeyBinderForWidget)})},
+args: [],
+source: "unbindKeys\x0a\x09widget unbindKeyDownUp",
+messageSends: ["unbindKeyDownUp"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "whileKeyPressed:do:",
+category: 'accessing',
+fn: function (aKey,aBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(self["@keyBindings"])._at_put_(aKey,aBlock);
+return self}, function($ctx1) {$ctx1.fill(self,"whileKeyPressed:do:",{aKey:aKey,aBlock:aBlock},smalltalk.HLRepeatingKeyBindingHandler)})},
+args: ["aKey", "aBlock"],
+source: "whileKeyPressed: aKey do: aBlock\x0a\x09keyBindings at: aKey put: aBlock",
+messageSends: ["at:put:"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "whileTheKeyIsPressedDo:",
+category: 'events-processing',
+fn: function (action){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@isKeyCurrentlyPressed"]=true;
+_st(action)._value();
+self["@delay"]=self._delayBeforeStartingRepeatWithAction_(action);
+return self}, function($ctx1) {$ctx1.fill(self,"whileTheKeyIsPressedDo:",{action:action},smalltalk.HLRepeatingKeyBinderForWidget)})},
+args: ["action"],
+source: "whileTheKeyIsPressedDo: action\x0a\x09isKeyCurrentlyPressed := true.\x0a\x09action value.\x0a\x09delay := self delayBeforeStartingRepeatWithAction: action",
+messageSends: ["value", "delayBeforeStartingRepeatWithAction:"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "widget:",
+category: 'accessing',
+fn: function (aWidget){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@widget"]=aWidget;
+return self}, function($ctx1) {$ctx1.fill(self,"widget:",{aWidget:aWidget},smalltalk.HLRepeatingKeyBinderForWidget)})},
+args: ["aWidget"],
+source: "widget: aWidget\x0a\x09widget := aWidget",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler);
+
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "forWidget:",
+category: 'instance-creation',
+fn: function (aWidget){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+$2=self._new();
+_st($2)._widget_(aWidget);
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"forWidget:",{aWidget:aWidget},smalltalk.HLRepeatingKeyBindingHandler.klass)})},
+args: ["aWidget"],
+source: "forWidget: aWidget\x0a\x09^self new\x0a\x09\x09widget: aWidget;\x0a\x09\x09yourself",
+messageSends: ["widget:", "new", "yourself"],
+referencedClasses: []
+}),
+smalltalk.HLRepeatingKeyBindingHandler.klass);
+
+

+ 21 - 50
st/Helios-Core.st

@@ -616,11 +616,23 @@ unregister
 
 !HLWidget methodsFor: 'keybindings'!
 
+bindKeyDown: keyDownBlock up: keyUpBlock
+	self wrapper asJQuery
+		keydown: keyDownBlock;
+		keyup: keyUpBlock
+!
+
 registerBindings
 	self registerBindingsOn: self manager keyBinder bindings
 !
 
 registerBindingsOn: aBindingGroup
+!
+
+unbindKeyDownUp
+	self wrapper asJQuery
+		unbind: 'keydown';
+		unbind: 'keyup'
 ! !
 
 !HLWidget methodsFor: 'rendering'!
@@ -698,10 +710,6 @@ blur
 
 focus
 	self wrapper asJQuery focus
-!
-
-hasFocus
-	^ self wrapper notNil and: [ self wrapper asJQuery is: ':focus' ]
 ! !
 
 !HLFocusableWidget methodsFor: 'rendering'!
@@ -726,6 +734,10 @@ renderOn: html
 
 canHaveFocus
 	^ true
+!
+
+hasFocus
+	^ self wrapper notNil and: [ self wrapper asJQuery hasClass: self focusClass ]
 ! !
 
 HLFocusableWidget subclass: #HLListWidget
@@ -845,52 +857,11 @@ defaultItems
 
 !HLListWidget methodsFor: 'events'!
 
-setupKeyBindings
-	"TODO: refactor this!!"
-	
-	| active interval delay repeatInterval |
-	
-	active := false.
-	repeatInterval := 70.
-	self wrapper asJQuery unbind: 'keydown'.
-
-	self wrapper asJQuery keydown: [ :e |
-		
-        (e which = 38 and: [ active = false ]) ifTrue: [ 
-			active := true.
-			self activatePreviousListItem.
-        	delay := [
-				interval := [
-					(self wrapper asJQuery hasClass: self focusClass)
-						ifTrue: [
-							self activatePreviousListItem ]
-						ifFalse: [
-							active := false.
-							interval ifNotNil: [ interval clearInterval ].
-							delay ifNotNil: [ delay clearTimeout] ] ]
-					valueWithInterval: repeatInterval ]
-						valueWithTimeout: 300 ].
-			
-      	(e which = 40 and: [ active = false ]) ifTrue: [
-            active := true.
-			self activateNextListItem.
-        	delay := [
-				interval := [ 
-					(self wrapper asJQuery hasClass: self focusClass)
-						ifTrue: [
-							self activateNextListItem ]
-						ifFalse: [
-							active := false.
-							interval ifNotNil: [ interval clearInterval ].
-							delay ifNotNil: [ delay clearTimeout] ] ]
-					valueWithInterval: repeatInterval ]
-						valueWithTimeout: 300 ] ].
-	
-	self wrapper asJQuery keyup: [ :e |
-		active ifTrue: [
-			active := false.
-			interval ifNotNil: [ interval clearInterval ].
-			delay ifNotNil: [ delay clearTimeout] ] ]
+setupKeyBindings 
+	(HLRepeatingKeyBindingHandler forWidget: self)
+		whileKeyPressed: 38 do: [ self activatePreviousListItem ];
+		whileKeyPressed: 40 do: [ self activateNextListItem ];
+		rebindKeys
 ! !
 
 !HLListWidget methodsFor: 'initialization'!

+ 98 - 0
st/Helios-KeyBindings.st

@@ -597,3 +597,101 @@ on: aKeyBinder
         yourself
 ! !
 
+Object subclass: #HLRepeatingKeyBindingHandler
+	instanceVariableNames: 'repeatInterval delay interval keyBindings widget isKeyCurrentlyPressed'
+	package: 'Helios-KeyBindings'!
+!HLRepeatingKeyBindingHandler commentStamp!
+##Usage
+
+    (HLRepeatingKeyBindingHandler forWidget: aWidget)
+        whileKeyPressed: keyCode do: [xxxx];
+        whileKeyPressed: anotherKey do: [yyy];
+        rebind
+
+Performs an action on a key press, waits for 300 ms and then preforms the action every repeatInterval ms until the button is released!
+
+!HLRepeatingKeyBindingHandler methodsFor: 'accessing'!
+
+repeatInterval: aMillisecondIntegerValue 
+	repeatInterval := aMillisecondIntegerValue
+!
+
+whileKeyPressed: aKey do: aBlock
+	keyBindings at: aKey put: aBlock
+!
+
+widget: aWidget
+	widget := aWidget
+! !
+
+!HLRepeatingKeyBindingHandler methodsFor: 'actions'!
+
+bindKeys
+	widget bindKeyDown: [ :e | self handleKeyDown: e ] up: [ :e | self handleKeyUp: e ]
+!
+
+delayBeforeStartingRepeatWithAction: action
+	^ [ interval := self startRepeatingAction: action ] valueWithTimeout: 300
+!
+
+handleKeyUp
+	isKeyCurrentlyPressed := false.
+	interval ifNotNil: [ interval clearInterval ].
+	delay ifNotNil: [ delay clearTimeout ]
+!
+
+rebindKeys
+	self unbindKeys;
+		bindKeys
+!
+
+startRepeatingAction: action
+	^ [ (widget hasFocus)
+		ifTrue: [ action value ]
+		ifFalse: [ self handleKeyUp ] ] valueWithInterval: repeatInterval
+!
+
+unbindKeys
+	widget unbindKeyDownUp
+! !
+
+!HLRepeatingKeyBindingHandler methodsFor: 'events-processing'!
+
+handleKeyDown: e
+	 keyBindings keysAndValuesDo: [ :key :action | 
+		self ifKey: key wasPressedIn: e thenDo: action ]
+!
+
+handleKeyUp: e
+	isKeyCurrentlyPressed
+		ifTrue: [ self handleKeyUp ]
+!
+
+ifKey: key wasPressedIn: e thenDo: action
+	(e which = key and: [ isKeyCurrentlyPressed = false ])
+		ifTrue: [  self whileTheKeyIsPressedDo: action ]
+!
+
+whileTheKeyIsPressedDo: action
+	isKeyCurrentlyPressed := true.
+	action value.
+	delay := self delayBeforeStartingRepeatWithAction: action
+! !
+
+!HLRepeatingKeyBindingHandler methodsFor: 'initialization'!
+
+initialize 
+	super initialize.
+	keyBindings := Dictionary new.
+	isKeyCurrentlyPressed := false.
+	repeatInterval := 70.
+! !
+
+!HLRepeatingKeyBindingHandler class methodsFor: 'instance-creation'!
+
+forWidget: aWidget
+	^self new
+		widget: aWidget;
+		yourself
+! !
+