1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * @fileoverview 7 * Apps v2 custom title bar implementation 8 */ 9 10 'use strict'; 11 12 /** @suppress {duplicate} */ 13 var remoting = remoting || {}; 14 15 /** 16 * @param {HTMLElement} titleBar The root node of the title-bar DOM hierarchy. 17 * @constructor 18 */ 19 remoting.WindowFrame = function(titleBar) { 20 /** 21 * @type {remoting.ClientSession} 22 * @private 23 */ 24 this.clientSession_ = null; 25 26 /** 27 * @type {HTMLElement} 28 * @private 29 */ 30 this.titleBar_ = titleBar; 31 32 /** 33 * @type {HTMLElement} 34 * @private 35 */ 36 this.title_ = /** @type {HTMLElement} */ 37 (titleBar.querySelector('.window-title')); 38 base.debug.assert(this.title_ != null); 39 40 /** 41 * @type {HTMLElement} 42 * @private 43 */ 44 this.maximizeRestoreControl_ = /** @type {HTMLElement} */ 45 (titleBar.querySelector('.window-maximize-restore')); 46 base.debug.assert(this.maximizeRestoreControl_ != null); 47 48 var optionsButton = titleBar.querySelector('.window-options'); 49 base.debug.assert(optionsButton != null); 50 this.optionMenuButton_ = new remoting.MenuButton( 51 optionsButton, 52 this.onShowOptionsMenu_.bind(this), 53 this.onHideOptionsMenu_.bind(this)); 54 55 /** 56 * @type {HTMLElement} 57 * @private 58 */ 59 this.optionsMenuList_ = /** @type {HTMLElement} */ 60 (optionsButton.querySelector('.window-options-menu')); 61 base.debug.assert(this.optionsMenuList_ != null); 62 63 /** 64 * @type {Array.<{cls:string, fn: function()}>} 65 */ 66 var handlers = [ 67 { cls: 'window-disconnect', fn: this.disconnectSession_.bind(this) }, 68 { cls: 'window-maximize-restore', 69 fn: this.maximizeOrRestoreWindow_.bind(this) }, 70 { cls: 'window-minimize', fn: this.minimizeWindow_.bind(this) }, 71 { cls: 'window-close', fn: window.close.bind(window) }, 72 { cls: 'window-controls-stub', fn: this.toggleWindowControls_.bind(this) } 73 ]; 74 for (var i = 0; i < handlers.length; ++i) { 75 var element = titleBar.querySelector('.' + handlers[i].cls); 76 base.debug.assert(element != null); 77 element.addEventListener('click', handlers[i].fn, false); 78 } 79 80 // Ensure that tool-tips are always correct. 81 this.handleWindowStateChange_(); 82 chrome.app.window.current().onMaximized.addListener( 83 this.handleWindowStateChange_.bind(this)); 84 chrome.app.window.current().onRestored.addListener( 85 this.handleWindowStateChange_.bind(this)); 86 chrome.app.window.current().onFullscreened.addListener( 87 this.handleWindowStateChange_.bind(this)); 88 chrome.app.window.current().onFullscreened.addListener( 89 this.showWindowControlsPreview_.bind(this)); 90 }; 91 92 /** 93 * @return {remoting.OptionsMenu} 94 */ 95 remoting.WindowFrame.prototype.createOptionsMenu = function() { 96 return new remoting.OptionsMenu( 97 this.titleBar_.querySelector('.menu-send-ctrl-alt-del'), 98 this.titleBar_.querySelector('.menu-send-print-screen'), 99 this.titleBar_.querySelector('.menu-resize-to-client'), 100 this.titleBar_.querySelector('.menu-shrink-to-fit'), 101 this.titleBar_.querySelector('.menu-new-connection'), 102 this.titleBar_.querySelector('.window-fullscreen'), 103 this.titleBar_.querySelector('.menu-start-stop-recording')); 104 }; 105 106 /** 107 * @param {remoting.ClientSession} clientSession The client session, or null if 108 * there is no connection. 109 */ 110 remoting.WindowFrame.prototype.setClientSession = function(clientSession) { 111 this.clientSession_ = clientSession; 112 var windowTitle = document.head.querySelector('title'); 113 if (this.clientSession_) { 114 this.title_.innerText = clientSession.getHostDisplayName(); 115 windowTitle.innerText = clientSession.getHostDisplayName() + ' - ' + 116 chrome.i18n.getMessage(/*i18n-content*/'PRODUCT_NAME'); 117 } else { 118 this.title_.innerHTML = ' '; 119 windowTitle.innerText = 120 chrome.i18n.getMessage(/*i18n-content*/'PRODUCT_NAME'); 121 } 122 this.handleWindowStateChange_(); 123 }; 124 125 /** 126 * @return {{width: number, height: number}} The size of the window, ignoring 127 * the title-bar and window borders, if visible. 128 */ 129 remoting.WindowFrame.prototype.getClientArea = function() { 130 if (chrome.app.window.current().isFullscreen()) { 131 return { 'height': window.innerHeight, 'width': window.innerWidth }; 132 } else { 133 var kBorderWidth = 1; 134 var titleHeight = this.titleBar_.clientHeight; 135 return { 136 'height': window.innerHeight - titleHeight - 2 * kBorderWidth, 137 'width': window.innerWidth - 2 * kBorderWidth 138 }; 139 } 140 }; 141 142 /** 143 * @private 144 */ 145 remoting.WindowFrame.prototype.disconnectSession_ = function() { 146 // When the user disconnects, exit full-screen mode. This should not be 147 // necessary, as we do the same thing in client_session.js when the plugin 148 // is removed. However, there seems to be a bug in chrome.AppWindow.restore 149 // that causes it to get stuck in full-screen mode without this. 150 if (chrome.app.window.current().isFullscreen()) { 151 chrome.app.window.current().restore(); 152 } 153 remoting.disconnect(); 154 }; 155 156 /** 157 * @private 158 */ 159 remoting.WindowFrame.prototype.maximizeOrRestoreWindow_ = function() { 160 /** @type {boolean} */ 161 var restore = 162 chrome.app.window.current().isFullscreen() || 163 chrome.app.window.current().isMaximized(); 164 if (restore) { 165 chrome.app.window.current().restore(); 166 } else { 167 chrome.app.window.current().maximize(); 168 } 169 }; 170 171 /** 172 * @private 173 */ 174 remoting.WindowFrame.prototype.minimizeWindow_ = function() { 175 chrome.app.window.current().minimize(); 176 }; 177 178 /** 179 * @private 180 */ 181 remoting.WindowFrame.prototype.toggleWindowControls_ = function() { 182 this.titleBar_.classList.toggle('opened'); 183 }; 184 185 /** 186 * Update the tool-top for the maximize/full-screen/restore icon to reflect 187 * its current behaviour. 188 * 189 * @private 190 */ 191 remoting.WindowFrame.prototype.handleWindowStateChange_ = function() { 192 // Set the title for the maximize/restore/full-screen button 193 /** @type {string} */ 194 var tag = ''; 195 if (chrome.app.window.current().isFullscreen()) { 196 tag = /*i18n-content*/'EXIT_FULL_SCREEN'; 197 } else if (chrome.app.window.current().isMaximized()) { 198 tag = /*i18n-content*/'RESTORE_WINDOW'; 199 } else { 200 tag = /*i18n-content*/'MAXIMIZE_WINDOW'; 201 } 202 this.maximizeRestoreControl_.title = l10n.getTranslationOrError(tag); 203 204 // Ensure that the options menu aligns correctly for the side of the window 205 // it occupies. 206 if (chrome.app.window.current().isFullscreen()) { 207 this.optionsMenuList_.classList.add('right-align'); 208 } else { 209 this.optionsMenuList_.classList.remove('right-align'); 210 } 211 }; 212 213 /** 214 * Callback invoked when the options menu is shown. 215 * @private 216 */ 217 remoting.WindowFrame.prototype.onShowOptionsMenu_ = function() { 218 remoting.optionsMenu.onShow(); 219 this.titleBar_.classList.add('menu-opened'); 220 }; 221 222 /** 223 * Callback invoked when the options menu is shown. 224 * @private 225 */ 226 remoting.WindowFrame.prototype.onHideOptionsMenu_ = function() { 227 this.titleBar_.classList.remove('menu-opened'); 228 }; 229 230 /** 231 * Show the window controls for a few seconds 232 * 233 * @private 234 */ 235 remoting.WindowFrame.prototype.showWindowControlsPreview_ = function() { 236 /** 237 * @type {HTMLElement} 238 */ 239 var target = this.titleBar_; 240 var kPreviewTimeoutMs = 3000; 241 var hidePreview = function() { 242 target.classList.remove('preview'); 243 }; 244 target.classList.add('preview'); 245 window.setTimeout(hidePreview, kPreviewTimeoutMs); 246 }; 247 248 249 /** @type {remoting.WindowFrame} */ 250 remoting.windowFrame = null; 251