Thomas Vachuska
Committed by Simon Hunt

GUI -- Added global key bindings mechanism

 - 'T' now toggles theme (light/dark) -- mast CSS done, other CSS to do
 - 'ESC' now closes alerts box if it is open
 - Minor cleanup to topo2.js

Change-Id: I506a6add4299a6c03dcb717c33394ad94be26997
......@@ -72,9 +72,9 @@
<!-- Initialize the UI...-->
<script type="text/javascript">
var ONOS = $.onos({
comment: "configuration options",
comment: 'configuration options',
theme: 'light',
startVid: 'topo',
// startVid: 'sampleKeys',
trace: false
});
</script>
......
......@@ -23,8 +23,15 @@
#mast {
height: 36px;
padding: 4px;
background-color: #bbb;
vertical-align: baseline;
}
.light #mast {
background-color: #bbb;
box-shadow: 0px 2px 8px #777;
}
.dark #mast {
background-color: #444;
box-shadow: 0px 2px 8px #777;
}
......@@ -35,12 +42,18 @@
}
#mast span.title {
color: #369;
font-size: 14pt;
font-style: italic;
vertical-align: 12px;
}
.light #mast span.title {
color: #369;
}
.dark #mast span.title {
color: #78a;
}
#mast span.right {
padding-top: 8px;
padding-right: 16px;
......@@ -50,16 +63,31 @@
#mast span.radio {
font-size: 10pt;
margin: 4px 2px;
border: 1px dotted #222;
padding: 1px 6px;
color: #eee;
cursor: pointer;
}
.light #mast span.radio {
border: 1px dotted #222;
color: #eee;
}
.dark #mast span.radio {
border: 1px dotted #bbb;
color: #888;
}
#mast span.radio.active {
padding: 1px 6px;
font-weight: bold;
}
.light #mast span.radio.active {
background-color: #bbb;
border: 1px solid #eee;
padding: 1px 6px;
color: #666;
font-weight: bold;
}
.dark #mast span.radio.active {
background-color: #222;
border: 1px solid #eee;
color: #aaf;
}
......
......@@ -32,7 +32,7 @@ div.onosView.currentView {
display: block;
}
div#alerts {
#alerts {
display: none;
position: absolute;
z-index: 2000;
......@@ -45,21 +45,28 @@ div#alerts {
box-shadow: 4px 6px 12px #777;
}
div#alerts pre {
#alerts pre {
margin: 0.2em 6px;
}
div#alerts span.close {
#alerts span.close {
color: #6af;
float: right;
right: 2px;
cursor: pointer;
}
div#alerts span.close:hover {
#alerts span.close:hover {
color: #fff;
}
#alerts p.footnote {
text-align: right;
font-size: 8pt;
margin: 8px 0 0 0;
color: #66d;
}
/*
* ==============================================================
* END OF NEW ONOS.JS file
......
......@@ -37,23 +37,34 @@
var defaultOptions = {
trace: false,
theme: 'light',
startVid: defaultVid
};
// compute runtime settings
var settings = $.extend({}, defaultOptions, options);
// set the selected theme
d3.select('body').classed(settings.theme, true);
// internal state
var views = {},
current = {
view: null,
ctx: ''
ctx: '',
theme: settings.theme
},
built = false,
errorCount = 0,
keyHandler = {
fn: null,
map: {}
globalKeys: {},
maskedKeys: {},
viewKeys: {},
viewFn: null
},
alerts = {
open: false,
count: 0
};
// DOM elements etc.
......@@ -240,8 +251,8 @@
// detach radio buttons, key handlers, etc.
$('#mastRadio').children().detach();
keyHandler.fn = null;
keyHandler.map = {};
keyHandler.viewKeys = {};
keyHandler.viewFn = null;
}
// cache new view and context
......@@ -322,20 +333,74 @@
$mastRadio.node().appendChild(btnG.node());
}
function setupGlobalKeys() {
keyHandler.globalKeys = {
esc: escapeKey,
T: toggleTheme
};
// Masked keys are global key handlers that always return true.
// That is, the view will never see the event for that key.
keyHandler.maskedKeys = {
T: true
};
}
function escapeKey(view, key, code, ev) {
if (alerts.open) {
closeAlerts();
return true;
}
return false;
}
function toggleTheme(view, key, code, ev) {
var body = d3.select('body');
current.theme = (current.theme === 'light') ? 'dark' : 'light';
body.classed('light dark', false);
body.classed(current.theme, true);
return true;
}
function setKeyBindings(keyArg) {
var viewKeys,
masked = [];
if ($.isFunction(keyArg)) {
// set general key handler callback
keyHandler.fn = keyArg;
keyHandler.viewFn = keyArg;
} else {
// set specific key filter map
keyHandler.map = keyArg;
viewKeys = d3.map(keyArg).keys();
viewKeys.forEach(function (key) {
if (keyHandler.maskedKeys[key]) {
masked.push(' Key "' + key + '" is reserved');
}
});
if (masked.length) {
doAlert('WARNING...\n\nsetKeys():\n' + masked.join('\n'));
}
keyHandler.viewKeys = keyArg;
}
}
var alerts = {
open: false,
count: 0
};
function keyIn() {
var event = d3.event,
keyCode = event.keyCode,
key = whatKey(keyCode),
gcb = isF(keyHandler.globalKeys[key]),
vcb = isF(keyHandler.viewKeys[key]) || isF(keyHandler.viewFn);
// global callback?
if (gcb && gcb(current.view.token(), key, keyCode, event)) {
// if the event was 'handled', we are done
return;
}
// otherwise, let the view callback have a shot
if (vcb) {
vcb(current.view.token(), key, keyCode, event);
}
}
function createAlerts() {
var al = d3.select('#alerts')
......@@ -345,15 +410,16 @@
.text('X')
.on('click', closeAlerts);
al.append('pre');
al.append('p').attr('class', 'footnote')
.text('Press ESCAPE to close');
alerts.open = true;
alerts.count = 0;
}
function closeAlerts() {
d3.select('#alerts')
.style('display', 'none');
d3.select('#alerts span').remove();
d3.select('#alerts pre').remove();
.style('display', 'none')
.html('');
alerts.open = false;
}
......@@ -384,17 +450,6 @@
addAlert(msg);
}
function keyIn() {
var event = d3.event,
keyCode = event.keyCode,
key = whatKey(keyCode),
cb = isF(keyHandler.map[key]) || isF(keyHandler.fn);
if (cb) {
cb(current.view.token(), key, keyCode, event);
}
}
function resize(e) {
d3.selectAll('.onosView').call(setViewDimensions);
// allow current view to react to resize event...
......@@ -683,6 +738,7 @@
$(window).on('resize', resize);
d3.select('body').on('keydown', keyIn);
setupGlobalKeys();
// Invoke hashchange callback to navigate to content
// indicated by the window location hash.
......
......@@ -117,7 +117,7 @@
S: injectStartupEvents, // TODO: remove (testing only)
space: injectTestEvent, // TODO: remove (testing only)
B: toggleBg,
B: toggleBg, // TODO: do we really need this?
L: cycleLabels,
P: togglePorts,
U: unpin,
......@@ -135,7 +135,6 @@
webSock,
labelIdx = 0,
//selected = {},
selectOrder = [],
selections = {},
......@@ -788,8 +787,9 @@
if (d.class === 'device') {
d.fixed = true;
d3.select(self).classed('fixed', true);
tellServerCoords(d);
// TODO: send new [x,y] back to server, via websocket.
if (config.useLiveData) {
tellServerCoords(d);
}
}
}
......