Initial fixes for buggy menu hiding feature.
[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 / 5) + 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     GuacamoleUI.menu.addEventListener('mouseout', function(e) {
155         
156         // Get parent of the element the mouse pointer is leaving
157         if (!e) e = window.event;
158         var target = e.relatedTarget || e.toElement;
159         
160         // Ensure target is not menu nor child of menu
161         var targetParent = target;
162         while (targetParent != null) {
163             if (targetParent == GuacamoleUI.menu) return;
164             targetParent = targetParent.parentNode;
165         }
166
167         // If not already waiting, start detection of mouse leave
168         if (detectMenuCloseTimeout == null) {
169             detectMenuCloseTimeout = window.setTimeout(function() {
170                 GuacamoleUI.shadeMenu();
171                 detectMenuCloseTimeout = null;
172             }, 750);
173         }
174  
175     }, true);
176
177     // When mouse hovers over top of screen, start detection of mouse hover
178     GuacamoleUI.menuControl.addEventListener('mousemove', function() {
179
180         // If we were waiting for menu close, we're not anymore
181         if (detectMenuCloseTimeout != null) {
182             window.clearTimeout(detectMenuCloseTimeout);
183             detectMenuCloseTimeout = null;
184         }
185         
186         // Clear old timeout if mouse moved while we were waiting
187         if (detectMenuOpenTimeout != null) {
188             window.clearTimeout(detectMenuOpenTimeout);
189             detectMenuOpenTimeout = null;
190         }
191
192         // If not alread waiting, wait for 250ms before showing menu
193         detectMenuOpenTimeout = window.setTimeout(function() {
194             GuacamoleUI.showMenu();
195             detectMenuOpenTimeout = null;
196         }, 250);
197
198     }, true);
199
200     // When mouse leaves top of screen, cancel showing the menu
201     GuacamoleUI.menuControl.addEventListener('mouseout', function() {
202
203         // If we were waiting for menu open, we're not anymore
204         if (detectMenuOpenTimeout != null) {
205             window.clearTimeout(detectMenuOpenTimeout);
206             detectMenuCloseTimeout = null;
207         }
208
209         // If not already waiting, start detection of mouse leave
210         if (detectMenuCloseTimeout == null) {
211             detectMenuCloseTimeout = window.setTimeout(function() {
212                 GuacamoleUI.shadeMenu();
213                 detectMenuCloseTimeout = null;
214             }, 750);
215         }
216
217     }, true);
218
219
220     // Reconnect button
221     GuacamoleUI.buttons.reconnect.onclick = function() {
222         window.location.reload();
223     };
224
225     // On-screen keyboard
226     GuacamoleUI.keyboard = new Guacamole.OnScreenKeyboard("layouts/en-us-qwerty.xml");
227     GuacamoleUI.containers.keyboard.appendChild(GuacamoleUI.keyboard);
228
229 })();
230
231 // Tie UI events / behavior to a specific Guacamole client
232 GuacamoleUI.attach = function(guac) {
233
234     // Mouse
235     var mouse = new Guacamole.Mouse(GuacamoleUI.display);
236     mouse.onmousedown = mouse.onmouseup = mouse.onmousemove =
237         function(mouseState) {
238             guac.sendMouseState(mouseState);
239         };
240
241     // Keyboard
242     var keyboard = new Guacamole.Keyboard(document);
243
244     function disableKeyboard() {
245         keyboard.onkeydown = null;
246         keyboard.onkeyup = null;
247     }
248
249     function enableKeyboard() {
250         keyboard.onkeydown = 
251             function (keysym) {
252                 guac.sendKeyEvent(1, keysym);
253             };
254
255         keyboard.onkeyup = 
256             function (keysym) {
257                 guac.sendKeyEvent(0, keysym);
258             };
259     }
260
261     // Enable keyboard by default
262     enableKeyboard();
263
264     // Handle client state change
265     guac.onstatechange = function(clientState) {
266         switch (clientState) {
267
268             // Idle
269             case 0:
270                 GuacamoleUI.state.textContent = "Idle."
271                 break;
272
273             // Connecting
274             case 1:
275                 GuacamoleUI.state.textContent = "Connecting...";
276                 break;
277
278             // Connected + waiting
279             case 2:
280                 GuacamoleUI.state.textContent = "Connected, waiting for first update...";
281                 break;
282
283             // Connected
284             case 3:
285                 
286                 GuacamoleUI.display.className =
287                     GuacamoleUI.display.className.replace(/guac-loading/, '');
288
289                 GuacamoleUI.menu.className = "connected";
290                 GuacamoleUI.state.textContent = "Connected.";
291                 GuacamoleUI.shadeMenu();
292                 break;
293
294             // Disconnecting
295             case 4:
296                 GuacamoleUI.state.textContent = "Disconnecting...";
297                 break;
298
299             // Disconnected
300             case 5:
301                 GuacamoleUI.state.textContent = "Disconnected.";
302                 break;
303
304             // Unknown status code
305             default:
306                 GuacamoleUI.state.textContent = "Unknown";
307
308         }
309     };
310
311     // Name instruction handler
312     guac.onname = function(name) {
313         document.title = name;
314     };
315
316     // Error handler
317     guac.onerror = function(error) {
318
319         // Disconnect, if connected
320         guac.disconnect();
321
322         // Display error message
323         GuacamoleUI.showError(error);
324
325         // Show error by desaturating display
326         var layers = guac.getLayers();
327         for (var i=0; i<layers.length; i++) {
328             layers[i].filter(desaturateFilter);
329         }
330
331         // Filter for desaturation
332         function desaturateFilter(data, width, height) {
333
334             for (var i=0; i<data.length; i+=4) {
335
336                 // Get RGB values
337                 var r = data[i];
338                 var g = data[i+1];
339                 var b = data[i+2];
340
341                 // Desaturate
342                 var v = Math.max(r, g, b) / 2;
343                 data[i]   = v;
344                 data[i+1] = v;
345                 data[i+2] = v;
346
347             }
348
349         }
350         
351     };
352
353     // Disconnect on close
354     window.onunload = function() {
355         guac.disconnect();
356     };
357
358     // Handle clipboard events
359     GuacamoleUI.clipboard.onchange = function() {
360
361         var text = GuacamoleUI.clipboard.value;
362         guac.setClipboard(text);
363
364     };
365
366     // Ignore keypresses when clipboard is focused
367     GuacamoleUI.clipboard.onfocus = function() {
368         disableKeyboard();
369     };
370
371     // Capture keypresses when clipboard is not focused
372     GuacamoleUI.clipboard.onblur = function() {
373         enableKeyboard();
374     };
375
376     // Server copy handler
377     guac.onclipboard = function(data) {
378         GuacamoleUI.clipboard.value = data;
379     };
380
381     GuacamoleUI.keyboard.setKeyPressedHandler(
382         function(keysym) {
383             guac.sendKeyEvent(1, keysym);
384         }
385     );
386
387     GuacamoleUI.keyboard.setKeyReleasedHandler(
388         function(keysym) {
389             guac.sendKeyEvent(0, keysym);
390         }
391     );
392
393     // Send Ctrl-Alt-Delete
394     GuacamoleUI.buttons.ctrlAltDelete.onclick = function() {
395
396         var KEYSYM_CTRL   = 0xFFE3;
397         var KEYSYM_ALT    = 0xFFE9;
398         var KEYSYM_DELETE = 0xFFFF;
399
400         guac.sendKeyEvent(1, KEYSYM_CTRL);
401         guac.sendKeyEvent(1, KEYSYM_ALT);
402         guac.sendKeyEvent(1, KEYSYM_DELETE);
403         guac.sendKeyEvent(0, KEYSYM_DELETE);
404         guac.sendKeyEvent(0, KEYSYM_ALT);
405         guac.sendKeyEvent(0, KEYSYM_CTRL);
406     };
407
408 };