Improved menu hiding behavior and style.
[guacamole.git] / src / main / webapp / scripts / interface.js
1
2 // UI Definition
3 var GuacamoleUI = {
4
5     "display"     : document.getElementById("display"),
6     "menu"        : document.getElementById("menu"),
7     "menuControl" : document.getElementById("menuControl"),
8     "logo"        : document.getElementById("status-logo"),
9     "state"       : document.getElementById("state"),
10
11     "buttons": {
12
13         "showClipboard": document.getElementById("showClipboard"),
14         "showKeyboard" : document.getElementById("showKeyboard"),
15         "ctrlAltDelete": document.getElementById("ctrlAltDelete"),
16         "reconnect"    : document.getElementById("reconnect"),
17         "logout"       : document.getElementById("logout")
18
19     },
20
21     "containers": {
22         "error"    : document.getElementById("errorDialog"),
23         "clipboard": document.getElementById("clipboardDiv"),
24         "keyboard" : document.getElementById("keyboardContainer")
25     },
26     
27     "error"     : document.getElementById("errorText"),
28     "clipboard" : document.getElementById("clipboard")
29
30 };
31
32 // Constant UI initialization and behavior
33 (function() {
34
35     var menu_shaded = false;
36
37     var shade_interval = null;
38     var show_interval = null;
39
40     // Cache error image (might not be available when error occurs)
41     var guacErrorImage = new Image();
42     guacErrorImage.src = "images/noguacamole-logo-24.png";
43
44     GuacamoleUI.showError = function(error) {
45
46         GuacamoleUI.menu.className = "error";
47         GuacamoleUI.display.className += " guac-error";
48
49         GuacamoleUI.logo.src = guacErrorImage.src;
50         GuacamoleUI.error.textContent = error;
51         GuacamoleUI.containers.error.style.visibility = "visible";
52
53     };
54
55     GuacamoleUI.shadeMenu = function() {
56
57         if (!menu_shaded) {
58
59             var step = Math.floor(GuacamoleUI.menu.offsetHeight / 10) + 1;
60             var offset = 0;
61             menu_shaded = true;
62
63             window.clearInterval(show_interval);
64             shade_interval = window.setInterval(function() {
65
66                 offset -= step;
67                 GuacamoleUI.menu.style.top = offset + "px";
68
69                 if (offset <= -GuacamoleUI.menu.offsetHeight) {
70                     window.clearInterval(shade_interval);
71                     GuacamoleUI.menu.style.visiblity = "hidden";
72                 }
73
74             }, 30);
75         }
76
77     };
78
79     GuacamoleUI.showMenu = function() {
80
81         if (menu_shaded) {
82
83             var step = Math.floor(GuacamoleUI.menu.offsetHeight / 5) + 1;
84             var offset = -GuacamoleUI.menu.offsetHeight;
85             menu_shaded = false;
86             GuacamoleUI.menu.style.visiblity = "";
87
88             window.clearInterval(shade_interval);
89             show_interval = window.setInterval(function() {
90
91                 offset += step;
92
93                 if (offset >= 0) {
94                     offset = 0;
95                     window.clearInterval(show_interval);
96                 }
97
98                 GuacamoleUI.menu.style.top = offset + "px";
99
100             }, 30);
101         }
102
103     };
104
105     // Show/Hide clipboard
106     GuacamoleUI.buttons.showClipboard.onclick = function() {
107
108         var displayed = GuacamoleUI.containers.clipboard.style.display;
109         if (displayed != "block") {
110             GuacamoleUI.containers.clipboard.style.display = "block";
111             GuacamoleUI.buttons.showClipboard.innerHTML = "Hide Clipboard";
112         }
113         else {
114             GuacamoleUI.containers.clipboard.style.display = "none";
115             GuacamoleUI.buttons.showClipboard.innerHTML = "Show Clipboard";
116             GuacamoleUI.clipboard.onchange();
117         }
118
119     };
120
121     // Show/Hide keyboard
122     GuacamoleUI.buttons.showKeyboard.onclick = function() {
123
124         var displayed = GuacamoleUI.containers.keyboard.style.display;
125         if (displayed != "block") {
126             GuacamoleUI.containers.keyboard.style.display = "block";
127             GuacamoleUI.buttons.showKeyboard.textContent = "Hide Keyboard";
128         }
129         else {
130             GuacamoleUI.containers.keyboard.style.display = "none";
131             GuacamoleUI.buttons.showKeyboard.textContent = "Show Keyboard";
132         }
133
134     };
135
136     // Logout
137     GuacamoleUI.buttons.logout.onclick = function() {
138         window.location.href = "logout";
139     };
140
141     var detectMenuOpenTimeout = null;
142     var detectMenuCloseTimeout = null;
143
144     GuacamoleUI.menu.addEventListener('mouseover', function() {
145
146         // If we were waiting for menu close, we're not anymore
147         if (detectMenuCloseTimeout != null) {
148             window.clearTimeout(detectMenuCloseTimeout);
149             detectMenuCloseTimeout = null;
150         }
151
152     }, true);
153
154     function menuShowHandler() {
155
156         // If we were waiting for menu close, we're not anymore
157         if (detectMenuCloseTimeout != null) {
158             window.clearTimeout(detectMenuCloseTimeout);
159             detectMenuCloseTimeout = null;
160         }
161         
162         // Clear old timeout if mouse moved while we were waiting
163         if (detectMenuOpenTimeout != null) {
164             window.clearTimeout(detectMenuOpenTimeout);
165             detectMenuOpenTimeout = null;
166         }
167
168         // If not alread waiting, wait before showing menu
169         detectMenuOpenTimeout = window.setTimeout(function() {
170             GuacamoleUI.showMenu();
171             detectMenuOpenTimeout = null;
172         }, 325);
173
174     }
175
176     // Show menu of mouseover any part of menu
177     GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.showMenu, true);
178
179     // When mouse hovers over top of screen, start detection of mouse hover
180     GuacamoleUI.menuControl.addEventListener('mousemove', menuShowHandler, true);
181     document.addEventListener('mouseout', function(e) {
182         
183         // Get parent of the element the mouse pointer is leaving
184         if (!e) e = window.event;
185         var target = e.relatedTarget || e.toElement;
186         
187         // Ensure target is not menu nor child of menu
188         var targetParent = target;
189         while (targetParent != null) {
190             if (targetParent == document) return;
191             targetParent = targetParent.parentNode;
192         }
193
194         menuShowHandler();
195  
196     }, true);
197
198     GuacamoleUI.display.addEventListener('mouseover', function() {
199
200         // If we were detecting menu open, stop it
201         if (detectMenuOpenTimeout != null) {
202             window.clearTimeout(detectMenuOpenTimeout);
203             detectMenuOpenTimeout = null;
204         }
205
206         // If not already waiting, start detection of mouse leave
207         if (detectMenuCloseTimeout == null) {
208             detectMenuCloseTimeout = window.setTimeout(function() {
209                 GuacamoleUI.shadeMenu();
210                 detectMenuCloseTimeout = null;
211             }, 500);
212         }
213
214     }, true);
215
216     // Reconnect button
217     GuacamoleUI.buttons.reconnect.onclick = function() {
218         window.location.reload();
219     };
220
221     // On-screen keyboard
222     GuacamoleUI.keyboard = new Guacamole.OnScreenKeyboard("layouts/en-us-qwerty.xml");
223     GuacamoleUI.containers.keyboard.appendChild(GuacamoleUI.keyboard);
224
225 })();
226
227 // Tie UI events / behavior to a specific Guacamole client
228 GuacamoleUI.attach = function(guac) {
229
230     // Mouse
231     var mouse = new Guacamole.Mouse(GuacamoleUI.display);
232     mouse.onmousedown = mouse.onmouseup = mouse.onmousemove =
233         function(mouseState) {
234             guac.sendMouseState(mouseState);
235         };
236
237     // Keyboard
238     var keyboard = new Guacamole.Keyboard(document);
239
240     function disableKeyboard() {
241         keyboard.onkeydown = null;
242         keyboard.onkeyup = null;
243     }
244
245     function enableKeyboard() {
246         keyboard.onkeydown = 
247             function (keysym) {
248                 guac.sendKeyEvent(1, keysym);
249             };
250
251         keyboard.onkeyup = 
252             function (keysym) {
253                 guac.sendKeyEvent(0, keysym);
254             };
255     }
256
257     // Enable keyboard by default
258     enableKeyboard();
259
260     // Handle client state change
261     guac.onstatechange = function(clientState) {
262         switch (clientState) {
263
264             // Idle
265             case 0:
266                 GuacamoleUI.state.textContent = "Idle."
267                 break;
268
269             // Connecting
270             case 1:
271                 GuacamoleUI.state.textContent = "Connecting...";
272                 break;
273
274             // Connected + waiting
275             case 2:
276                 GuacamoleUI.state.textContent = "Connected, waiting for first update...";
277                 break;
278
279             // Connected
280             case 3:
281                 
282                 GuacamoleUI.display.className =
283                     GuacamoleUI.display.className.replace(/guac-loading/, '');
284
285                 GuacamoleUI.menu.className = "connected";
286                 GuacamoleUI.state.textContent = "Connected.";
287                 GuacamoleUI.shadeMenu();
288                 break;
289
290             // Disconnecting
291             case 4:
292                 GuacamoleUI.state.textContent = "Disconnecting...";
293                 break;
294
295             // Disconnected
296             case 5:
297                 GuacamoleUI.state.textContent = "Disconnected.";
298                 break;
299
300             // Unknown status code
301             default:
302                 GuacamoleUI.state.textContent = "Unknown";
303
304         }
305     };
306
307     // Name instruction handler
308     guac.onname = function(name) {
309         document.title = name;
310     };
311
312     // Error handler
313     guac.onerror = function(error) {
314
315         // Disconnect, if connected
316         guac.disconnect();
317
318         // Display error message
319         GuacamoleUI.showError(error);
320
321         // Show error by desaturating display
322         var layers = guac.getLayers();
323         for (var i=0; i<layers.length; i++) {
324             layers[i].filter(desaturateFilter);
325         }
326
327         // Filter for desaturation
328         function desaturateFilter(data, width, height) {
329
330             for (var i=0; i<data.length; i+=4) {
331
332                 // Get RGB values
333                 var r = data[i];
334                 var g = data[i+1];
335                 var b = data[i+2];
336
337                 // Desaturate
338                 var v = Math.max(r, g, b) / 2;
339                 data[i]   = v;
340                 data[i+1] = v;
341                 data[i+2] = v;
342
343             }
344
345         }
346         
347     };
348
349     // Disconnect on close
350     window.onunload = function() {
351         guac.disconnect();
352     };
353
354     // Handle clipboard events
355     GuacamoleUI.clipboard.onchange = function() {
356
357         var text = GuacamoleUI.clipboard.value;
358         guac.setClipboard(text);
359
360     };
361
362     // Ignore keypresses when clipboard is focused
363     GuacamoleUI.clipboard.onfocus = function() {
364         disableKeyboard();
365     };
366
367     // Capture keypresses when clipboard is not focused
368     GuacamoleUI.clipboard.onblur = function() {
369         enableKeyboard();
370     };
371
372     // Server copy handler
373     guac.onclipboard = function(data) {
374         GuacamoleUI.clipboard.value = data;
375     };
376
377     GuacamoleUI.keyboard.setKeyPressedHandler(
378         function(keysym) {
379             guac.sendKeyEvent(1, keysym);
380         }
381     );
382
383     GuacamoleUI.keyboard.setKeyReleasedHandler(
384         function(keysym) {
385             guac.sendKeyEvent(0, keysym);
386         }
387     );
388
389     // Send Ctrl-Alt-Delete
390     GuacamoleUI.buttons.ctrlAltDelete.onclick = function() {
391
392         var KEYSYM_CTRL   = 0xFFE3;
393         var KEYSYM_ALT    = 0xFFE9;
394         var KEYSYM_DELETE = 0xFFFF;
395
396         guac.sendKeyEvent(1, KEYSYM_CTRL);
397         guac.sendKeyEvent(1, KEYSYM_ALT);
398         guac.sendKeyEvent(1, KEYSYM_DELETE);
399         guac.sendKeyEvent(0, KEYSYM_DELETE);
400         guac.sendKeyEvent(0, KEYSYM_ALT);
401         guac.sendKeyEvent(0, KEYSYM_CTRL);
402     };
403
404 };