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