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