blob: c1da4b40dea2cfcca740ee42ba624a2a8c1dd14e [file] [log] [blame]
Simon Hunt1eecfa22014-12-16 14:46:29 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2014-present Open Networking Laboratory
Simon Hunt1eecfa22014-12-16 14:46:29 -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/*
Simon Huntdc6362a2014-12-18 19:55:23 -080018 ONOS GUI -- Util -- Key Handler Service
Simon Hunt1eecfa22014-12-16 14:46:29 -080019 */
Simon Huntdc6362a2014-12-18 19:55:23 -080020(function () {
Simon Hunt1eecfa22014-12-16 14:46:29 -080021 'use strict';
22
23 // references to injected services
Simon Huntf4ef6dd2016-02-03 17:05:14 -080024 var $log, $timeout, fs, ts, ns, ee, qhs;
Simon Hunt1eecfa22014-12-16 14:46:29 -080025
26 // internal state
Simon Hunt36fc15c2015-02-12 17:02:58 -080027 var enabled = true,
Simon Huntd5579252015-10-06 15:09:14 -070028 globalEnabled = true,
Simon Hunt36fc15c2015-02-12 17:02:58 -080029 keyHandler = {
Simon Hunt1eecfa22014-12-16 14:46:29 -080030 globalKeys: {},
31 maskedKeys: {},
Simon Hunt5198f082016-02-04 13:41:17 -080032 dialogKeys: {},
Simon Hunt1eecfa22014-12-16 14:46:29 -080033 viewKeys: {},
34 viewFn: null,
35 viewGestures: []
Simon Hunt9f5e97d2015-12-11 10:32:09 -080036 },
Simon Huntf4ef6dd2016-02-03 17:05:14 -080037 seq = {},
38 matching = false,
39 matched = '',
40 lookup;
Simon Hunt1eecfa22014-12-16 14:46:29 -080041
Simon Huntf4ef6dd2016-02-03 17:05:14 -080042 function matchSeq(key) {
43 if (!matching && key === 'shift') {
44 matching = true;
Thomas Vachuskafe1f01a2015-12-10 23:56:02 -080045 return true;
Simon Hunt9f5e97d2015-12-11 10:32:09 -080046 }
Simon Huntf4ef6dd2016-02-03 17:05:14 -080047 if (matching) {
48 matched += key;
49 lookup = fs.trieLookup(seq, matched);
50 if (lookup === -1) {
51 return true;
52 }
53 matching = false;
54 matched = '';
55 if (!lookup) {
56 return;
57 }
58 ee.cluck(lookup);
59 return true;
Thomas Vachuskafe1f01a2015-12-10 23:56:02 -080060 }
Thomas Vachuskafe1f01a2015-12-10 23:56:02 -080061 }
62
Simon Hunt1eecfa22014-12-16 14:46:29 -080063 function whatKey(code) {
64 switch (code) {
Simon Hunt18d4bc82016-01-08 14:09:35 -080065 case 8: return 'delete';
66 case 9: return 'tab';
Simon Hunt1eecfa22014-12-16 14:46:29 -080067 case 13: return 'enter';
68 case 16: return 'shift';
69 case 17: return 'ctrl';
70 case 18: return 'alt';
71 case 27: return 'esc';
72 case 32: return 'space';
73 case 37: return 'leftArrow';
74 case 38: return 'upArrow';
75 case 39: return 'rightArrow';
76 case 40: return 'downArrow';
77 case 91: return 'cmdLeft';
78 case 93: return 'cmdRight';
Simon Hunt18d4bc82016-01-08 14:09:35 -080079 case 186: return 'semicolon';
Simon Hunt1eecfa22014-12-16 14:46:29 -080080 case 187: return 'equals';
Simon Hunt90dcc3e2015-03-25 15:01:27 -070081 case 188: return 'comma';
Simon Hunt1eecfa22014-12-16 14:46:29 -080082 case 189: return 'dash';
Simon Hunt90dcc3e2015-03-25 15:01:27 -070083 case 190: return 'dot';
Simon Hunt1eecfa22014-12-16 14:46:29 -080084 case 191: return 'slash';
85 case 192: return 'backQuote';
Simon Hunt18d4bc82016-01-08 14:09:35 -080086 case 219: return 'openBracket';
Simon Hunt1eecfa22014-12-16 14:46:29 -080087 case 220: return 'backSlash';
Simon Hunt18d4bc82016-01-08 14:09:35 -080088 case 221: return 'closeBracket';
89 case 222: return 'quote';
Simon Hunt1eecfa22014-12-16 14:46:29 -080090 default:
91 if ((code >= 48 && code <= 57) ||
92 (code >= 65 && code <= 90)) {
93 return String.fromCharCode(code);
94 } else if (code >= 112 && code <= 123) {
95 return 'F' + (code - 111);
96 }
97 return '.';
98 }
99 }
100
Fan Cheng12ed9af2016-06-17 15:04:27 +0800101 function textFieldInput() {
102 var t = d3.event.target.tagName.toLowerCase();
103 return t === 'input' || t === 'textarea';
104 }
105
Simon Hunt1eecfa22014-12-16 14:46:29 -0800106 function keyIn() {
Fan Cheng12ed9af2016-06-17 15:04:27 +0800107 if (textFieldInput()) {
108 return;
109 }
Simon Hunt1eecfa22014-12-16 14:46:29 -0800110 var event = d3.event,
111 keyCode = event.keyCode,
112 key = whatKey(keyCode),
113 kh = keyHandler,
114 gk = kh.globalKeys[key],
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800115 gcb = fs.isF(gk) || (fs.isA(gk) && fs.isF(gk[0])),
Simon Hunt5198f082016-02-04 13:41:17 -0800116 dk = kh.dialogKeys[key],
117 dcb = fs.isF(dk),
Simon Hunt1eecfa22014-12-16 14:46:29 -0800118 vk = kh.viewKeys[key],
Simon Hunt09060142015-03-18 20:23:32 -0700119 kl = fs.isF(kh.viewKeys._keyListener),
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800120 vcb = fs.isF(vk) || (fs.isA(vk) && fs.isF(vk[0])) || fs.isF(kh.viewFn),
Simon Hunt1bf1e8c2015-04-08 10:12:43 -0700121 token = 'keyev'; // indicate this was a key-pressed event
Simon Hunt1eecfa22014-12-16 14:46:29 -0800122
Simon Huntdc6362a2014-12-18 19:55:23 -0800123 d3.event.stopPropagation();
124
Simon Hunt36fc15c2015-02-12 17:02:58 -0800125 if (enabled) {
Simon Huntf4ef6dd2016-02-03 17:05:14 -0800126 if (matchSeq(key)) return;
Thomas Vachuskafe1f01a2015-12-10 23:56:02 -0800127
Simon Hunt36fc15c2015-02-12 17:02:58 -0800128 // global callback?
129 if (gcb && gcb(token, key, keyCode, event)) {
130 // if the event was 'handled', we are done
131 return;
132 }
Simon Hunt5198f082016-02-04 13:41:17 -0800133 // dialog callback?
134 if (dcb) {
135 dcb(token, key, keyCode, event);
136 // assume dialog handled the event
137 return;
138 }
Simon Hunt36fc15c2015-02-12 17:02:58 -0800139 // otherwise, let the view callback have a shot
140 if (vcb) {
141 vcb(token, key, keyCode, event);
142 }
Simon Hunt09060142015-03-18 20:23:32 -0700143 if (kl) {
144 kl(key);
145 }
Simon Hunt1eecfa22014-12-16 14:46:29 -0800146 }
147 }
148
149 function setupGlobalKeys() {
Simon Hunt404f6b22015-01-21 14:00:56 -0800150 angular.extend(keyHandler, {
Simon Hunt1eecfa22014-12-16 14:46:29 -0800151 globalKeys: {
152 backSlash: [quickHelp, 'Show / hide Quick Help'],
153 slash: [quickHelp, 'Show / hide Quick Help'],
154 esc: [escapeKey, 'Dismiss dialog or cancel selections'],
155 T: [toggleTheme, "Toggle theme"]
156 },
157 globalFormat: ['backSlash', 'slash', 'esc', 'T'],
158
159 // Masked keys are global key handlers that always return true.
160 // That is, the view will never see the event for that key.
161 maskedKeys: {
162 slash: true,
163 backSlash: true,
164 T: true
165 }
166 });
167 }
168
169 function quickHelp(view, key, code, ev) {
Simon Huntd5579252015-10-06 15:09:14 -0700170 if (!globalEnabled) {
171 return false;
172 }
Simon Hunt639dc662015-02-18 14:19:20 -0800173 qhs.showQuickHelp(keyHandler);
Simon Hunt1eecfa22014-12-16 14:46:29 -0800174 return true;
175 }
176
Simon Hunta0eb0a82015-02-11 12:30:06 -0800177 // returns true if we 'consumed' the ESC keypress, false otherwise
Simon Hunt1eecfa22014-12-16 14:46:29 -0800178 function escapeKey(view, key, code, ev) {
Simon Hunt9d286562015-03-09 13:53:50 -0700179 return ns.hideIfShown() || qhs.hideQuickHelp();
Simon Hunt1eecfa22014-12-16 14:46:29 -0800180 }
181
182 function toggleTheme(view, key, code, ev) {
Simon Huntd5579252015-10-06 15:09:14 -0700183 if (!globalEnabled) {
184 return false;
185 }
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800186 ts.toggleTheme();
Simon Hunt1eecfa22014-12-16 14:46:29 -0800187 return true;
188 }
189
Simon Hunt5198f082016-02-04 13:41:17 -0800190 function filterMaskedKeys(map, caller, remove) {
191 var masked = [],
192 msgs = [];
Simon Hunt59df0b22014-12-17 10:32:25 -0800193
Simon Hunt5198f082016-02-04 13:41:17 -0800194 d3.map(map).keys().forEach(function (key) {
195 if (keyHandler.maskedKeys[key]) {
196 masked.push(key);
197 msgs.push(caller, ': Key "' + key + '" is reserved');
Simon Hunt59df0b22014-12-17 10:32:25 -0800198 }
Simon Hunt5198f082016-02-04 13:41:17 -0800199 });
200
201 if (msgs.length) {
202 $log.warn(msgs.join('\n'));
203 }
204
205 if (remove) {
206 masked.forEach(function (k) {
207 delete map[k];
208 });
209 }
210 return masked;
211 }
212
213 function unexParam(fname, x) {
214 $log.warn(fname, ": unexpected parameter-- ", x);
215 }
216
217 function setKeyBindings(keyArg) {
218 var fname = 'setKeyBindings()',
219 kFunc = fs.isF(keyArg),
220 kMap = fs.isO(keyArg);
221
222 if (kFunc) {
223 // set general key handler callback
224 keyHandler.viewFn = kFunc;
225 } else if (kMap) {
226 filterMaskedKeys(kMap, fname, true);
227 keyHandler.viewKeys = kMap;
228 } else {
229 unexParam(fname, keyArg);
Simon Hunt59df0b22014-12-17 10:32:25 -0800230 }
231 }
232
233 function getKeyBindings() {
234 var gkeys = d3.map(keyHandler.globalKeys).keys(),
235 masked = d3.map(keyHandler.maskedKeys).keys(),
236 vkeys = d3.map(keyHandler.viewKeys).keys(),
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800237 vfn = !!fs.isF(keyHandler.viewFn);
Simon Hunt59df0b22014-12-17 10:32:25 -0800238
239 return {
240 globalKeys: gkeys,
241 maskedKeys: masked,
242 viewKeys: vkeys,
243 viewFunction: vfn
244 };
245 }
246
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700247 function unbindKeys() {
248 keyHandler.viewKeys = {};
249 keyHandler.viewFn = null;
250 keyHandler.viewGestures = [];
251 }
252
Simon Hunt5198f082016-02-04 13:41:17 -0800253 function bindDialogKeys(map) {
254 var fname = 'bindDialogKeys()',
255 kMap = fs.isO(map);
256
257 if (kMap) {
258 filterMaskedKeys(map, fname, true);
259 keyHandler.dialogKeys = kMap;
260 } else {
261 unexParam(fname, map);
262 }
263 }
264
265 function unbindDialogKeys() {
266 keyHandler.dialogKeys = {};
267 }
268
Simon Hunt71892222015-09-29 13:39:40 -0700269 function checkNotGlobal(o) {
270 var oops = [];
271 if (fs.isO(o)) {
272 angular.forEach(o, function (val, key) {
273 if (keyHandler.globalKeys[key]) {
274 oops.push(key);
275 }
276 });
277 if (oops.length) {
278 $log.warn('Ignoring reserved global key(s):', oops.join(','));
279 oops.forEach(function (key) {
280 delete o[key];
281 });
282 }
283 }
284 }
285
Simon Huntdc6362a2014-12-18 19:55:23 -0800286 angular.module('onosUtil')
Simon Hunt639dc662015-02-18 14:19:20 -0800287 .factory('KeyService',
Simon Hunt9f5e97d2015-12-11 10:32:09 -0800288 ['$log', '$timeout', 'FnService', 'ThemeService', 'NavService',
Simon Huntf4ef6dd2016-02-03 17:05:14 -0800289 'EeService',
Simon Hunt639dc662015-02-18 14:19:20 -0800290
Simon Huntf4ef6dd2016-02-03 17:05:14 -0800291 function (_$log_, _$timeout_, _fs_, _ts_, _ns_, _ee_) {
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800292 $log = _$log_;
Simon Hunt9f5e97d2015-12-11 10:32:09 -0800293 $timeout = _$timeout_;
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800294 fs = _fs_;
295 ts = _ts_;
Simon Hunt9d286562015-03-09 13:53:50 -0700296 ns = _ns_;
Simon Huntf4ef6dd2016-02-03 17:05:14 -0800297 ee = _ee_;
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800298
Simon Huntdc6362a2014-12-18 19:55:23 -0800299 return {
Simon Hunt639dc662015-02-18 14:19:20 -0800300 bindQhs: function (_qhs_) {
301 qhs = _qhs_;
302 },
Simon Huntdc6362a2014-12-18 19:55:23 -0800303 installOn: function (elem) {
304 elem.on('keydown', keyIn);
305 setupGlobalKeys();
306 },
Simon Huntdc6362a2014-12-18 19:55:23 -0800307 keyBindings: function (x) {
308 if (x === undefined) {
309 return getKeyBindings();
310 } else {
311 setKeyBindings(x);
312 }
313 },
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700314 unbindKeys: unbindKeys,
Simon Hunt5198f082016-02-04 13:41:17 -0800315 dialogKeys: function (x) {
316 if (x === undefined) {
317 unbindDialogKeys();
318 } else {
319 bindDialogKeys(x);
320 }
321 },
Simon Huntf4ef6dd2016-02-03 17:05:14 -0800322 addSeq: function (word, data) {
323 fs.addToTrie(seq, word, data);
324 },
325 remSeq: function (word) {
326 fs.removeFromTrie(seq, word);
327 },
Simon Huntdc6362a2014-12-18 19:55:23 -0800328 gestureNotes: function (g) {
329 if (g === undefined) {
330 return keyHandler.viewGestures;
331 } else {
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800332 keyHandler.viewGestures = fs.isA(g) || [];
Simon Huntdc6362a2014-12-18 19:55:23 -0800333 }
Simon Hunt36fc15c2015-02-12 17:02:58 -0800334 },
335 enableKeys: function (b) {
336 enabled = b;
Simon Hunt71892222015-09-29 13:39:40 -0700337 },
Simon Huntd5579252015-10-06 15:09:14 -0700338 enableGlobalKeys: function (b) {
339 globalEnabled = b;
340 },
Simon Hunt71892222015-09-29 13:39:40 -0700341 checkNotGlobal: checkNotGlobal
Simon Huntdc6362a2014-12-18 19:55:23 -0800342 };
Simon Hunt1eecfa22014-12-16 14:46:29 -0800343 }]);
344
Simon Huntdc6362a2014-12-18 19:55:23 -0800345}());