Home | History | Annotate | Download | only in webapp
      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 = '&nbsp;';
    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