浏览代码

[WIP] Add short-lived suggestion when accessing a command by the menu

Steven Rémot 7 年之前
父节点
当前提交
ffdb53ef83
共有 4 个文件被更改,包括 527 次插入23 次删除
  1. 64 0
      src/Helios-Commands-Core.js
  2. 21 0
      src/Helios-Commands-Core.st
  3. 338 18
      src/Helios-Core.js
  4. 104 5
      src/Helios-Core.st

+ 64 - 0
src/Helios-Commands-Core.js

@@ -377,6 +377,29 @@ messageSends: ["charCodeAt:", "asUppercase", "key"]
 }),
 $globals.HLCommand);
 
+$core.addMethod(
+$core.method({
+selector: "keySequence",
+protocol: "accessing",
+fn: function(){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return $recv($self._class())._keySequence();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"keySequence",{},$globals.HLCommand)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "keySequence\x0a\x09^ self class keySequence",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["keySequence", "class"]
+}),
+$globals.HLCommand);
+
 $core.addMethod(
 $core.method({
 selector: "label",
@@ -563,6 +586,47 @@ messageSends: []
 }),
 $globals.HLCommand.a$cls);
 
+$core.addMethod(
+$core.method({
+selector: "keySequence",
+protocol: "accessing",
+fn: function(){
+var self=this,$self=this;
+var ownCommands,parentCommands;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1,$2,$receiver;
+ownCommands=$recv($globals.OrderedCollection)._new();
+$1=$self.__eq($globals.HLCommand);
+if($core.assert($1)){
+return ownCommands;
+}
+$2=$self._key();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["key"]=1;
+//>>excludeEnd("ctx");
+if(($receiver = $2) == null || $receiver.a$nil){
+$2;
+} else {
+$recv(ownCommands)._add_($self._key());
+}
+parentCommands=$recv($self._superclass())._keySequence();
+$recv(parentCommands)._addAll_(ownCommands);
+return parentCommands;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"keySequence",{ownCommands:ownCommands,parentCommands:parentCommands},$globals.HLCommand.a$cls)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "keySequence\x0a\x09| ownCommands parentCommands |\x0a\x09\x0a\x09ownCommands := OrderedCollection new.\x0a\x09\x0a\x09(self = HLCommand)\x0a\x09\x09ifTrue: [ ^ ownCommands ].\x0a\x09\x09\x0a\x09self key ifNotNil: [ ownCommands add: self key ].\x0a\x09\x0a\x09parentCommands := self superclass keySequence.\x0a\x09\x0a\x09parentCommands addAll: ownCommands.\x0a\x09\x0a\x09^ parentCommands",
+referencedClasses: ["OrderedCollection", "HLCommand"],
+//>>excludeEnd("ide");
+messageSends: ["new", "ifTrue:", "=", "ifNotNil:", "key", "add:", "keySequence", "superclass", "addAll:"]
+}),
+$globals.HLCommand.a$cls);
+
 $core.addMethod(
 $core.method({
 selector: "label",

+ 21 - 0
src/Helios-Commands-Core.st

@@ -33,6 +33,10 @@ keyCode
 	^ self key asUppercase charCodeAt: 1
 !
 
+keySequence
+	^ self class keySequence
+!
+
 label
 	^ self class label
 !
@@ -114,6 +118,23 @@ key
 	^ nil
 !
 
+keySequence
+	| ownCommands parentCommands |
+	
+	ownCommands := OrderedCollection new.
+	
+	(self = HLCommand)
+		ifTrue: [ ^ ownCommands ].
+		
+	self key ifNotNil: [ ownCommands add: self key ].
+	
+	parentCommands := self superclass keySequence.
+	
+	parentCommands addAll: ownCommands.
+	
+	^ parentCommands
+!
+
 label
 	^ ''
 !

+ 338 - 18
src/Helios-Core.js

@@ -2164,15 +2164,18 @@ $core.addMethod(
 $core.method({
 selector: "execute:",
 protocol: "actions",
-fn: function (aCommand){
+fn: function(aCommand){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $1;
+var $1,$2;
 $1=$recv($recv($globals.HLManager)._current())._keyBinder();
 $recv($1)._activate();
 $recv($1)._applyBinding_($recv(aCommand)._asBinding());
+$2=$recv($globals.HLShortcutSuggestionWidget)._new();
+$recv($2)._command_(aCommand);
+$recv($2)._show();
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,"execute:",{aCommand:aCommand},$globals.HLWidget)});
@@ -2180,10 +2183,10 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aCommand"],
-source: "execute: aCommand\x0a\x09HLManager current keyBinder\x0a\x09\x09activate;\x0a\x09\x09applyBinding: aCommand asBinding",
-referencedClasses: ["HLManager"],
+source: "execute: aCommand\x0a\x09HLManager current keyBinder\x0a\x09\x09activate;\x0a\x09\x09applyBinding: aCommand asBinding.\x0a\x0a\x09(HLShortcutSuggestionWidget new)\x0a\x09\x09command: aCommand;\x0a\x09\x09show.",
+referencedClasses: ["HLManager", "HLShortcutSuggestionWidget"],
 //>>excludeEnd("ide");
-messageSends: ["activate", "keyBinder", "current", "applyBinding:", "asBinding"]
+messageSends: ["activate", "keyBinder", "current", "applyBinding:", "asBinding", "command:", "new", "show"]
 }),
 $globals.HLWidget);
 
@@ -2685,6 +2688,143 @@ messageSends: []
 $globals.HLWidget.a$cls);
 
 
+$core.addClass("HLContextMenuWidget", $globals.HLWidget, ["commands"], "Helios-Core");
+$core.addMethod(
+$core.method({
+selector: "bindToElement:",
+protocol: "as yet unclassified",
+fn: function(anElement){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+$1=$recv(anElement)._asDomNode();
+$recv($1)._setAttribute_put_("data-toggle","dropdown");
+$recv($1)._addEventListener_do_("contextmenu",(function(evt){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$recv(evt)._preventDefault();
+return $self._openForEvent_(evt);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({evt:evt},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"bindToElement:",{anElement:anElement},$globals.HLContextMenuWidget)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anElement"],
+source: "bindToElement: anElement\x0a\x09anElement asDomNode\x0a\x09\x09setAttribute: 'data-toggle' put: 'dropdown';\x0a\x09\x09addEventListener: 'contextmenu' do: [ :evt |\x0a\x09\x09\x09evt preventDefault.\x0a\x09\x09\x09self openForEvent: evt\x0a\x09\x09]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["setAttribute:put:", "asDomNode", "addEventListener:do:", "preventDefault", "openForEvent:"]
+}),
+$globals.HLContextMenuWidget);
+
+$core.addMethod(
+$core.method({
+selector: "openForEvent:",
+protocol: "as yet unclassified",
+fn: function(anEvent){
+var self=this,$self=this;
+var rect,x,y;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $4,$3,$2,$1,$5;
+x=$recv(anEvent)._clientX();
+y=$recv(anEvent)._clientY();
+$4=$self._wrapper();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["wrapper"]=1;
+//>>excludeEnd("ctx");
+$3=$recv($4)._asDomNode();
+$2=$recv($3)._querySelector_(".dropdown-menu");
+$1=$recv($2)._style();
+$5=$recv(x).__comma("px");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx[","]=1;
+//>>excludeEnd("ctx");
+$recv($1)._left_($5);
+$recv($1)._top_($recv(y).__comma("px"));
+$recv($recv($recv($self._wrapper())._asJQuery())._dropdown())._toggle();
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"openForEvent:",{anEvent:anEvent,rect:rect,x:x,y:y},$globals.HLContextMenuWidget)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anEvent"],
+source: "openForEvent: anEvent\x0a\x09| rect x y |\x0a\x09x := anEvent clientX.\x0a\x09y := anEvent clientY.\x0a\x09(self wrapper asDomNode querySelector: '.dropdown-menu') style\x0a\x09\x09left: x, 'px';\x0a\x09\x09top: y, 'px'.\x0a\x09self wrapper asJQuery dropdown toggle",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["clientX", "clientY", "left:", "style", "querySelector:", "asDomNode", "wrapper", ",", "top:", "toggle", "dropdown", "asJQuery"]
+}),
+$globals.HLContextMenuWidget);
+
+$core.addMethod(
+$core.method({
+selector: "renderContentOn:",
+protocol: "as yet unclassified",
+fn: function (html){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1,$2;
+$1=$recv(html)._ul();
+$recv($1)._class_("dropdown-menu");
+$recv($1)._style_("position: fixed");
+$recv($1)._width_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv($recv(html)._li())._with_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx3) {
+//>>excludeEnd("ctx");
+$2=$recv(html)._a();
+$recv($2)._with_("Test");
+return $recv($2)._onClick_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx4) {
+//>>excludeEnd("ctx");
+return $recv(window)._alert_("Clicked Test");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx4) {$ctx4.fillBlock({},$ctx3,3)});
+//>>excludeEnd("ctx");
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2,2)});
+//>>excludeEnd("ctx");
+}));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx2.sendIdx["with:"]=1;
+//>>excludeEnd("ctx");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html},$globals.HLContextMenuWidget)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["html"],
+source: "renderContentOn: html\x0a\x09html ul\x0a\x09\x09class: 'dropdown-menu';\x0a\x09\x09style: 'position: fixed';\x0a\x09\x09width: [\x0a\x09\x09\x09html li\x0a\x09\x09\x09\x09with: [\x0a\x09\x09\x09\x09\x09html a\x0a\x09\x09\x09\x09\x09\x09with: 'Test';\x0a\x09\x09\x09\x09\x09\x09onClick: [ window alert: 'Clicked Test' ]\x0a\x09\x09\x09\x09]\x0a\x09\x09]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["class:", "ul", "style:", "width:", "with:", "li", "a", "onClick:", "alert:"]
+}),
+$globals.HLContextMenuWidget);
+
+
+
 $core.addClass("HLFocusableWidget", $globals.HLWidget, [], "Helios-Core");
 //>>excludeStart("ide", pragmas.excludeIdeData);
 $globals.HLFocusableWidget.comment="I am a widget that can be focused.\x0a\x0a## API \x0a\x0aInstead of overriding `#renderOn:` as with other `Widget` subclasses, my subclasses should override `#renderContentOn:`.\x0a\x0aTo bring the focus to the widget, use the `#focus` method.";
@@ -3556,12 +3696,14 @@ $core.addMethod(
 $core.method({
 selector: "renderContentOn:",
 protocol: "rendering",
-fn: function (html){
+fn: function(html){
 var self=this,$self=this;
+var ul,contextMenu;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-var $1,$2;
+var $1,$2,$3;
+contextMenu=$recv($globals.HLContextMenuWidget)._new();
 $1=$recv(html)._ul();
 $recv($1)._class_($self._listCssClass());
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -3579,18 +3721,20 @@ return $self._renderListOn_(html);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["with:"]=1;
 //>>excludeEnd("ctx");
-$recv($1)._onClick_((function(){
+ul=$recv($1)._onClick_((function(evt){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx2) {
 //>>excludeEnd("ctx");
+$recv(console)._log_(evt);
 return $self._focus();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
+}, function($ctx2) {$ctx2.fillBlock({evt:evt},$ctx1,2)});
 //>>excludeEnd("ctx");
 }));
+$recv(contextMenu)._bindToElement_(ul);
 $2=$recv(html)._div();
 $recv($2)._class_($self._buttonsDivCssClass());
-$recv($2)._with_((function(){
+$3=$recv($2)._with_((function(){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx2) {
 //>>excludeEnd("ctx");
@@ -3599,18 +3743,22 @@ return $self._renderButtonsOn_(html);
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1,3)});
 //>>excludeEnd("ctx");
 }));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["with:"]=2;
+//>>excludeEnd("ctx");
+$recv(html)._with_(contextMenu);
 $self._setupKeyBindings();
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html},$globals.HLListWidget)});
+}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html,ul:ul,contextMenu:contextMenu},$globals.HLListWidget)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["html"],
-source: "renderContentOn: html\x0a\x09html ul \x0a    \x09class: self listCssClass;\x0a        with: [ self renderListOn: html ];\x0a\x09\x09onClick: [ self focus ].\x0a    html div class: self buttonsDivCssClass; with: [\x0a      \x09self renderButtonsOn: html ].\x0a        \x0a   self setupKeyBindings",
-referencedClasses: [],
+source: "renderContentOn: html\x0a\x09| ul contextMenu |\x0a\x09contextMenu := HLContextMenuWidget new.\x0a\x09\x0a\x09ul := html ul\x0a    \x09class: self listCssClass;\x0a        with: [ self renderListOn: html ];\x0a\x09\x09onClick: [ :evt | console log: evt. self focus ].\x0a\x09\x09\x0a\x09contextMenu bindToElement: ul.\x0a\x09\x0a    html div class: self buttonsDivCssClass; with: [\x0a      \x09self renderButtonsOn: html ].\x0a\x09\x09\x0a\x09html with: contextMenu.\x0a        \x0a   self setupKeyBindings",
+referencedClasses: ["HLContextMenuWidget"],
 //>>excludeEnd("ide");
-messageSends: ["class:", "ul", "listCssClass", "with:", "renderListOn:", "onClick:", "focus", "div", "buttonsDivCssClass", "renderButtonsOn:", "setupKeyBindings"]
+messageSends: ["new", "class:", "ul", "listCssClass", "with:", "renderListOn:", "onClick:", "log:", "focus", "bindToElement:", "div", "buttonsDivCssClass", "renderButtonsOn:", "setupKeyBindings"]
 }),
 $globals.HLListWidget);
 
@@ -6373,9 +6521,8 @@ $core.addMethod(
 $core.method({
 selector: "renderContentOn:",
 protocol: "rendering",
-fn: function (html){
+fn: function(html){
 var self=this,$self=this;
-var confirmButton;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
@@ -6404,12 +6551,12 @@ $recv(".dialog"._asJQuery())._addClass_("active");
 $self._setupKeyBindings();
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html,confirmButton:confirmButton},$globals.HLModalWidget)});
+}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html},$globals.HLModalWidget)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["html"],
-source: "renderContentOn: html\x0a\x09| confirmButton |\x0a\x09\x0a\x09html div id: 'overlay'.\x0a\x09\x0a\x09html div \x0a\x09\x09class: 'dialog ', self cssClass;\x0a\x09\x09with: [\x0a\x09\x09\x09self renderMainOn: html.\x0a\x09\x09\x09self hasButtons ifTrue: [ \x0a\x09\x09\x09\x09self renderButtonsOn: html ] ].\x0a\x0a\x09'.dialog' asJQuery addClass: 'active'.\x0a\x09self setupKeyBindings",
+source: "renderContentOn: html\x0a\x09html div id: 'overlay'.\x0a\x09\x0a\x09html div \x0a\x09\x09class: 'dialog ', self cssClass;\x0a\x09\x09with: [\x0a\x09\x09\x09self renderMainOn: html.\x0a\x09\x09\x09self hasButtons ifTrue: [ \x0a\x09\x09\x09\x09self renderButtonsOn: html ] ].\x0a\x0a\x09'.dialog' asJQuery addClass: 'active'.\x0a\x09self setupKeyBindings",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: ["id:", "div", "class:", ",", "cssClass", "with:", "renderMainOn:", "ifTrue:", "hasButtons", "renderButtonsOn:", "addClass:", "asJQuery", "setupKeyBindings"]
@@ -7503,6 +7650,179 @@ messageSends: ["ifNil:", "new"]
 $globals.HLProgressWidget.a$cls);
 
 
+$core.addClass("HLShortcutSuggestionWidget", $globals.HLModalWidget, ["command"], "Helios-Core");
+//>>excludeStart("ide", pragmas.excludeIdeData);
+$globals.HLShortcutSuggestionWidget.comment="I am a modal that displays the keyboard shortcut associated to a specific command.";
+//>>excludeEnd("ide");
+$core.addMethod(
+$core.method({
+selector: "command",
+protocol: "accessing",
+fn: function(){
+var self=this,$self=this;
+return $self["@command"];
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "command\x0a\x09^ command",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.HLShortcutSuggestionWidget);
+
+$core.addMethod(
+$core.method({
+selector: "command:",
+protocol: "accessing",
+fn: function(aCommand){
+var self=this,$self=this;
+$self["@command"]=aCommand;
+return self;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aCommand"],
+source: "command: aCommand\x0a\x09command := aCommand",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.HLShortcutSuggestionWidget);
+
+$core.addMethod(
+$core.method({
+selector: "hasButtons",
+protocol: "rendering",
+fn: function(){
+var self=this,$self=this;
+return false;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "hasButtons\x0a\x09^ false",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.HLShortcutSuggestionWidget);
+
+$core.addMethod(
+$core.method({
+selector: "renderMainOn:",
+protocol: "rendering",
+fn: function(html){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1,$2;
+$1=$recv(html)._div();
+$recv($1)._class_("title");
+$2=$recv($1)._with_($recv($self["@command"])._label());
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.sendIdx["with:"]=1;
+//>>excludeEnd("ctx");
+$recv($recv(html)._p())._with_("Ctrl-space + ".__comma($recv($recv($self["@command"])._keySequence())._join_(" + ")));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"renderMainOn:",{html:html},$globals.HLShortcutSuggestionWidget)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["html"],
+source: "renderMainOn: html\x0a\x09html div\x0a\x09\x09class: 'title';\x0a\x09\x09with: command label.\x0a\x09\x09\x0a\x09html p\x0a\x09\x09with: 'Ctrl-space + ', (command keySequence join: ' + ')",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["class:", "div", "with:", "label", "p", ",", "join:", "keySequence"]
+}),
+$globals.HLShortcutSuggestionWidget);
+
+$core.addMethod(
+$core.method({
+selector: "show",
+protocol: "actions",
+fn: function(){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+(
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = true,
+//>>excludeEnd("ctx");
+($globals.HLShortcutSuggestionWidget.superclass||$boot.nilAsClass).fn.prototype._show.apply($self, []));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = false;
+//>>excludeEnd("ctx");;
+$recv((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._remove();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)});
+//>>excludeEnd("ctx");
+}))._valueWithTimeout_($self._visibleDelay());
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"show",{},$globals.HLShortcutSuggestionWidget)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "show\x0a\x09super show.\x0a\x09[ self remove ] valueWithTimeout: self visibleDelay.",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["show", "valueWithTimeout:", "remove", "visibleDelay"]
+}),
+$globals.HLShortcutSuggestionWidget);
+
+$core.addMethod(
+$core.method({
+selector: "visibleDelay",
+protocol: "constants",
+fn: function(){
+var self=this,$self=this;
+return (2000);
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "visibleDelay\x0a\x09^ 2000",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.HLShortcutSuggestionWidget);
+
+
+$core.addMethod(
+$core.method({
+selector: "command:",
+protocol: "instance creation",
+fn: function(aCommand){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+return $recv($self._new())._command_(aCommand);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"command:",{aCommand:aCommand},$globals.HLShortcutSuggestionWidget.a$cls)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aCommand"],
+source: "command: aCommand\x0a\x09^ self new command: aCommand",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["command:", "new"]
+}),
+$globals.HLShortcutSuggestionWidget.a$cls);
+
+
 $core.addClass("HLTabSelectionWidget", $globals.HLModalWidget, ["tabs", "tabList", "selectedTab", "selectCallback", "cancelCallback", "confirmCallback"], "Helios-Core");
 //>>excludeStart("ide", pragmas.excludeIdeData);
 $globals.HLTabSelectionWidget.comment="I am a modal window used to select or create tabs.";

+ 104 - 5
src/Helios-Core.st

@@ -575,7 +575,11 @@ confirm: aString ifTrue: aBlock ifFalse: anotherBlock
 execute: aCommand
 	HLManager current keyBinder
 		activate;
-		applyBinding: aCommand asBinding
+		applyBinding: aCommand asBinding.
+
+	(HLShortcutSuggestionWidget new)
+		command: aCommand;
+		show.
 !
 
 inform: aString
@@ -686,6 +690,45 @@ canBeOpenAsTab
 	^ false
 ! !
 
+HLWidget subclass: #HLContextMenuWidget
+	instanceVariableNames: 'commands'
+	package: 'Helios-Core'!
+
+!HLContextMenuWidget methodsFor: 'as yet unclassified'!
+
+bindToElement: anElement
+	anElement asDomNode
+		setAttribute: 'data-toggle' put: 'dropdown';
+		addEventListener: 'contextmenu' do: [ :evt |
+			evt preventDefault.
+			self openForEvent: evt
+		]
+!
+
+openForEvent: anEvent
+	| rect x y |
+	x := anEvent clientX.
+	y := anEvent clientY.
+	(self wrapper asDomNode querySelector: '.dropdown-menu') style
+		left: x, 'px';
+		top: y, 'px'.
+	self wrapper asJQuery dropdown toggle
+!
+
+renderContentOn: html
+	html ul
+		class: 'dropdown-menu';
+		style: 'position: fixed';
+		width: [
+			html li
+				with: [
+					html a
+						with: 'Test';
+						onClick: [ window alert: 'Clicked Test' ]
+				]
+		]
+! !
+
 HLWidget subclass: #HLFocusableWidget
 	instanceVariableNames: ''
 	package: 'Helios-Core'!
@@ -894,12 +937,20 @@ renderButtonsOn: html
 !
 
 renderContentOn: html
-	html ul 
+	| ul contextMenu |
+	contextMenu := HLContextMenuWidget new.
+	
+	ul := html ul
     	class: self listCssClass;
         with: [ self renderListOn: html ];
-		onClick: [ self focus ].
+		onClick: [ :evt | console log: evt. self focus ].
+		
+	contextMenu bindToElement: ul.
+	
     html div class: self buttonsDivCssClass; with: [
       	self renderButtonsOn: html ].
+		
+	html with: contextMenu.
         
    self setupKeyBindings
 !
@@ -1502,8 +1553,6 @@ renderButtonsOn: html
 !
 
 renderContentOn: html
-	| confirmButton |
-	
 	html div id: 'overlay'.
 	
 	html div 
@@ -1771,6 +1820,56 @@ default
 	^ default ifNil: [ default := self new ]
 ! !
 
+HLModalWidget subclass: #HLShortcutSuggestionWidget
+	instanceVariableNames: 'command'
+	package: 'Helios-Core'!
+!HLShortcutSuggestionWidget commentStamp!
+I am a modal that displays the keyboard shortcut associated to a specific command.!
+
+!HLShortcutSuggestionWidget methodsFor: 'accessing'!
+
+command
+	^ command
+!
+
+command: aCommand
+	command := aCommand
+! !
+
+!HLShortcutSuggestionWidget methodsFor: 'actions'!
+
+show
+	super show.
+	[ self remove ] valueWithTimeout: self visibleDelay.
+! !
+
+!HLShortcutSuggestionWidget methodsFor: 'constants'!
+
+visibleDelay
+	^ 2000
+! !
+
+!HLShortcutSuggestionWidget methodsFor: 'rendering'!
+
+hasButtons
+	^ false
+!
+
+renderMainOn: html
+	html div
+		class: 'title';
+		with: command label.
+		
+	html p
+		with: 'Ctrl-space + ', (command keySequence join: ' + ')
+! !
+
+!HLShortcutSuggestionWidget class methodsFor: 'instance creation'!
+
+command: aCommand
+	^ self new command: aCommand
+! !
+
 HLModalWidget subclass: #HLTabSelectionWidget
 	instanceVariableNames: 'tabs tabList selectedTab selectCallback cancelCallback confirmCallback'
 	package: 'Helios-Core'!