blob: 5ef4369931a5968722c5e32e9589bccbc8ec04b9 [file] [log] [blame]
Simon Hunt639dc662015-02-18 14:19:20 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Simon Hunt639dc662015-02-18 14:19:20 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 ONOS GUI -- Layer -- Quick Help Service
19
20 Provides a mechanism to display key bindings and mouse gesture notes.
21 */
22(function () {
23 'use strict';
24
25 // injected references
Simon Hunta6ab9f02017-07-11 11:45:50 -070026 var $log, fs, sus, ls;
Simon Hunt639dc662015-02-18 14:19:20 -080027
28 // configuration
29 var defaultSettings = {
30 fade: 500
31 },
32 w = '100%',
33 h = '80%',
34 vbox = '-200 0 400 400',
35 pad = 10,
36 offy = 45,
37 sepYDelta = 20,
38 colXDelta = 16,
39 yTextSpc = 12,
40 offDesc = 8;
41
42 // internal state
43 var settings,
44 data = [],
45 yCount;
46
47 // DOM elements
48 var qhdiv, svg, pane, rect, items;
49
50 // key-logical-name to key-display lookup..
51 var keyDisp = {
52 equals: '=',
Simon Hunt639dc662015-02-18 14:19:20 -080053 slash: '/',
54 backSlash: '\\',
55 backQuote: '`',
56 leftArrow: 'L-arrow',
57 upArrow: 'U-arrow',
58 rightArrow: 'R-arrow',
59 downArrow: 'D-arrow'
60 };
61
Bri Prebilic Cole2efc7152015-04-29 15:47:06 -070062 // list of needed bindings to use in aggregateData
63 var neededBindings = [
64 'globalKeys', 'globalFormat', 'viewKeys', 'viewGestures'
65 ];
66
Simon Hunt639dc662015-02-18 14:19:20 -080067 // ===========================================
68 // === Function Definitions ===
69
Simon Hunt639dc662015-02-18 14:19:20 -080070 function mkKeyDisp(id) {
71 var v = keyDisp[id] || id;
Simon Hunt27a5cc82015-02-19 14:49:55 -080072 return fs.cap(v);
Simon Hunt639dc662015-02-18 14:19:20 -080073 }
74
75 function addSeparator(el, i) {
76 var y = sepYDelta/2 - 5;
77 el.append('line')
78 .attr({ 'class': 'qhrowsep', x1: 0, y1: y, x2: 0, y2: y });
79 }
80
81 function addContent(el, data, ri) {
82 var xCount = 0,
83 clsPfx = 'qh-r' + ri + '-c';
84
85 function addColumn(el, c, i) {
86 var cls = clsPfx + i,
87 oy = 0,
88 aggKey = el.append('g').attr('visibility', 'hidden'),
89 gcol = el.append('g').attr({
90 'class': cls,
91 transform: sus.translate(xCount, 0)
92 });
93
94 c.forEach(function (j) {
95 var k = j[0],
96 v = j[1];
97
98 if (k !== '-') {
99 aggKey.append('text').text(k);
100
101 gcol.append('text').text(k)
102 .attr({
103 'class': 'key',
104 y: oy
105 });
106 gcol.append('text').text(v)
107 .attr({
108 'class': 'desc',
109 y: oy
110 });
111 }
112
113 oy += yTextSpc;
114 });
115
116 // adjust position of descriptions, based on widest key
117 var kbox = aggKey.node().getBBox(),
118 ox = kbox.width + offDesc;
119 gcol.selectAll('.desc').attr('x', ox);
120 aggKey.remove();
121
122 // now update x-offset for next column
123 var bbox = gcol.node().getBBox();
124 xCount += bbox.width + colXDelta;
125 }
126
127 data.forEach(function (d, i) {
128 addColumn(el, d, i);
129 });
130
131 // finally, return the height of the row..
132 return el.node().getBBox().height;
133 }
134
135 function updateKeyItems() {
136 var rows = items.selectAll('.qhRow').data(data);
137
138 yCount = offy;
139
140 var entering = rows.enter()
141 .append('g')
142 .attr({
143 'class': 'qhrow'
144 });
145
146 entering.each(function (r, i) {
147 var el = d3.select(this),
148 sep = r.type === 'sep',
149 dy;
150
151 el.attr('transform', sus.translate(0, yCount));
152
153 if (sep) {
154 addSeparator(el, i);
155 yCount += sepYDelta;
156 } else {
157 dy = addContent(el, r.data, i);
158 yCount += dy;
159 }
160 });
161
162 // size the backing rectangle
163 var ibox = items.node().getBBox(),
164 paneW = ibox.width + pad * 2,
165 paneH = ibox.height + offy;
166
167 items.selectAll('.qhrowsep').attr('x2', ibox.width);
168 items.attr('transform', sus.translate(-paneW/2, -pad));
169 rect.attr({
170 width: paneW,
171 height: paneH,
172 transform: sus.translate(-paneW/2-pad, 0)
173 });
174
175 }
176
177 function checkFmt(fmt) {
178 // should be a single array of keys,
179 // or array of arrays of keys (one per column).
180 // return null if there is a problem.
181 var a = fs.isA(fmt),
182 n = a && a.length,
183 ns = 0,
184 na = 0;
185
186 if (n) {
187 // it is an array which has some content
188 a.forEach(function (d) {
189 fs.isA(d) && na++;
190 fs.isS(d) && ns++;
191 });
192 if (na === n || ns === n) {
193 // all arrays or all strings...
194 return a;
195 }
196 }
197 return null;
198 }
199
200 function buildBlock(map, fmt) {
201 var b = [];
202 fmt.forEach(function (k) {
203 var v = map.get(k),
204 a = fs.isA(v),
Simon Hunta6ab9f02017-07-11 11:45:50 -0700205 d = (a && a[1]),
206 dfn = fs.isF(d),
207 dval = (dfn && dfn()) || d;
Simon Hunt639dc662015-02-18 14:19:20 -0800208
209 // '-' marks a separator; d is the description
Simon Hunta6ab9f02017-07-11 11:45:50 -0700210 if (k === '-' || dval) {
211 b.push([mkKeyDisp(k), dval]);
Simon Hunt639dc662015-02-18 14:19:20 -0800212 }
213 });
214 return b;
215 }
216
217 function emptyRow() {
218 return { type: 'row', data: [] };
219 }
220
221 function mkArrRow(fmt) {
222 var d = emptyRow();
223 d.data.push(fmt);
224 return d;
225 }
226
227 function mkColumnarRow(map, fmt) {
228 var d = emptyRow();
229 fmt.forEach(function (a) {
230 d.data.push(buildBlock(map, a));
231 });
232 return d;
233 }
234
235 function mkMapRow(map, fmt) {
236 var d = emptyRow();
237 d.data.push(buildBlock(map, fmt));
238 return d;
239 }
240
241 function addRow(row) {
242 var d = row || { type: 'sep' };
243 data.push(d);
244 }
245
246 function aggregateData(bindings) {
247 var hf = '_helpFormat',
248 gmap = d3.map(bindings.globalKeys),
249 gfmt = bindings.globalFormat,
250 vmap = d3.map(bindings.viewKeys),
251 vgest = bindings.viewGestures,
252 vfmt, vkeys;
253
254 // filter out help format entry
255 vfmt = checkFmt(vmap.get(hf));
256 vmap.remove(hf);
257
258 // if bad (or no) format, fallback to sorted keys
259 if (!vfmt) {
260 vkeys = vmap.keys();
261 vfmt = vkeys.sort();
262 }
263
264 data = [];
265
266 addRow(mkMapRow(gmap, gfmt));
267 addRow();
268 addRow(fs.isA(vfmt[0]) ? mkColumnarRow(vmap, vfmt) : mkMapRow(vmap, vfmt));
269 addRow();
270 addRow(mkArrRow(vgest));
271 }
272
Simon Hunta6ab9f02017-07-11 11:45:50 -0700273 function qhlion_title() {
274 var lion = ls.bundle('core.fw.QuickHelp');
275 return lion('qh_title');
276 }
Simon Hunt639dc662015-02-18 14:19:20 -0800277
278 function popBind(bindings) {
279 pane = svg.append('g')
280 .attr({
281 class: 'help',
282 opacity: 0
283 });
284
285 rect = pane.append('rect')
286 .attr('rx', 8);
287
288 pane.append('text')
Simon Hunta6ab9f02017-07-11 11:45:50 -0700289 .text(qhlion_title())
Simon Hunt639dc662015-02-18 14:19:20 -0800290 .attr({
291 class: 'title',
292 dy: '1.2em',
293 transform: sus.translate(-pad,0)
294 });
295
296 items = pane.append('g');
297 aggregateData(bindings);
298 updateKeyItems();
299
300 _fade(1);
301 }
302
303 function fadeBindings() {
304 _fade(0);
305 }
306
307 function _fade(o) {
308 svg.selectAll('g.help')
309 .transition()
310 .duration(settings.fade)
311 .attr('opacity', o);
312 }
313
314 function addSvg() {
315 svg = qhdiv.append('svg')
316 .attr({
317 width: w,
318 height: h,
319 viewBox: vbox
320 });
321 }
322
323 function removeSvg() {
324 svg.transition()
325 .delay(settings.fade + 20)
326 .remove();
327 }
328
Bri Prebilic Cole2efc7152015-04-29 15:47:06 -0700329 function goodBindings(bindings) {
330 var warnPrefix = 'Quickhelp Service: showQuickHelp(), ';
331 if (!bindings || !fs.isO(bindings) || fs.isEmptyObject(bindings)) {
332 $log.warn(warnPrefix + 'invalid bindings object');
333 return false;
334 }
335 if (!(neededBindings.every(function (key) { return key in bindings; }))) {
336 $log.warn(
337 warnPrefix +
338 'needed bindings for help panel not provided:',
339 neededBindings
340 );
341 return false
342 }
343 return true;
344 }
Simon Hunt639dc662015-02-18 14:19:20 -0800345
346 // ===========================================
347 // === Module Definition ===
348
349 angular.module('onosLayer')
350 .factory('QuickHelpService',
Simon Hunta6ab9f02017-07-11 11:45:50 -0700351 ['$log', 'FnService', 'SvgUtilService', 'LionService',
Simon Hunt639dc662015-02-18 14:19:20 -0800352
Simon Hunta6ab9f02017-07-11 11:45:50 -0700353 function (_$log_, _fs_, _sus_, _ls_) {
Simon Hunt639dc662015-02-18 14:19:20 -0800354 $log = _$log_;
355 fs = _fs_;
356 sus = _sus_;
Simon Hunta6ab9f02017-07-11 11:45:50 -0700357 ls = _ls_;
Simon Hunt639dc662015-02-18 14:19:20 -0800358
359 function initQuickHelp(opts) {
Bri Prebilic Cole2efc7152015-04-29 15:47:06 -0700360 settings = angular.extend({}, defaultSettings, fs.isO(opts));
Simon Hunt639dc662015-02-18 14:19:20 -0800361 qhdiv = d3.select('#quickhelp');
362 }
363
364 function showQuickHelp(bindings) {
365 svg = qhdiv.select('svg');
Bri Prebilic Cole2efc7152015-04-29 15:47:06 -0700366 if (!goodBindings(bindings)) {
367 return null;
368 }
Simon Hunt639dc662015-02-18 14:19:20 -0800369 if (svg.empty()) {
370 addSvg();
371 popBind(bindings);
372 } else {
373 hideQuickHelp();
374 }
375 }
376
377 function hideQuickHelp() {
378 svg = qhdiv.select('svg');
379 if (!svg.empty()) {
380 fadeBindings();
381 removeSvg();
382 return true;
383 }
384 return false;
385 }
386
387 return {
388 initQuickHelp: initQuickHelp,
389 showQuickHelp: showQuickHelp,
390 hideQuickHelp: hideQuickHelp
391 };
392 }]);
393
394}());