Simon Hunt

GUI - Rebuilt Quick Help layout to allow multi-column key binding section.

Change-Id: Icada50c695ce60c8cbedb38d86434a842d935e77
...@@ -417,19 +417,23 @@ ...@@ -417,19 +417,23 @@
417 } 417 }
418 418
419 function setupGlobalKeys() { 419 function setupGlobalKeys() {
420 - keyHandler.globalKeys = { 420 + $.extend(keyHandler, {
421 - slash: [quickHelp, 'Show / hide Quick Help'], 421 + globalKeys: {
422 - backSlash: [quickHelp, 'Show / hide Quick Help'], 422 + backSlash: [quickHelp, 'Show / hide Quick Help'],
423 - esc: [escapeKey, 'Dismiss dialog or cancel selections'], 423 + slash: [quickHelp, 'Show / hide Quick Help'],
424 - T: [toggleTheme, "Toggle theme"] 424 + esc: [escapeKey, 'Dismiss dialog or cancel selections'],
425 - }; 425 + T: [toggleTheme, "Toggle theme"]
426 - // Masked keys are global key handlers that always return true. 426 + },
427 - // That is, the view will never see the event for that key. 427 + globalFormat: ['backSlash', 'slash', 'esc', 'T'],
428 - keyHandler.maskedKeys = { 428 +
429 - slash: true, 429 + // Masked keys are global key handlers that always return true.
430 - backSlash: true, 430 + // That is, the view will never see the event for that key.
431 - T: true 431 + maskedKeys: {
432 - }; 432 + slash: true,
433 + backSlash: true,
434 + T: true
435 + }
436 + });
433 } 437 }
434 438
435 function quickHelp(view, key, code, ev) { 439 function quickHelp(view, key, code, ev) {
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
26 26
27 #quickhelp svg { 27 #quickhelp svg {
28 position: absolute; 28 position: absolute;
29 - bottom: 40px; 29 + top: 180px;
30 opacity: 1; 30 opacity: 1;
31 } 31 }
32 32
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
36 } 36 }
37 37
38 #quickhelp svg text.title { 38 #quickhelp svg text.title {
39 - font-size: 8pt; 39 + font-size: 10pt;
40 font-style: italic; 40 font-style: italic;
41 text-anchor: middle; 41 text-anchor: middle;
42 fill: #999; 42 fill: #999;
...@@ -46,23 +46,21 @@ ...@@ -46,23 +46,21 @@
46 fill: white; 46 fill: white;
47 } 47 }
48 48
49 -#quickhelp svg g.keyItem line { 49 +#quickhelp svg g line.qhrowsep {
50 stroke: #888; 50 stroke: #888;
51 stroke-dasharray: 2 2; 51 stroke-dasharray: 2 2;
52 } 52 }
53 53
54 #quickhelp svg text { 54 #quickhelp svg text {
55 - font-size: 5pt; 55 + font-size: 7pt;
56 alignment-baseline: middle; 56 alignment-baseline: middle;
57 } 57 }
58 58
59 #quickhelp svg text.key { 59 #quickhelp svg text.key {
60 - font-size: 5pt;
61 fill: #add; 60 fill: #add;
62 } 61 }
63 62
64 #quickhelp svg text.desc { 63 #quickhelp svg text.desc {
65 - font-size: 5pt;
66 fill: #ddd; 64 fill: #ddd;
67 } 65 }
68 66
......
...@@ -26,29 +26,35 @@ ...@@ -26,29 +26,35 @@
26 (function (onos){ 26 (function (onos){
27 'use strict'; 27 'use strict';
28 28
29 - // API's
30 - var api = onos.api;
31 -
32 // Config variables 29 // Config variables
33 var w = '100%', 30 var w = '100%',
34 h = '80%', 31 h = '80%',
35 fade = 500, 32 fade = 500,
36 vb = '-200 0 400 400'; 33 vb = '-200 0 400 400';
37 34
35 + // layout configuration
36 + var pad = 10,
37 + offy = 45,
38 + sepYDelta = 20,
39 + colXDelta = 16,
40 + yTextSpc = 12,
41 + offDesc = 8;
42 +
38 // State variables 43 // State variables
39 - var data = []; 44 + var data = [],
45 + yCount;
40 46
41 // DOM elements and the like 47 // DOM elements and the like
42 var qhdiv = d3.select('#quickhelp'), 48 var qhdiv = d3.select('#quickhelp'),
43 svg = qhdiv.select('svg'), 49 svg = qhdiv.select('svg'),
44 - pane, 50 + pane, rect, items;
45 - rect,
46 - items,
47 - keyAgg;
48 51
49 // General functions 52 // General functions
50 - function isA(a) { 53 + function isA(a) { return $.isArray(a) ? a : null; }
51 - return $.isArray(a) ? a : null; 54 + function isS(s) { return typeof s === 'string'; }
55 +
56 + function cap(s) {
57 + return s.replace(/^[a-z]/, function (m) { return m.toUpperCase(); });
52 } 58 }
53 59
54 var keyDisp = { 60 var keyDisp = {
...@@ -63,144 +69,212 @@ ...@@ -63,144 +69,212 @@
63 downArrow: 'D-arrow' 69 downArrow: 'D-arrow'
64 }; 70 };
65 71
66 - function cap(s) {
67 - return s.replace(/^[a-z]/, function (m) { return m.toUpperCase(); });
68 - }
69 -
70 function mkKeyDisp(id) { 72 function mkKeyDisp(id) {
71 var v = keyDisp[id] || id; 73 var v = keyDisp[id] || id;
72 return cap(v); 74 return cap(v);
73 } 75 }
74 76
75 - // layout configuration 77 + function addSeparator(el, i) {
76 - var pad = 8, 78 + var y = sepYDelta/2 - 5;
77 - offy = 45, 79 + el.append('line')
78 - dy = 10, 80 + .attr({ 'class': 'qhrowsep', x1: 0, y1: y, x2: 0, y2: y });
79 - offDesc = 8; 81 + }
82 +
83 + function addContent(el, data, ri) {
84 + var xCount = 0,
85 + clsPfx = 'qh-r' + ri + '-c';
86 +
87 + function addColumn(el, c, i) {
88 + var cls = clsPfx + i,
89 + oy = 0,
90 + aggKey = el.append('g').attr('visibility', 'hidden'),
91 + gcol = el.append('g').attr({
92 + 'class': cls,
93 + transform: translate(xCount, 0)
94 + });
95 +
96 + c.forEach(function (j) {
97 + var k = j[0],
98 + v = j[1];
99 +
100 + if (k !== '-') {
101 + aggKey.append('text').text(k);
102 +
103 + gcol.append('text').text(k)
104 + .attr({
105 + 'class': 'key',
106 + y: oy
107 + });
108 + gcol.append('text').text(v)
109 + .attr({
110 + 'class': 'desc',
111 + y: oy
112 + });
113 + }
114 +
115 + oy += yTextSpc;
116 + });
117 +
118 + // adjust position of descriptions, based on widest key
119 + var kbox = aggKey.node().getBBox(),
120 + ox = kbox.width + offDesc;
121 + gcol.selectAll('.desc').attr('x', ox);
122 + aggKey.remove();
123 +
124 + // now update x-offset for next column
125 + var bbox = gcol.node().getBBox();
126 + xCount += bbox.width + colXDelta;
127 + }
128 +
129 + data.forEach(function (d, i) {
130 + addColumn(el, d, i);
131 + });
132 +
133 + // finally, return the height of the row..
134 + return el.node().getBBox().height;
135 + }
80 136
81 - // D3 magic
82 function updateKeyItems() { 137 function updateKeyItems() {
83 - var keyItems = items.selectAll('.keyItem') 138 + var rows = items.selectAll('.qhRow').data(data);
84 - .data(data);
85 139
86 - var entering = keyItems.enter() 140 + yCount = offy;
141 +
142 + var entering = rows.enter()
87 .append('g') 143 .append('g')
88 .attr({ 144 .attr({
89 - id: function (d) { return d.id; }, 145 + 'class': 'qhrow'
90 - class: 'keyItem'
91 }); 146 });
92 147
93 - entering.each(function (d, i) { 148 + entering.each(function (r, i) {
94 var el = d3.select(this), 149 var el = d3.select(this),
95 - y = offy + dy * i; 150 + sep = r.type === 'sep',
151 + dy;
152 +
153 + el.attr('transform', translate(0, yCount));
96 154
97 - if (d.id[0] === '_') { 155 + if (sep) {
98 - el.append('line') 156 + addSeparator(el, i);
99 - .attr({ x1: 0, y1: y, x2: 1, y2: y}); 157 + yCount += sepYDelta;
100 } else { 158 } else {
101 - el.append('text') 159 + dy = addContent(el, r.data, i);
102 - .text(d.key) 160 + yCount += dy;
103 - .attr({
104 - class: 'key',
105 - x: 0,
106 - y: y
107 - });
108 - // NOTE: used for sizing column width...
109 - keyAgg.append('text').text(d.key).attr('class', 'key');
110 -
111 - el.append('text')
112 - .text(d.desc)
113 - .attr({
114 - class: 'desc',
115 - x: offDesc,
116 - y: y
117 - });
118 } 161 }
119 }); 162 });
120 163
121 - var kbox = keyAgg.node().getBBox(); 164 + // size the backing rectangle
122 - items.selectAll('.desc').attr('x', kbox.width + offDesc); 165 + var ibox = items.node().getBBox(),
166 + paneW = ibox.width + pad * 2,
167 + paneH = ibox.height + offy;
123 168
124 - var box = items.node().getBBox(), 169 + items.selectAll('.qhrowsep').attr('x2', ibox.width);
125 - paneW = box.width + pad * 2,
126 - paneH = box.height + offy;
127 -
128 - items.selectAll('line').attr('x2', box.width);
129 items.attr('transform', translate(-paneW/2, -pad)); 170 items.attr('transform', translate(-paneW/2, -pad));
130 rect.attr({ 171 rect.attr({
131 width: paneW, 172 width: paneW,
132 height: paneH, 173 height: paneH,
133 transform: translate(-paneW/2-pad, 0) 174 transform: translate(-paneW/2-pad, 0)
134 }); 175 });
176 +
135 } 177 }
136 178
137 function translate(x, y) { 179 function translate(x, y) {
138 return 'translate(' + x + ',' + y + ')'; 180 return 'translate(' + x + ',' + y + ')';
139 } 181 }
140 182
183 + function checkFmt(fmt) {
184 + // should be a single array of keys,
185 + // or array of arrays of keys (one per column).
186 + // return null if there is a problem.
187 + var a = isA(fmt),
188 + n = a && a.length,
189 + ns = 0,
190 + na = 0;
191 +
192 + if (n) {
193 + // it is an array which has some content
194 + a.forEach(function (d) {
195 + isA(d) && na++;
196 + isS(d) && ns++;
197 + });
198 + if (na === n || ns === n) {
199 + // all arrays or all strings...
200 + return a;
201 + }
202 + }
203 + return null;
204 + }
205 +
206 + function buildBlock(map, fmt) {
207 + var b = [];
208 + fmt.forEach(function (k) {
209 + var v = map.get(k),
210 + a = isA(v),
211 + d = (a && a[1]);
212 +
213 + // '-' marks a separator; d is the description
214 + if (k === '-' || d) {
215 + b.push([mkKeyDisp(k), d]);
216 + }
217 + });
218 + return b;
219 + }
220 +
221 + function emptyRow() {
222 + return { type: 'row', data: []};
223 + }
224 +
225 + function mkArrRow(fmt) {
226 + var d = emptyRow();
227 + d.data.push(fmt);
228 + return d;
229 + }
230 +
231 + function mkColumnarRow(map, fmt) {
232 + var d = emptyRow();
233 + fmt.forEach(function (a) {
234 + d.data.push(buildBlock(map, a));
235 + });
236 + return d;
237 + }
238 +
239 + function mkMapRow(map, fmt) {
240 + var d = emptyRow();
241 + d.data.push(buildBlock(map, fmt));
242 + return d;
243 + }
244 +
245 + function addRow(row) {
246 + var d = row || { type: 'sep' };
247 + data.push(d);
248 + }
249 +
141 function aggregateData(bindings) { 250 function aggregateData(bindings) {
142 var hf = '_helpFormat', 251 var hf = '_helpFormat',
143 gmap = d3.map(bindings.globalKeys), 252 gmap = d3.map(bindings.globalKeys),
253 + gfmt = bindings.globalFormat,
144 vmap = d3.map(bindings.viewKeys), 254 vmap = d3.map(bindings.viewKeys),
145 - fmt = vmap.get(hf),
146 vgest = bindings.viewGestures, 255 vgest = bindings.viewGestures,
147 - gkeys = gmap.keys(), 256 + vfmt, vkeys;
148 - vkeys,
149 - sep = 0;
150 257
151 // filter out help format entry 258 // filter out help format entry
259 + vfmt = checkFmt(vmap.get(hf));
152 vmap.remove(hf); 260 vmap.remove(hf);
153 - vkeys = vmap.keys(),
154 261
155 - gkeys.sort(); 262 + // if bad (or no) format, fallback to sorted keys
156 - vkeys.sort(); 263 + if (!vfmt) {
264 + vkeys = vmap.keys();
265 + vfmt = vkeys.sort();
266 + }
157 267
158 data = []; 268 data = [];
159 - gkeys.forEach(function (k) {
160 - addItem('glob', k, gmap.get(k));
161 - });
162 - addItem('sep');
163 - vkeys.forEach(function (k) {
164 - addItem('view', k, vmap.get(k));
165 - });
166 - addItem('sep');
167 - vgest.forEach(function (g) {
168 - if (g.length === 2) {
169 - addItem('gest', g[0], g[1]);
170 - }
171 - });
172 269
173 - 270 + addRow(mkMapRow(gmap, gfmt));
174 - function addItem(type, k, d) { 271 + addRow();
175 - var id = type + '-' + k, 272 + addRow(isA(vfmt[0]) ? mkColumnarRow(vmap, vfmt) : mkMapRow(vmap, vfmt));
176 - a = isA(d), 273 + addRow();
177 - desc = a && a[1]; 274 + addRow(mkArrRow(vgest));
178 -
179 - if (type === 'sep') {
180 - data.push({
181 - id: '_' + sep++,
182 - type: type
183 - });
184 - } else if (type === 'gest') {
185 - data.push({
186 - id: id,
187 - type: type,
188 - key: k,
189 - desc: d
190 - });
191 - } else if (desc) {
192 - data.push(
193 - {
194 - id: id,
195 - type: type,
196 - key: mkKeyDisp(k),
197 - desc: desc
198 - }
199 - );
200 - }
201 - }
202 } 275 }
203 276
277 +
204 function popBind(bindings) { 278 function popBind(bindings) {
205 pane = svg.append('g') 279 pane = svg.append('g')
206 .attr({ 280 .attr({
...@@ -220,8 +294,6 @@ ...@@ -220,8 +294,6 @@
220 }); 294 });
221 295
222 items = pane.append('g'); 296 items = pane.append('g');
223 - keyAgg = pane.append('g').style('visibility', 'hidden');
224 -
225 aggregateData(bindings); 297 aggregateData(bindings);
226 updateKeyItems(); 298 updateKeyItems();
227 299
......