##// END OF EJS Templates
Like, OMG, keyboardmanager.js is a beast.
Brian E. Granger -
Show More
This diff has been collapsed as it changes many lines, (724 lines changed) Show them Hide them
@@ -12,13 +12,490 b''
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var key = IPython.utils.keycodes;
15 // Setup global keycodes and inverse keycodes.
16
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
20 // but have minor differences.
21
22 // These apply to Firefox, (Webkit and IE)
23 var _keycodes = {
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
30 '\\ |': 220, '- _': 109, '\' "': 222,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
39 'insert': 45, 'delete': 46, 'numlock': 144,
40 };
41
42 // These apply to Firefox and Opera
43 var _mozilla_keycodes = {
44 '; :': 59, '= +': 61
45 }
46
47 // This apply to Webkit and IE
48 var _ie_keycodes = {
49 '; :': 186, '= +': 187,
50 }
51
52 var browser = IPython.utils.browser[0];
53
54 if (browser === 'Firefox' || browser === 'Opera') {
55 $.extend(_keycodes, _mozilla_keycodes);
56 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
57 $.extend(_keycodes, _ie_keycodes);
58 }
59
60 var keycodes = {};
61 var inv_keycodes = {};
62 for (var name in _keycodes) {
63 console.log(name);
64 var names = name.split(' ');
65 if (names.length === 1) {
66 var n = names[0]
67 keycodes[n] = _keycodes[n]
68 inv_keycodes[_keycodes[n]] = n
69 // console.log(keycodes[n], inv_keycodes[_keycodes[n]]);
70 } else {
71 var primary = names[0];
72 var secondary = names[1];
73 keycodes[primary] = _keycodes[name]
74 keycodes[secondary] = _keycodes[name]
75 inv_keycodes[_keycodes[name]] = primary
76 // console.log(keycodes[primary], keycodes[secondary], inv_keycodes[_keycodes[name]])
77 }
78 }
79
80
81 // Default keyboard shortcuts
82
83 var default_common_shortcuts = {
84 'meta+s' : {
85 help : 'save notebook',
86 handler : function (event) {
87 IPython.notebook.save_checkpoint();
88 event.preventDefault();
89 return false;
90 }
91 },
92 'ctrl+s' : {
93 help : 'save notebook',
94 handler : function (event) {
95 IPython.notebook.save_checkpoint();
96 event.preventDefault();
97 return false;
98 }
99 },
100 'shift' : {
101 help : '',
102 handler : function (event) {
103 // ignore shift keydown
104 return true;
105 }
106 },
107 'shift+enter' : {
108 help : 'run cell',
109 handler : function (event) {
110 IPython.notebook.execute_selected_cell('shift');
111 return false;
112 }
113 },
114 'alt+enter' : {
115 help : 'run cell',
116 handler : function (event) {
117 IPython.notebook.execute_selected_cell('alt');
118 return false;
119 }
120 },
121 'ctrl+enter' : {
122 help : 'run cell',
123 handler : function (event) {
124 IPython.notebook.execute_selected_cell('ctrl');
125 return false;
126 }
127 }
128 }
129
130 // Edit mode defaults
131
132 var default_edit_shortcuts = {
133 'esc' : {
134 help : 'command mode',
135 handler : function (event) {
136 IPython.notebook.command_mode();
137 return false;
138 }
139 },
140 'ctrl+m' : {
141 help : 'command mode',
142 handler : function (event) {
143 IPython.notebook.command_mode();
144 return false;
145 }
146 },
147 'up' : {
148 help : 'select previous cell',
149 handler : function (event) {
150 var cell = IPython.notebook.get_selected_cell();
151 if (cell && cell.at_top()) {
152 event.preventDefault();
153 IPython.notebook.command_mode()
154 IPython.notebook.select_prev();
155 IPython.notebook.edit_mode();
156 return false;
157 };
158 }
159 },
160 'down' : {
161 help : 'select next cell',
162 handler : function (event) {
163 var cell = IPython.notebook.get_selected_cell();
164 if (cell && cell.at_bottom()) {
165 event.preventDefault();
166 IPython.notebook.command_mode()
167 IPython.notebook.select_next();
168 IPython.notebook.edit_mode();
169 return false;
170 };
171 }
172 },
173
174 }
175
176 // Command mode defaults
177
178 var default_command_shortcuts = {
179 'enter' : {
180 help : 'edit mode',
181 handler : function (event) {
182 IPython.notebook.edit_mode();
183 return false;
184 }
185 },
186 'up' : {
187 help : 'select previous cell',
188 handler : function (event) {
189 var index = IPython.notebook.get_selected_index();
190 if (index !== 0 && index !== null) {
191 IPython.notebook.select_prev();
192 var cell = IPython.notebook.get_selected_cell();
193 cell.focus_cell();
194 };
195 return false;
196 }
197 },
198 'down' : {
199 help : 'select next cell',
200 handler : function (event) {
201 var index = IPython.notebook.get_selected_index();
202 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
203 IPython.notebook.select_next();
204 var cell = IPython.notebook.get_selected_cell();
205 cell.focus_cell();
206 };
207 return false;
208 }
209 },
210 'x' : {
211 help : 'cut cell',
212 handler : function (event) {
213 IPython.IPython.notebook.cut_cell();
214 return false;
215 }
216 },
217 'c' : {
218 help : 'copy cell',
219 handler : function (event) {
220 IPython.IPython.notebook.copy_cell();
221 return false;
222 }
223 },
224 'v' : {
225 help : 'paste cell below',
226 handler : function (event) {
227 IPython.IPython.notebook.paste_cell_below();
228 return false;
229 }
230 },
231 'd' : {
232 help : 'delete cell (press twice)',
233 handler : function (event) {
234 var dc = IPython.delete_count;
235 console.log('delete_count', dc);
236 if (dc === undefined) {
237 IPython.delete_count = 0;
238 } else if (dc === 0) {
239 IPython.delete_count = 1;
240 setTimeout(function () {
241 IPython.delete_count = 0;
242 }, 800);
243 } else if (dc === 1) {
244 IPython.notebook.delete_cell();
245 IPython.delete_count = 0;
246 }
247 return false;
248 }
249 },
250 'a' : {
251 help : 'insert cell above',
252 handler : function (event) {
253 IPython.notebook.insert_cell_above('code');
254 IPython.notebook.select_prev();
255 return false;
256 }
257 },
258 'b' : {
259 help : 'insert cell below',
260 handler : function (event) {
261 IPython.notebook.insert_cell_below('code');
262 IPython.notebook.select_next();
263 return false;
264 }
265 },
266 'y' : {
267 help : 'to code',
268 handler : function (event) {
269 IPython.notebook.to_code();
270 return false;
271 }
272 },
273 'm' : {
274 help : 'to markdown',
275 handler : function (event) {
276 IPython.notebook.to_markdown();
277 return false;
278 }
279 },
280 't' : {
281 help : 'to raw',
282 handler : function (event) {
283 IPython.notebook.to_raw();
284 return false;
285 }
286 },
287 '1' : {
288 help : 'to heading 1',
289 handler : function (event) {
290 IPython.notebook.to_heading(undefined, 1);
291 return false;
292 }
293 },
294 '2' : {
295 help : 'to heading 2',
296 handler : function (event) {
297 IPython.notebook.to_heading(undefined, 2);
298 return false;
299 }
300 },
301 '3' : {
302 help : 'to heading 3',
303 handler : function (event) {
304 IPython.notebook.to_heading(undefined, 3);
305 return false;
306 }
307 },
308 '4' : {
309 help : 'to heading 4',
310 handler : function (event) {
311 IPython.notebook.to_heading(undefined, 4);
312 return false;
313 }
314 },
315 '5' : {
316 help : 'to heading 5',
317 handler : function (event) {
318 IPython.notebook.to_heading(undefined, 5);
319 return false;
320 }
321 },
322 '6' : {
323 help : 'to heading 6',
324 handler : function (event) {
325 IPython.notebook.to_heading(undefined, 6);
326 return false;
327 }
328 },
329 'o' : {
330 help : 'toggle output',
331 handler : function (event) {
332 IPython.notebook.toggle_output();
333 return false;
334 }
335 },
336 'shift+o' : {
337 help : 'toggle output',
338 handler : function (event) {
339 IPython.notebook.toggle_output_scroll();
340 return false;
341 }
342 },
343 's' : {
344 help : 'save notebook',
345 handler : function (event) {
346 IPython.notebook.save_checkpoint();
347 return false;
348 }
349 },
350 'ctrl+j' : {
351 help : 'move cell down',
352 handler : function (event) {
353 IPython.notebook.move_cell_down();
354 return false;
355 }
356 },
357 'ctrl+k' : {
358 help : 'move cell up',
359 handler : function (event) {
360 IPython.notebook.move_cell_up();
361 return false;
362 }
363 },
364 'l' : {
365 help : 'toggle line numbers',
366 handler : function (event) {
367 IPython.notebook.cell_toggle_line_numbers();
368 return false;
369 }
370 },
371 'i' : {
372 help : 'interrupt kernel',
373 handler : function (event) {
374 IPython.notebook.kernel.interrupt();
375 return false;
376 }
377 },
378 '.' : {
379 help : 'restart kernel',
380 handler : function (event) {
381 IPython.notebook.restart_kernel();
382 return false;
383 }
384 },
385 'h' : {
386 help : 'keyboard shortcuts',
387 handler : function (event) {
388 IPython.quick_help.show_keyboard_shortcuts();
389 return false;
390 }
391 },
392 'z' : {
393 help : 'undo last delete',
394 handler : function (event) {
395 IPython.notebook.undelete_cell();
396 return false;
397 }
398 },
399 '-' : {
400 help : 'split cell',
401 handler : function (event) {
402 IPython.notebook.split_cell();
403 return false;
404 }
405 },
406 'shift+=' : {
407 help : 'merge cell below',
408 handler : function (event) {
409 IPython.notebook.merge_cell_below();
410 return false;
411 }
412 },
413 }
414
415
416 // Shortcut manager class
417
418 var ShortcutManager = function () {
419 this._shortcuts = {}
420 }
421
422 ShortcutManager.prototype.canonicalize_key = function (key) {
423 return inv_keycodes[keycodes[key]];
424 }
425
426 ShortcutManager.prototype.canonicalize_shortcut = function (shortcut) {
427 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
428 var values = shortcut.split("+");
429 if (values.length === 1) {
430 return this.canonicalize_key(values[0])
431 } else {
432 var modifiers = values.slice(0,-1);
433 var key = this.canonicalize_key(values[-1]);
434 modifiers.sort();
435 return modifiers.join('+') + '+' + key;
436 }
437 }
438
439 ShortcutManager.prototype.event_to_shortcut = function (event) {
440 // Convert a jQuery keyboard event to a strong based keyboard shortcut
441 var shortcut = '';
442 var key = inv_keycodes[event.which]
443 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
444 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
445 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
446 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
447 shortcut += key;
448 return shortcut
449 }
450
451 ShortcutManager.prototype.clear_shortcuts = function () {
452 this._shortcuts = {};
453 }
454
455 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
456 shortcut = this.canonicalize_shortcut(shortcut);
457 this._shortcuts[shortcut] = data;
458 }
459
460 ShortcutManager.prototype.add_shortcuts = function (data) {
461 for (var shortcut in data) {
462 this.add_shortcut(shortcut, data[shortcut]);
463 }
464 }
465
466 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
467 shortcut = this.canonicalize_shortcut(shortcut);
468 delete this._shortcuts[shortcut];
469 }
470
471 ShortcutManager.prototype.call_handler = function (event) {
472 var shortcut = this.event_to_shortcut(event);
473 var data = this._shortcuts[shortcut];
474 if (data !== undefined) {
475 console.log('call_handler', shortcut, data['help']);
476 var handler = data['handler'];
477 if (handler !== undefined) {
478 return handler(event);
479 }
480 }
481 return true;
482 }
483
484
485
486 // Main keyboard manager for the notebook
16
487
17 var KeyboardManager = function () {
488 var KeyboardManager = function () {
18 this.mode = 'command';
489 this.mode = 'command';
19 this.enabled = true;
490 this.enabled = true;
20 this.delete_count = 0;
491 this.delete_count = 0;
21 this.bind_events();
492 this.bind_events();
493 this.command_shortcuts = new ShortcutManager();
494 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
495 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
496 this.edit_shortcuts = new ShortcutManager();
497 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
498 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
22 };
499 };
23
500
24 KeyboardManager.prototype.bind_events = function () {
501 KeyboardManager.prototype.bind_events = function () {
@@ -33,238 +510,29 b' var IPython = (function (IPython) {'
33
510
34 console.log('keyboard_manager', this.mode, event.keyCode);
511 console.log('keyboard_manager', this.mode, event.keyCode);
35
512
36 if (event.which === key.ESC) {
513 if (event.which === keycodes['esc']) {
37 // Intercept escape at highest level to avoid closing
514 // Intercept escape at highest level to avoid closing
38 // websocket connection with firefox
515 // websocket connection with firefox
39 event.preventDefault();
516 event.preventDefault();
40 }
517 }
41
518
42 if (!this.enabled) {
519 if (!this.enabled) {
520 if (event.which === keycodes['esc']) {
521 // ESC
522 notebook.command_mode();
523 return false;
524 }
43 return true;
525 return true;
44 }
526 }
45
527
46 // Event handlers for both command and edit mode
47 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
48 // Save (CTRL+S) or (Command+S on Mac)
49 notebook.save_checkpoint();
50 event.preventDefault();
51 return false;
52 } else if (event.which === key.ESC) {
53 // Intercept escape at highest level to avoid closing
54 // websocket connection with firefox
55 event.preventDefault();
56 // Don't return yet to allow edit/command modes to handle
57 } else if (event.which === key.SHIFT) {
58 // ignore shift keydown
59 return true;
60 } else if (event.which === key.ENTER && event.shiftKey) {
61 notebook.execute_selected_cell('shift');
62 return false;
63 } else if (event.which === key.ENTER && event.altKey) {
64 // Execute code cell, and insert new in place
65 notebook.execute_selected_cell('alt');
66 return false;
67 } else if (event.which === key.ENTER && event.ctrlKey) {
68 notebook.execute_selected_cell('ctrl');
69 return false;
70 }
71
72 if (this.mode === 'edit') {
528 if (this.mode === 'edit') {
73 return this.handle_edit_mode(event);
529 return this.edit_shortcuts.call_handler(event);
74 } else if (this.mode === 'command' && !(event.ctrlKey || event.altKey || event.metaKey)) {
530 } else if (this.mode === 'command') {
75 return this.handle_command_mode(event);
531 return this.command_shortcuts.call_handler(event);
76 }
532 }
77 }
78
79 KeyboardManager.prototype.handle_edit_mode = function (event) {
80 var notebook = IPython.notebook;
81
82 if (event.which === key.ESC) {
83 // ESC
84 notebook.command_mode();
85 return false;
86 } else if (event.which === 77 && event.ctrlKey) {
87 // Ctrl-m
88 notebook.command_mode();
89 return false;
90 } else if (event.which === key.UPARROW && !event.shiftKey) {
91 var cell = notebook.get_selected_cell();
92 if (cell && cell.at_top()) {
93 event.preventDefault();
94 notebook.command_mode()
95 notebook.select_prev();
96 notebook.edit_mode();
97 return false;
98 };
99 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
100 var cell = notebook.get_selected_cell();
101 if (cell && cell.at_bottom()) {
102 event.preventDefault();
103 notebook.command_mode()
104 notebook.select_next();
105 notebook.edit_mode();
106 return false;
107 };
108 };
109 return true;
533 return true;
110 }
534 }
111
535
112 KeyboardManager.prototype.handle_command_mode = function (event) {
113 var that = this;
114 var notebook = IPython.notebook;
115
116 if (event.which === key.ENTER && !(event.ctrlKey || event.altKey || event.shiftKey)) {
117 // Enter edit mode = ENTER alone
118 notebook.edit_mode();
119 return false;
120 } else if (event.which === key.UPARROW && !event.shiftKey) {
121 var index = notebook.get_selected_index();
122 if (index !== 0 && index !== null) {
123 notebook.select_prev();
124 var cell = notebook.get_selected_cell();
125 cell.focus_cell();
126 };
127 return false;
128 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
129 var index = notebook.get_selected_index();
130 if (index !== (notebook.ncells()-1) && index !== null) {
131 notebook.select_next();
132 var cell = notebook.get_selected_cell();
133 cell.focus_cell();
134 };
135 return false;
136 } else if (event.which === 88) {
137 // Cut selected cell = x
138 notebook.cut_cell();
139 return false;
140 } else if (event.which === 67) {
141 // Copy selected cell = c
142 notebook.copy_cell();
143 return false;
144 } else if (event.which === 86) {
145 // Paste below selected cell = v
146 notebook.paste_cell_below();
147 return false;
148 } else if (event.which === 68) {
149 // Delete selected cell = d
150 var dc = this.delete_count;
151 console.log('delete_count', dc);
152 if (dc === 0) {
153 this.delete_count = 1;
154 setTimeout(function () {
155 that.delete_count = 0;
156 }, 1000);
157 } else if (dc === 1) {
158 notebook.delete_cell();
159 }
160 return false;
161 } else if (event.which === 65) {
162 // Insert code cell above selected = a
163 notebook.insert_cell_above('code');
164 notebook.select_prev();
165 return false;
166 } else if (event.which === 66) {
167 // Insert code cell below selected = b
168 notebook.insert_cell_below('code');
169 notebook.select_next();
170 return false;
171 } else if (event.which === 89) {
172 // To code = y
173 notebook.to_code();
174 return false;
175 } else if (event.which === 77) {
176 // To markdown = m
177 notebook.to_markdown();
178 return false;
179 } else if (event.which === 84) {
180 // To Raw = t
181 notebook.to_raw();
182 return false;
183 } else if (event.which === 49) {
184 // To Heading 1 = 1
185 notebook.to_heading(undefined, 1);
186 return false;
187 } else if (event.which === 50) {
188 // To Heading 2 = 2
189 notebook.to_heading(undefined, 2);
190 return false;
191 } else if (event.which === 51) {
192 // To Heading 3 = 3
193 notebook.to_heading(undefined, 3);
194 return false;
195 } else if (event.which === 52) {
196 // To Heading 4 = 4
197 notebook.to_heading(undefined, 4);
198 return false;
199 } else if (event.which === 53) {
200 // To Heading 5 = 5
201 notebook.to_heading(undefined, 5);
202 return false;
203 } else if (event.which === 54) {
204 // To Heading 6 = 6
205 notebook.to_heading(undefined, 6);
206 return false;
207 } else if (event.which === 79) {
208 // Toggle output = o
209 if (event.shiftKey) {
210 notebook.toggle_output_scroll();
211 } else {
212 notebook.toggle_output();
213 };
214 return false;
215 } else if (event.which === 83) {
216 // Save notebook = s
217 notebook.save_checkpoint();
218 return false;
219 } else if (event.which === 74) {
220 // Move cell down = j
221 notebook.move_cell_down();
222 return false;
223 } else if (event.which === 75) {
224 // Move cell up = k
225 notebook.move_cell_up();
226 return false;
227 } else if (event.which === 80) {
228 // Select previous = p
229 notebook.select_prev();
230 return false;
231 } else if (event.which === 78) {
232 // Select next = n
233 notebook.select_next();
234 return false;
235 } else if (event.which === 76) {
236 // Toggle line numbers = l
237 notebook.cell_toggle_line_numbers();
238 return false;
239 } else if (event.which === 73) {
240 // Interrupt kernel = i
241 notebook.kernel.interrupt();
242 return false;
243 } else if (event.which === 190) {
244 // Restart kernel = . # matches qt console
245 notebook.restart_kernel();
246 return false;
247 } else if (event.which === 72) {
248 // Show keyboard shortcuts = h
249 IPython.quick_help.show_keyboard_shortcuts();
250 return false;
251 } else if (event.which === 90) {
252 // Undo last cell delete = z
253 notebook.undelete_cell();
254 return false;
255 } else if (event.which === 189 || event.which === 173) {
256 // how fun! '-' is 189 in Chrome, but 173 in FF and Opera
257 // Split cell = -
258 notebook.split_cell();
259 return false;
260 } else if ((event.which === 61 || event.which === 187) && event.shiftKey) {
261 notebook.merge_cell_below();
262 return false;
263 };
264 // If we havn't handled it, let someone else.
265 return true;
266 };
267
268 KeyboardManager.prototype.edit_mode = function () {
536 KeyboardManager.prototype.edit_mode = function () {
269 console.log('KeyboardManager', 'changing to edit mode');
537 console.log('KeyboardManager', 'changing to edit mode');
270 this.last_mode = this.mode;
538 this.last_mode = this.mode;
@@ -285,7 +553,25 b' var IPython = (function (IPython) {'
285 this.enabled = false;
553 this.enabled = false;
286 }
554 }
287
555
556 KeyboardManager.prototype.register_events = function (e) {
557 var that = this;
558 e.on('focusin', function () {
559 that.command_mode();
560 that.disable();
561 });
562 e.on('focusout', function () {
563 that.command_mode();
564 that.enable();
565 })
566 }
567
288
568
569 IPython.keycodes = keycodes;
570 IPython.inv_keycodes = inv_keycodes;
571 IPython.default_common_shortcuts = default_common_shortcuts;
572 IPython.default_edit_shortcuts = default_edit_shortcuts;
573 IPython.default_command_shortcuts = default_command_shortcuts;
574 IPython.ShortcutManager = ShortcutManager;
289 IPython.KeyboardManager = KeyboardManager;
575 IPython.KeyboardManager = KeyboardManager;
290
576
291 return IPython;
577 return IPython;
General Comments 0
You need to be logged in to leave comments. Login now