blob: 9c60e832bb454ef6bb1bdb2d3dd8f19956e8bcc2 [file] [log] [blame]
Simon Hunt1eecfa22014-12-16 14:46:29 -08001/*
Simon Hunt8ead3a22015-01-06 11:00:15 -08002 * Copyright 2014,2015 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: {},
32 viewKeys: {},
33 viewFn: null,
34 viewGestures: []
Simon Hunt9f5e97d2015-12-11 10:32:09 -080035 },
Simon Huntf4ef6dd2016-02-03 17:05:14 -080036 seq = {},
37 matching = false,
38 matched = '',
39 lookup;
Simon Hunt1eecfa22014-12-16 14:46:29 -080040
Simon Huntf4ef6dd2016-02-03 17:05:14 -080041 function matchSeq(key) {
42 if (!matching && key === 'shift') {
43 matching = true;
Thomas Vachuskafe1f01a2015-12-10 23:56:02 -080044 return true;
Simon Hunt9f5e97d2015-12-11 10:32:09 -080045 }
Simon Huntf4ef6dd2016-02-03 17:05:14 -080046 if (matching) {
47 matched += key;
48 lookup = fs.trieLookup(seq, matched);
49 if (lookup === -1) {
50 return true;
51 }
52 matching = false;
53 matched = '';
54 if (!lookup) {
55 return;
56 }
57 ee.cluck(lookup);
58 return true;
Thomas Vachuskafe1f01a2015-12-10 23:56:02 -080059 }
Thomas Vachuskafe1f01a2015-12-10 23:56:02 -080060 }
61
Simon Hunt1eecfa22014-12-16 14:46:29 -080062 function whatKey(code) {
63 switch (code) {
Simon Hunt18d4bc82016-01-08 14:09:35 -080064 case 8: return 'delete';
65 case 9: return 'tab';
Simon Hunt1eecfa22014-12-16 14:46:29 -080066 case 13: return 'enter';
67 case 16: return 'shift';
68 case 17: return 'ctrl';
69 case 18: return 'alt';
70 case 27: return 'esc';
71 case 32: return 'space';
72 case 37: return 'leftArrow';
73 case 38: return 'upArrow';
74 case 39: return 'rightArrow';
75 case 40: return 'downArrow';
76 case 91: return 'cmdLeft';
77 case 93: return 'cmdRight';
Simon Hunt18d4bc82016-01-08 14:09:35 -080078 case 186: return 'semicolon';
Simon Hunt1eecfa22014-12-16 14:46:29 -080079 case 187: return 'equals';
Simon Hunt90dcc3e2015-03-25 15:01:27 -070080 case 188: return 'comma';
Simon Hunt1eecfa22014-12-16 14:46:29 -080081 case 189: return 'dash';
Simon Hunt90dcc3e2015-03-25 15:01:27 -070082 case 190: return 'dot';
Simon Hunt1eecfa22014-12-16 14:46:29 -080083 case 191: return 'slash';
84 case 192: return 'backQuote';
Simon Hunt18d4bc82016-01-08 14:09:35 -080085 case 219: return 'openBracket';
Simon Hunt1eecfa22014-12-16 14:46:29 -080086 case 220: return 'backSlash';
Simon Hunt18d4bc82016-01-08 14:09:35 -080087 case 221: return 'closeBracket';
88 case 222: return 'quote';
Simon Hunt1eecfa22014-12-16 14:46:29 -080089 default:
90 if ((code >= 48 && code <= 57) ||
91 (code >= 65 && code <= 90)) {
92 return String.fromCharCode(code);
93 } else if (code >= 112 && code <= 123) {
94 return 'F' + (code - 111);
95 }
96 return '.';
97 }
98 }
99
100 function keyIn() {
101 var event = d3.event,
102 keyCode = event.keyCode,
103 key = whatKey(keyCode),
104 kh = keyHandler,
105 gk = kh.globalKeys[key],
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800106 gcb = fs.isF(gk) || (fs.isA(gk) && fs.isF(gk[0])),
Simon Hunt1eecfa22014-12-16 14:46:29 -0800107 vk = kh.viewKeys[key],
Simon Hunt09060142015-03-18 20:23:32 -0700108 kl = fs.isF(kh.viewKeys._keyListener),
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800109 vcb = fs.isF(vk) || (fs.isA(vk) && fs.isF(vk[0])) || fs.isF(kh.viewFn),
Simon Hunt1bf1e8c2015-04-08 10:12:43 -0700110 token = 'keyev'; // indicate this was a key-pressed event
Simon Hunt1eecfa22014-12-16 14:46:29 -0800111
Simon Huntdc6362a2014-12-18 19:55:23 -0800112 d3.event.stopPropagation();
113
Simon Hunt36fc15c2015-02-12 17:02:58 -0800114 if (enabled) {
Simon Huntf4ef6dd2016-02-03 17:05:14 -0800115 if (matchSeq(key)) return;
Thomas Vachuskafe1f01a2015-12-10 23:56:02 -0800116
Simon Hunt36fc15c2015-02-12 17:02:58 -0800117 // global callback?
118 if (gcb && gcb(token, key, keyCode, event)) {
119 // if the event was 'handled', we are done
120 return;
121 }
122 // otherwise, let the view callback have a shot
123 if (vcb) {
124 vcb(token, key, keyCode, event);
125 }
Simon Hunt09060142015-03-18 20:23:32 -0700126 if (kl) {
127 kl(key);
128 }
Simon Hunt1eecfa22014-12-16 14:46:29 -0800129 }
130 }
131
132 function setupGlobalKeys() {
Simon Hunt404f6b22015-01-21 14:00:56 -0800133 angular.extend(keyHandler, {
Simon Hunt1eecfa22014-12-16 14:46:29 -0800134 globalKeys: {
135 backSlash: [quickHelp, 'Show / hide Quick Help'],
136 slash: [quickHelp, 'Show / hide Quick Help'],
137 esc: [escapeKey, 'Dismiss dialog or cancel selections'],
138 T: [toggleTheme, "Toggle theme"]
139 },
140 globalFormat: ['backSlash', 'slash', 'esc', 'T'],
141
142 // Masked keys are global key handlers that always return true.
143 // That is, the view will never see the event for that key.
144 maskedKeys: {
145 slash: true,
146 backSlash: true,
147 T: true
148 }
149 });
150 }
151
152 function quickHelp(view, key, code, ev) {
Simon Huntd5579252015-10-06 15:09:14 -0700153 if (!globalEnabled) {
154 return false;
155 }
Simon Hunt639dc662015-02-18 14:19:20 -0800156 qhs.showQuickHelp(keyHandler);
Simon Hunt1eecfa22014-12-16 14:46:29 -0800157 return true;
158 }
159
Simon Hunta0eb0a82015-02-11 12:30:06 -0800160 // returns true if we 'consumed' the ESC keypress, false otherwise
Simon Hunt1eecfa22014-12-16 14:46:29 -0800161 function escapeKey(view, key, code, ev) {
Simon Hunt9d286562015-03-09 13:53:50 -0700162 return ns.hideIfShown() || qhs.hideQuickHelp();
Simon Hunt1eecfa22014-12-16 14:46:29 -0800163 }
164
165 function toggleTheme(view, key, code, ev) {
Simon Huntd5579252015-10-06 15:09:14 -0700166 if (!globalEnabled) {
167 return false;
168 }
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800169 ts.toggleTheme();
Simon Hunt1eecfa22014-12-16 14:46:29 -0800170 return true;
171 }
172
Simon Hunt59df0b22014-12-17 10:32:25 -0800173 function setKeyBindings(keyArg) {
174 var viewKeys,
175 masked = [];
176
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800177 if (fs.isF(keyArg)) {
Simon Hunt59df0b22014-12-17 10:32:25 -0800178 // set general key handler callback
179 keyHandler.viewFn = keyArg;
180 } else {
181 // set specific key filter map
182 viewKeys = d3.map(keyArg).keys();
183 viewKeys.forEach(function (key) {
184 if (keyHandler.maskedKeys[key]) {
Simon Huntaf322072014-12-18 13:23:40 -0800185 masked.push('setKeyBindings(): Key "' + key + '" is reserved');
Simon Hunt59df0b22014-12-17 10:32:25 -0800186 }
187 });
188
189 if (masked.length) {
Simon Huntaf322072014-12-18 13:23:40 -0800190 $log.warn(masked.join('\n'));
Simon Hunt59df0b22014-12-17 10:32:25 -0800191 }
192 keyHandler.viewKeys = keyArg;
193 }
194 }
195
196 function getKeyBindings() {
197 var gkeys = d3.map(keyHandler.globalKeys).keys(),
198 masked = d3.map(keyHandler.maskedKeys).keys(),
199 vkeys = d3.map(keyHandler.viewKeys).keys(),
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800200 vfn = !!fs.isF(keyHandler.viewFn);
Simon Hunt59df0b22014-12-17 10:32:25 -0800201
202 return {
203 globalKeys: gkeys,
204 maskedKeys: masked,
205 viewKeys: vkeys,
206 viewFunction: vfn
207 };
208 }
209
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700210 function unbindKeys() {
211 keyHandler.viewKeys = {};
212 keyHandler.viewFn = null;
213 keyHandler.viewGestures = [];
214 }
215
Simon Hunt71892222015-09-29 13:39:40 -0700216 function checkNotGlobal(o) {
217 var oops = [];
218 if (fs.isO(o)) {
219 angular.forEach(o, function (val, key) {
220 if (keyHandler.globalKeys[key]) {
221 oops.push(key);
222 }
223 });
224 if (oops.length) {
225 $log.warn('Ignoring reserved global key(s):', oops.join(','));
226 oops.forEach(function (key) {
227 delete o[key];
228 });
229 }
230 }
231 }
232
Simon Huntdc6362a2014-12-18 19:55:23 -0800233 angular.module('onosUtil')
Simon Hunt639dc662015-02-18 14:19:20 -0800234 .factory('KeyService',
Simon Hunt9f5e97d2015-12-11 10:32:09 -0800235 ['$log', '$timeout', 'FnService', 'ThemeService', 'NavService',
Simon Huntf4ef6dd2016-02-03 17:05:14 -0800236 'EeService',
Simon Hunt639dc662015-02-18 14:19:20 -0800237
Simon Huntf4ef6dd2016-02-03 17:05:14 -0800238 function (_$log_, _$timeout_, _fs_, _ts_, _ns_, _ee_) {
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800239 $log = _$log_;
Simon Hunt9f5e97d2015-12-11 10:32:09 -0800240 $timeout = _$timeout_;
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800241 fs = _fs_;
242 ts = _ts_;
Simon Hunt9d286562015-03-09 13:53:50 -0700243 ns = _ns_;
Simon Huntf4ef6dd2016-02-03 17:05:14 -0800244 ee = _ee_;
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800245
Simon Huntdc6362a2014-12-18 19:55:23 -0800246 return {
Simon Hunt639dc662015-02-18 14:19:20 -0800247 bindQhs: function (_qhs_) {
248 qhs = _qhs_;
249 },
Simon Huntdc6362a2014-12-18 19:55:23 -0800250 installOn: function (elem) {
251 elem.on('keydown', keyIn);
252 setupGlobalKeys();
253 },
Simon Huntdc6362a2014-12-18 19:55:23 -0800254 keyBindings: function (x) {
255 if (x === undefined) {
256 return getKeyBindings();
257 } else {
258 setKeyBindings(x);
259 }
260 },
Bri Prebilic Cole9dcaea52015-07-21 14:39:48 -0700261 unbindKeys: unbindKeys,
Simon Huntf4ef6dd2016-02-03 17:05:14 -0800262 addSeq: function (word, data) {
263 fs.addToTrie(seq, word, data);
264 },
265 remSeq: function (word) {
266 fs.removeFromTrie(seq, word);
267 },
Simon Huntdc6362a2014-12-18 19:55:23 -0800268 gestureNotes: function (g) {
269 if (g === undefined) {
270 return keyHandler.viewGestures;
271 } else {
Yuta HIGUCHI4f39bcd2014-12-18 20:46:14 -0800272 keyHandler.viewGestures = fs.isA(g) || [];
Simon Huntdc6362a2014-12-18 19:55:23 -0800273 }
Simon Hunt36fc15c2015-02-12 17:02:58 -0800274 },
275 enableKeys: function (b) {
276 enabled = b;
Simon Hunt71892222015-09-29 13:39:40 -0700277 },
Simon Huntd5579252015-10-06 15:09:14 -0700278 enableGlobalKeys: function (b) {
279 globalEnabled = b;
280 },
Simon Hunt71892222015-09-29 13:39:40 -0700281 checkNotGlobal: checkNotGlobal
Simon Huntdc6362a2014-12-18 19:55:23 -0800282 };
Simon Hunt1eecfa22014-12-16 14:46:29 -0800283 }]);
284
Simon Huntdc6362a2014-12-18 19:55:23 -0800285}());