Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / options / chromeos / display_options.js
blob7400745cbc027d15c5421184a5a6e2bb9f3c7ae4
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.
5 cr.exportPath('options');
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;
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
39 /**
40  * Enumeration of multi display mode.  The value has to be same as the
41  * values in ash/display/display_manager..
42  * @enum {number}
43  */
44 options.MultiDisplayMode = {
45   EXTENDED: 0,
46   MIRRORING: 1,
47   UNIFIED: 2,
50 cr.define('options', function() {
51   var Page = cr.ui.pageManager.Page;
52   var PageManager = cr.ui.pageManager.PageManager;
54   // The scale ratio of the display rectangle to its original size.
55   /** @const */ var VISUAL_SCALE = 1 / 10;
57   // The number of pixels to share the edges between displays.
58   /** @const */ var MIN_OFFSET_OVERLAP = 5;
60   /**
61    * Calculates the bounds of |element| relative to the page.
62    * @param {HTMLElement} element The element to be known.
63    * @return {Object} The object for the bounds, with x, y, width, and height.
64    */
65   function getBoundsInPage(element) {
66     var bounds = {
67       x: element.offsetLeft,
68       y: element.offsetTop,
69       width: element.offsetWidth,
70       height: element.offsetHeight
71     };
72     var parent = element.offsetParent;
73     while (parent && parent != document.body) {
74       bounds.x += parent.offsetLeft;
75       bounds.y += parent.offsetTop;
76       parent = parent.offsetParent;
77     }
78     return bounds;
79   }
81   /**
82    * Gets the position of |point| to |rect|, left, right, top, or bottom.
83    * @param {Object} rect The base rectangle with x, y, width, and height.
84    * @param {Object} point The point to check the position.
85    * @return {options.SecondaryDisplayLayout} The position of the calculated
86    *     point.
87    */
88   function getPositionToRectangle(rect, point) {
89     // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of
90     // the rect, and decides which area the display should reside.
91     var diagonalSlope = rect.height / rect.width;
92     var topDownIntercept = rect.y - rect.x * diagonalSlope;
93     var bottomUpIntercept = rect.y + rect.height + rect.x * diagonalSlope;
95     if (point.y > topDownIntercept + point.x * diagonalSlope) {
96       if (point.y > bottomUpIntercept - point.x * diagonalSlope)
97         return options.SecondaryDisplayLayout.BOTTOM;
98       else
99         return options.SecondaryDisplayLayout.LEFT;
100     } else {
101       if (point.y > bottomUpIntercept - point.x * diagonalSlope)
102         return options.SecondaryDisplayLayout.RIGHT;
103       else
104         return options.SecondaryDisplayLayout.TOP;
105     }
106   }
108   /**
109    * Encapsulated handling of the 'Display' page.
110    * @constructor
111    * @extends {cr.ui.pageManager.Page}
112    */
113   function DisplayOptions() {
114     Page.call(this, 'display',
115               loadTimeData.getString('displayOptionsPageTabTitle'),
116               'display-options-page');
117   }
119   cr.addSingletonGetter(DisplayOptions);
121   DisplayOptions.prototype = {
122     __proto__: Page.prototype,
124     /**
125      * Whether the current output status is mirroring displays or not.
126      * @private
127      */
128     mirroring_: false,
130     /**
131      * Whether the unified desktop is enable or not.
132      * @private
133      */
134     unifiedDesktopEnabled_: false,
136     /**
137      * Whether the unified desktop option should be present.
138      * @private
139      */
140     showUnifiedDesktopOption_: false,
142     /**
143      * The current secondary display layout.
144      * @private
145      */
146     layout_: options.SecondaryDisplayLayout.RIGHT,
148     /**
149      * The array of current output displays.  It also contains the display
150      * rectangles currently rendered on screen.
151      * @type {Array<options.DisplayInfo>}
152      * @private
153      */
154     displays_: [],
156     /**
157      * The index for the currently focused display in the options UI.  null if
158      * no one has focus.
159      * @private
160      */
161     focusedIndex_: null,
163     /**
164      * The primary display.
165      * @private
166      */
167     primaryDisplay_: null,
169     /**
170      * The secondary display.
171      * @private
172      */
173     secondaryDisplay_: null,
175     /**
176      * The container div element which contains all of the display rectangles.
177      * @private
178      */
179     displaysView_: null,
181     /**
182      * The scale factor of the actual display size to the drawn display
183      * rectangle size.
184      * @private
185      */
186     visualScale_: VISUAL_SCALE,
188     /**
189      * The location where the last touch event happened.  This is used to
190      * prevent unnecessary dragging events happen.  Set to null unless it's
191      * during touch events.
192      * @private
193      */
194     lastTouchLocation_: null,
196     /**
197      * Whether the display settings can be shown.
198      * @private
199      */
200     enabled_: true,
202     /** @override */
203     initializePage: function() {
204       Page.prototype.initializePage.call(this);
206       $('display-options-select-mirroring').onchange = (function() {
207         this.mirroring_ =
208             $('display-options-select-mirroring').value == 'mirroring';
209         chrome.send('setMirroring', [this.mirroring_]);
210       }).bind(this);
212       var container = $('display-options-displays-view-host');
213       container.onmousemove = this.onMouseMove_.bind(this);
214       window.addEventListener('mouseup', this.endDragging_.bind(this), true);
215       container.ontouchmove = this.onTouchMove_.bind(this);
216       container.ontouchend = this.endDragging_.bind(this);
218       $('display-options-set-primary').onclick = (function() {
219         chrome.send('setPrimary', [this.displays_[this.focusedIndex_].id]);
220       }).bind(this);
221       $('display-options-resolution-selection').onchange = (function(ev) {
222         var display = this.displays_[this.focusedIndex_];
223         var resolution = display.resolutions[ev.target.value];
224         chrome.send('setDisplayMode', [display.id, resolution]);
225       }).bind(this);
226       $('display-options-orientation-selection').onchange = (function(ev) {
227         var displayIndex =
228           (this.focusedIndex_ === null) ? 0 : this.focusedIndex_;
229         chrome.send('setOrientation', [this.displays_[displayIndex].id,
230                                        ev.target.value]);
231       }).bind(this);
232       $('display-options-color-profile-selection').onchange = (function(ev) {
233         chrome.send('setColorProfile', [this.displays_[this.focusedIndex_].id,
234                                         ev.target.value]);
235       }).bind(this);
236       $('selected-display-start-calibrating-overscan').onclick = (function() {
237         // Passes the target display ID. Do not specify it through URL hash,
238         // we do not care back/forward.
239         var displayOverscan = options.DisplayOverscan.getInstance();
240         displayOverscan.setDisplayId(this.displays_[this.focusedIndex_].id);
241         PageManager.showPageByName('displayOverscan');
242         chrome.send('coreOptionsUserMetricsAction',
243                     ['Options_DisplaySetOverscan']);
244       }).bind(this);
246       $('display-options-done').onclick = function() {
247         PageManager.closeOverlay();
248       };
250       $('display-options-toggle-unified-desktop').onclick = (function() {
251         this.unifiedDesktopEnabled_ = !this.unifiedDesktopEnabled_;
252         chrome.send('setUnifiedDesktopEnabled',
253                     [this.unifiedDesktopEnabled_]);
254       }).bind(this);
255     },
257     /** @override */
258     didShowPage: function() {
259       var optionTitles = document.getElementsByClassName(
260           'selected-display-option-title');
261       var maxSize = 0;
262       for (var i = 0; i < optionTitles.length; i++)
263         maxSize = Math.max(maxSize, optionTitles[i].clientWidth);
264       for (var i = 0; i < optionTitles.length; i++)
265         optionTitles[i].style.width = maxSize + 'px';
266       chrome.send('getDisplayInfo');
267     },
269     /** @override */
270     canShowPage: function() {
271       return this.enabled_;
272     },
274     /**
275      * Enables or disables the page. When disabled, the page will not be able to
276      * open, and will close if currently opened.
277      * @param {boolean} enabled Whether the page should be enabled.
278      * @param {boolean} showUnifiedDesktop Whether the unified desktop option
279      *     should be present.
280      */
281     setEnabled: function(enabled, showUnifiedDesktop) {
282       if (this.enabled_ == enabled &&
283           this.showUnifiedDesktopOption_ == showUnifiedDesktop) {
284         return;
285       }
286       this.enabled_ = enabled;
287       this.showUnifiedDesktopOption_ = showUnifiedDesktop;
288       if (!enabled && this.visible)
289         PageManager.closeOverlay();
290     },
292     /**
293      * Mouse move handler for dragging display rectangle.
294      * @param {Event} e The mouse move event.
295      * @private
296      */
297     onMouseMove_: function(e) {
298       return this.processDragging_(e, {x: e.pageX, y: e.pageY});
299     },
301     /**
302      * Touch move handler for dragging display rectangle.
303      * @param {Event} e The touch move event.
304      * @private
305      */
306     onTouchMove_: function(e) {
307       if (e.touches.length != 1)
308         return true;
310       var touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY};
311       // Touch move events happen even if the touch location doesn't change, but
312       // it doesn't need to process the dragging.  Since sometimes the touch
313       // position changes slightly even though the user doesn't think to move
314       // the finger, very small move is just ignored.
315       /** @const */ var IGNORABLE_TOUCH_MOVE_PX = 1;
316       var xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x);
317       var yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y);
318       if (xDiff <= IGNORABLE_TOUCH_MOVE_PX &&
319           yDiff <= IGNORABLE_TOUCH_MOVE_PX) {
320         return true;
321       }
323       this.lastTouchLocation_ = touchLocation;
324       return this.processDragging_(e, touchLocation);
325     },
327     /**
328      * Mouse down handler for dragging display rectangle.
329      * @param {Event} e The mouse down event.
330      * @private
331      */
332     onMouseDown_: function(e) {
333       if (this.mirroring_)
334         return true;
336       if (e.button != 0)
337         return true;
339       e.preventDefault();
340       var target = assertInstanceof(e.target, HTMLElement);
341       return this.startDragging_(target, {x: e.pageX, y: e.pageY});
342     },
344     /**
345      * Touch start handler for dragging display rectangle.
346      * @param {Event} e The touch start event.
347      * @private
348      */
349     onTouchStart_: function(e) {
350       if (this.mirroring_)
351         return true;
353       if (e.touches.length != 1)
354         return false;
356       e.preventDefault();
357       var touch = e.touches[0];
358       this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY};
359       var target = assertInstanceof(e.target, HTMLElement);
360       return this.startDragging_(target, this.lastTouchLocation_);
361     },
363     /**
364      * Collects the current data and sends it to Chrome.
365      * @private
366      */
367     applyResult_: function() {
368       // Offset is calculated from top or left edge.
369       var primary = this.primaryDisplay_;
370       var secondary = this.secondaryDisplay_;
371       var offset;
372       if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
373           this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
374         offset = secondary.div.offsetTop - primary.div.offsetTop;
375       } else {
376         offset = secondary.div.offsetLeft - primary.div.offsetLeft;
377       }
378       chrome.send('setDisplayLayout',
379                   [this.layout_, offset / this.visualScale_]);
380     },
382     /**
383      * Snaps the region [point, width] to [basePoint, baseWidth] if
384      * the [point, width] is close enough to the base's edge.
385      * @param {number} point The starting point of the region.
386      * @param {number} width The width of the region.
387      * @param {number} basePoint The starting point of the base region.
388      * @param {number} baseWidth The width of the base region.
389      * @return {number} The moved point.  Returns point itself if it doesn't
390      *     need to snap to the edge.
391      * @private
392      */
393     snapToEdge_: function(point, width, basePoint, baseWidth) {
394       // If the edge of the regions is smaller than this, it will snap to the
395       // base's edge.
396       /** @const */ var SNAP_DISTANCE_PX = 16;
398       var startDiff = Math.abs(point - basePoint);
399       var endDiff = Math.abs(point + width - (basePoint + baseWidth));
400       // Prefer the closer one if both edges are close enough.
401       if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff)
402         return basePoint;
403       else if (endDiff < SNAP_DISTANCE_PX)
404         return basePoint + baseWidth - width;
406       return point;
407     },
409     /**
410      * Processes the actual dragging of display rectangle.
411      * @param {Event} e The event which triggers this drag.
412      * @param {Object} eventLocation The location where the event happens.
413      * @private
414      */
415     processDragging_: function(e, eventLocation) {
416       if (!this.dragging_)
417         return true;
419       var index = -1;
420       for (var i = 0; i < this.displays_.length; i++) {
421         if (this.displays_[i] == this.dragging_.display) {
422           index = i;
423           break;
424         }
425       }
426       if (index < 0)
427         return true;
429       e.preventDefault();
431       // Note that current code of moving display-rectangles doesn't work
432       // if there are >=3 displays.  This is our assumption for M21.
433       // TODO(mukai): Fix the code to allow >=3 displays.
434       var newPosition = {
435         x: this.dragging_.originalLocation.x +
436             (eventLocation.x - this.dragging_.eventLocation.x),
437         y: this.dragging_.originalLocation.y +
438             (eventLocation.y - this.dragging_.eventLocation.y)
439       };
441       var baseDiv = this.dragging_.display.isPrimary ?
442           this.secondaryDisplay_.div : this.primaryDisplay_.div;
443       var draggingDiv = this.dragging_.display.div;
445       newPosition.x = this.snapToEdge_(newPosition.x, draggingDiv.offsetWidth,
446                                        baseDiv.offsetLeft, baseDiv.offsetWidth);
447       newPosition.y = this.snapToEdge_(newPosition.y, draggingDiv.offsetHeight,
448                                        baseDiv.offsetTop, baseDiv.offsetHeight);
450       var newCenter = {
451         x: newPosition.x + draggingDiv.offsetWidth / 2,
452         y: newPosition.y + draggingDiv.offsetHeight / 2
453       };
455       var baseBounds = {
456         x: baseDiv.offsetLeft,
457         y: baseDiv.offsetTop,
458         width: baseDiv.offsetWidth,
459         height: baseDiv.offsetHeight
460       };
461       switch (getPositionToRectangle(baseBounds, newCenter)) {
462         case options.SecondaryDisplayLayout.RIGHT:
463           this.layout_ = this.dragging_.display.isPrimary ?
464               options.SecondaryDisplayLayout.LEFT :
465               options.SecondaryDisplayLayout.RIGHT;
466           break;
467         case options.SecondaryDisplayLayout.LEFT:
468           this.layout_ = this.dragging_.display.isPrimary ?
469               options.SecondaryDisplayLayout.RIGHT :
470               options.SecondaryDisplayLayout.LEFT;
471           break;
472         case options.SecondaryDisplayLayout.TOP:
473           this.layout_ = this.dragging_.display.isPrimary ?
474               options.SecondaryDisplayLayout.BOTTOM :
475               options.SecondaryDisplayLayout.TOP;
476           break;
477         case options.SecondaryDisplayLayout.BOTTOM:
478           this.layout_ = this.dragging_.display.isPrimary ?
479               options.SecondaryDisplayLayout.TOP :
480               options.SecondaryDisplayLayout.BOTTOM;
481           break;
482       }
484       if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
485           this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
486         if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight)
487           this.layout_ = this.dragging_.display.isPrimary ?
488               options.SecondaryDisplayLayout.TOP :
489               options.SecondaryDisplayLayout.BOTTOM;
490         else if (newPosition.y + draggingDiv.offsetHeight <
491                  baseDiv.offsetTop)
492           this.layout_ = this.dragging_.display.isPrimary ?
493               options.SecondaryDisplayLayout.BOTTOM :
494               options.SecondaryDisplayLayout.TOP;
495       } else {
496         if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth)
497           this.layout_ = this.dragging_.display.isPrimary ?
498               options.SecondaryDisplayLayout.LEFT :
499               options.SecondaryDisplayLayout.RIGHT;
500         else if (newPosition.x + draggingDiv.offsetWidth <
501                    baseDiv.offsetLeft)
502           this.layout_ = this.dragging_.display.isPrimary ?
503               options.SecondaryDisplayLayout.RIGHT :
504               options.SecondaryDisplayLayout.LEFT;
505       }
507       var layoutToBase;
508       if (!this.dragging_.display.isPrimary) {
509         layoutToBase = this.layout_;
510       } else {
511         switch (this.layout_) {
512           case options.SecondaryDisplayLayout.RIGHT:
513             layoutToBase = options.SecondaryDisplayLayout.LEFT;
514             break;
515           case options.SecondaryDisplayLayout.LEFT:
516             layoutToBase = options.SecondaryDisplayLayout.RIGHT;
517             break;
518           case options.SecondaryDisplayLayout.TOP:
519             layoutToBase = options.SecondaryDisplayLayout.BOTTOM;
520             break;
521           case options.SecondaryDisplayLayout.BOTTOM:
522             layoutToBase = options.SecondaryDisplayLayout.TOP;
523             break;
524         }
525       }
527       switch (layoutToBase) {
528         case options.SecondaryDisplayLayout.RIGHT:
529           draggingDiv.style.left =
530               baseDiv.offsetLeft + baseDiv.offsetWidth + 'px';
531           draggingDiv.style.top = newPosition.y + 'px';
532           break;
533         case options.SecondaryDisplayLayout.LEFT:
534           draggingDiv.style.left =
535               baseDiv.offsetLeft - draggingDiv.offsetWidth + 'px';
536           draggingDiv.style.top = newPosition.y + 'px';
537           break;
538         case options.SecondaryDisplayLayout.TOP:
539           draggingDiv.style.top =
540               baseDiv.offsetTop - draggingDiv.offsetHeight + 'px';
541           draggingDiv.style.left = newPosition.x + 'px';
542           break;
543         case options.SecondaryDisplayLayout.BOTTOM:
544           draggingDiv.style.top =
545               baseDiv.offsetTop + baseDiv.offsetHeight + 'px';
546           draggingDiv.style.left = newPosition.x + 'px';
547           break;
548       }
550       return false;
551     },
553     /**
554      * start dragging of a display rectangle.
555      * @param {HTMLElement} target The event target.
556      * @param {Object} eventLocation The object to hold the location where
557      *     this event happens.
558      * @private
559      */
560     startDragging_: function(target, eventLocation) {
561       this.focusedIndex_ = null;
562       for (var i = 0; i < this.displays_.length; i++) {
563         var display = this.displays_[i];
564         if (display.div == target ||
565             (target.offsetParent && target.offsetParent == display.div)) {
566           this.focusedIndex_ = i;
567           break;
568         }
569       }
571       for (var i = 0; i < this.displays_.length; i++) {
572         var display = this.displays_[i];
573         display.div.className = 'displays-display';
574         if (i != this.focusedIndex_)
575           continue;
577         display.div.classList.add('displays-focused');
578         if (this.displays_.length > 1) {
579           this.dragging_ = {
580             display: display,
581             originalLocation: {
582               x: display.div.offsetLeft, y: display.div.offsetTop
583             },
584             eventLocation: eventLocation
585           };
586         }
587       }
589       this.updateSelectedDisplayDescription_();
590       return false;
591     },
593     /**
594      * finish the current dragging of displays.
595      * @param {Event} e The event which triggers this.
596      * @private
597      */
598     endDragging_: function(e) {
599       this.lastTouchLocation_ = null;
600       if (this.dragging_) {
601         // Make sure the dragging location is connected.
602         var baseDiv = this.dragging_.display.isPrimary ?
603             this.secondaryDisplay_.div : this.primaryDisplay_.div;
604         var draggingDiv = this.dragging_.display.div;
605         if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
606             this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
607           var top = Math.max(draggingDiv.offsetTop,
608                              baseDiv.offsetTop - draggingDiv.offsetHeight +
609                              MIN_OFFSET_OVERLAP);
610           top = Math.min(top,
611                          baseDiv.offsetTop + baseDiv.offsetHeight -
612                          MIN_OFFSET_OVERLAP);
613           draggingDiv.style.top = top + 'px';
614         } else {
615           var left = Math.max(draggingDiv.offsetLeft,
616                               baseDiv.offsetLeft - draggingDiv.offsetWidth +
617                               MIN_OFFSET_OVERLAP);
618           left = Math.min(left,
619                           baseDiv.offsetLeft + baseDiv.offsetWidth -
620                           MIN_OFFSET_OVERLAP);
621           draggingDiv.style.left = left + 'px';
622         }
623         var originalPosition = this.dragging_.display.originalPosition;
624         if (originalPosition.x != draggingDiv.offsetLeft ||
625             originalPosition.y != draggingDiv.offsetTop)
626           this.applyResult_();
627         this.dragging_ = null;
628       }
629       this.updateSelectedDisplayDescription_();
630       return false;
631     },
633     /**
634      * Updates the description of selected display section for mirroring mode.
635      * @private
636      */
637     updateSelectedDisplaySectionMirroring_: function() {
638       $('display-configuration-arrow').hidden = true;
639       $('display-options-set-primary').disabled = true;
640       $('display-options-select-mirroring').disabled = false;
641       $('selected-display-start-calibrating-overscan').disabled = true;
642       var display = this.displays_[0];
643       var orientation = $('display-options-orientation-selection');
644       orientation.disabled = false;
645       var orientationOptions = orientation.getElementsByTagName('option');
646       orientationOptions[display.orientation].selected = true;
647       $('selected-display-name').textContent =
648           loadTimeData.getString('mirroringDisplay');
649       var resolution = $('display-options-resolution-selection');
650       var option = document.createElement('option');
651       option.value = 'default';
652       option.textContent = display.width + 'x' + display.height;
653       resolution.appendChild(option);
654       resolution.disabled = true;
655     },
657     /**
658      * Updates the description of selected display section when no display is
659      * selected.
660      * @private
661      */
662     updateSelectedDisplaySectionNoSelected_: function() {
663       $('display-configuration-arrow').hidden = true;
664       $('display-options-set-primary').disabled = true;
665       $('display-options-select-mirroring').disabled = true;
666       $('selected-display-start-calibrating-overscan').disabled = true;
667       $('display-options-orientation-selection').disabled = true;
668       $('selected-display-name').textContent = '';
669       var resolution = $('display-options-resolution-selection');
670       resolution.appendChild(document.createElement('option'));
671       resolution.disabled = true;
672     },
674     /**
675      * Updates the description of selected display section for the selected
676      * display.
677      * @param {Object} display The selected display object.
678      * @private
679      */
680     updateSelectedDisplaySectionForDisplay_: function(display) {
681       var arrow = $('display-configuration-arrow');
682       arrow.hidden = false;
683       // Adding 1 px to the position to fit the border line and the border in
684       // arrow precisely.
685       arrow.style.top = $('display-configurations').offsetTop -
686           arrow.offsetHeight / 2 + 'px';
687       arrow.style.left = display.div.offsetLeft +
688           display.div.offsetWidth / 2 - arrow.offsetWidth / 2 + 'px';
690       $('display-options-set-primary').disabled = display.isPrimary;
691       $('display-options-select-mirroring').disabled =
692           (this.displays_.length <= 1 && !this.unifiedDesktopEnabled_);
693       $('selected-display-start-calibrating-overscan').disabled =
694           display.isInternal;
696       var orientation = $('display-options-orientation-selection');
697       orientation.disabled = this.unifiedDesktopEnabled_;
699       var orientationOptions = orientation.getElementsByTagName('option');
700       orientationOptions[display.orientation].selected = true;
702       $('selected-display-name').textContent = display.name;
704       var resolution = $('display-options-resolution-selection');
705       if (display.resolutions.length <= 1) {
706         var option = document.createElement('option');
707         option.value = 'default';
708         option.textContent = display.width + 'x' + display.height;
709         option.selected = true;
710         resolution.appendChild(option);
711         resolution.disabled = true;
712       } else {
713         var previousOption;
714         for (var i = 0; i < display.resolutions.length; i++) {
715           var option = document.createElement('option');
716           option.value = i;
717           option.textContent = display.resolutions[i].width + 'x' +
718               display.resolutions[i].height;
719           if (display.resolutions[i].isBest) {
720             option.textContent += ' ' +
721                 loadTimeData.getString('annotateBest');
722           } else if (display.resolutions[i].isNative) {
723             option.textContent += ' ' +
724                 loadTimeData.getString('annotateNative');
725           }
726           if (display.resolutions[i].deviceScaleFactor && previousOption &&
727               previousOption.textContent == option.textContent) {
728             option.textContent +=
729                 ' (' + display.resolutions[i].deviceScaleFactor + 'x)';
730           }
731           option.selected = display.resolutions[i].selected;
732           resolution.appendChild(option);
733           previousOption = option;
734         }
735         resolution.disabled = (display.resolutions.length <= 1);
736       }
738       if (display.availableColorProfiles.length <= 1) {
739         $('selected-display-color-profile-row').hidden = true;
740       } else {
741         $('selected-display-color-profile-row').hidden = false;
742         var profiles = $('display-options-color-profile-selection');
743         profiles.innerHTML = '';
744         for (var i = 0; i < display.availableColorProfiles.length; i++) {
745           var option = document.createElement('option');
746           var colorProfile = display.availableColorProfiles[i];
747           option.value = colorProfile.profileId;
748           option.textContent = colorProfile.name;
749           option.selected = (
750               display.colorProfile == colorProfile.profileId);
751           profiles.appendChild(option);
752         }
753       }
754     },
756     /**
757      * Updates the description of the selected display section.
758      * @private
759      */
760     updateSelectedDisplayDescription_: function() {
761       var resolution = $('display-options-resolution-selection');
762       resolution.textContent = '';
763       var orientation = $('display-options-orientation-selection');
764       var orientationOptions = orientation.getElementsByTagName('option');
765       for (var i = 0; i < orientationOptions.length; i++)
766         orientationOptions.selected = false;
768       if (this.mirroring_) {
769         this.updateSelectedDisplaySectionMirroring_();
770       } else if (this.focusedIndex_ == null ||
771           this.displays_[this.focusedIndex_] == null) {
772         this.updateSelectedDisplaySectionNoSelected_();
773       } else {
774         this.updateSelectedDisplaySectionForDisplay_(
775             this.displays_[this.focusedIndex_]);
776       }
777     },
779     /**
780      * Clears the drawing area for display rectangles.
781      * @private
782      */
783     resetDisplaysView_: function() {
784       var displaysViewHost = $('display-options-displays-view-host');
785       displaysViewHost.removeChild(displaysViewHost.firstChild);
786       this.displaysView_ = document.createElement('div');
787       this.displaysView_.id = 'display-options-displays-view';
788       displaysViewHost.appendChild(this.displaysView_);
789     },
791     /**
792      * Lays out the display rectangles for mirroring.
793      * @private
794      */
795     layoutMirroringDisplays_: function() {
796       // Offset pixels for secondary display rectangles. The offset includes the
797       // border width.
798       /** @const */ var MIRRORING_OFFSET_PIXELS = 3;
799       // Always show two displays because there must be two displays when
800       // the display_options is enabled.  Don't rely on displays_.length because
801       // there is only one display from chrome's perspective in mirror mode.
802       /** @const */ var MIN_NUM_DISPLAYS = 2;
803       /** @const */ var MIRRORING_VERTICAL_MARGIN = 20;
805       // The width/height should be same as the first display:
806       var width = Math.ceil(this.displays_[0].width * this.visualScale_);
807       var height = Math.ceil(this.displays_[0].height * this.visualScale_);
809       var numDisplays = Math.max(MIN_NUM_DISPLAYS, this.displays_.length);
811       var totalWidth = width + numDisplays * MIRRORING_OFFSET_PIXELS;
812       var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS;
814       this.displaysView_.style.height = totalHeight + 'px';
816       // The displays should be centered.
817       var offsetX =
818           $('display-options-displays-view').offsetWidth / 2 - totalWidth / 2;
820       for (var i = 0; i < numDisplays; i++) {
821         var div = document.createElement('div');
822         div.className = 'displays-display';
823         div.style.top = i * MIRRORING_OFFSET_PIXELS + 'px';
824         div.style.left = i * MIRRORING_OFFSET_PIXELS + offsetX + 'px';
825         div.style.width = width + 'px';
826         div.style.height = height + 'px';
827         div.style.zIndex = i;
828         // set 'display-mirrored' class for the background display rectangles.
829         if (i != numDisplays - 1)
830           div.classList.add('display-mirrored');
831         this.displaysView_.appendChild(div);
832       }
833     },
835     /**
836      * Creates a div element representing the specified display.
837      * @param {Object} display The display object.
838      * @param {boolean} focused True if it's focused.
839      * @private
840      */
841     createDisplayRectangle_: function(display, focused) {
842       var div = document.createElement('div');
843       display.div = div;
844       div.className = 'displays-display';
845       if (focused)
846         div.classList.add('displays-focused');
848       // div needs to be added to the DOM tree first, otherwise offsetHeight for
849       // nameContainer below cannot be computed.
850       this.displaysView_.appendChild(div);
852       var nameContainer = document.createElement('div');
853       nameContainer.textContent = display.name;
854       div.appendChild(nameContainer);
855       div.style.width = Math.floor(display.width * this.visualScale_) + 'px';
856       var newHeight = Math.floor(display.height * this.visualScale_);
857       div.style.height = newHeight + 'px';
858       nameContainer.style.marginTop =
859           (newHeight - nameContainer.offsetHeight) / 2 + 'px';
861       div.onmousedown = this.onMouseDown_.bind(this);
862       div.ontouchstart = this.onTouchStart_.bind(this);
863       return div;
864     },
866     /**
867      * Layouts the display rectangles according to the current layout_.
868      * @private
869      */
870     layoutDisplays_: function() {
871       var maxWidth = 0;
872       var maxHeight = 0;
873       var boundingBox = {left: 0, right: 0, top: 0, bottom: 0};
874       this.primaryDisplay_ = null;
875       this.secondaryDisplay_ = null;
876       var focusedDisplay = null;
877       for (var i = 0; i < this.displays_.length; i++) {
878         var display = this.displays_[i];
879         if (display.isPrimary)
880           this.primaryDisplay_ = display;
881         else
882           this.secondaryDisplay_ = display;
883         if (i == this.focusedIndex_)
884           focusedDisplay = display;
886         boundingBox.left = Math.min(boundingBox.left, display.x);
887         boundingBox.right = Math.max(
888             boundingBox.right, display.x + display.width);
889         boundingBox.top = Math.min(boundingBox.top, display.y);
890         boundingBox.bottom = Math.max(
891             boundingBox.bottom, display.y + display.height);
892         maxWidth = Math.max(maxWidth, display.width);
893         maxHeight = Math.max(maxHeight, display.height);
894       }
895       if (!this.primaryDisplay_)
896         return;
898       // Make the margin around the bounding box.
899       var areaWidth = boundingBox.right - boundingBox.left + maxWidth;
900       var areaHeight = boundingBox.bottom - boundingBox.top + maxHeight;
902       // Calculates the scale by the width since horizontal size is more strict.
903       // TODO(mukai): Adds the check of vertical size in case.
904       this.visualScale_ = Math.min(
905           VISUAL_SCALE, this.displaysView_.offsetWidth / areaWidth);
907       // Prepare enough area for thisplays_view by adding the maximum height.
908       this.displaysView_.style.height =
909           Math.ceil(areaHeight * this.visualScale_) + 'px';
911       // Centering the bounding box of the display rectangles.
912       var offset = {
913         x: Math.floor(this.displaysView_.offsetWidth / 2 -
914             (boundingBox.right + boundingBox.left) * this.visualScale_ / 2),
915         y: Math.floor(this.displaysView_.offsetHeight / 2 -
916             (boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2)
917       };
919       // Layouting the display rectangles. First layout the primaryDisplay and
920       // then layout the secondary which is attaching to the primary.
921       var primaryDiv = this.createDisplayRectangle_(
922           this.primaryDisplay_, this.primaryDisplay_ == focusedDisplay);
923       primaryDiv.style.left =
924           Math.floor(this.primaryDisplay_.x * this.visualScale_) +
925               offset.x + 'px';
926       primaryDiv.style.top =
927           Math.floor(this.primaryDisplay_.y * this.visualScale_) +
928               offset.y + 'px';
929       this.primaryDisplay_.originalPosition = {
930         x: primaryDiv.offsetLeft, y: primaryDiv.offsetTop};
932       if (this.secondaryDisplay_) {
933         var secondaryDiv = this.createDisplayRectangle_(
934             this.secondaryDisplay_, this.secondaryDisplay_ == focusedDisplay);
935         // Don't trust the secondary display's x or y, because it may cause a
936         // 1px gap due to rounding, which will create a fake update on end
937         // dragging. See crbug.com/386401
938         switch (this.layout_) {
939         case options.SecondaryDisplayLayout.TOP:
940           secondaryDiv.style.left =
941               Math.floor(this.secondaryDisplay_.x * this.visualScale_) +
942               offset.x + 'px';
943           secondaryDiv.style.top =
944               primaryDiv.offsetTop - secondaryDiv.offsetHeight + 'px';
945           break;
946         case options.SecondaryDisplayLayout.RIGHT:
947           secondaryDiv.style.left =
948               primaryDiv.offsetLeft + primaryDiv.offsetWidth + 'px';
949           secondaryDiv.style.top =
950               Math.floor(this.secondaryDisplay_.y * this.visualScale_) +
951               offset.y + 'px';
952           break;
953         case options.SecondaryDisplayLayout.BOTTOM:
954           secondaryDiv.style.left =
955               Math.floor(this.secondaryDisplay_.x * this.visualScale_) +
956               offset.x + 'px';
957           secondaryDiv.style.top =
958               primaryDiv.offsetTop + primaryDiv.offsetHeight + 'px';
959           break;
960         case options.SecondaryDisplayLayout.LEFT:
961           secondaryDiv.style.left =
962               primaryDiv.offsetLeft - secondaryDiv.offsetWidth + 'px';
963           secondaryDiv.style.top =
964               Math.floor(this.secondaryDisplay_.y * this.visualScale_) +
965               offset.y + 'px';
966           break;
967         }
968         this.secondaryDisplay_.originalPosition = {
969           x: secondaryDiv.offsetLeft, y: secondaryDiv.offsetTop};
970       }
971     },
973     /**
974      * Called when the display arrangement has changed.
975      * @param {options.MultiDisplayMode} mode multi display mode.
976      * @param {Array<options.DisplayInfo>} displays The list of the display
977      *     information.
978      * @param {options.SecondaryDisplayLayout} layout The layout strategy.
979      * @param {number} offset The offset of the secondary display.
980      * @private
981      */
982     onDisplayChanged_: function(mode, displays, layout, offset) {
983       if (!this.visible)
984         return;
986       var hasExternal = false;
987       for (var i = 0; i < displays.length; i++) {
988         if (!displays[i].isInternal) {
989           hasExternal = true;
990           break;
991         }
992       }
994       this.layout_ = layout;
996       var mirroring = mode == options.MultiDisplayMode.MIRRORING;
997       var unifiedDesktopEnabled = mode == options.MultiDisplayMode.UNIFIED;
999       // Focus to the first display next to the primary one when |displays| list
1000       // is updated.
1001       if (mirroring) {
1002         this.focusedIndex_ = null;
1003       } else if (this.mirroring_ != mirroring ||
1004                  this.unifiedDesktopEnabled_ != unifiedDesktopEnabled ||
1005                  this.displays_.length != displays.length) {
1006         this.focusedIndex_ = 0;
1007       }
1009       this.mirroring_ = mirroring;
1010       this.unifiedDesktopEnabled_ = unifiedDesktopEnabled;
1011       this.displays_ = displays;
1013       this.resetDisplaysView_();
1014       if (this.mirroring_)
1015         this.layoutMirroringDisplays_();
1016       else
1017         this.layoutDisplays_();
1019       $('display-options-unified-desktop').hidden =
1020           !this.showUnifiedDesktopOption_;
1022       $('display-options-toggle-unified-desktop').checked =
1023           this.unifiedDesktopEnabled_;
1025       var disableUnifiedDesktopOption =
1026            (this.mirroring_ ||
1027             (!this.unifiedDesktopEnabled_ &&
1028               this.displays_.length == 1));
1030       $('display-options-toggle-unified-desktop').disabled =
1031           disableUnifiedDesktopOption;
1033       this.updateSelectedDisplayDescription_();
1034     }
1035   };
1037   DisplayOptions.setDisplayInfo = function(
1038       mode, displays, layout, offset) {
1039     DisplayOptions.getInstance().onDisplayChanged_(
1040         mode, displays, layout, offset);
1041   };
1043   // Export
1044   return {
1045     DisplayOptions: DisplayOptions
1046   };