Bladeren bron

Merge branch 'master' into requirejs

Herbert Vojčík 11 jaren geleden
bovenliggende
commit
72a9635d14

+ 52 - 18
css/helios.css

@@ -35,7 +35,6 @@ html[xmlns] .clearfix {
   overflow: hidden;
   height: 100%;
   width: 100%;
-  /*background: #fff*/;
 }
 #helios .CodeMirror-hints {
   border-radius: 0;
@@ -111,7 +110,8 @@ html[xmlns] .clearfix {
   font-size: 11px;
   line-height: 16px;
 }
-#helios .navbar-fixed-top a span {
+#helios .navbar-fixed-top a span,
+#helios .dialog .nav a span {
   padding: 1px;
   padding-left: 18px;
   background-position: center left;
@@ -127,22 +127,28 @@ html[xmlns] .clearfix {
   background-position: center left;
   margin-top: 2px;
 }
-#helios .navbar-fixed-top a span.references {
+#helios .navbar-fixed-top a span.references,
+#helios .dialog .nav a span.references {
   background-image: url('../images/references.png');
 }
-#helios .navbar-fixed-top a span.browser {
+#helios .navbar-fixed-top a span.browser,
+#helios .dialog .nav a span.browser {
   background-image: url('../images/browser.png');
 }
-#helios .navbar-fixed-top a span.sunit {
+#helios .navbar-fixed-top a span.sunit,
+#helios .dialog .nav a span.sunit {
   background-image: url('../images/sunit.png');
 }
-#helios .navbar-fixed-top a span.workspace {
+#helios .navbar-fixed-top a span.workspace,
+#helios .dialog .nav a span.workspace {
   background-image: url('../images/workspace.png');
 }
-#helios .navbar-fixed-top a span.debugger {
+#helios .navbar-fixed-top a span.debugger,
+#helios .dialog .nav a span.debugger {
   background-image: url('../images/debugger.png');
 }
-#helios .navbar-fixed-top a span.inspector {
+#helios .navbar-fixed-top a span.inspector,
+#helios .dialog .nav a span.inspector {
   background-image: url('../images/inspector.png');
 }
 #helios .navbar-fixed-top .navbar-inner {
@@ -514,6 +520,9 @@ html[xmlns] .clearfix {
 #helios .key_helper .command {
   padding: 0 2px;
 }
+#helios .key_helper #binding-helper-main {
+  display: inline;
+}
 #helios .key_helper .label {
   padding: 1px 4px;
   font-family: Menlo, Monaco, "Lucida Console", Courier, monospace;
@@ -563,12 +572,12 @@ html[xmlns] .clearfix {
   color: #B91010;
   font-weight: bold;
 }
-#helios .typeahead.dropdown-menu {
+#helios .key_helper .typeahead.dropdown-menu {
   position: fixed !important;
   top: auto !important;
   bottom: 30px !important;
 }
-#helios #cog-helper {
+#helios .key_helper #cog-helper {
   position: fixed;
   bottom: 2px;
   right: 2px;
@@ -580,7 +589,7 @@ html[xmlns] .clearfix {
   -o-transition: all .5s;
   -ms-transition: all .5s;
 }
-#helios #cog-helper:hover {
+#helios .key_helper #cog-helper:hover {
   opacity: 1;
 }
 #helios #helper {
@@ -629,24 +638,49 @@ html[xmlns] .clearfix {
   -moz-transition: top .5s;
   -o-transition: top .5s;
 }
-#helios .confirmation.large,
-#helios .dialog.large {
+#helios .confirmation .hl_widget:focus,
+#helios .dialog .hl_widget:focus {
+  outline: 0 none;
+}
+#helios .confirmation .hl_widget .form-actions,
+#helios .dialog .hl_widget .form-actions {
+  padding: 0;
+  border: 0;
+}
+#helios .confirmation .nav,
+#helios .dialog .nav {
+  border: 1px solid #999;
+}
+#helios .confirmation .nav span,
+#helios .dialog .nav span {
+  font-size: 11px;
+  font-weight: normal;
+}
+#helios .confirmation .title,
+#helios .dialog .title {
+  font-size: 16px;
+  margin-bottom: 15px;
+}
+#helios .confirmation .large,
+#helios .dialog .large {
   width: 400px;
   margin-left: -220px;
 }
+#helios .confirmation .large textarea,
+#helios .dialog .large textarea {
+  width: 385px;
+  height: 200px;
+}
 #helios .confirmation textarea,
 #helios .dialog textarea {
   display: block;
   width: 235px;
 }
-#helios .confirmation.large textarea,
-#helios .dialog.large textarea {
-  width: 385px;
-  height: 200px;
-}
+#helios .confirmation .progress,
 #helios .dialog .progress {
   height: 5px;
 }
+#helios .confirmation .progress .bar,
 #helios .dialog .progress .bar {
   background-color: #e9eaf5;
   background-image: -webkit-linear-gradient(top, #b1bdd5, #8999b8);

+ 159 - 131
css/helios.less

@@ -44,7 +44,6 @@ html[xmlns] .clearfix {
 	overflow: hidden;
 	height: 100%;
 	width: 100%;
-	background: #fff;
     }
 
     .CodeMirror-hints {
@@ -133,7 +132,8 @@ html[xmlns] .clearfix {
 	line-height: 16px;
     }
 
-    .navbar-fixed-top a span {
+    .navbar-fixed-top a span,
+    .dialog .nav a span {
 	padding: 1px;
 	padding-left: 18px;
 	background-position: center left;
@@ -151,27 +151,31 @@ html[xmlns] .clearfix {
 	margin-top: 2px;
     }
 
-    .navbar-fixed-top a span.references {
-	background-image: url('../images/references.png')
-    }
+    .navbar-fixed-top a,
+    .dialog .nav a {
 
-    .navbar-fixed-top a span.browser {
-	background-image: url('../images/browser.png')
-    }
+	span.references {
+	    background-image: url('../images/references.png')
+	}
 
-    .navbar-fixed-top a span.sunit {
-	background-image: url('../images/sunit.png')
-    }
+	span.browser {
+	    background-image: url('../images/browser.png')
+	}
 
-    .navbar-fixed-top a span.workspace {
-	background-image: url('../images/workspace.png')
-    }
+	span.sunit {
+	    background-image: url('../images/sunit.png')
+	}
 
-    .navbar-fixed-top a span.debugger {
-	background-image: url('../images/debugger.png')
-    }
-    .navbar-fixed-top a span.inspector {
-	background-image: url('../images/inspector.png')
+	span.workspace {
+	    background-image: url('../images/workspace.png')
+	}
+
+	span.debugger {
+	    background-image: url('../images/debugger.png')
+	}
+	span.inspector {
+	    background-image: url('../images/inspector.png')
+	}
     }
 
 
@@ -595,89 +599,94 @@ html[xmlns] .clearfix {
 	border-top: 1px solid #aaa;
 	font-size: 11px;
 	height: 22px;
-    }
 
-    .key_helper .command {
-	padding: 0 2px;
-    }
+	.command {
+	    padding: 0 2px;
+	}
 
-    .key_helper .label {
-	padding: 1px 4px;
-	font-family: Menlo, Monaco, "Lucida Console", Courier, monospace;
-	background: transparent;
-	color: #08C;
-	text-shadow: none;
-	border: 0 none;
-    }
+	#binding-helper-main {
+	    display: inline;
+	}
 
-    .key_helper .action {
-	padding: 0 5px;
-	color: #666;
-    }
+	.label {
+	    padding: 1px 4px;
+	    font-family: Menlo, Monaco, "Lucida Console", Courier, monospace;
+	    background: transparent;
+	    color: #08C;
+	    text-shadow: none;
+	    border: 0 none;
+	}
 
-    .key_helper .selected {
-	background-image: linear-gradient(top, #ccc, #aaa);
-	background-image: -moz-linear-gradient(top, #ccc, #aaa);
-	background-image: -o-linear-gradient(top, #ccc, #aaa);
-	background-image: -webkit-linear-gradient(top, #ccc, #aaa);
-	height: 30px;
-	padding: 0 8px;
-	color: #333;
-	font-weight: bold;
-	text-shadow: 0 1px 0 #fff;
-	display: inline-block;
-	border-right: 1px solid #aaa;
-    }
+	.action {
+	    padding: 0 5px;
+	    color: #666;
+	}
 
-    .key_helper .close {
-	font-size: 14px;
-	line-height: 26px;
-	opacity: 0.6;
-    }
+	.selected {
+	    background-image: linear-gradient(top, #ccc, #aaa);
+	    background-image: -moz-linear-gradient(top, #ccc, #aaa);
+	    background-image: -o-linear-gradient(top, #ccc, #aaa);
+	    background-image: -webkit-linear-gradient(top, #ccc, #aaa);
+	    height: 30px;
+	    padding: 0 8px;
+	    color: #333;
+	    font-weight: bold;
+	    text-shadow: 0 1px 0 #fff;
+	    display: inline-block;
+	    border-right: 1px solid #aaa;
+	}
 
-    .key_helper .close:hover {
-	opacity: 0.8;
-    }
+	.close {
+	    font-size: 14px;
+	    line-height: 26px;
+	    opacity: 0.6;
+	}
 
-    .key_helper input {
-	outline: none;
-	font-size: 11px;
-	padding: 2px 8px;
-	background: #fff;
-	border: 1px solid #a1a1a1;
-	border-radius: 12px;
-	box-shadow: inset 0 0px 2px 0px #aaa;
-	margin: 2px 4px;
-	line-height: 1em;
-    }
+	.close:hover {
+	    opacity: 0.8;
+	}
 
-    .key_helper .error .help-inline,
-    .key_helper .error input {
-	color: #B91010;
-	font-weight: bold;
-    }
+	input {
+	    outline: none;
+	    font-size: 11px;
+	    padding: 2px 8px;
+	    background: #fff;
+	    border: 1px solid #a1a1a1;
+	    border-radius: 12px;
+	    box-shadow: inset 0 0px 2px 0px #aaa;
+	    margin: 2px 4px;
+	    line-height: 1em;
+	}
 
-    .typeahead.dropdown-menu {
-	position: fixed !important;
-	top: auto !important;
-	bottom: 30px !important;
-    }
+	.error .help-inline,
+	.error input {
+	    color: #B91010;
+	    font-weight: bold;
+	}
 
-    #cog-helper {
-	position: fixed;
-	bottom: 2px;
-	right: 2px;
-	z-index: 1000;
-	opacity: 0.6;
-	transition: all .5s;
-	-webkit-transition: all .5s;
-	-moz-transition: all .5s;
-	-o-transition: all .5s;
-	-ms-transition: all .5s;
-    }
+	.typeahead.dropdown-menu {
+	    position: fixed !important;
+	    top: auto !important;
+	    bottom: 30px !important;
+	}
+
+	#cog-helper {
+	    position: fixed;
+	    bottom: 2px;
+	    right: 2px;
+	    z-index: 1000;
+	    opacity: 0.6;
+	    transition: all .5s;
+	    -webkit-transition: all .5s;
+	    -moz-transition: all .5s;
+	    -o-transition: all .5s;
+	    -ms-transition: all .5s;
+	}
+
+	#cog-helper:hover {
+	    opacity: 1;
+	}
 
-    #cog-helper:hover {
-	opacity: 1;
     }
 
     #helper {
@@ -726,54 +735,73 @@ html[xmlns] .clearfix {
 	-webkit-transition: top .5s;
 	-moz-transition: top .5s;
 	-o-transition: top .5s;
-    }
 
-    .confirmation.large,
-    .dialog.large {
-	width: 400px;
-	margin-left: -220px;
-    }
+	.hl_widget {
+	
+	    &:focus {
+		outline: 0 none;
+	    }
+
+	    .form-actions {
+		padding: 0;
+		border: 0;
+	    }
+	}
 
-    .confirmation textarea,
-    .dialog textarea {
-	display: block;
-	width: 235px;
-    }
+	.nav {
+	    border: 1px solid #999;
 
-    .confirmation.large textarea,
-    .dialog.large textarea {
-	width: 385px;
-	height: 200px;
-    }
+	    span {
+		font-size: 11px;
+		font-weight: normal;
+	    }
+	}
 
-    .dialog .progress {
-	height: 5px;
-    }
+	.title {
+	    font-size: 16px;
+	    margin-bottom: 15px;
+	}
 
-    .dialog .progress .bar {
-	background-color: #e9eaf5;
-	background-image: -webkit-linear-gradient(top, #B1BDD5, #8999b8);
-	background-image: -moz-linear-gradient(top, #B1BDD5, #8999b8);
-	background-image: -o-linear-gradient(top, #B1BDD5, #8999b8);
-	background-image: linear-gradient(top, #B1BDD5, #8999b8);
-	
-    }
+	.large {
+	    width: 400px;
+	    margin-left: -220px;
 
-    .confirmation span,
-    .dialog span {
-	font-size: 13px;
-	font-weight: bold;
-    }
+	    textarea {
+		width: 385px;
+		height: 200px;
+	    }
+	}
 
-    .confirmation .buttons,
-    .dialog .buttons {
-	text-align: right;
-	margin-top: 20px;
-    }
+	textarea {
+	    display: block;
+	    width: 235px;
+	}
 
-    .confirmation.active,
-    .dialog.active {
-	top: 0;
+	.progress {
+	    height: 5px;
+
+	    .bar {
+		background-color: #e9eaf5;
+		background-image: -webkit-linear-gradient(top, #B1BDD5, #8999b8);
+		background-image: -moz-linear-gradient(top, #B1BDD5, #8999b8);
+		background-image: -o-linear-gradient(top, #B1BDD5, #8999b8);
+		background-image: linear-gradient(top, #B1BDD5, #8999b8);
+	    }
+	}
+
+	span {
+	    font-size: 13px;
+	    font-weight: bold;
+	}
+
+	.buttons {
+	    text-align: right;
+	    margin-top: 20px;
+	}
+
+	&.active {
+	    top: 0;
+	}
     }
 
     .button {

BIN
images/overridden.png


BIN
images/override.png


+ 85 - 0
js/Helios-Commands-Core.deploy.js

@@ -616,6 +616,91 @@ messageSends: []}),
 smalltalk.HLOpenWorkspaceCommand.klass);
 
 
+smalltalk.addClass('HLSwitchTabCommand', smalltalk.HLCommand, [], 'Helios-Commands-Core');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "execute",
+fn: function (){
+var self=this;
+var activeTab;
+function $HLTabSelectionWidget(){return smalltalk.HLTabSelectionWidget||(typeof HLTabSelectionWidget=="undefined"?nil:HLTabSelectionWidget)}
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+activeTab=self._selectedTab();
+$2=_st($HLTabSelectionWidget())._new();
+_st($2)._tabs_(self._tabs());
+_st($2)._selectedTab_(self._selectedTab());
+_st($2)._selectCallback_((function(tab){
+return smalltalk.withContext(function($ctx2) {
+return _st(tab)._activate();
+}, function($ctx2) {$ctx2.fillBlock({tab:tab},$ctx1)})}));
+_st($2)._confirmCallback_((function(tab){
+return smalltalk.withContext(function($ctx2) {
+return _st(tab)._focus();
+}, function($ctx2) {$ctx2.fillBlock({tab:tab},$ctx1)})}));
+_st($2)._cancelCallback_((function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(activeTab)._activate();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+$3=_st($2)._show();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"execute",{activeTab:activeTab},smalltalk.HLSwitchTabCommand)})},
+messageSends: ["selectedTab", "tabs:", "tabs", "new", "selectedTab:", "selectCallback:", "activate", "confirmCallback:", "focus", "cancelCallback:", "show"]}),
+smalltalk.HLSwitchTabCommand);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectedTab",
+fn: function (){
+var self=this;
+function $HLManager(){return smalltalk.HLManager||(typeof HLManager=="undefined"?nil:HLManager)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st($HLManager())._current())._activeTab();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"selectedTab",{},smalltalk.HLSwitchTabCommand)})},
+messageSends: ["activeTab", "current"]}),
+smalltalk.HLSwitchTabCommand);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "tabs",
+fn: function (){
+var self=this;
+function $HLManager(){return smalltalk.HLManager||(typeof HLManager=="undefined"?nil:HLManager)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st($HLManager())._current())._tabs();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"tabs",{},smalltalk.HLSwitchTabCommand)})},
+messageSends: ["tabs", "current"]}),
+smalltalk.HLSwitchTabCommand);
+
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "key",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return "s";
+}, function($ctx1) {$ctx1.fill(self,"key",{},smalltalk.HLSwitchTabCommand.klass)})},
+messageSends: []}),
+smalltalk.HLSwitchTabCommand.klass);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "label",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return "Switch";
+}, function($ctx1) {$ctx1.fill(self,"label",{},smalltalk.HLSwitchTabCommand.klass)})},
+messageSends: []}),
+smalltalk.HLSwitchTabCommand.klass);
+
+
 smalltalk.addClass('HLViewCommand', smalltalk.HLCommand, [], 'Helios-Commands-Core');
 
 smalltalk.addMethod(

+ 110 - 0
js/Helios-Commands-Core.js

@@ -841,6 +841,116 @@ referencedClasses: []
 smalltalk.HLOpenWorkspaceCommand.klass);
 
 
+smalltalk.addClass('HLSwitchTabCommand', smalltalk.HLCommand, [], 'Helios-Commands-Core');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "execute",
+category: 'executing',
+fn: function (){
+var self=this;
+var activeTab;
+function $HLTabSelectionWidget(){return smalltalk.HLTabSelectionWidget||(typeof HLTabSelectionWidget=="undefined"?nil:HLTabSelectionWidget)}
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+activeTab=self._selectedTab();
+$2=_st($HLTabSelectionWidget())._new();
+_st($2)._tabs_(self._tabs());
+_st($2)._selectedTab_(self._selectedTab());
+_st($2)._selectCallback_((function(tab){
+return smalltalk.withContext(function($ctx2) {
+return _st(tab)._activate();
+}, function($ctx2) {$ctx2.fillBlock({tab:tab},$ctx1)})}));
+_st($2)._confirmCallback_((function(tab){
+return smalltalk.withContext(function($ctx2) {
+return _st(tab)._focus();
+}, function($ctx2) {$ctx2.fillBlock({tab:tab},$ctx1)})}));
+_st($2)._cancelCallback_((function(){
+return smalltalk.withContext(function($ctx2) {
+return _st(activeTab)._activate();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+$3=_st($2)._show();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"execute",{activeTab:activeTab},smalltalk.HLSwitchTabCommand)})},
+args: [],
+source: "execute\x0a\x09| activeTab |\x0a\x09\x0a\x09activeTab := self selectedTab.\x0a\x09\x0a\x09^ HLTabSelectionWidget new\x0a\x09\x09tabs: self tabs;\x0a\x09\x09selectedTab: self selectedTab;\x0a\x09\x09selectCallback: [ :tab | tab activate ];\x0a\x09\x09confirmCallback: [ :tab | tab focus ];\x0a\x09\x09cancelCallback: [ activeTab activate ];\x0a\x09\x09show",
+messageSends: ["selectedTab", "tabs:", "tabs", "new", "selectedTab:", "selectCallback:", "activate", "confirmCallback:", "focus", "cancelCallback:", "show"],
+referencedClasses: ["HLTabSelectionWidget"]
+}),
+smalltalk.HLSwitchTabCommand);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectedTab",
+category: 'accessing',
+fn: function (){
+var self=this;
+function $HLManager(){return smalltalk.HLManager||(typeof HLManager=="undefined"?nil:HLManager)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st($HLManager())._current())._activeTab();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"selectedTab",{},smalltalk.HLSwitchTabCommand)})},
+args: [],
+source: "selectedTab\x0a\x09^ HLManager current activeTab",
+messageSends: ["activeTab", "current"],
+referencedClasses: ["HLManager"]
+}),
+smalltalk.HLSwitchTabCommand);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "tabs",
+category: 'accessing',
+fn: function (){
+var self=this;
+function $HLManager(){return smalltalk.HLManager||(typeof HLManager=="undefined"?nil:HLManager)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st($HLManager())._current())._tabs();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"tabs",{},smalltalk.HLSwitchTabCommand)})},
+args: [],
+source: "tabs\x0a\x09^ HLManager current tabs",
+messageSends: ["tabs", "current"],
+referencedClasses: ["HLManager"]
+}),
+smalltalk.HLSwitchTabCommand);
+
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "key",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return "s";
+}, function($ctx1) {$ctx1.fill(self,"key",{},smalltalk.HLSwitchTabCommand.klass)})},
+args: [],
+source: "key\x0a\x09^ 's'",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLSwitchTabCommand.klass);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "label",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return "Switch";
+}, function($ctx1) {$ctx1.fill(self,"label",{},smalltalk.HLSwitchTabCommand.klass)})},
+args: [],
+source: "label\x0a\x09^ 'Switch'",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLSwitchTabCommand.klass);
+
+
 smalltalk.addClass('HLViewCommand', smalltalk.HLCommand, [], 'Helios-Commands-Core');
 
 smalltalk.addMethod(

+ 471 - 107
js/Helios-Core.deploy.js

@@ -1147,7 +1147,7 @@ smalltalk.HLTabWidget.klass);
 smalltalk.addClass('HLWidget', smalltalk.Widget, ['wrapper'], 'Helios-Core');
 smalltalk.addMethod(
 smalltalk.method({
-selector: "bindKeyDown:up:",
+selector: "bindKeyDown:keyUp:",
 fn: function (keyDownBlock,keyUpBlock){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
@@ -1155,7 +1155,7 @@ 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)})},
+return self}, function($ctx1) {$ctx1.fill(self,"bindKeyDown:keyUp:",{keyDownBlock:keyDownBlock,keyUpBlock:keyUpBlock},smalltalk.HLWidget)})},
 messageSends: ["keydown:", "asJQuery", "wrapper", "keyup:"]}),
 smalltalk.HLWidget);
 
@@ -1329,7 +1329,7 @@ smalltalk.HLWidget);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "unbindKeyDownUp",
+selector: "unbindKeyDownKeyUp",
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
@@ -1337,7 +1337,7 @@ 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)})},
+return self}, function($ctx1) {$ctx1.fill(self,"unbindKeyDownKeyUp",{},smalltalk.HLWidget)})},
 messageSends: ["unbind:", "asJQuery", "wrapper"]}),
 smalltalk.HLWidget);
 
@@ -1915,21 +1915,21 @@ smalltalk.method({
 selector: "setupKeyBindings",
 fn: function (){
 var self=this;
-function $HLRepeatingKeyBindingHandler(){return smalltalk.HLRepeatingKeyBindingHandler||(typeof HLRepeatingKeyBindingHandler=="undefined"?nil:HLRepeatingKeyBindingHandler)}
+function $HLRepeatedKeyDownHandler(){return smalltalk.HLRepeatedKeyDownHandler||(typeof HLRepeatedKeyDownHandler=="undefined"?nil:HLRepeatedKeyDownHandler)}
 return smalltalk.withContext(function($ctx1) { 
 var $1,$2;
-$1=_st($HLRepeatingKeyBindingHandler())._forWidget_(self);
-_st($1)._whileKeyPressed_do_((38),(function(){
+$1=_st($HLRepeatedKeyDownHandler())._on_(self);
+_st($1)._whileKeyDown_do_((38),(function(){
 return smalltalk.withContext(function($ctx2) {
 return self._activatePreviousListItem();
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
-_st($1)._whileKeyPressed_do_((40),(function(){
+_st($1)._whileKeyDown_do_((40),(function(){
 return smalltalk.withContext(function($ctx2) {
 return self._activateNextListItem();
 }, 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"]}),
+messageSends: ["whileKeyDown:do:", "activatePreviousListItem", "on:", "activateNextListItem", "rebindKeys"]}),
 smalltalk.HLListWidget);
 
 
@@ -2334,7 +2334,67 @@ messageSends: ["model:", "new", "yourself"]}),
 smalltalk.HLToolListWidget.klass);
 
 
-smalltalk.addClass('HLManager', smalltalk.HLWidget, ['tabs', 'activeTab', 'keyBinder', 'environment', 'history'], 'Helios-Core');
+smalltalk.addClass('HLTabListWidget', smalltalk.HLListWidget, ['callback'], 'Helios-Core');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "callback",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@callback"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$1=(function(){
+return smalltalk.withContext(function($ctx2) {
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})});
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"callback",{},smalltalk.HLTabListWidget)})},
+messageSends: ["ifNil:"]}),
+smalltalk.HLTabListWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "callback:",
+fn: function (aBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@callback"]=aBlock;
+return self}, function($ctx1) {$ctx1.fill(self,"callback:",{aBlock:aBlock},smalltalk.HLTabListWidget)})},
+messageSends: []}),
+smalltalk.HLTabListWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderItemLabel:on:",
+fn: function (aTab,html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(html)._span();
+_st($1)._class_(_st(aTab)._cssClass());
+$2=_st($1)._with_(_st(aTab)._label());
+return self}, function($ctx1) {$ctx1.fill(self,"renderItemLabel:on:",{aTab:aTab,html:html},smalltalk.HLTabListWidget)})},
+messageSends: ["class:", "cssClass", "span", "with:", "label"]}),
+smalltalk.HLTabListWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectItem:",
+fn: function (aTab){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLTabListWidget.superclass.fn.prototype._selectItem_.apply(_st(self), [aTab]);
+_st(self._callback())._value_(aTab);
+return self}, function($ctx1) {$ctx1.fill(self,"selectItem:",{aTab:aTab},smalltalk.HLTabListWidget)})},
+messageSends: ["selectItem:", "value:", "callback"]}),
+smalltalk.HLTabListWidget);
+
+
+
+smalltalk.addClass('HLManager', smalltalk.HLWidget, ['tabs', 'activeTab', 'environment', 'history'], 'Helios-Core');
 smalltalk.addMethod(
 smalltalk.method({
 selector: "activate:",
@@ -2401,10 +2461,9 @@ var $1,$2;
 $1=_st($HLConfirmationWidget())._new();
 _st($1)._confirmationString_(aString);
 _st($1)._cancelBlock_(aBlock);
-$2=_st($1)._yourself();
-_st($2)._appendToJQuery_("body"._asJQuery());
+$2=_st($1)._show();
 return self}, function($ctx1) {$ctx1.fill(self,"confirm:ifFalse:",{aString:aString,aBlock:aBlock},smalltalk.HLManager)})},
-messageSends: ["appendToJQuery:", "asJQuery", "confirmationString:", "new", "cancelBlock:", "yourself"]}),
+messageSends: ["confirmationString:", "new", "cancelBlock:", "show"]}),
 smalltalk.HLManager);
 
 smalltalk.addMethod(
@@ -2418,10 +2477,9 @@ var $1,$2;
 $1=_st($HLConfirmationWidget())._new();
 _st($1)._confirmationString_(aString);
 _st($1)._actionBlock_(aBlock);
-$2=_st($1)._yourself();
-_st($2)._appendToJQuery_("body"._asJQuery());
+$2=_st($1)._show();
 return self}, function($ctx1) {$ctx1.fill(self,"confirm:ifTrue:",{aString:aString,aBlock:aBlock},smalltalk.HLManager)})},
-messageSends: ["appendToJQuery:", "asJQuery", "confirmationString:", "new", "actionBlock:", "yourself"]}),
+messageSends: ["confirmationString:", "new", "actionBlock:", "show"]}),
 smalltalk.HLManager);
 
 smalltalk.addMethod(
@@ -2542,17 +2600,11 @@ fn: function (){
 var self=this;
 function $HLKeyBinder(){return smalltalk.HLKeyBinder||(typeof HLKeyBinder=="undefined"?nil:HLKeyBinder)}
 return smalltalk.withContext(function($ctx1) { 
-var $2,$1;
-$2=self["@keyBinder"];
-if(($receiver = $2) == nil || $receiver == undefined){
-self["@keyBinder"]=_st($HLKeyBinder())._new();
-$1=self["@keyBinder"];
-} else {
-$1=$2;
-};
+var $1;
+$1=_st($HLKeyBinder())._current();
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"keyBinder",{},smalltalk.HLManager)})},
-messageSends: ["ifNil:", "new"]}),
+messageSends: ["current"]}),
 smalltalk.HLManager);
 
 smalltalk.addMethod(
@@ -2797,10 +2849,9 @@ $1=_st($HLRequestWidget())._new();
 _st($1)._confirmationString_(aString);
 _st($1)._actionBlock_(aBlock);
 _st($1)._value_(valueString);
-$2=_st($1)._yourself();
-_st($2)._appendToJQuery_("body"._asJQuery());
+$2=_st($1)._show();
 return self}, function($ctx1) {$ctx1.fill(self,"request:value:do:",{aString:aString,valueString:valueString,aBlock:aBlock},smalltalk.HLManager)})},
-messageSends: ["appendToJQuery:", "asJQuery", "confirmationString:", "new", "actionBlock:", "value:", "yourself"]}),
+messageSends: ["confirmationString:", "new", "actionBlock:", "value:", "show"]}),
 smalltalk.HLManager);
 
 smalltalk.addMethod(
@@ -2919,6 +2970,17 @@ return self}, function($ctx1) {$ctx1.fill(self,"cancel",{},smalltalk.HLModalWidg
 messageSends: ["remove"]}),
 smalltalk.HLModalWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "confirm",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self._remove();
+return self}, function($ctx1) {$ctx1.fill(self,"confirm",{},smalltalk.HLModalWidget)})},
+messageSends: ["remove"]}),
+smalltalk.HLModalWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "cssClass",
@@ -2930,6 +2992,17 @@ return "";
 messageSends: []}),
 smalltalk.HLModalWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "hasButtons",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return true;
+}, function($ctx1) {$ctx1.fill(self,"hasButtons",{},smalltalk.HLModalWidget)})},
+messageSends: []}),
+smalltalk.HLModalWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "remove",
@@ -2951,9 +3024,34 @@ smalltalk.method({
 selector: "renderButtonsOn:",
 fn: function (html){
 var self=this;
+var confirmButton;
 return smalltalk.withContext(function($ctx1) { 
-return self}, function($ctx1) {$ctx1.fill(self,"renderButtonsOn:",{html:html},smalltalk.HLModalWidget)})},
-messageSends: []}),
+var $1,$3,$4,$5,$6,$2;
+$1=_st(html)._div();
+_st($1)._class_("buttons");
+$2=_st($1)._with_((function(){
+return smalltalk.withContext(function($ctx2) {
+$3=_st(html)._button();
+_st($3)._class_("button");
+_st($3)._with_("Cancel");
+$4=_st($3)._onClick_((function(){
+return smalltalk.withContext(function($ctx3) {
+return self._cancel();
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
+$4;
+$5=_st(html)._button();
+_st($5)._class_("button default");
+_st($5)._with_("Confirm");
+$6=_st($5)._onClick_((function(){
+return smalltalk.withContext(function($ctx3) {
+return self._confirm();
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
+confirmButton=$6;
+return confirmButton;
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+_st(_st(confirmButton)._asJQuery())._focus();
+return self}, function($ctx1) {$ctx1.fill(self,"renderButtonsOn:",{html:html,confirmButton:confirmButton},smalltalk.HLModalWidget)})},
+messageSends: ["class:", "div", "with:", "button", "onClick:", "cancel", "confirm", "focus", "asJQuery"]}),
 smalltalk.HLModalWidget);
 
 smalltalk.addMethod(
@@ -2963,21 +3061,22 @@ fn: function (html){
 var self=this;
 var confirmButton;
 return smalltalk.withContext(function($ctx1) { 
-var $1,$3,$4,$2;
+var $1,$3,$2;
 _st(_st(html)._div())._id_("overlay");
 $1=_st(html)._div();
 _st($1)._class_("dialog ".__comma(self._cssClass()));
 $2=_st($1)._with_((function(){
 return smalltalk.withContext(function($ctx2) {
-$3=self;
-_st($3)._renderMainOn_(html);
-$4=_st($3)._renderButtonsOn_(html);
-return $4;
+self._renderMainOn_(html);
+$3=self._hasButtons();
+if(smalltalk.assert($3)){
+return self._renderButtonsOn_(html);
+};
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
 _st(".dialog"._asJQuery())._addClass_("active");
 self._setupKeyBindings();
 return self}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html,confirmButton:confirmButton},smalltalk.HLModalWidget)})},
-messageSends: ["id:", "div", "class:", ",", "cssClass", "with:", "renderMainOn:", "renderButtonsOn:", "addClass:", "asJQuery", "setupKeyBindings"]}),
+messageSends: ["id:", "div", "class:", ",", "cssClass", "with:", "renderMainOn:", "ifTrue:", "renderButtonsOn:", "hasButtons", "addClass:", "asJQuery", "setupKeyBindings"]}),
 smalltalk.HLModalWidget);
 
 smalltalk.addMethod(
@@ -2995,17 +3094,29 @@ smalltalk.method({
 selector: "setupKeyBindings",
 fn: function (){
 var self=this;
+function $String(){return smalltalk.String||(typeof String=="undefined"?nil:String)}
 return smalltalk.withContext(function($ctx1) { 
 var $1;
 _st(".dialog"._asJQuery())._keyup_((function(e){
 return smalltalk.withContext(function($ctx2) {
-$1=_st(_st(e)._keyCode()).__eq((27));
+$1=_st(_st(e)._keyCode()).__eq(_st(_st($String())._esc())._asciiValue());
 if(smalltalk.assert($1)){
 return self._cancel();
 };
 }, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}));
 return self}, function($ctx1) {$ctx1.fill(self,"setupKeyBindings",{},smalltalk.HLModalWidget)})},
-messageSends: ["keyup:", "ifTrue:", "cancel", "=", "keyCode", "asJQuery"]}),
+messageSends: ["keyup:", "ifTrue:", "cancel", "=", "asciiValue", "esc", "keyCode", "asJQuery"]}),
+smalltalk.HLModalWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "show",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self._appendToJQuery_("body"._asJQuery());
+return self}, function($ctx1) {$ctx1.fill(self,"show",{},smalltalk.HLModalWidget)})},
+messageSends: ["appendToJQuery:", "asJQuery"]}),
 smalltalk.HLModalWidget);
 
 
@@ -3049,9 +3160,9 @@ fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 _st(self._cancelBlock())._value();
-self._remove();
+smalltalk.HLConfirmationWidget.superclass.fn.prototype._cancel.apply(_st(self), []);
 return self}, function($ctx1) {$ctx1.fill(self,"cancel",{},smalltalk.HLConfirmationWidget)})},
-messageSends: ["value", "cancelBlock", "remove"]}),
+messageSends: ["value", "cancelBlock", "cancel"]}),
 smalltalk.HLConfirmationWidget);
 
 smalltalk.addMethod(
@@ -3091,10 +3202,10 @@ selector: "confirm",
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLConfirmationWidget.superclass.fn.prototype._confirm.apply(_st(self), []);
 _st(self._actionBlock())._value();
-self._remove();
 return self}, function($ctx1) {$ctx1.fill(self,"confirm",{},smalltalk.HLConfirmationWidget)})},
-messageSends: ["value", "actionBlock", "remove"]}),
+messageSends: ["confirm", "value", "actionBlock"]}),
 smalltalk.HLConfirmationWidget);
 
 smalltalk.addMethod(
@@ -3126,57 +3237,6 @@ return self}, function($ctx1) {$ctx1.fill(self,"confirmationString:",{aString:aS
 messageSends: []}),
 smalltalk.HLConfirmationWidget);
 
-smalltalk.addMethod(
-smalltalk.method({
-selector: "remove",
-fn: function (){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-_st(".dialog"._asJQuery())._removeClass_("active");
-_st((function(){
-return smalltalk.withContext(function($ctx2) {
-_st("#overlay"._asJQuery())._remove();
-return _st(".dialog"._asJQuery())._remove();
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._valueWithTimeout_((300));
-return self}, function($ctx1) {$ctx1.fill(self,"remove",{},smalltalk.HLConfirmationWidget)})},
-messageSends: ["removeClass:", "asJQuery", "valueWithTimeout:", "remove"]}),
-smalltalk.HLConfirmationWidget);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "renderButtonsOn:",
-fn: function (html){
-var self=this;
-var confirmButton;
-return smalltalk.withContext(function($ctx1) { 
-var $1,$3,$4,$5,$6,$2;
-$1=_st(html)._div();
-_st($1)._class_("buttons");
-$2=_st($1)._with_((function(){
-return smalltalk.withContext(function($ctx2) {
-$3=_st(html)._button();
-_st($3)._class_("button");
-_st($3)._with_("Cancel");
-$4=_st($3)._onClick_((function(){
-return smalltalk.withContext(function($ctx3) {
-return self._cancel();
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
-$4;
-$5=_st(html)._button();
-_st($5)._class_("button default");
-_st($5)._with_("Confirm");
-$6=_st($5)._onClick_((function(){
-return smalltalk.withContext(function($ctx3) {
-return self._confirm();
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
-confirmButton=$6;
-return confirmButton;
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
-_st(_st(confirmButton)._asJQuery())._focus();
-return self}, function($ctx1) {$ctx1.fill(self,"renderButtonsOn:",{html:html,confirmButton:confirmButton},smalltalk.HLConfirmationWidget)})},
-messageSends: ["class:", "div", "with:", "button", "onClick:", "cancel", "confirm", "focus", "asJQuery"]}),
-smalltalk.HLConfirmationWidget);
-
 smalltalk.addMethod(
 smalltalk.method({
 selector: "renderMainOn:",
@@ -3197,10 +3257,10 @@ selector: "confirm",
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLRequestWidget.superclass.fn.prototype._confirm.apply(_st(self), []);
 _st(self._actionBlock())._value_(_st(_st(self["@input"])._asJQuery())._val());
-self._remove();
 return self}, function($ctx1) {$ctx1.fill(self,"confirm",{},smalltalk.HLRequestWidget)})},
-messageSends: ["value:", "val", "asJQuery", "actionBlock", "remove"]}),
+messageSends: ["confirm", "value:", "val", "asJQuery", "actionBlock"]}),
 smalltalk.HLRequestWidget);
 
 smalltalk.addMethod(
@@ -3308,6 +3368,17 @@ return self}, function($ctx1) {$ctx1.fill(self,"flush",{},smalltalk.HLProgressWi
 messageSends: ["do:", "removeProgressBar:", "progressBars"]}),
 smalltalk.HLProgressWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "hasButtons",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return false;
+}, function($ctx1) {$ctx1.fill(self,"hasButtons",{},smalltalk.HLProgressWidget)})},
+messageSends: []}),
+smalltalk.HLProgressWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "isVisible",
@@ -3381,16 +3452,6 @@ return self}, function($ctx1) {$ctx1.fill(self,"removeProgressBar:",{aProgressBa
 messageSends: ["remove:ifAbsent:", "progressBars", "remove", "asJQuery", "wrapper", "ifEmpty:"]}),
 smalltalk.HLProgressWidget);
 
-smalltalk.addMethod(
-smalltalk.method({
-selector: "renderButtonsOn:",
-fn: function (html){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-return self}, function($ctx1) {$ctx1.fill(self,"renderButtonsOn:",{html:html},smalltalk.HLProgressWidget)})},
-messageSends: []}),
-smalltalk.HLProgressWidget);
-
 smalltalk.addMethod(
 smalltalk.method({
 selector: "renderMainOn:",
@@ -3416,10 +3477,10 @@ $1=self._isVisible();
 if(! smalltalk.assert($1)){
 self["@visible"]=true;
 self["@visible"];
-self._appendToJQuery_("body"._asJQuery());
+smalltalk.HLProgressWidget.superclass.fn.prototype._show.apply(_st(self), []);
 };
 return self}, function($ctx1) {$ctx1.fill(self,"show",{},smalltalk.HLProgressWidget)})},
-messageSends: ["ifFalse:", "appendToJQuery:", "asJQuery", "isVisible"]}),
+messageSends: ["ifFalse:", "show", "isVisible"]}),
 smalltalk.HLProgressWidget);
 
 
@@ -3444,6 +3505,309 @@ messageSends: ["ifNil:", "new"]}),
 smalltalk.HLProgressWidget.klass);
 
 
+smalltalk.addClass('HLTabSelectionWidget', smalltalk.HLModalWidget, ['tabs', 'tabList', 'selectedTab', 'selectCallback', 'cancelCallback', 'confirmCallback'], 'Helios-Core');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cancel",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLTabSelectionWidget.superclass.fn.prototype._cancel.apply(_st(self), []);
+_st(self._cancelCallback())._value();
+return self}, function($ctx1) {$ctx1.fill(self,"cancel",{},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["cancel", "value", "cancelCallback"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cancelCallback",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@cancelCallback"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$1=(function(){
+return smalltalk.withContext(function($ctx2) {
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})});
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"cancelCallback",{},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["ifNil:"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cancelCallback:",
+fn: function (aBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@cancelCallback"]=aBlock;
+return self}, function($ctx1) {$ctx1.fill(self,"cancelCallback:",{aBlock:aBlock},smalltalk.HLTabSelectionWidget)})},
+messageSends: []}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "confirm",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLTabSelectionWidget.superclass.fn.prototype._confirm.apply(_st(self), []);
+_st(self._confirmCallback())._value_(self._selectedTab());
+return self}, function($ctx1) {$ctx1.fill(self,"confirm",{},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["confirm", "value:", "selectedTab", "confirmCallback"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "confirmCallback",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@confirmCallback"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$1=(function(){
+return smalltalk.withContext(function($ctx2) {
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})});
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"confirmCallback",{},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["ifNil:"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "confirmCallback:",
+fn: function (aBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@confirmCallback"]=aBlock;
+return self}, function($ctx1) {$ctx1.fill(self,"confirmCallback:",{aBlock:aBlock},smalltalk.HLTabSelectionWidget)})},
+messageSends: []}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderContentOn:",
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLTabSelectionWidget.superclass.fn.prototype._renderContentOn_.apply(_st(self), [html]);
+_st(self._tabList())._focus();
+return self}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["renderContentOn:", "focus", "tabList"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderMainOn:",
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(html)._div();
+_st($1)._class_("title");
+$2=_st($1)._with_("Tab selection");
+_st(html)._with_(self._tabList());
+return self}, function($ctx1) {$ctx1.fill(self,"renderMainOn:",{html:html},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["class:", "div", "with:", "tabList"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderTab:on:",
+fn: function (aTab,html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(html)._span();
+_st($1)._class_(_st(aTab)._cssClass());
+$2=_st($1)._with_(_st(aTab)._label());
+return self}, function($ctx1) {$ctx1.fill(self,"renderTab:on:",{aTab:aTab,html:html},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["class:", "cssClass", "span", "with:", "label"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderTabsOn:",
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+_st(self._tabs())._do_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st(_st(html)._li())._with_((function(){
+return smalltalk.withContext(function($ctx3) {
+$1=_st(html)._a();
+_st($1)._with_((function(){
+return smalltalk.withContext(function($ctx4) {
+return self._renderTab_on_(each,html);
+}, function($ctx4) {$ctx4.fillBlock({},$ctx3)})}));
+$2=_st($1)._onClick_((function(){
+return smalltalk.withContext(function($ctx4) {
+return self._selectTab_(each);
+}, function($ctx4) {$ctx4.fillBlock({},$ctx3)})}));
+return $2;
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"renderTabsOn:",{html:html},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["do:", "with:", "renderTab:on:", "a", "onClick:", "selectTab:", "li", "tabs"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectCallback",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@selectCallback"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$1=(function(){
+return smalltalk.withContext(function($ctx2) {
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})});
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"selectCallback",{},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["ifNil:"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectCallback:",
+fn: function (aBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@selectCallback"]=aBlock;
+return self}, function($ctx1) {$ctx1.fill(self,"selectCallback:",{aBlock:aBlock},smalltalk.HLTabSelectionWidget)})},
+messageSends: []}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectTab:",
+fn: function (aTab){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self._selectedTab_(aTab);
+_st(self._selectCallback())._value_(aTab);
+return self}, function($ctx1) {$ctx1.fill(self,"selectTab:",{aTab:aTab},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["selectedTab:", "value:", "selectCallback"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectedTab",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@selectedTab"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"selectedTab",{},smalltalk.HLTabSelectionWidget)})},
+messageSends: []}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectedTab:",
+fn: function (aTab){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@selectedTab"]=aTab;
+return self}, function($ctx1) {$ctx1.fill(self,"selectedTab:",{aTab:aTab},smalltalk.HLTabSelectionWidget)})},
+messageSends: []}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "setupKeyBindings",
+fn: function (){
+var self=this;
+function $String(){return smalltalk.String||(typeof String=="undefined"?nil:String)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+smalltalk.HLTabSelectionWidget.superclass.fn.prototype._setupKeyBindings.apply(_st(self), []);
+_st(".dialog"._asJQuery())._keyup_((function(e){
+return smalltalk.withContext(function($ctx2) {
+$1=_st(_st(e)._keyCode()).__eq(_st(_st($String())._cr())._asciiValue());
+if(smalltalk.assert($1)){
+return self._confirm();
+};
+}, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"setupKeyBindings",{},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["setupKeyBindings", "keyup:", "ifTrue:", "confirm", "=", "asciiValue", "cr", "keyCode", "asJQuery"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "tabList",
+fn: function (){
+var self=this;
+function $HLTabListWidget(){return smalltalk.HLTabListWidget||(typeof HLTabListWidget=="undefined"?nil:HLTabListWidget)}
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3,$4;
+$1=self["@tabList"];
+if(($receiver = $1) == nil || $receiver == undefined){
+self["@tabList"]=_st($HLTabListWidget())._new();
+self["@tabList"];
+$2=self["@tabList"];
+_st($2)._callback_((function(tab){
+return smalltalk.withContext(function($ctx2) {
+self._selectTab_(tab);
+return _st(self["@tabList"])._focus();
+}, function($ctx2) {$ctx2.fillBlock({tab:tab},$ctx1)})}));
+_st($2)._selectedItem_(self._selectedTab());
+$3=_st($2)._items_(self._tabs());
+$3;
+} else {
+$1;
+};
+$4=self["@tabList"];
+return $4;
+}, function($ctx1) {$ctx1.fill(self,"tabList",{},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["ifNil:", "new", "callback:", "selectTab:", "focus", "selectedItem:", "selectedTab", "items:", "tabs"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "tabs",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@tabs"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$1=[];
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"tabs",{},smalltalk.HLTabSelectionWidget)})},
+messageSends: ["ifNil:"]}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "tabs:",
+fn: function (aCollection){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@tabs"]=aCollection;
+return self}, function($ctx1) {$ctx1.fill(self,"tabs:",{aCollection:aCollection},smalltalk.HLTabSelectionWidget)})},
+messageSends: []}),
+smalltalk.HLTabSelectionWidget);
+
+
+
 smalltalk.addClass('HLProgressBarWidget', smalltalk.HLWidget, ['label', 'parent', 'workBlock', 'collection', 'bar'], 'Helios-Core');
 smalltalk.addMethod(
 smalltalk.method({

+ 623 - 137
js/Helios-Core.js

@@ -1502,7 +1502,7 @@ smalltalk.addClass('HLWidget', smalltalk.Widget, ['wrapper'], 'Helios-Core');
 smalltalk.HLWidget.comment="I am the abstract superclass of all Helios widgets.\x0a\x0aI provide common methods, additional behavior to widgets useful for Helios, like dialog creation, command execution and tab creation.\x0a\x0a## API\x0a\x0a1. Rendering\x0a\x0a    Instead of overriding `#renderOn:` as with other Widget subclasses, my subclasses should override `#renderContentOn:`.\x0a\x0a2. Refreshing\x0a\x0a    To re-render a widget, use `#refresh`.\x0a\x0a3. Key bindings registration and tabs\x0a\x0a    When displayed as a tab, the widget has a chance to register keybindings with the `#registerBindingsOn:` hook method.\x0a    \x0a4. Unregistration\x0a\x0a    When a widget has subscribed to announcements or other actions that need to be cleared when closing the tab, the hook method `#unregister` will be called by helios.\x0a\x0a5. Tabs\x0a\x0a   To enable a widget class to be open as a tab, override the class-side `#canBeOpenAsTab` method to answer `true`. `#tabClass` and `#tabPriority` can be overridden too to respectively change the css class of the tab and the order of tabs in the main menu.\x0a\x0a6. Command execution\x0a\x0a    An helios command (instance of `HLCommand` or one of its subclass) can be executed with `#execute:`.";
 smalltalk.addMethod(
 smalltalk.method({
-selector: "bindKeyDown:up:",
+selector: "bindKeyDown:keyUp:",
 category: 'keybindings',
 fn: function (keyDownBlock,keyUpBlock){
 var self=this;
@@ -1511,9 +1511,9 @@ 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)})},
+return self}, function($ctx1) {$ctx1.fill(self,"bindKeyDown:keyUp:",{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",
+source: "bindKeyDown: keyDownBlock keyUp: keyUpBlock\x0a\x09self wrapper asJQuery\x0a\x09\x09keydown: keyDownBlock;\x0a\x09\x09keyup: keyUpBlock",
 messageSends: ["keydown:", "asJQuery", "wrapper", "keyup:"],
 referencedClasses: []
 }),
@@ -1754,7 +1754,7 @@ smalltalk.HLWidget);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "unbindKeyDownUp",
+selector: "unbindKeyDownKeyUp",
 category: 'keybindings',
 fn: function (){
 var self=this;
@@ -1763,9 +1763,9 @@ 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)})},
+return self}, function($ctx1) {$ctx1.fill(self,"unbindKeyDownKeyUp",{},smalltalk.HLWidget)})},
 args: [],
-source: "unbindKeyDownUp\x0a\x09self wrapper asJQuery\x0a\x09\x09unbind: 'keydown';\x0a\x09\x09unbind: 'keyup'",
+source: "unbindKeyDownKeyUp\x0a\x09self wrapper asJQuery\x0a\x09\x09unbind: 'keydown';\x0a\x09\x09unbind: 'keyup'",
 messageSends: ["unbind:", "asJQuery", "wrapper"],
 referencedClasses: []
 }),
@@ -2537,24 +2537,24 @@ selector: "setupKeyBindings",
 category: 'events',
 fn: function (){
 var self=this;
-function $HLRepeatingKeyBindingHandler(){return smalltalk.HLRepeatingKeyBindingHandler||(typeof HLRepeatingKeyBindingHandler=="undefined"?nil:HLRepeatingKeyBindingHandler)}
+function $HLRepeatedKeyDownHandler(){return smalltalk.HLRepeatedKeyDownHandler||(typeof HLRepeatedKeyDownHandler=="undefined"?nil:HLRepeatedKeyDownHandler)}
 return smalltalk.withContext(function($ctx1) { 
 var $1,$2;
-$1=_st($HLRepeatingKeyBindingHandler())._forWidget_(self);
-_st($1)._whileKeyPressed_do_((38),(function(){
+$1=_st($HLRepeatedKeyDownHandler())._on_(self);
+_st($1)._whileKeyDown_do_((38),(function(){
 return smalltalk.withContext(function($ctx2) {
 return self._activatePreviousListItem();
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
-_st($1)._whileKeyPressed_do_((40),(function(){
+_st($1)._whileKeyDown_do_((40),(function(){
 return smalltalk.withContext(function($ctx2) {
 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(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"]
+source: "setupKeyBindings \x0a\x09(HLRepeatedKeyDownHandler on: self)\x0a\x09\x09whileKeyDown: 38 do: [ self activatePreviousListItem ];\x0a\x09\x09whileKeyDown: 40 do: [ self activateNextListItem ];\x0a\x09\x09rebindKeys",
+messageSends: ["whileKeyDown:do:", "activatePreviousListItem", "on:", "activateNextListItem", "rebindKeys"],
+referencedClasses: ["HLRepeatedKeyDownHandler"]
 }),
 smalltalk.HLListWidget);
 
@@ -3080,7 +3080,88 @@ referencedClasses: []
 smalltalk.HLToolListWidget.klass);
 
 
-smalltalk.addClass('HLManager', smalltalk.HLWidget, ['tabs', 'activeTab', 'keyBinder', 'environment', 'history'], 'Helios-Core');
+smalltalk.addClass('HLTabListWidget', smalltalk.HLListWidget, ['callback'], 'Helios-Core');
+smalltalk.HLTabListWidget.comment="I am a widget used to display a list of helios tabs.\x0a\x0aWhen a tab is selected, `callback` is evaluated with the selected tab as argument.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "callback",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@callback"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$1=(function(){
+return smalltalk.withContext(function($ctx2) {
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})});
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"callback",{},smalltalk.HLTabListWidget)})},
+args: [],
+source: "callback\x0a\x09^ callback ifNil: [ [] ]",
+messageSends: ["ifNil:"],
+referencedClasses: []
+}),
+smalltalk.HLTabListWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "callback:",
+category: 'accessing',
+fn: function (aBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@callback"]=aBlock;
+return self}, function($ctx1) {$ctx1.fill(self,"callback:",{aBlock:aBlock},smalltalk.HLTabListWidget)})},
+args: ["aBlock"],
+source: "callback: aBlock\x0a\x09callback := aBlock",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLTabListWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderItemLabel:on:",
+category: 'rendering',
+fn: function (aTab,html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(html)._span();
+_st($1)._class_(_st(aTab)._cssClass());
+$2=_st($1)._with_(_st(aTab)._label());
+return self}, function($ctx1) {$ctx1.fill(self,"renderItemLabel:on:",{aTab:aTab,html:html},smalltalk.HLTabListWidget)})},
+args: ["aTab", "html"],
+source: "renderItemLabel: aTab on: html\x0a\x09html span\x0a\x09\x09class: aTab cssClass;\x0a\x09\x09with: aTab label",
+messageSends: ["class:", "cssClass", "span", "with:", "label"],
+referencedClasses: []
+}),
+smalltalk.HLTabListWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectItem:",
+category: 'actions',
+fn: function (aTab){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLTabListWidget.superclass.fn.prototype._selectItem_.apply(_st(self), [aTab]);
+_st(self._callback())._value_(aTab);
+return self}, function($ctx1) {$ctx1.fill(self,"selectItem:",{aTab:aTab},smalltalk.HLTabListWidget)})},
+args: ["aTab"],
+source: "selectItem: aTab\x0a\x09super selectItem: aTab.\x0a\x09self callback value: aTab",
+messageSends: ["selectItem:", "value:", "callback"],
+referencedClasses: []
+}),
+smalltalk.HLTabListWidget);
+
+
+
+smalltalk.addClass('HLManager', smalltalk.HLWidget, ['tabs', 'activeTab', 'environment', 'history'], 'Helios-Core');
 smalltalk.addMethod(
 smalltalk.method({
 selector: "activate:",
@@ -3168,12 +3249,11 @@ var $1,$2;
 $1=_st($HLConfirmationWidget())._new();
 _st($1)._confirmationString_(aString);
 _st($1)._cancelBlock_(aBlock);
-$2=_st($1)._yourself();
-_st($2)._appendToJQuery_("body"._asJQuery());
+$2=_st($1)._show();
 return self}, function($ctx1) {$ctx1.fill(self,"confirm:ifFalse:",{aString:aString,aBlock:aBlock},smalltalk.HLManager)})},
 args: ["aString", "aBlock"],
-source: "confirm: aString ifFalse: aBlock\x0a\x09(HLConfirmationWidget new\x0a\x09\x09confirmationString: aString;\x0a\x09\x09cancelBlock: aBlock;\x0a\x09\x09yourself)\x0a\x09\x09\x09appendToJQuery: 'body' asJQuery",
-messageSends: ["appendToJQuery:", "asJQuery", "confirmationString:", "new", "cancelBlock:", "yourself"],
+source: "confirm: aString ifFalse: aBlock\x0a\x09HLConfirmationWidget new\x0a\x09\x09confirmationString: aString;\x0a\x09\x09cancelBlock: aBlock;\x0a\x09\x09show",
+messageSends: ["confirmationString:", "new", "cancelBlock:", "show"],
 referencedClasses: ["HLConfirmationWidget"]
 }),
 smalltalk.HLManager);
@@ -3190,12 +3270,11 @@ var $1,$2;
 $1=_st($HLConfirmationWidget())._new();
 _st($1)._confirmationString_(aString);
 _st($1)._actionBlock_(aBlock);
-$2=_st($1)._yourself();
-_st($2)._appendToJQuery_("body"._asJQuery());
+$2=_st($1)._show();
 return self}, function($ctx1) {$ctx1.fill(self,"confirm:ifTrue:",{aString:aString,aBlock:aBlock},smalltalk.HLManager)})},
 args: ["aString", "aBlock"],
-source: "confirm: aString ifTrue: aBlock\x0a\x09(HLConfirmationWidget new\x0a\x09\x09confirmationString: aString;\x0a\x09\x09actionBlock: aBlock;\x0a\x09\x09yourself)\x0a\x09\x09\x09appendToJQuery: 'body' asJQuery",
-messageSends: ["appendToJQuery:", "asJQuery", "confirmationString:", "new", "actionBlock:", "yourself"],
+source: "confirm: aString ifTrue: aBlock\x0a\x09HLConfirmationWidget new\x0a\x09\x09confirmationString: aString;\x0a\x09\x09actionBlock: aBlock;\x0a\x09\x09show",
+messageSends: ["confirmationString:", "new", "actionBlock:", "show"],
 referencedClasses: ["HLConfirmationWidget"]
 }),
 smalltalk.HLManager);
@@ -3349,19 +3428,13 @@ fn: function (){
 var self=this;
 function $HLKeyBinder(){return smalltalk.HLKeyBinder||(typeof HLKeyBinder=="undefined"?nil:HLKeyBinder)}
 return smalltalk.withContext(function($ctx1) { 
-var $2,$1;
-$2=self["@keyBinder"];
-if(($receiver = $2) == nil || $receiver == undefined){
-self["@keyBinder"]=_st($HLKeyBinder())._new();
-$1=self["@keyBinder"];
-} else {
-$1=$2;
-};
+var $1;
+$1=_st($HLKeyBinder())._current();
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"keyBinder",{},smalltalk.HLManager)})},
 args: [],
-source: "keyBinder\x0a\x09^ keyBinder ifNil: [ keyBinder := HLKeyBinder new ]",
-messageSends: ["ifNil:", "new"],
+source: "keyBinder\x0a\x09^ HLKeyBinder current",
+messageSends: ["current"],
 referencedClasses: ["HLKeyBinder"]
 }),
 smalltalk.HLManager);
@@ -3664,12 +3737,11 @@ $1=_st($HLRequestWidget())._new();
 _st($1)._confirmationString_(aString);
 _st($1)._actionBlock_(aBlock);
 _st($1)._value_(valueString);
-$2=_st($1)._yourself();
-_st($2)._appendToJQuery_("body"._asJQuery());
+$2=_st($1)._show();
 return self}, function($ctx1) {$ctx1.fill(self,"request:value:do:",{aString:aString,valueString:valueString,aBlock:aBlock},smalltalk.HLManager)})},
 args: ["aString", "valueString", "aBlock"],
-source: "request: aString value: valueString do: aBlock\x0a\x09(HLRequestWidget new\x0a\x09\x09confirmationString: aString;\x0a\x09\x09actionBlock: aBlock;\x0a\x09\x09value: valueString;\x0a\x09\x09yourself)\x0a\x09\x09\x09appendToJQuery: 'body' asJQuery",
-messageSends: ["appendToJQuery:", "asJQuery", "confirmationString:", "new", "actionBlock:", "value:", "yourself"],
+source: "request: aString value: valueString do: aBlock\x0a\x09HLRequestWidget new\x0a\x09\x09confirmationString: aString;\x0a\x09\x09actionBlock: aBlock;\x0a\x09\x09value: valueString;\x0a\x09\x09show",
+messageSends: ["confirmationString:", "new", "actionBlock:", "value:", "show"],
 referencedClasses: ["HLRequestWidget"]
 }),
 smalltalk.HLManager);
@@ -3826,6 +3898,22 @@ referencedClasses: []
 }),
 smalltalk.HLModalWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "confirm",
+category: 'actions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self._remove();
+return self}, function($ctx1) {$ctx1.fill(self,"confirm",{},smalltalk.HLModalWidget)})},
+args: [],
+source: "confirm\x0a\x09\x22Override in subclasses\x22\x0a\x09self remove",
+messageSends: ["remove"],
+referencedClasses: []
+}),
+smalltalk.HLModalWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "cssClass",
@@ -3842,6 +3930,22 @@ referencedClasses: []
 }),
 smalltalk.HLModalWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "hasButtons",
+category: 'rendering',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return true;
+}, function($ctx1) {$ctx1.fill(self,"hasButtons",{},smalltalk.HLModalWidget)})},
+args: [],
+source: "hasButtons\x0a\x09^ true",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLModalWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "remove",
@@ -3869,11 +3973,36 @@ selector: "renderButtonsOn:",
 category: 'rendering',
 fn: function (html){
 var self=this;
+var confirmButton;
 return smalltalk.withContext(function($ctx1) { 
-return self}, function($ctx1) {$ctx1.fill(self,"renderButtonsOn:",{html:html},smalltalk.HLModalWidget)})},
+var $1,$3,$4,$5,$6,$2;
+$1=_st(html)._div();
+_st($1)._class_("buttons");
+$2=_st($1)._with_((function(){
+return smalltalk.withContext(function($ctx2) {
+$3=_st(html)._button();
+_st($3)._class_("button");
+_st($3)._with_("Cancel");
+$4=_st($3)._onClick_((function(){
+return smalltalk.withContext(function($ctx3) {
+return self._cancel();
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
+$4;
+$5=_st(html)._button();
+_st($5)._class_("button default");
+_st($5)._with_("Confirm");
+$6=_st($5)._onClick_((function(){
+return smalltalk.withContext(function($ctx3) {
+return self._confirm();
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
+confirmButton=$6;
+return confirmButton;
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+_st(_st(confirmButton)._asJQuery())._focus();
+return self}, function($ctx1) {$ctx1.fill(self,"renderButtonsOn:",{html:html,confirmButton:confirmButton},smalltalk.HLModalWidget)})},
 args: ["html"],
-source: "renderButtonsOn: html",
-messageSends: [],
+source: "renderButtonsOn: html\x0a\x09| confirmButton |\x0a\x09\x0a\x09html div \x0a\x09\x09class: 'buttons';\x0a\x09\x09with: [\x0a\x09\x09\x09html button\x0a\x09\x09\x09\x09class: 'button';\x0a\x09\x09\x09\x09with: 'Cancel';\x0a\x09\x09\x09\x09onClick: [ self cancel ].\x0a\x09\x09\x09confirmButton := html button\x0a\x09\x09\x09\x09class: 'button default';\x0a\x09\x09\x09\x09with: 'Confirm';\x0a\x09\x09\x09\x09onClick: [ self confirm ] ].\x0a\x0a\x09confirmButton asJQuery focus",
+messageSends: ["class:", "div", "with:", "button", "onClick:", "cancel", "confirm", "focus", "asJQuery"],
 referencedClasses: []
 }),
 smalltalk.HLModalWidget);
@@ -3886,23 +4015,24 @@ fn: function (html){
 var self=this;
 var confirmButton;
 return smalltalk.withContext(function($ctx1) { 
-var $1,$3,$4,$2;
+var $1,$3,$2;
 _st(_st(html)._div())._id_("overlay");
 $1=_st(html)._div();
 _st($1)._class_("dialog ".__comma(self._cssClass()));
 $2=_st($1)._with_((function(){
 return smalltalk.withContext(function($ctx2) {
-$3=self;
-_st($3)._renderMainOn_(html);
-$4=_st($3)._renderButtonsOn_(html);
-return $4;
+self._renderMainOn_(html);
+$3=self._hasButtons();
+if(smalltalk.assert($3)){
+return self._renderButtonsOn_(html);
+};
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
 _st(".dialog"._asJQuery())._addClass_("active");
 self._setupKeyBindings();
 return self}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html,confirmButton:confirmButton},smalltalk.HLModalWidget)})},
 args: ["html"],
-source: "renderContentOn: html\x0a\x09| confirmButton |\x0a\x09\x0a\x09html div id: 'overlay'.\x0a\x09html div \x0a\x09\x09class: 'dialog ', self cssClass;\x0a\x09\x09with: [\x0a\x09\x09\x09self\x0a\x09\x09\x09\x09renderMainOn: html;\x0a\x09\x09\x09\x09renderButtonsOn: html ].\x0a\x0a\x09'.dialog' asJQuery addClass: 'active'.\x0a\x09self setupKeyBindings",
-messageSends: ["id:", "div", "class:", ",", "cssClass", "with:", "renderMainOn:", "renderButtonsOn:", "addClass:", "asJQuery", "setupKeyBindings"],
+source: "renderContentOn: html\x0a\x09| confirmButton |\x0a\x09\x0a\x09html div id: 'overlay'.\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",
+messageSends: ["id:", "div", "class:", ",", "cssClass", "with:", "renderMainOn:", "ifTrue:", "renderButtonsOn:", "hasButtons", "addClass:", "asJQuery", "setupKeyBindings"],
 referencedClasses: []
 }),
 smalltalk.HLModalWidget);
@@ -3928,19 +4058,36 @@ selector: "setupKeyBindings",
 category: 'rendering',
 fn: function (){
 var self=this;
+function $String(){return smalltalk.String||(typeof String=="undefined"?nil:String)}
 return smalltalk.withContext(function($ctx1) { 
 var $1;
 _st(".dialog"._asJQuery())._keyup_((function(e){
 return smalltalk.withContext(function($ctx2) {
-$1=_st(_st(e)._keyCode()).__eq((27));
+$1=_st(_st(e)._keyCode()).__eq(_st(_st($String())._esc())._asciiValue());
 if(smalltalk.assert($1)){
 return self._cancel();
 };
 }, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}));
 return self}, function($ctx1) {$ctx1.fill(self,"setupKeyBindings",{},smalltalk.HLModalWidget)})},
 args: [],
-source: "setupKeyBindings\x0a\x09'.dialog' asJQuery keyup: [ :e |\x0a\x09\x09e keyCode = 27 ifTrue: [ self cancel ] ]",
-messageSends: ["keyup:", "ifTrue:", "cancel", "=", "keyCode", "asJQuery"],
+source: "setupKeyBindings\x0a\x09'.dialog' asJQuery keyup: [ :e |\x0a\x09\x09e keyCode = String esc asciiValue ifTrue: [ self cancel ] ]",
+messageSends: ["keyup:", "ifTrue:", "cancel", "=", "asciiValue", "esc", "keyCode", "asJQuery"],
+referencedClasses: ["String"]
+}),
+smalltalk.HLModalWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "show",
+category: 'actions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self._appendToJQuery_("body"._asJQuery());
+return self}, function($ctx1) {$ctx1.fill(self,"show",{},smalltalk.HLModalWidget)})},
+args: [],
+source: "show\x0a\x09self appendToJQuery: 'body' asJQuery",
+messageSends: ["appendToJQuery:", "asJQuery"],
 referencedClasses: []
 }),
 smalltalk.HLModalWidget);
@@ -3998,11 +4145,11 @@ fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 _st(self._cancelBlock())._value();
-self._remove();
+smalltalk.HLConfirmationWidget.superclass.fn.prototype._cancel.apply(_st(self), []);
 return self}, function($ctx1) {$ctx1.fill(self,"cancel",{},smalltalk.HLConfirmationWidget)})},
 args: [],
-source: "cancel\x0a\x09self cancelBlock value.\x0a\x09self remove",
-messageSends: ["value", "cancelBlock", "remove"],
+source: "cancel\x0a\x09self cancelBlock value.\x0a\x09super cancel",
+messageSends: ["value", "cancelBlock", "cancel"],
 referencedClasses: []
 }),
 smalltalk.HLConfirmationWidget);
@@ -4055,12 +4202,12 @@ category: 'actions',
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLConfirmationWidget.superclass.fn.prototype._confirm.apply(_st(self), []);
 _st(self._actionBlock())._value();
-self._remove();
 return self}, function($ctx1) {$ctx1.fill(self,"confirm",{},smalltalk.HLConfirmationWidget)})},
 args: [],
-source: "confirm\x0a\x09self actionBlock value.\x0a\x09self remove",
-messageSends: ["value", "actionBlock", "remove"],
+source: "confirm\x0a\x09super confirm.\x0a\x09self actionBlock value",
+messageSends: ["confirm", "value", "actionBlock"],
 referencedClasses: []
 }),
 smalltalk.HLConfirmationWidget);
@@ -4104,67 +4251,6 @@ referencedClasses: []
 }),
 smalltalk.HLConfirmationWidget);
 
-smalltalk.addMethod(
-smalltalk.method({
-selector: "remove",
-category: 'actions',
-fn: function (){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-_st(".dialog"._asJQuery())._removeClass_("active");
-_st((function(){
-return smalltalk.withContext(function($ctx2) {
-_st("#overlay"._asJQuery())._remove();
-return _st(".dialog"._asJQuery())._remove();
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._valueWithTimeout_((300));
-return self}, function($ctx1) {$ctx1.fill(self,"remove",{},smalltalk.HLConfirmationWidget)})},
-args: [],
-source: "remove\x0a\x09'.dialog' asJQuery removeClass: 'active'.\x0a\x09[ \x0a\x09\x09'#overlay' asJQuery remove.\x0a\x09\x09'.dialog' asJQuery remove\x0a\x09] valueWithTimeout: 300",
-messageSends: ["removeClass:", "asJQuery", "valueWithTimeout:", "remove"],
-referencedClasses: []
-}),
-smalltalk.HLConfirmationWidget);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "renderButtonsOn:",
-category: 'rendering',
-fn: function (html){
-var self=this;
-var confirmButton;
-return smalltalk.withContext(function($ctx1) { 
-var $1,$3,$4,$5,$6,$2;
-$1=_st(html)._div();
-_st($1)._class_("buttons");
-$2=_st($1)._with_((function(){
-return smalltalk.withContext(function($ctx2) {
-$3=_st(html)._button();
-_st($3)._class_("button");
-_st($3)._with_("Cancel");
-$4=_st($3)._onClick_((function(){
-return smalltalk.withContext(function($ctx3) {
-return self._cancel();
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
-$4;
-$5=_st(html)._button();
-_st($5)._class_("button default");
-_st($5)._with_("Confirm");
-$6=_st($5)._onClick_((function(){
-return smalltalk.withContext(function($ctx3) {
-return self._confirm();
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
-confirmButton=$6;
-return confirmButton;
-}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
-_st(_st(confirmButton)._asJQuery())._focus();
-return self}, function($ctx1) {$ctx1.fill(self,"renderButtonsOn:",{html:html,confirmButton:confirmButton},smalltalk.HLConfirmationWidget)})},
-args: ["html"],
-source: "renderButtonsOn: html\x0a\x09| confirmButton |\x0a\x09\x0a\x09html div \x0a\x09\x09class: 'buttons';\x0a\x09\x09with: [\x0a\x09\x09\x09html button\x0a\x09\x09\x09\x09class: 'button';\x0a\x09\x09\x09\x09with: 'Cancel';\x0a\x09\x09\x09\x09onClick: [ self cancel ].\x0a\x09\x09\x09confirmButton := html button\x0a\x09\x09\x09\x09class: 'button default';\x0a\x09\x09\x09\x09with: 'Confirm';\x0a\x09\x09\x09\x09onClick: [ self confirm ] ].\x0a\x0a\x09confirmButton asJQuery focus",
-messageSends: ["class:", "div", "with:", "button", "onClick:", "cancel", "confirm", "focus", "asJQuery"],
-referencedClasses: []
-}),
-smalltalk.HLConfirmationWidget);
-
 smalltalk.addMethod(
 smalltalk.method({
 selector: "renderMainOn:",
@@ -4192,12 +4278,12 @@ category: 'actions',
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLRequestWidget.superclass.fn.prototype._confirm.apply(_st(self), []);
 _st(self._actionBlock())._value_(_st(_st(self["@input"])._asJQuery())._val());
-self._remove();
 return self}, function($ctx1) {$ctx1.fill(self,"confirm",{},smalltalk.HLRequestWidget)})},
 args: [],
-source: "confirm\x0a\x09self actionBlock value: input asJQuery val.\x0a\x09self remove",
-messageSends: ["value:", "val", "asJQuery", "actionBlock", "remove"],
+source: "confirm\x0a\x09super confirm.\x0a\x09self actionBlock value: input asJQuery val",
+messageSends: ["confirm", "value:", "val", "asJQuery", "actionBlock"],
 referencedClasses: []
 }),
 smalltalk.HLRequestWidget);
@@ -4343,6 +4429,22 @@ referencedClasses: []
 }),
 smalltalk.HLProgressWidget);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "hasButtons",
+category: 'testing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return false;
+}, function($ctx1) {$ctx1.fill(self,"hasButtons",{},smalltalk.HLProgressWidget)})},
+args: [],
+source: "hasButtons\x0a\x09^ false",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLProgressWidget);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "isVisible",
@@ -4436,21 +4538,6 @@ referencedClasses: []
 }),
 smalltalk.HLProgressWidget);
 
-smalltalk.addMethod(
-smalltalk.method({
-selector: "renderButtonsOn:",
-category: 'rendering',
-fn: function (html){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-return self}, function($ctx1) {$ctx1.fill(self,"renderButtonsOn:",{html:html},smalltalk.HLProgressWidget)})},
-args: ["html"],
-source: "renderButtonsOn: html",
-messageSends: [],
-referencedClasses: []
-}),
-smalltalk.HLProgressWidget);
-
 smalltalk.addMethod(
 smalltalk.method({
 selector: "renderMainOn:",
@@ -4482,12 +4569,12 @@ $1=self._isVisible();
 if(! smalltalk.assert($1)){
 self["@visible"]=true;
 self["@visible"];
-self._appendToJQuery_("body"._asJQuery());
+smalltalk.HLProgressWidget.superclass.fn.prototype._show.apply(_st(self), []);
 };
 return self}, function($ctx1) {$ctx1.fill(self,"show",{},smalltalk.HLProgressWidget)})},
 args: [],
-source: "show\x0a\x09self isVisible ifFalse: [\x0a\x09\x09visible := true.\x0a\x09\x09self appendToJQuery: 'body' asJQuery ]",
-messageSends: ["ifFalse:", "appendToJQuery:", "asJQuery", "isVisible"],
+source: "show\x0a\x09self isVisible ifFalse: [\x0a\x09\x09visible := true.\x0a\x09\x09super show ]",
+messageSends: ["ifFalse:", "show", "isVisible"],
 referencedClasses: []
 }),
 smalltalk.HLProgressWidget);
@@ -4519,6 +4606,405 @@ referencedClasses: []
 smalltalk.HLProgressWidget.klass);
 
 
+smalltalk.addClass('HLTabSelectionWidget', smalltalk.HLModalWidget, ['tabs', 'tabList', 'selectedTab', 'selectCallback', 'cancelCallback', 'confirmCallback'], 'Helios-Core');
+smalltalk.HLTabSelectionWidget.comment="I am a modal window used to select or create tabs.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cancel",
+category: 'actions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLTabSelectionWidget.superclass.fn.prototype._cancel.apply(_st(self), []);
+_st(self._cancelCallback())._value();
+return self}, function($ctx1) {$ctx1.fill(self,"cancel",{},smalltalk.HLTabSelectionWidget)})},
+args: [],
+source: "cancel\x0a\x09super cancel.\x0a\x09self cancelCallback value",
+messageSends: ["cancel", "value", "cancelCallback"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cancelCallback",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@cancelCallback"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$1=(function(){
+return smalltalk.withContext(function($ctx2) {
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})});
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"cancelCallback",{},smalltalk.HLTabSelectionWidget)})},
+args: [],
+source: "cancelCallback\x0a\x09^ cancelCallback ifNil: [ [] ]",
+messageSends: ["ifNil:"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cancelCallback:",
+category: 'accessing',
+fn: function (aBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@cancelCallback"]=aBlock;
+return self}, function($ctx1) {$ctx1.fill(self,"cancelCallback:",{aBlock:aBlock},smalltalk.HLTabSelectionWidget)})},
+args: ["aBlock"],
+source: "cancelCallback: aBlock\x0a\x09cancelCallback := aBlock",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "confirm",
+category: 'actions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLTabSelectionWidget.superclass.fn.prototype._confirm.apply(_st(self), []);
+_st(self._confirmCallback())._value_(self._selectedTab());
+return self}, function($ctx1) {$ctx1.fill(self,"confirm",{},smalltalk.HLTabSelectionWidget)})},
+args: [],
+source: "confirm\x0a\x09super confirm.\x0a\x09self confirmCallback value: self selectedTab",
+messageSends: ["confirm", "value:", "selectedTab", "confirmCallback"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "confirmCallback",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@confirmCallback"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$1=(function(){
+return smalltalk.withContext(function($ctx2) {
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})});
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"confirmCallback",{},smalltalk.HLTabSelectionWidget)})},
+args: [],
+source: "confirmCallback\x0a\x09^ confirmCallback ifNil: [ [] ]",
+messageSends: ["ifNil:"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "confirmCallback:",
+category: 'accessing',
+fn: function (aBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@confirmCallback"]=aBlock;
+return self}, function($ctx1) {$ctx1.fill(self,"confirmCallback:",{aBlock:aBlock},smalltalk.HLTabSelectionWidget)})},
+args: ["aBlock"],
+source: "confirmCallback: aBlock\x0a\x09confirmCallback := aBlock",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderContentOn:",
+category: 'rendering',
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.HLTabSelectionWidget.superclass.fn.prototype._renderContentOn_.apply(_st(self), [html]);
+_st(self._tabList())._focus();
+return self}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html},smalltalk.HLTabSelectionWidget)})},
+args: ["html"],
+source: "renderContentOn: html\x0a\x09super renderContentOn: html.\x0a\x09self tabList focus",
+messageSends: ["renderContentOn:", "focus", "tabList"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderMainOn:",
+category: 'rendering',
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(html)._div();
+_st($1)._class_("title");
+$2=_st($1)._with_("Tab selection");
+_st(html)._with_(self._tabList());
+return self}, function($ctx1) {$ctx1.fill(self,"renderMainOn:",{html:html},smalltalk.HLTabSelectionWidget)})},
+args: ["html"],
+source: "renderMainOn: html\x0a\x09html div \x0a\x09\x09class: 'title'; \x0a\x09\x09with: 'Tab selection'.\x0a\x09\x0a\x09html with: self tabList",
+messageSends: ["class:", "div", "with:", "tabList"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderTab:on:",
+category: 'rendering',
+fn: function (aTab,html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+$1=_st(html)._span();
+_st($1)._class_(_st(aTab)._cssClass());
+$2=_st($1)._with_(_st(aTab)._label());
+return self}, function($ctx1) {$ctx1.fill(self,"renderTab:on:",{aTab:aTab,html:html},smalltalk.HLTabSelectionWidget)})},
+args: ["aTab", "html"],
+source: "renderTab: aTab on: html\x0a\x09html \x0a\x09\x09span \x0a\x09\x09\x09class: aTab cssClass;\x0a\x09\x09\x09with: aTab label",
+messageSends: ["class:", "cssClass", "span", "with:", "label"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderTabsOn:",
+category: 'rendering',
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2;
+_st(self._tabs())._do_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st(_st(html)._li())._with_((function(){
+return smalltalk.withContext(function($ctx3) {
+$1=_st(html)._a();
+_st($1)._with_((function(){
+return smalltalk.withContext(function($ctx4) {
+return self._renderTab_on_(each,html);
+}, function($ctx4) {$ctx4.fillBlock({},$ctx3)})}));
+$2=_st($1)._onClick_((function(){
+return smalltalk.withContext(function($ctx4) {
+return self._selectTab_(each);
+}, function($ctx4) {$ctx4.fillBlock({},$ctx3)})}));
+return $2;
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2)})}));
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"renderTabsOn:",{html:html},smalltalk.HLTabSelectionWidget)})},
+args: ["html"],
+source: "renderTabsOn: html\x0a\x09self tabs do: [ :each |\x0a\x09\x09html li with: [ \x0a\x09\x09\x09html a \x0a\x09\x09\x09\x09with: [ \x0a\x09\x09\x09\x09\x09self renderTab: each on: html ];\x0a\x09\x09\x09\x09onClick: [ self selectTab: each ] ] ]",
+messageSends: ["do:", "with:", "renderTab:on:", "a", "onClick:", "selectTab:", "li", "tabs"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectCallback",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@selectCallback"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$1=(function(){
+return smalltalk.withContext(function($ctx2) {
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})});
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"selectCallback",{},smalltalk.HLTabSelectionWidget)})},
+args: [],
+source: "selectCallback\x0a\x09^ selectCallback ifNil: [ [] ]",
+messageSends: ["ifNil:"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectCallback:",
+category: 'accessing',
+fn: function (aBlock){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@selectCallback"]=aBlock;
+return self}, function($ctx1) {$ctx1.fill(self,"selectCallback:",{aBlock:aBlock},smalltalk.HLTabSelectionWidget)})},
+args: ["aBlock"],
+source: "selectCallback: aBlock\x0a\x09selectCallback := aBlock",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectTab:",
+category: 'actions',
+fn: function (aTab){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self._selectedTab_(aTab);
+_st(self._selectCallback())._value_(aTab);
+return self}, function($ctx1) {$ctx1.fill(self,"selectTab:",{aTab:aTab},smalltalk.HLTabSelectionWidget)})},
+args: ["aTab"],
+source: "selectTab: aTab\x0a\x09self selectedTab: aTab.\x0a\x09self selectCallback value: aTab",
+messageSends: ["selectedTab:", "value:", "selectCallback"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectedTab",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@selectedTab"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"selectedTab",{},smalltalk.HLTabSelectionWidget)})},
+args: [],
+source: "selectedTab\x0a\x09^ selectedTab",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "selectedTab:",
+category: 'accessing',
+fn: function (aTab){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@selectedTab"]=aTab;
+return self}, function($ctx1) {$ctx1.fill(self,"selectedTab:",{aTab:aTab},smalltalk.HLTabSelectionWidget)})},
+args: ["aTab"],
+source: "selectedTab: aTab\x0a\x09selectedTab := aTab",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "setupKeyBindings",
+category: 'actions',
+fn: function (){
+var self=this;
+function $String(){return smalltalk.String||(typeof String=="undefined"?nil:String)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+smalltalk.HLTabSelectionWidget.superclass.fn.prototype._setupKeyBindings.apply(_st(self), []);
+_st(".dialog"._asJQuery())._keyup_((function(e){
+return smalltalk.withContext(function($ctx2) {
+$1=_st(_st(e)._keyCode()).__eq(_st(_st($String())._cr())._asciiValue());
+if(smalltalk.assert($1)){
+return self._confirm();
+};
+}, function($ctx2) {$ctx2.fillBlock({e:e},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"setupKeyBindings",{},smalltalk.HLTabSelectionWidget)})},
+args: [],
+source: "setupKeyBindings\x0a\x09super setupKeyBindings.\x0a\x09'.dialog' asJQuery keyup: [ :e |\x0a\x09\x09e keyCode = String cr asciiValue ifTrue: [ self confirm ] ]",
+messageSends: ["setupKeyBindings", "keyup:", "ifTrue:", "confirm", "=", "asciiValue", "cr", "keyCode", "asJQuery"],
+referencedClasses: ["String"]
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "tabList",
+category: 'rendering',
+fn: function (){
+var self=this;
+function $HLTabListWidget(){return smalltalk.HLTabListWidget||(typeof HLTabListWidget=="undefined"?nil:HLTabListWidget)}
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3,$4;
+$1=self["@tabList"];
+if(($receiver = $1) == nil || $receiver == undefined){
+self["@tabList"]=_st($HLTabListWidget())._new();
+self["@tabList"];
+$2=self["@tabList"];
+_st($2)._callback_((function(tab){
+return smalltalk.withContext(function($ctx2) {
+self._selectTab_(tab);
+return _st(self["@tabList"])._focus();
+}, function($ctx2) {$ctx2.fillBlock({tab:tab},$ctx1)})}));
+_st($2)._selectedItem_(self._selectedTab());
+$3=_st($2)._items_(self._tabs());
+$3;
+} else {
+$1;
+};
+$4=self["@tabList"];
+return $4;
+}, function($ctx1) {$ctx1.fill(self,"tabList",{},smalltalk.HLTabSelectionWidget)})},
+args: [],
+source: "tabList\x0a\x09tabList ifNil: [ \x0a\x09\x09tabList := HLTabListWidget new.\x0a\x09\x09tabList\x0a\x09\x09\x09callback: [ :tab | self selectTab: tab. tabList focus ];\x0a\x09\x09\x09selectedItem: self selectedTab;\x0a\x09\x09\x09items: self tabs ].\x0a\x09\x0a\x09^ tabList",
+messageSends: ["ifNil:", "new", "callback:", "selectTab:", "focus", "selectedItem:", "selectedTab", "items:", "tabs"],
+referencedClasses: ["HLTabListWidget"]
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "tabs",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@tabs"];
+if(($receiver = $2) == nil || $receiver == undefined){
+$1=[];
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"tabs",{},smalltalk.HLTabSelectionWidget)})},
+args: [],
+source: "tabs\x0a\x09^ tabs ifNil: [ #() ]",
+messageSends: ["ifNil:"],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "tabs:",
+category: 'accessing',
+fn: function (aCollection){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@tabs"]=aCollection;
+return self}, function($ctx1) {$ctx1.fill(self,"tabs:",{aCollection:aCollection},smalltalk.HLTabSelectionWidget)})},
+args: ["aCollection"],
+source: "tabs: aCollection\x0a\x09tabs := aCollection",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.HLTabSelectionWidget);
+
+
+
 smalltalk.addClass('HLProgressBarWidget', smalltalk.HLWidget, ['label', 'parent', 'workBlock', 'collection', 'bar'], 'Helios-Core');
 smalltalk.HLProgressBarWidget.comment="I am a widget used to display a progress bar while iterating over a collection.";
 smalltalk.addMethod(

File diff suppressed because it is too large
+ 322 - 321
js/Helios-KeyBindings.deploy.js


File diff suppressed because it is too large
+ 254 - 300
js/Helios-KeyBindings.js


+ 1 - 1
js/Helios-Workspace-Tests.js

@@ -16,7 +16,7 @@ self._assert_(_st(_st($HLCodeWidget())._pcKeyMap())._isKindOf_($HashedCollection
 self._assert_(_st(_st($HLCodeWidget())._macKeyMap())._isKindOf_($HashedCollection()));
 return self}, function($ctx1) {$ctx1.fill(self,"testKeyMap",{},smalltalk.HLCodeWidgetTest)})},
 args: [],
-source: "testKeyMap\x0a\x22Key maps are a collection of associations.\x22\x0aself assert: ( HLCodeWidget pcKeyMap isKindOf: HashedCollection ).\x0aself assert: ( HLCodeWidget macKeyMap isKindOf: HashedCollection ).",
+source: "testKeyMap\x0a\x09\x22Key maps are a collection of associations.\x22\x0a\x09self assert: (HLCodeWidget pcKeyMap isKindOf: HashedCollection).\x0a\x09self assert: (HLCodeWidget macKeyMap isKindOf: HashedCollection)",
 messageSends: ["assert:", "isKindOf:", "pcKeyMap", "macKeyMap"],
 referencedClasses: ["HashedCollection", "HLCodeWidget"]
 }),

+ 13 - 0
js/Kernel-Collections.deploy.js

@@ -3298,6 +3298,19 @@ return self}, function($ctx1) {$ctx1.fill(self,"crlf",{},smalltalk.String.klass)
 messageSends: []}),
 smalltalk.String.klass);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "esc",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self._fromCharCode_((27));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"esc",{},smalltalk.String.klass)})},
+messageSends: ["fromCharCode:"]}),
+smalltalk.String.klass);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "fromCharCode:",

+ 18 - 0
js/Kernel-Collections.js

@@ -4432,6 +4432,24 @@ referencedClasses: []
 }),
 smalltalk.String.klass);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "esc",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self._fromCharCode_((27));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"esc",{},smalltalk.String.klass)})},
+args: [],
+source: "esc\x0a\x09^ self fromCharCode: 27",
+messageSends: ["fromCharCode:"],
+referencedClasses: []
+}),
+smalltalk.String.klass);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "fromCharCode:",

+ 40 - 0
st/Helios-Commands-Core.st

@@ -273,6 +273,46 @@ label
 	^ 'Workspace'
 ! !
 
+HLCommand subclass: #HLSwitchTabCommand
+	instanceVariableNames: ''
+	package: 'Helios-Commands-Core'!
+
+!HLSwitchTabCommand methodsFor: 'accessing'!
+
+selectedTab
+	^ HLManager current activeTab
+!
+
+tabs
+	^ HLManager current tabs
+! !
+
+!HLSwitchTabCommand methodsFor: 'executing'!
+
+execute
+	| activeTab |
+	
+	activeTab := self selectedTab.
+	
+	^ HLTabSelectionWidget new
+		tabs: self tabs;
+		selectedTab: self selectedTab;
+		selectCallback: [ :tab | tab activate ];
+		confirmCallback: [ :tab | tab focus ];
+		cancelCallback: [ activeTab activate ];
+		show
+! !
+
+!HLSwitchTabCommand class methodsFor: 'accessing'!
+
+key
+	^ 's'
+!
+
+label
+	^ 'Switch'
+! !
+
 HLCommand subclass: #HLViewCommand
 	instanceVariableNames: ''
 	package: 'Helios-Commands-Core'!

+ 201 - 55
st/Helios-Core.st

@@ -612,7 +612,7 @@ unregister
 
 !HLWidget methodsFor: 'keybindings'!
 
-bindKeyDown: keyDownBlock up: keyUpBlock
+bindKeyDown: keyDownBlock keyUp: keyUpBlock
 	self wrapper asJQuery
 		keydown: keyDownBlock;
 		keyup: keyUpBlock
@@ -625,7 +625,7 @@ registerBindings
 registerBindingsOn: aBindingGroup
 !
 
-unbindKeyDownUp
+unbindKeyDownKeyUp
 	self wrapper asJQuery
 		unbind: 'keydown';
 		unbind: 'keyup'
@@ -854,9 +854,9 @@ defaultItems
 !HLListWidget methodsFor: 'events'!
 
 setupKeyBindings 
-	(HLRepeatingKeyBindingHandler forWidget: self)
-		whileKeyPressed: 38 do: [ self activatePreviousListItem ];
-		whileKeyPressed: 40 do: [ self activateNextListItem ];
+	(HLRepeatedKeyDownHandler on: self)
+		whileKeyDown: 38 do: [ self activatePreviousListItem ];
+		whileKeyDown: 40 do: [ self activateNextListItem ];
 		rebindKeys
 ! !
 
@@ -1089,8 +1089,41 @@ on: aModel
         yourself
 ! !
 
+HLListWidget subclass: #HLTabListWidget
+	instanceVariableNames: 'callback'
+	package: 'Helios-Core'!
+!HLTabListWidget commentStamp!
+I am a widget used to display a list of helios tabs.
+
+When a tab is selected, `callback` is evaluated with the selected tab as argument.!
+
+!HLTabListWidget methodsFor: 'accessing'!
+
+callback
+	^ callback ifNil: [ [] ]
+!
+
+callback: aBlock
+	callback := aBlock
+! !
+
+!HLTabListWidget methodsFor: 'actions'!
+
+selectItem: aTab
+	super selectItem: aTab.
+	self callback value: aTab
+! !
+
+!HLTabListWidget methodsFor: 'rendering'!
+
+renderItemLabel: aTab on: html
+	html span
+		class: aTab cssClass;
+		with: aTab label
+! !
+
 HLWidget subclass: #HLManager
-	instanceVariableNames: 'tabs activeTab keyBinder environment history'
+	instanceVariableNames: 'tabs activeTab environment history'
 	package: 'Helios-Core'!
 
 !HLManager methodsFor: 'accessing'!
@@ -1118,7 +1151,7 @@ history: aCollection
 !
 
 keyBinder
-	^ keyBinder ifNil: [ keyBinder := HLKeyBinder new ]
+	^ HLKeyBinder current
 !
 
 tabs
@@ -1149,19 +1182,17 @@ addToHistory: aTab
 !
 
 confirm: aString ifFalse: aBlock
-	(HLConfirmationWidget new
+	HLConfirmationWidget new
 		confirmationString: aString;
 		cancelBlock: aBlock;
-		yourself)
-			appendToJQuery: 'body' asJQuery
+		show
 !
 
 confirm: aString ifTrue: aBlock
-	(HLConfirmationWidget new
+	HLConfirmationWidget new
 		confirmationString: aString;
 		actionBlock: aBlock;
-		yourself)
-			appendToJQuery: 'body' asJQuery
+		show
 !
 
 registerErrorHandler: anErrorHandler
@@ -1204,12 +1235,11 @@ request: aString do: aBlock
 !
 
 request: aString value: valueString do: aBlock
-	(HLRequestWidget new
+	HLRequestWidget new
 		confirmationString: aString;
 		actionBlock: aBlock;
 		value: valueString;
-		yourself)
-			appendToJQuery: 'body' asJQuery
+		show
 ! !
 
 !HLManager methodsFor: 'defaults'!
@@ -1354,17 +1384,45 @@ cancel
 	self remove
 !
 
+confirm
+	"Override in subclasses"
+	self remove
+!
+
 remove
 	'.dialog' asJQuery removeClass: 'active'.
 	[ 
 		'#overlay' asJQuery remove.
 		'.dialog' asJQuery remove
 	] valueWithTimeout: 300
+!
+
+show
+	self appendToJQuery: 'body' asJQuery
 ! !
 
 !HLModalWidget methodsFor: 'rendering'!
 
+hasButtons
+	^ true
+!
+
 renderButtonsOn: html
+	| confirmButton |
+	
+	html div 
+		class: 'buttons';
+		with: [
+			html button
+				class: 'button';
+				with: 'Cancel';
+				onClick: [ self cancel ].
+			confirmButton := html button
+				class: 'button default';
+				with: 'Confirm';
+				onClick: [ self confirm ] ].
+
+	confirmButton asJQuery focus
 !
 
 renderContentOn: html
@@ -1374,9 +1432,9 @@ renderContentOn: html
 	html div 
 		class: 'dialog ', self cssClass;
 		with: [
-			self
-				renderMainOn: html;
-				renderButtonsOn: html ].
+			self renderMainOn: html.
+			self hasButtons ifTrue: [ 
+				self renderButtonsOn: html ] ].
 
 	'.dialog' asJQuery addClass: 'active'.
 	self setupKeyBindings
@@ -1387,7 +1445,7 @@ renderMainOn: html
 
 setupKeyBindings
 	'.dialog' asJQuery keyup: [ :e |
-		e keyCode = 27 ifTrue: [ self cancel ] ]
+		e keyCode = String esc asciiValue ifTrue: [ self cancel ] ]
 ! !
 
 HLModalWidget subclass: #HLConfirmationWidget
@@ -1428,42 +1486,16 @@ confirmationString: aString
 
 cancel
 	self cancelBlock value.
-	self remove
+	super cancel
 !
 
 confirm
-	self actionBlock value.
-	self remove
-!
-
-remove
-	'.dialog' asJQuery removeClass: 'active'.
-	[ 
-		'#overlay' asJQuery remove.
-		'.dialog' asJQuery remove
-	] valueWithTimeout: 300
+	super confirm.
+	self actionBlock value
 ! !
 
 !HLConfirmationWidget methodsFor: 'rendering'!
 
-renderButtonsOn: html
-	| confirmButton |
-	
-	html div 
-		class: 'buttons';
-		with: [
-			html button
-				class: 'button';
-				with: 'Cancel';
-				onClick: [ self cancel ].
-			confirmButton := html button
-				class: 'button default';
-				with: 'Confirm';
-				onClick: [ self confirm ] ].
-
-	confirmButton asJQuery focus
-!
-
 renderMainOn: html
 	html span with: self confirmationString
 ! !
@@ -1493,8 +1525,8 @@ value: aString
 !HLRequestWidget methodsFor: 'actions'!
 
 confirm
-	self actionBlock value: input asJQuery val.
-	self remove
+	super confirm.
+	self actionBlock value: input asJQuery val
 ! !
 
 !HLRequestWidget methodsFor: 'rendering'!
@@ -1564,14 +1596,11 @@ removeProgressBar: aProgressBar
 show
 	self isVisible ifFalse: [
 		visible := true.
-		self appendToJQuery: 'body' asJQuery ]
+		super show ]
 ! !
 
 !HLProgressWidget methodsFor: 'rendering'!
 
-renderButtonsOn: html
-!
-
 renderMainOn: html
 	self progressBars do: [ :each |
 		html with: each ]
@@ -1579,6 +1608,10 @@ renderMainOn: html
 
 !HLProgressWidget methodsFor: 'testing'!
 
+hasButtons
+	^ false
+!
+
 isVisible
 	^ visible ifNil: [ false ]
 ! !
@@ -1591,6 +1624,119 @@ default
 	^ default ifNil: [ default := self new ]
 ! !
 
+HLModalWidget subclass: #HLTabSelectionWidget
+	instanceVariableNames: 'tabs tabList selectedTab selectCallback cancelCallback confirmCallback'
+	package: 'Helios-Core'!
+!HLTabSelectionWidget commentStamp!
+I am a modal window used to select or create tabs.!
+
+!HLTabSelectionWidget methodsFor: 'accessing'!
+
+cancelCallback
+	^ cancelCallback ifNil: [ [] ]
+!
+
+cancelCallback: aBlock
+	cancelCallback := aBlock
+!
+
+confirmCallback
+	^ confirmCallback ifNil: [ [] ]
+!
+
+confirmCallback: aBlock
+	confirmCallback := aBlock
+!
+
+selectCallback
+	^ selectCallback ifNil: [ [] ]
+!
+
+selectCallback: aBlock
+	selectCallback := aBlock
+!
+
+selectedTab
+	^ selectedTab
+!
+
+selectedTab: aTab
+	selectedTab := aTab
+!
+
+tabs
+	^ tabs ifNil: [ #() ]
+!
+
+tabs: aCollection
+	tabs := aCollection
+! !
+
+!HLTabSelectionWidget methodsFor: 'actions'!
+
+cancel
+	super cancel.
+	self cancelCallback value
+!
+
+confirm
+	super confirm.
+	self confirmCallback value: self selectedTab
+!
+
+selectTab: aTab
+	self selectedTab: aTab.
+	self selectCallback value: aTab
+!
+
+setupKeyBindings
+	super setupKeyBindings.
+	'.dialog' asJQuery keyup: [ :e |
+		e keyCode = String cr asciiValue ifTrue: [ self confirm ] ]
+! !
+
+!HLTabSelectionWidget methodsFor: 'rendering'!
+
+renderContentOn: html
+	super renderContentOn: html.
+	self tabList focus
+!
+
+renderMainOn: html
+	html div 
+		class: 'title'; 
+		with: 'Tab selection'.
+	
+	html with: self tabList
+!
+
+renderTab: aTab on: html
+	html 
+		span 
+			class: aTab cssClass;
+			with: aTab label
+!
+
+renderTabsOn: html
+	self tabs do: [ :each |
+		html li with: [ 
+			html a 
+				with: [ 
+					self renderTab: each on: html ];
+				onClick: [ self selectTab: each ] ] ]
+!
+
+tabList
+	tabList ifNil: [ 
+		tabList := HLTabListWidget new.
+		tabList
+			callback: [ :tab | self selectTab: tab. tabList focus ];
+			selectedItem: self selectedTab;
+			items: self tabs ].
+	
+	^ tabList
+! !
+
 HLWidget subclass: #HLProgressBarWidget
 	instanceVariableNames: 'label parent workBlock collection bar'
 	package: 'Helios-Core'!

+ 218 - 165
st/Helios-KeyBindings.st

@@ -2,10 +2,21 @@ Smalltalk current createPackage: 'Helios-KeyBindings'!
 Object subclass: #HLBinding
 	instanceVariableNames: 'key label'
 	package: 'Helios-KeyBindings'!
+!HLBinding commentStamp!
+I am the abstract representation of a keybinding in Helios. My instances hold a key (integer value) and a label. 
+
+Bindings are built into a tree of keys, so pressing a key may result in more key choices (for example, to open a workspace, 'o' is pressed first then 'w' is pressed).
+
+Binding action handling and selection is handled by the `current` instance of `HLKeyBinder`.
+
+Subclasses implement specific behavior like evaluating actions or (sub-)grouping other bindings.!
 
 !HLBinding methodsFor: 'accessing'!
 
 atKey: aKey
+	"Answer the sub-binding at key aKey.
+	Always answer nil here. See HLBindingGroup for more."
+	
 	^ nil
 !
 
@@ -35,7 +46,7 @@ shortcut
 
 !HLBinding methodsFor: 'actions'!
 
-applyOn: aKeyBinder
+apply
 !
 
 release
@@ -43,17 +54,6 @@ release
 
 !HLBinding methodsFor: 'rendering'!
 
-renderActionFor: aBinder html: html
-	html span class: 'command'; with: [
-		html span 
-			class: 'label'; 
-			with: self shortcut asLowercase.
-  		html a 
-        	class: 'action'; 
-            with: self displayLabel;
-  			onClick: [ aBinder applyBinding: self ] ]
-!
-
 renderOn: aBindingHelper html: html
 ! !
 
@@ -61,12 +61,6 @@ renderOn: aBindingHelper html: html
 
 isActive
 	^ self subclassResponsibility
-!
-
-isFinal
-	" Answer true if the receiver is the final binding of a sequence "
-	
-	^ false
 ! !
 
 !HLBinding class methodsFor: 'instance creation'!
@@ -81,6 +75,10 @@ on: anInteger labelled: aString
 HLBinding subclass: #HLBindingAction
 	instanceVariableNames: 'command'
 	package: 'Helios-KeyBindings'!
+!HLBindingAction commentStamp!
+My instances are the leafs of the binding tree. They evaluate actions through commands, instances of concrete subclasses of `HLCommand`.
+
+The `#apply` methods is used to evaluate the `command`. If the command requires user input, an `inputWidget` will be displayed to the user.!
 
 !HLBindingAction methodsFor: 'accessing'!
 
@@ -92,6 +90,10 @@ command: aCommand
 	command := aCommand
 !
 
+input: aString
+	self command input: aString
+!
+
 inputBinding
 	^ HLBindingInput new
 		label: self command inputLabel;
@@ -103,29 +105,46 @@ inputBinding
 				input: val;
 				execute ];
 		yourself
+!
+
+inputWidget
+	^ HLBindingActionInputWidget new
+		ghostText: self command displayLabel;
+		defaultValue: self command defaultInput;
+		inputCompletion: self command inputCompletion;
+		callback: [ :value | 
+			self 
+				input: value;
+				executeCommand ];
+		yourself
 ! !
 
 !HLBindingAction methodsFor: 'actions'!
 
-applyOn: aKeyBinder
+apply
 	self command isInputRequired
-		ifTrue: [ aKeyBinder selectBinding: self inputBinding ]
-		ifFalse: [ self command execute ]
+		ifTrue: [ HLKeyBinder current helper showWidget: self inputWidget ]
+		ifFalse: [ self executeCommand ]
+!
+
+executeCommand
+	self command execute.
+	HLKeyBinder current deactivate
 ! !
 
 !HLBindingAction methodsFor: 'testing'!
 
 isActive
 	^ self command isActive
-!
-
-isFinal
-	^ self command isInputRequired not
 ! !
 
 HLBinding subclass: #HLBindingGroup
 	instanceVariableNames: 'bindings'
 	package: 'Helios-KeyBindings'!
+!HLBindingGroup commentStamp!
+My instances hold other bindings, either actions or groups, and do not have actions by themselves.
+
+Children are accessed with `atKey:` and added with the `add*` methods.!
 
 !HLBindingGroup methodsFor: 'accessing'!
 
@@ -133,20 +152,6 @@ activeBindings
 	^ self bindings select: [ :each | each isActive ]
 !
 
-add: aBinding
-	^ self bindings add: aBinding
-!
-
-addActionKey: anInteger labelled: aString callback: aBlock
-	self add: ((HLBindingAction on: anInteger labelled: aString)
-    	callback: aBlock;
-        yourself)
-!
-
-addGroupKey: anInteger labelled: aString
-	self add: (HLBindingGroup on: anInteger labelled: aString)
-!
-
 at: aString
 	^ self bindings 
     	detect: [ :each | each label = aString ]
@@ -182,6 +187,24 @@ release
 	self bindings do: [ :each | each release ]
 ! !
 
+!HLBindingGroup methodsFor: 'add'!
+
+addGroupKey: anInteger labelled: aString
+	self add: (HLBindingGroup on: anInteger labelled: aString)
+! !
+
+!HLBindingGroup methodsFor: 'adding'!
+
+add: aBinding
+	^ self bindings add: aBinding
+!
+
+addActionKey: anInteger labelled: aString callback: aBlock
+	self add: ((HLBindingAction on: anInteger labelled: aString)
+    	callback: aBlock;
+        yourself)
+! !
+
 !HLBindingGroup methodsFor: 'rendering'!
 
 renderOn: aBindingHelper html: html
@@ -195,15 +218,13 @@ isActive
 	^ self activeBindings notEmpty
 ! !
 
-HLBinding subclass: #HLBindingInput
-	instanceVariableNames: 'input callback status wrapper binder ghostText isFinal message messageTag inputCompletion defaultValue'
+HLWidget subclass: #HLBindingActionInputWidget
+	instanceVariableNames: 'input callback status wrapper ghostText message inputCompletion defaultValue messageTag'
 	package: 'Helios-KeyBindings'!
+!HLBindingActionInputWidget commentStamp!
+My instances are built when a `HLBindingAction` that requires user input is applied.!
 
-!HLBindingInput methodsFor: 'accessing'!
-
-atKey: aKey
-	aKey = 13 ifFalse: [ ^ nil ]
-!
+!HLBindingActionInputWidget methodsFor: 'accessing'!
 
 callback
 	^ callback ifNil: [ callback := [ :value | ] ]
@@ -257,12 +278,7 @@ status: aStatus
 	status := aStatus
 ! !
 
-!HLBindingInput methodsFor: 'actions'!
-
-applyOn: aKeyBinder
-	self isFinal: true.
-	self evaluate: self input asJQuery val
-!
+!HLBindingActionInputWidget methodsFor: 'actions'!
 
 clearStatus
 	self status: 'info'.
@@ -275,36 +291,27 @@ errorStatus
 	self refresh
 !
 
-evaluate: aString
-	
+evaluate: aString	
 	[ self callback value: aString ]
-	on: Error
-	do: [:ex |
-		self input asJQuery 
-			one: 'keydown' 
-			do: [ self clearStatus ].
-		self message: ex messageText.
-		self errorStatus.
-		self isFinal: false ].
+		on: Error
+		do: [:ex |
+			self input asJQuery 
+				one: 'keydown' 
+				do: [ self clearStatus ].
+			self message: ex messageText.
+			self errorStatus ]
 !
 
-release
-	status := nil.
-	wrapper := nil.
-	binder := nil
-! !
-
-!HLBindingInput methodsFor: 'rendering'!
-
 refresh
 	wrapper ifNil: [ ^ self ].
     
 	wrapper class: self status.
 	messageTag contents: self message
-!
+! !
 
-renderOn: aBinder html: html
-	binder := aBinder.
+!HLBindingActionInputWidget methodsFor: 'rendering'!
+
+renderOn: html
 	wrapper ifNil: [ wrapper := html span ].
 
 	wrapper 
@@ -313,6 +320,9 @@ renderOn: aBinder html: html
 			input := html input
 				placeholder: self ghostText;
 				value: self defaultValue;
+				onKeyDown: [ :event | 
+					event which = 13 ifTrue: [
+						self evaluate: input asJQuery val ] ]
 				yourself.
 			input asJQuery 
 				typeahead: #{ 'source' -> self inputCompletion }.
@@ -328,23 +338,19 @@ renderOn: aBinder html: html
 	[ input asJQuery focus ] valueWithTimeout: 10
 ! !
 
-!HLBindingInput methodsFor: 'testing'!
+Object subclass: #HLKeyBinder
+	instanceVariableNames: 'modifierKey helper bindings selectedBinding'
+	package: 'Helios-KeyBindings'!
+!HLKeyBinder commentStamp!
+My `current` instance holds keybindings for Helios actions and evaluate them.
 
-isActive
-	^ true
-!
+Bindings can be nested by groups. The `bindings` instance variable holds the root of the key bindings tree.
 
-isFinal
-	^ isFinal ifNil: [ isFinal := super isFinal ]
-!
+Bindings are instances of a concrete subclass of `HLBinding`.
 
-isFinal: aBoolean
-	isFinal := aBoolean
-! !
+I am always either in 'active' or 'inactive' state. In active state I capture key down events and my `helper` widget is displayed at the bottom of the window. My `selectedBinding`, if any, is displayed by the helper.
 
-Object subclass: #HLKeyBinder
-	instanceVariableNames: 'modifierKey helper bindings selectedBinding'
-	package: 'Helios-KeyBindings'!
+Bindings are evaluated through `applyBinding:`. If a binding is final (not a group of other bindings), evaluating it will result in deactivating the binder, and hiding the `helper` widget.!
 
 !HLKeyBinder methodsFor: 'accessing'!
 
@@ -384,9 +390,7 @@ applyBinding: aBinding
 	aBinding isActive ifFalse: [ ^ self ].
 	
 	self selectBinding: aBinding.
-    aBinding applyOn: self.
-	
-	aBinding isFinal ifTrue: [ self deactivate ]
+    aBinding apply
 !
 
 deactivate
@@ -412,8 +416,8 @@ defaultBindings
 	| group |
 	
 	group := HLBindingGroup new
-		addGroupKey: 86 labelled: 'View';
 		add: HLCloseTabCommand new asBinding;
+		add: HLSwitchTabCommand new asBinding;
 		yourself.
 		
 	HLOpenCommand registerConcreteClassesOn: group.
@@ -469,7 +473,7 @@ setupEvents
 
 initialize
 	super initialize.
-	helper := HLKeyBinderHelper on: self.
+	helper := HLKeyBinderHelperWidget on: self.
 	helper 	
 		renderStart;
 		renderCog
@@ -485,11 +489,27 @@ systemIsMac
 	^ navigator platform match: 'Mac'
 ! !
 
-HLWidget subclass: #HLKeyBinderHelper
+HLKeyBinder class instanceVariableNames: 'current'!
+
+!HLKeyBinder class methodsFor: 'instance creation'!
+
+current
+	^ current ifNil: [ current := super new ]
+!
+
+new
+	self shouldNotImplement
+! !
+
+HLWidget subclass: #HLKeyBinderHelperWidget
 	instanceVariableNames: 'keyBinder'
 	package: 'Helios-KeyBindings'!
+!HLKeyBinderHelperWidget commentStamp!
+I am the widget responsible for displaying active keybindings in a bar at the bottom of the window. Each keybinding is an instance of `HLBinding`. 
 
-!HLKeyBinderHelper methodsFor: 'accessing'!
+Rendering is done through a double dispatch, see `#renderSelectedBindingOn:`.!
+
+!HLKeyBinderHelperWidget methodsFor: 'accessing'!
 
 cssClass
 	^ 'key_helper'
@@ -503,11 +523,15 @@ keyBinder: aKeyBinder
 	keyBinder := aKeyBinder
 !
 
+mainId
+	^ 'binding-helper-main'
+!
+
 selectedBinding
 	^ self keyBinder selectedBinding
 ! !
 
-!HLKeyBinderHelper methodsFor: 'actions'!
+!HLKeyBinderHelperWidget methodsFor: 'actions'!
 
 hide
 	('.', self cssClass) asJQuery remove.
@@ -525,24 +549,33 @@ show
 
 showCog
 	'#cog-helper' asJQuery show
-! !
-
-!HLKeyBinderHelper methodsFor: 'keyBindings'!
+!
 
-registerBindings
-	"Do nothing"
+showWidget: aWidget
+	"Some actions need to display more info to the user or request input.
+	This method is the right place for that"
+	
+	('#', self mainId) asJQuery empty.
+	aWidget appendToJQuery: ('#', self mainId) asJQuery
 ! !
 
-!HLKeyBinderHelper methodsFor: 'rendering'!
+!HLKeyBinderHelperWidget methodsFor: 'rendering'!
+
+renderBindingActionFor: aBinding on: html
+	html span class: 'command'; with: [
+		html span 
+			class: 'label'; 
+			with: aBinding shortcut asLowercase.
+  		html a 
+        	class: 'action'; 
+            with: aBinding displayLabel;
+  			onClick: [ self keyBinder applyBinding: aBinding ] ]
+!
 
 renderBindingGroup: aBindingGroup on: html
 	(aBindingGroup activeBindings 
     	sorted: [ :a :b | a key < b key ])
-        do: [ :each | each renderActionFor: self keyBinder html: html ]
-!
-
-renderBindingOn: html
-	self selectedBinding renderOn: self html: html
+        do: [ :each | self renderBindingActionFor: each on: html ]
 !
 
 renderCloseOn: html
@@ -565,18 +598,23 @@ renderCog
 
 renderContentOn: html
 	html div class: self cssClass; with: [
-      	self 
-        	renderSelectionOn:html;
-          	renderBindingOn: html;
-			renderCloseOn: html ]
+      	self renderLabelOn:html.
+		html div
+			id: self mainId;
+			with: [ self renderSelectedBindingOn: html ].
+		self renderCloseOn: html ]
 !
 
-renderSelectionOn: html
+renderLabelOn: html
 		html span 
         	class: 'selected'; 
             with: (self selectedBinding label ifNil: [ 'Action' ])
 !
 
+renderSelectedBindingOn: html
+	self selectedBinding renderOn: self html: html
+!
+
 renderStart
 	'#helper' asJQuery remove.
 
@@ -589,7 +627,7 @@ renderStart
 		valueWithTimeout: 2000
 ! !
 
-!HLKeyBinderHelper class methodsFor: 'instance creation'!
+!HLKeyBinderHelperWidget class methodsFor: 'instance creation'!
 
 on: aKeyBinder
 	^ self new
@@ -597,100 +635,115 @@ on: aKeyBinder
         yourself
 ! !
 
-Object subclass: #HLRepeatingKeyBindingHandler
-	instanceVariableNames: 'repeatInterval delay interval keyBindings widget isKeyCurrentlyPressed'
+Object subclass: #HLRepeatedKeyDownHandler
+	instanceVariableNames: 'repeatInterval delay interval keyBindings widget keyDown'
 	package: 'Helios-KeyBindings'!
-!HLRepeatingKeyBindingHandler commentStamp!
+!HLRepeatedKeyDownHandler commentStamp!
+I am responsible for handling repeated key down actions for widgets.
+
 ##Usage
 
-    (HLRepeatingKeyBindingHandler forWidget: aWidget)
-        whileKeyPressed: keyCode do: [xxxx];
-        whileKeyPressed: anotherKey do: [yyy];
-        rebind
+    (self on: aWidget)
+        whileKeyDown: 38 do: aBlock;
+        whileKeyDown: 40 do: anotherBlock;
+        bindKeys
+
+I perform an action block on a key press, wait for 300 ms and then preform the same action block every `repeatInterval` milliseconds until the key is released.!
 
-Performs an action on a key press, waits for 300 ms and then preforms the action every repeatInterval ms until the button is released!
+!HLRepeatedKeyDownHandler methodsFor: 'accessing'!
 
-!HLRepeatingKeyBindingHandler methodsFor: 'accessing'!
+keyBindings
+	^ keyBindings ifNil: [ keyBindings := Dictionary new ]
+!
 
-repeatInterval: aMillisecondIntegerValue 
-	repeatInterval := aMillisecondIntegerValue
+repeatInterval
+	^ repeatInterval ifNil: [ self defaultRepeatInterval ]
 !
 
-whileKeyPressed: aKey do: aBlock
-	keyBindings at: aKey put: aBlock
+repeatInterval: anInteger
+	repeatInterval := anInteger
+!
+
+widget
+	^ widget
 !
 
 widget: aWidget
 	widget := aWidget
 ! !
 
-!HLRepeatingKeyBindingHandler methodsFor: 'actions'!
+!HLRepeatedKeyDownHandler methodsFor: 'actions'!
 
-bindKeys
-	widget bindKeyDown: [ :e | self handleKeyDown: e ] up: [ :e | self handleKeyUp: e ]
+startRepeatingAction: aBlock
+	^ [ (self widget hasFocus)
+		ifTrue: [ aBlock value ]
+		ifFalse: [ self handleKeyUp ] ] valueWithInterval: self repeatInterval
 !
 
-delayBeforeStartingRepeatWithAction: action
-	^ [ interval := self startRepeatingAction: action ] valueWithTimeout: 300
-!
+whileKeyDown: aKey do: aBlock
+	self keyBindings at: aKey put: aBlock
+! !
 
-handleKeyUp
-	isKeyCurrentlyPressed := false.
-	interval ifNotNil: [ interval clearInterval ].
-	delay ifNotNil: [ delay clearTimeout ]
+!HLRepeatedKeyDownHandler methodsFor: 'binding'!
+
+bindKeys
+	self widget 
+		bindKeyDown: [ :e | self handleKeyDown: e ] 
+		keyUp: [ :e | self handleKeyUp ]
 !
 
 rebindKeys
-	self unbindKeys;
+	self 
+		unbindKeys;
 		bindKeys
 !
 
-startRepeatingAction: action
-	^ [ (widget hasFocus)
-		ifTrue: [ action value ]
-		ifFalse: [ self handleKeyUp ] ] valueWithInterval: repeatInterval
-!
-
 unbindKeys
-	widget unbindKeyDownUp
+	self widget unbindKeyDownKeyUp
+! !
+
+!HLRepeatedKeyDownHandler methodsFor: 'defaults'!
+
+defaultRepeatInterval
+	^ 70
 ! !
 
-!HLRepeatingKeyBindingHandler methodsFor: 'events-processing'!
+!HLRepeatedKeyDownHandler methodsFor: 'events handling'!
 
-handleKeyDown: e
-	 keyBindings keysAndValuesDo: [ :key :action | 
-		self ifKey: key wasPressedIn: e thenDo: action ]
+handleEvent: anEvent forKey: anInteger action: aBlock
+	(anEvent which = anInteger and: [ self isKeyDown not ])
+		ifTrue: [ self whileKeyDownDo: aBlock ]
 !
 
-handleKeyUp: e
-	isKeyCurrentlyPressed
-		ifTrue: [ self handleKeyUp ]
+handleKeyDown: anEvent
+	self keyBindings keysAndValuesDo: [ :key :action | 
+		self handleEvent: anEvent forKey: key action: action ]
 !
 
-ifKey: key wasPressedIn: e thenDo: action
-	(e which = key and: [ isKeyCurrentlyPressed = false ])
-		ifTrue: [  self whileTheKeyIsPressedDo: action ]
+handleKeyUp
+	self isKeyDown ifTrue: [
+		keyDown := false.
+		interval ifNotNil: [ interval clearInterval ].
+		delay ifNotNil: [ delay clearTimeout ] ]
 !
 
-whileTheKeyIsPressedDo: action
-	isKeyCurrentlyPressed := true.
-	action value.
-	delay := self delayBeforeStartingRepeatWithAction: action
+whileKeyDownDo: aBlock
+	keyDown := true.
+	aBlock value.
+	delay := [ interval := self startRepeatingAction: aBlock ] 
+		valueWithTimeout: 300
 ! !
 
-!HLRepeatingKeyBindingHandler methodsFor: 'initialization'!
+!HLRepeatedKeyDownHandler methodsFor: 'testing'!
 
-initialize 
-	super initialize.
-	keyBindings := Dictionary new.
-	isKeyCurrentlyPressed := false.
-	repeatInterval := 70.
+isKeyDown
+	^ keyDown ifNil: [ false ]
 ! !
 
-!HLRepeatingKeyBindingHandler class methodsFor: 'instance-creation'!
+!HLRepeatedKeyDownHandler class methodsFor: 'instance creation'!
 
-forWidget: aWidget
-	^self new
+on: aWidget
+	^ self new
 		widget: aWidget;
 		yourself
 ! !

+ 3 - 3
st/Helios-Workspace-Tests.st

@@ -6,8 +6,8 @@ TestCase subclass: #HLCodeWidgetTest
 !HLCodeWidgetTest methodsFor: 'tests'!
 
 testKeyMap
-"Key maps are a collection of associations."
-self assert: ( HLCodeWidget pcKeyMap isKindOf: HashedCollection ).
-self assert: ( HLCodeWidget macKeyMap isKindOf: HashedCollection ).
+	"Key maps are a collection of associations."
+	self assert: (HLCodeWidget pcKeyMap isKindOf: HashedCollection).
+	self assert: (HLCodeWidget macKeyMap isKindOf: HashedCollection)
 ! !
 

+ 4 - 0
st/Kernel-Collections.st

@@ -1466,6 +1466,10 @@ crlf
 	<return '\r\n'>
 !
 
+esc
+	^ self fromCharCode: 27
+!
+
 lf
 	<return '\n'>
 !

Some files were not shown because too many files changed in this diff