Home | History | Annotate | Download | only in chromeos
      1 // Copyright (c) 2012 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 cr.exportPath('options');
      6 
      7 /**
      8  * @typedef {{
      9  *   availableColorProfiles: Array.<{profileId: number, name: string}>,
     10  *   colorProfile: number,
     11  *   height: number,
     12  *   id: string,
     13  *   isInternal: boolean,
     14  *   isPrimary: boolean,
     15  *   resolutions: Array.<{width: number, height: number, originalWidth: number,
     16  *       originalHeight: number, deviceScaleFactor: number, scale: number,
     17  *       refreshRate: number, isBest: boolean, selected: boolean}>,
     18  *   name: string,
     19  *   orientation: number,
     20  *   width: number,
     21  *   x: number,
     22  *   y: number
     23  * }}
     24  */
     25 options.DisplayInfo;
     26 
     27 /**
     28  * Enumeration of secondary display layout.  The value has to be same as the
     29  * values in ash/display/display_controller.cc.
     30  * @enum {number}
     31  */
     32 options.SecondaryDisplayLayout = {
     33   TOP: 0,
     34   RIGHT: 1,
     35   BOTTOM: 2,
     36   LEFT: 3
     37 };
     38 
     39 cr.define('options', function() {
     40   var Page = cr.ui.pageManager.Page;
     41   var PageManager = cr.ui.pageManager.PageManager;
     42 
     43   // The scale ratio of the display rectangle to its original size.
     44   /** @const */ var VISUAL_SCALE = 1 / 10;
     45 
     46   // The number of pixels to share the edges between displays.
     47   /** @const */ var MIN_OFFSET_OVERLAP = 5;
     48 
     49   /**
     50    * Calculates the bounds of |element| relative to the page.
     51    * @param {HTMLElement} element The element to be known.
     52    * @return {Object} The object for the bounds, with x, y, width, and height.
     53    */
     54   function getBoundsInPage(element) {
     55     var bounds = {
     56       x: element.offsetLeft,
     57       y: element.offsetTop,
     58       width: element.offsetWidth,
     59       height: element.offsetHeight
     60     };
     61     var parent = element.offsetParent;
     62     while (parent && parent != document.body) {
     63       bounds.x += parent.offsetLeft;
     64       bounds.y += parent.offsetTop;
     65       parent = parent.offsetParent;
     66     }
     67     return bounds;
     68   }
     69 
     70   /**
     71    * Gets the position of |point| to |rect|, left, right, top, or bottom.
     72    * @param {Object} rect The base rectangle with x, y, width, and height.
     73    * @param {Object} point The point to check the position.
     74    * @return {options.SecondaryDisplayLayout} The position of the calculated
     75    *     point.
     76    */
     77   function getPositionToRectangle(rect, point) {
     78     // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of
     79     // the rect, and decides which area the display should reside.
     80     var diagonalSlope = rect.height / rect.width;
     81     var topDownIntercept = rect.y - rect.x * diagonalSlope;
     82     var bottomUpIntercept = rect.y + rect.height + rect.x * diagonalSlope;
     83 
     84     if (point.y > topDownIntercept + point.x * diagonalSlope) {
     85       if (point.y > bottomUpIntercept - point.x * diagonalSlope)
     86         return options.SecondaryDisplayLayout.BOTTOM;
     87       else
     88         return options.SecondaryDisplayLayout.LEFT;
     89     } else {
     90       if (point.y > bottomUpIntercept - point.x * diagonalSlope)
     91         return options.SecondaryDisplayLayout.RIGHT;
     92       else
     93         return options.SecondaryDisplayLayout.TOP;
     94     }
     95   }
     96 
     97   /**
     98    * Encapsulated handling of the 'Display' page.
     99    * @constructor
    100    * @extends {cr.ui.pageManager.Page}
    101    */
    102   function DisplayOptions() {
    103     Page.call(this, 'display',
    104               loadTimeData.getString('displayOptionsPageTabTitle'),
    105               'display-options-page');
    106   }
    107 
    108   cr.addSingletonGetter(DisplayOptions);
    109 
    110   DisplayOptions.prototype = {
    111     __proto__: Page.prototype,
    112 
    113     /**
    114      * Whether the current output status is mirroring displays or not.
    115      * @private
    116      */
    117     mirroring_: false,
    118 
    119     /**
    120      * The current secondary display layout.
    121      * @private
    122      */
    123     layout_: options.SecondaryDisplayLayout.RIGHT,
    124 
    125     /**
    126      * The array of current output displays.  It also contains the display
    127      * rectangles currently rendered on screen.
    128      * @type {Array.<options.DisplayInfo>}
    129      * @private
    130      */
    131     displays_: [],
    132 
    133     /**
    134      * The index for the currently focused display in the options UI.  null if
    135      * no one has focus.
    136      * @private
    137      */
    138     focusedIndex_: null,
    139 
    140     /**
    141      * The primary display.
    142      * @private
    143      */
    144     primaryDisplay_: null,
    145 
    146     /**
    147      * The secondary display.
    148      * @private
    149      */
    150     secondaryDisplay_: null,
    151 
    152     /**
    153      * The container div element which contains all of the display rectangles.
    154      * @private
    155      */
    156     displaysView_: null,
    157 
    158     /**
    159      * The scale factor of the actual display size to the drawn display
    160      * rectangle size.
    161      * @private
    162      */
    163     visualScale_: VISUAL_SCALE,
    164 
    165     /**
    166      * The location where the last touch event happened.  This is used to
    167      * prevent unnecessary dragging events happen.  Set to null unless it's
    168      * during touch events.
    169      * @private
    170      */
    171     lastTouchLocation_: null,
    172 
    173     /** @override */
    174     initializePage: function() {
    175       Page.prototype.initializePage.call(this);
    176 
    177       $('display-options-toggle-mirroring').onclick = (function() {
    178         this.mirroring_ = !this.mirroring_;
    179         chrome.send('setMirroring', [this.mirroring_]);
    180       }).bind(this);
    181 
    182       var container = $('display-options-displays-view-host');
    183       container.onmousemove = this.onMouseMove_.bind(this);
    184       window.addEventListener('mouseup', this.endDragging_.bind(this), true);
    185       container.ontouchmove = this.onTouchMove_.bind(this);
    186       container.ontouchend = this.endDragging_.bind(this);
    187 
    188       $('display-options-set-primary').onclick = (function() {
    189         chrome.send('setPrimary', [this.displays_[this.focusedIndex_].id]);
    190       }).bind(this);
    191       $('display-options-resolution-selection').onchange = (function(ev) {
    192         var display = this.displays_[this.focusedIndex_];
    193         var resolution = display.resolutions[ev.target.value];
    194         chrome.send('setDisplayMode', [display.id, resolution]);
    195       }).bind(this);
    196       $('display-options-orientation-selection').onchange = (function(ev) {
    197         chrome.send('setOrientation', [this.displays_[this.focusedIndex_].id,
    198                                        ev.target.value]);
    199       }).bind(this);
    200       $('display-options-color-profile-selection').onchange = (function(ev) {
    201         chrome.send('setColorProfile', [this.displays_[this.focusedIndex_].id,
    202                                         ev.target.value]);
    203       }).bind(this);
    204       $('selected-display-start-calibrating-overscan').onclick = (function() {
    205         // Passes the target display ID. Do not specify it through URL hash,
    206         // we do not care back/forward.
    207         var displayOverscan = options.DisplayOverscan.getInstance();
    208         displayOverscan.setDisplayId(this.displays_[this.focusedIndex_].id);
    209         PageManager.showPageByName('displayOverscan');
    210         chrome.send('coreOptionsUserMetricsAction',
    211                     ['Options_DisplaySetOverscan']);
    212       }).bind(this);
    213     },
    214 
    215     /** @override */
    216     didShowPage: function() {
    217       var optionTitles = document.getElementsByClassName(
    218           'selected-display-option-title');
    219       var maxSize = 0;
    220       for (var i = 0; i < optionTitles.length; i++)
    221         maxSize = Math.max(maxSize, optionTitles[i].clientWidth);
    222       for (var i = 0; i < optionTitles.length; i++)
    223         optionTitles[i].style.width = maxSize + 'px';
    224       chrome.send('getDisplayInfo');
    225     },
    226 
    227     /**
    228      * Mouse move handler for dragging display rectangle.
    229      * @param {Event} e The mouse move event.
    230      * @private
    231      */
    232     onMouseMove_: function(e) {
    233       return this.processDragging_(e, {x: e.pageX, y: e.pageY});
    234     },
    235 
    236     /**
    237      * Touch move handler for dragging display rectangle.
    238      * @param {Event} e The touch move event.
    239      * @private
    240      */
    241     onTouchMove_: function(e) {
    242       if (e.touches.length != 1)
    243         return true;
    244 
    245       var touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY};
    246       // Touch move events happen even if the touch location doesn't change, but
    247       // it doesn't need to process the dragging.  Since sometimes the touch
    248       // position changes slightly even though the user doesn't think to move
    249       // the finger, very small move is just ignored.
    250       /** @const */ var IGNORABLE_TOUCH_MOVE_PX = 1;
    251       var xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x);
    252       var yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y);
    253       if (xDiff <= IGNORABLE_TOUCH_MOVE_PX &&
    254           yDiff <= IGNORABLE_TOUCH_MOVE_PX) {
    255         return true;
    256       }
    257 
    258       this.lastTouchLocation_ = touchLocation;
    259       return this.processDragging_(e, touchLocation);
    260     },
    261 
    262     /**
    263      * Mouse down handler for dragging display rectangle.
    264      * @param {Event} e The mouse down event.
    265      * @private
    266      */
    267     onMouseDown_: function(e) {
    268       if (this.mirroring_)
    269         return true;
    270 
    271       if (e.button != 0)
    272         return true;
    273 
    274       e.preventDefault();
    275       var target = assertInstanceof(e.target, HTMLElement);
    276       return this.startDragging_(target, {x: e.pageX, y: e.pageY});
    277     },
    278 
    279     /**
    280      * Touch start handler for dragging display rectangle.
    281      * @param {Event} e The touch start event.
    282      * @private
    283      */
    284     onTouchStart_: function(e) {
    285       if (this.mirroring_)
    286         return true;
    287 
    288       if (e.touches.length != 1)
    289         return false;
    290 
    291       e.preventDefault();
    292       var touch = e.touches[0];
    293       this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY};
    294       var target = assertInstanceof(e.target, HTMLElement);
    295       return this.startDragging_(target, this.lastTouchLocation_);
    296     },
    297 
    298     /**
    299      * Collects the current data and sends it to Chrome.
    300      * @private
    301      */
    302     applyResult_: function() {
    303       // Offset is calculated from top or left edge.
    304       var primary = this.primaryDisplay_;
    305       var secondary = this.secondaryDisplay_;
    306       var offset;
    307       if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
    308           this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
    309         offset = secondary.div.offsetTop - primary.div.offsetTop;
    310       } else {
    311         offset = secondary.div.offsetLeft - primary.div.offsetLeft;
    312       }
    313       chrome.send('setDisplayLayout',
    314                   [this.layout_, offset / this.visualScale_]);
    315     },
    316 
    317     /**
    318      * Snaps the region [point, width] to [basePoint, baseWidth] if
    319      * the [point, width] is close enough to the base's edge.
    320      * @param {number} point The starting point of the region.
    321      * @param {number} width The width of the region.
    322      * @param {number} basePoint The starting point of the base region.
    323      * @param {number} baseWidth The width of the base region.
    324      * @return {number} The moved point.  Returns point itself if it doesn't
    325      *     need to snap to the edge.
    326      * @private
    327      */
    328     snapToEdge_: function(point, width, basePoint, baseWidth) {
    329       // If the edge of the regions is smaller than this, it will snap to the
    330       // base's edge.
    331       /** @const */ var SNAP_DISTANCE_PX = 16;
    332 
    333       var startDiff = Math.abs(point - basePoint);
    334       var endDiff = Math.abs(point + width - (basePoint + baseWidth));
    335       // Prefer the closer one if both edges are close enough.
    336       if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff)
    337         return basePoint;
    338       else if (endDiff < SNAP_DISTANCE_PX)
    339         return basePoint + baseWidth - width;
    340 
    341       return point;
    342     },
    343 
    344     /**
    345      * Processes the actual dragging of display rectangle.
    346      * @param {Event} e The event which triggers this drag.
    347      * @param {Object} eventLocation The location where the event happens.
    348      * @private
    349      */
    350     processDragging_: function(e, eventLocation) {
    351       if (!this.dragging_)
    352         return true;
    353 
    354       var index = -1;
    355       for (var i = 0; i < this.displays_.length; i++) {
    356         if (this.displays_[i] == this.dragging_.display) {
    357           index = i;
    358           break;
    359         }
    360       }
    361       if (index < 0)
    362         return true;
    363 
    364       e.preventDefault();
    365 
    366       // Note that current code of moving display-rectangles doesn't work
    367       // if there are >=3 displays.  This is our assumption for M21.
    368       // TODO(mukai): Fix the code to allow >=3 displays.
    369       var newPosition = {
    370         x: this.dragging_.originalLocation.x +
    371             (eventLocation.x - this.dragging_.eventLocation.x),
    372         y: this.dragging_.originalLocation.y +
    373             (eventLocation.y - this.dragging_.eventLocation.y)
    374       };
    375 
    376       var baseDiv = this.dragging_.display.isPrimary ?
    377           this.secondaryDisplay_.div : this.primaryDisplay_.div;
    378       var draggingDiv = this.dragging_.display.div;
    379 
    380       newPosition.x = this.snapToEdge_(newPosition.x, draggingDiv.offsetWidth,
    381                                        baseDiv.offsetLeft, baseDiv.offsetWidth);
    382       newPosition.y = this.snapToEdge_(newPosition.y, draggingDiv.offsetHeight,
    383                                        baseDiv.offsetTop, baseDiv.offsetHeight);
    384 
    385       var newCenter = {
    386         x: newPosition.x + draggingDiv.offsetWidth / 2,
    387         y: newPosition.y + draggingDiv.offsetHeight / 2
    388       };
    389 
    390       var baseBounds = {
    391         x: baseDiv.offsetLeft,
    392         y: baseDiv.offsetTop,
    393         width: baseDiv.offsetWidth,
    394         height: baseDiv.offsetHeight
    395       };
    396       switch (getPositionToRectangle(baseBounds, newCenter)) {
    397         case options.SecondaryDisplayLayout.RIGHT:
    398           this.layout_ = this.dragging_.display.isPrimary ?
    399               options.SecondaryDisplayLayout.LEFT :
    400               options.SecondaryDisplayLayout.RIGHT;
    401           break;
    402         case options.SecondaryDisplayLayout.LEFT:
    403           this.layout_ = this.dragging_.display.isPrimary ?
    404               options.SecondaryDisplayLayout.RIGHT :
    405               options.SecondaryDisplayLayout.LEFT;
    406           break;
    407         case options.SecondaryDisplayLayout.TOP:
    408           this.layout_ = this.dragging_.display.isPrimary ?
    409               options.SecondaryDisplayLayout.BOTTOM :
    410               options.SecondaryDisplayLayout.TOP;
    411           break;
    412         case options.SecondaryDisplayLayout.BOTTOM:
    413           this.layout_ = this.dragging_.display.isPrimary ?
    414               options.SecondaryDisplayLayout.TOP :
    415               options.SecondaryDisplayLayout.BOTTOM;
    416           break;
    417       }
    418 
    419       if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
    420           this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
    421         if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight)
    422           this.layout_ = this.dragging_.display.isPrimary ?
    423               options.SecondaryDisplayLayout.TOP :
    424               options.SecondaryDisplayLayout.BOTTOM;
    425         else if (newPosition.y + draggingDiv.offsetHeight <
    426                  baseDiv.offsetTop)
    427           this.layout_ = this.dragging_.display.isPrimary ?
    428               options.SecondaryDisplayLayout.BOTTOM :
    429               options.SecondaryDisplayLayout.TOP;
    430       } else {
    431         if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth)
    432           this.layout_ = this.dragging_.display.isPrimary ?
    433               options.SecondaryDisplayLayout.LEFT :
    434               options.SecondaryDisplayLayout.RIGHT;
    435         else if (newPosition.x + draggingDiv.offsetWidth <
    436                    baseDiv.offsetLeft)
    437           this.layout_ = this.dragging_.display.isPrimary ?
    438               options.SecondaryDisplayLayout.RIGHT :
    439               options.SecondaryDisplayLayout.LEFT;
    440       }
    441 
    442       var layoutToBase;
    443       if (!this.dragging_.display.isPrimary) {
    444         layoutToBase = this.layout_;
    445       } else {
    446         switch (this.layout_) {
    447           case options.SecondaryDisplayLayout.RIGHT:
    448             layoutToBase = options.SecondaryDisplayLayout.LEFT;
    449             break;
    450           case options.SecondaryDisplayLayout.LEFT:
    451             layoutToBase = options.SecondaryDisplayLayout.RIGHT;
    452             break;
    453           case options.SecondaryDisplayLayout.TOP:
    454             layoutToBase = options.SecondaryDisplayLayout.BOTTOM;
    455             break;
    456           case options.SecondaryDisplayLayout.BOTTOM:
    457             layoutToBase = options.SecondaryDisplayLayout.TOP;
    458             break;
    459         }
    460       }
    461 
    462       switch (layoutToBase) {
    463         case options.SecondaryDisplayLayout.RIGHT:
    464           draggingDiv.style.left =
    465               baseDiv.offsetLeft + baseDiv.offsetWidth + 'px';
    466           draggingDiv.style.top = newPosition.y + 'px';
    467           break;
    468         case options.SecondaryDisplayLayout.LEFT:
    469           draggingDiv.style.left =
    470               baseDiv.offsetLeft - draggingDiv.offsetWidth + 'px';
    471           draggingDiv.style.top = newPosition.y + 'px';
    472           break;
    473         case options.SecondaryDisplayLayout.TOP:
    474           draggingDiv.style.top =
    475               baseDiv.offsetTop - draggingDiv.offsetHeight + 'px';
    476           draggingDiv.style.left = newPosition.x + 'px';
    477           break;
    478         case options.SecondaryDisplayLayout.BOTTOM:
    479           draggingDiv.style.top =
    480               baseDiv.offsetTop + baseDiv.offsetHeight + 'px';
    481           draggingDiv.style.left = newPosition.x + 'px';
    482           break;
    483       }
    484 
    485       return false;
    486     },
    487 
    488     /**
    489      * start dragging of a display rectangle.
    490      * @param {HTMLElement} target The event target.
    491      * @param {Object} eventLocation The object to hold the location where
    492      *     this event happens.
    493      * @private
    494      */
    495     startDragging_: function(target, eventLocation) {
    496       this.focusedIndex_ = null;
    497       for (var i = 0; i < this.displays_.length; i++) {
    498         var display = this.displays_[i];
    499         if (display.div == target ||
    500             (target.offsetParent && target.offsetParent == display.div)) {
    501           this.focusedIndex_ = i;
    502           break;
    503         }
    504       }
    505 
    506       for (var i = 0; i < this.displays_.length; i++) {
    507         var display = this.displays_[i];
    508         display.div.className = 'displays-display';
    509         if (i != this.focusedIndex_)
    510           continue;
    511 
    512         display.div.classList.add('displays-focused');
    513         if (this.displays_.length > 1) {
    514           this.dragging_ = {
    515             display: display,
    516             originalLocation: {
    517               x: display.div.offsetLeft, y: display.div.offsetTop
    518             },
    519             eventLocation: eventLocation
    520           };
    521         }
    522       }
    523 
    524       this.updateSelectedDisplayDescription_();
    525       return false;
    526     },
    527 
    528     /**
    529      * finish the current dragging of displays.
    530      * @param {Event} e The event which triggers this.
    531      * @private
    532      */
    533     endDragging_: function(e) {
    534       this.lastTouchLocation_ = null;
    535       if (this.dragging_) {
    536         // Make sure the dragging location is connected.
    537         var baseDiv = this.dragging_.display.isPrimary ?
    538             this.secondaryDisplay_.div : this.primaryDisplay_.div;
    539         var draggingDiv = this.dragging_.display.div;
    540         if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
    541             this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
    542           var top = Math.max(draggingDiv.offsetTop,
    543                              baseDiv.offsetTop - draggingDiv.offsetHeight +
    544                              MIN_OFFSET_OVERLAP);
    545           top = Math.min(top,
    546                          baseDiv.offsetTop + baseDiv.offsetHeight -
    547                          MIN_OFFSET_OVERLAP);
    548           draggingDiv.style.top = top + 'px';
    549         } else {
    550           var left = Math.max(draggingDiv.offsetLeft,
    551                               baseDiv.offsetLeft - draggingDiv.offsetWidth +
    552                               MIN_OFFSET_OVERLAP);
    553           left = Math.min(left,
    554                           baseDiv.offsetLeft + baseDiv.offsetWidth -
    555                           MIN_OFFSET_OVERLAP);
    556           draggingDiv.style.left = left + 'px';
    557         }
    558         var originalPosition = this.dragging_.display.originalPosition;
    559         if (originalPosition.x != draggingDiv.offsetLeft ||
    560             originalPosition.y != draggingDiv.offsetTop)
    561           this.applyResult_();
    562         this.dragging_ = null;
    563       }
    564       this.updateSelectedDisplayDescription_();
    565       return false;
    566     },
    567 
    568     /**
    569      * Updates the description of selected display section for mirroring mode.
    570      * @private
    571      */
    572     updateSelectedDisplaySectionMirroring_: function() {
    573       $('display-configuration-arrow').hidden = true;
    574       $('display-options-set-primary').disabled = true;
    575       $('display-options-toggle-mirroring').disabled = false;
    576       $('selected-display-start-calibrating-overscan').disabled = true;
    577       $('display-options-orientation-selection').disabled = true;
    578       var display = this.displays_[0];
    579       $('selected-display-name').textContent =
    580           loadTimeData.getString('mirroringDisplay');
    581       var resolution = $('display-options-resolution-selection');
    582       var option = document.createElement('option');
    583       option.value = 'default';
    584       option.textContent = display.width + 'x' + display.height;
    585       resolution.appendChild(option);
    586       resolution.disabled = true;
    587     },
    588 
    589     /**
    590      * Updates the description of selected display section when no display is
    591      * selected.
    592      * @private
    593      */
    594     updateSelectedDisplaySectionNoSelected_: function() {
    595       $('display-configuration-arrow').hidden = true;
    596       $('display-options-set-primary').disabled = true;
    597       $('display-options-toggle-mirroring').disabled = true;
    598       $('selected-display-start-calibrating-overscan').disabled = true;
    599       $('display-options-orientation-selection').disabled = true;
    600       $('selected-display-name').textContent = '';
    601       var resolution = $('display-options-resolution-selection');
    602       resolution.appendChild(document.createElement('option'));
    603       resolution.disabled = true;
    604     },
    605 
    606     /**
    607      * Updates the description of selected display section for the selected
    608      * display.
    609      * @param {Object} display The selected display object.
    610      * @private
    611      */
    612     updateSelectedDisplaySectionForDisplay_: function(display) {
    613       var arrow = $('display-configuration-arrow');
    614       arrow.hidden = false;
    615       // Adding 1 px to the position to fit the border line and the border in
    616       // arrow precisely.
    617       arrow.style.top = $('display-configurations').offsetTop -
    618           arrow.offsetHeight / 2 + 'px';
    619       arrow.style.left = display.div.offsetLeft +
    620           display.div.offsetWidth / 2 - arrow.offsetWidth / 2 + 'px';
    621 
    622       $('display-options-set-primary').disabled = display.isPrimary;
    623       $('display-options-toggle-mirroring').disabled =
    624           (this.displays_.length <= 1);
    625       $('selected-display-start-calibrating-overscan').disabled =
    626           display.isInternal;
    627 
    628       var orientation = $('display-options-orientation-selection');
    629       orientation.disabled = false;
    630       var orientationOptions = orientation.getElementsByTagName('option');
    631       orientationOptions[display.orientation].selected = true;
    632 
    633       $('selected-display-name').textContent = display.name;
    634 
    635       var resolution = $('display-options-resolution-selection');
    636       if (display.resolutions.length <= 1) {
    637         var option = document.createElement('option');
    638         option.value = 'default';
    639         option.textContent = display.width + 'x' + display.height;
    640         option.selected = true;
    641         resolution.appendChild(option);
    642         resolution.disabled = true;
    643       } else {
    644         var previousOption;
    645         for (var i = 0; i < display.resolutions.length; i++) {
    646           var option = document.createElement('option');
    647           option.value = i;
    648           option.textContent = display.resolutions[i].width + 'x' +
    649               display.resolutions[i].height;
    650           if (display.resolutions[i].isBest) {
    651             option.textContent += ' ' +
    652                 loadTimeData.getString('annotateBest');
    653           } else if (display.resolutions[i].isNative) {
    654             option.textContent += ' ' +
    655                 loadTimeData.getString('annotateNative');
    656           }
    657           if (display.resolutions[i].deviceScaleFactor && previousOption &&
    658               previousOption.textContent == option.textContent) {
    659             option.textContent +=
    660                 ' (' + display.resolutions[i].deviceScaleFactor + 'x)';
    661           }
    662           option.selected = display.resolutions[i].selected;
    663           resolution.appendChild(option);
    664           previousOption = option;
    665         }
    666         resolution.disabled = (display.resolutions.length <= 1);
    667       }
    668 
    669       if (display.availableColorProfiles.length <= 1) {
    670         $('selected-display-color-profile-row').hidden = true;
    671       } else {
    672         $('selected-display-color-profile-row').hidden = false;
    673         var profiles = $('display-options-color-profile-selection');
    674         profiles.innerHTML = '';
    675         for (var i = 0; i < display.availableColorProfiles.length; i++) {
    676           var option = document.createElement('option');
    677           var colorProfile = display.availableColorProfiles[i];
    678           option.value = colorProfile.profileId;
    679           option.textContent = colorProfile.name;
    680           option.selected = (
    681               display.colorProfile == colorProfile.profileId);
    682           profiles.appendChild(option);
    683         }
    684       }
    685     },
    686 
    687     /**
    688      * Updates the description of the selected display section.
    689      * @private
    690      */
    691     updateSelectedDisplayDescription_: function() {
    692       var resolution = $('display-options-resolution-selection');
    693       resolution.textContent = '';
    694       var orientation = $('display-options-orientation-selection');
    695       var orientationOptions = orientation.getElementsByTagName('option');
    696       for (var i = 0; i < orientationOptions.length; i++)
    697         orientationOptions.selected = false;
    698 
    699       if (this.mirroring_) {
    700         this.updateSelectedDisplaySectionMirroring_();
    701       } else if (this.focusedIndex_ == null ||
    702           this.displays_[this.focusedIndex_] == null) {
    703         this.updateSelectedDisplaySectionNoSelected_();
    704       } else {
    705         this.updateSelectedDisplaySectionForDisplay_(
    706             this.displays_[this.focusedIndex_]);
    707       }
    708     },
    709 
    710     /**
    711      * Clears the drawing area for display rectangles.
    712      * @private
    713      */
    714     resetDisplaysView_: function() {
    715       var displaysViewHost = $('display-options-displays-view-host');
    716       displaysViewHost.removeChild(displaysViewHost.firstChild);
    717       this.displaysView_ = document.createElement('div');
    718       this.displaysView_.id = 'display-options-displays-view';
    719       displaysViewHost.appendChild(this.displaysView_);
    720     },
    721 
    722     /**
    723      * Lays out the display rectangles for mirroring.
    724      * @private
    725      */
    726     layoutMirroringDisplays_: function() {
    727       // Offset pixels for secondary display rectangles. The offset includes the
    728       // border width.
    729       /** @const */ var MIRRORING_OFFSET_PIXELS = 3;
    730       // Always show two displays because there must be two displays when
    731       // the display_options is enabled.  Don't rely on displays_.length because
    732       // there is only one display from chrome's perspective in mirror mode.
    733       /** @const */ var MIN_NUM_DISPLAYS = 2;
    734       /** @const */ var MIRRORING_VERTICAL_MARGIN = 20;
    735 
    736       // The width/height should be same as the first display:
    737       var width = Math.ceil(this.displays_[0].width * this.visualScale_);
    738       var height = Math.ceil(this.displays_[0].height * this.visualScale_);
    739 
    740       var numDisplays = Math.max(MIN_NUM_DISPLAYS, this.displays_.length);
    741 
    742       var totalWidth = width + numDisplays * MIRRORING_OFFSET_PIXELS;
    743       var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS;
    744 
    745       this.displaysView_.style.height = totalHeight + 'px';
    746       this.displaysView_.classList.add(
    747           'display-options-displays-view-mirroring');
    748 
    749       // The displays should be centered.
    750       var offsetX =
    751           $('display-options-displays-view').offsetWidth / 2 - totalWidth / 2;
    752 
    753       for (var i = 0; i < numDisplays; i++) {
    754         var div = document.createElement('div');
    755         div.className = 'displays-display';
    756         div.style.top = i * MIRRORING_OFFSET_PIXELS + 'px';
    757         div.style.left = i * MIRRORING_OFFSET_PIXELS + offsetX + 'px';
    758         div.style.width = width + 'px';
    759         div.style.height = height + 'px';
    760         div.style.zIndex = i;
    761         // set 'display-mirrored' class for the background display rectangles.
    762         if (i != numDisplays - 1)
    763           div.classList.add('display-mirrored');
    764         this.displaysView_.appendChild(div);
    765       }
    766     },
    767 
    768     /**
    769      * Layouts the display rectangles according to the current layout_.
    770      * @private
    771      */
    772     layoutDisplays_: function() {
    773       var maxWidth = 0;
    774       var maxHeight = 0;
    775       var boundingBox = {left: 0, right: 0, top: 0, bottom: 0};
    776       for (var i = 0; i < this.displays_.length; i++) {
    777         var display = this.displays_[i];
    778         boundingBox.left = Math.min(boundingBox.left, display.x);
    779         boundingBox.right = Math.max(
    780             boundingBox.right, display.x + display.width);
    781         boundingBox.top = Math.min(boundingBox.top, display.y);
    782         boundingBox.bottom = Math.max(
    783             boundingBox.bottom, display.y + display.height);
    784         maxWidth = Math.max(maxWidth, display.width);
    785         maxHeight = Math.max(maxHeight, display.height);
    786       }
    787 
    788       // Make the margin around the bounding box.
    789       var areaWidth = boundingBox.right - boundingBox.left + maxWidth;
    790       var areaHeight = boundingBox.bottom - boundingBox.top + maxHeight;
    791 
    792       // Calculates the scale by the width since horizontal size is more strict.
    793       // TODO(mukai): Adds the check of vertical size in case.
    794       this.visualScale_ = Math.min(
    795           VISUAL_SCALE, this.displaysView_.offsetWidth / areaWidth);
    796 
    797       // Prepare enough area for thisplays_view by adding the maximum height.
    798       this.displaysView_.style.height =
    799           Math.ceil(areaHeight * this.visualScale_) + 'px';
    800 
    801       var boundingCenter = {
    802         x: Math.floor((boundingBox.right + boundingBox.left) *
    803             this.visualScale_ / 2),
    804         y: Math.floor((boundingBox.bottom + boundingBox.top) *
    805             this.visualScale_ / 2)
    806       };
    807 
    808       // Centering the bounding box of the display rectangles.
    809       var offset = {
    810         x: Math.floor(this.displaysView_.offsetWidth / 2 -
    811             (boundingBox.right + boundingBox.left) * this.visualScale_ / 2),
    812         y: Math.floor(this.displaysView_.offsetHeight / 2 -
    813             (boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2)
    814       };
    815 
    816       for (var i = 0; i < this.displays_.length; i++) {
    817         var display = this.displays_[i];
    818         var div = document.createElement('div');
    819         display.div = div;
    820 
    821         div.className = 'displays-display';
    822         if (i == this.focusedIndex_)
    823           div.classList.add('displays-focused');
    824 
    825         if (display.isPrimary) {
    826           this.primaryDisplay_ = display;
    827         } else {
    828           this.secondaryDisplay_ = display;
    829         }
    830         var displayNameContainer = document.createElement('div');
    831         displayNameContainer.textContent = display.name;
    832         div.appendChild(displayNameContainer);
    833         display.nameContainer = displayNameContainer;
    834         display.div.style.width =
    835             Math.floor(display.width * this.visualScale_) + 'px';
    836         var newHeight = Math.floor(display.height * this.visualScale_);
    837         display.div.style.height = newHeight + 'px';
    838         div.style.left =
    839             Math.floor(display.x * this.visualScale_) + offset.x + 'px';
    840         div.style.top =
    841             Math.floor(display.y * this.visualScale_) + offset.y + 'px';
    842         display.nameContainer.style.marginTop =
    843             (newHeight - display.nameContainer.offsetHeight) / 2 + 'px';
    844 
    845         div.onmousedown = this.onMouseDown_.bind(this);
    846         div.ontouchstart = this.onTouchStart_.bind(this);
    847 
    848         this.displaysView_.appendChild(div);
    849 
    850         // Set the margin top to place the display name at the middle of the
    851         // rectangle.  Note that this has to be done after it's added into the
    852         // |displaysView_|.  Otherwise its offsetHeight is yet 0.
    853         displayNameContainer.style.marginTop =
    854             (div.offsetHeight - displayNameContainer.offsetHeight) / 2 + 'px';
    855         display.originalPosition = {x: div.offsetLeft, y: div.offsetTop};
    856       }
    857     },
    858 
    859     /**
    860      * Called when the display arrangement has changed.
    861      * @param {boolean} mirroring Whether current mode is mirroring or not.
    862      * @param {Array.<options.DisplayInfo>} displays The list of the display
    863      *     information.
    864      * @param {options.SecondaryDisplayLayout} layout The layout strategy.
    865      * @param {number} offset The offset of the secondary display.
    866      * @private
    867      */
    868     onDisplayChanged_: function(mirroring, displays, layout, offset) {
    869       if (!this.visible)
    870         return;
    871 
    872       var hasExternal = false;
    873       for (var i = 0; i < displays.length; i++) {
    874         if (!displays[i].isInternal) {
    875           hasExternal = true;
    876           break;
    877         }
    878       }
    879 
    880       this.layout_ = layout;
    881 
    882       $('display-options-toggle-mirroring').textContent =
    883           loadTimeData.getString(
    884               mirroring ? 'stopMirroring' : 'startMirroring');
    885 
    886       // Focus to the first display next to the primary one when |displays| list
    887       // is updated.
    888       if (mirroring) {
    889         this.focusedIndex_ = null;
    890       } else if (this.mirroring_ != mirroring ||
    891                  this.displays_.length != displays.length) {
    892         this.focusedIndex_ = 0;
    893       }
    894 
    895       this.mirroring_ = mirroring;
    896       this.displays_ = displays;
    897 
    898       this.resetDisplaysView_();
    899       if (this.mirroring_)
    900         this.layoutMirroringDisplays_();
    901       else
    902         this.layoutDisplays_();
    903 
    904       this.updateSelectedDisplayDescription_();
    905     }
    906   };
    907 
    908   DisplayOptions.setDisplayInfo = function(
    909       mirroring, displays, layout, offset) {
    910     DisplayOptions.getInstance().onDisplayChanged_(
    911         mirroring, displays, layout, offset);
    912   };
    913 
    914   // Export
    915   return {
    916     DisplayOptions: DisplayOptions
    917   };
    918 });
    919