blob: 04ce89d8e03d9af415be38627417fb91f2de0c21 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* 'settings-display' is the settings subpage for display settings.
*/
/**
* The types of Night Light automatic schedule. The values of the enum values
* are synced with the pref "prefs.ash.night_light.schedule_type".
* @enum {number}
*/
const NightLightScheduleType = {
NEVER: 0,
SUNSET_TO_SUNRISE: 1,
CUSTOM: 2,
};
cr.define('settings.display', function() {
const systemDisplayApi =
/** @type {!SystemDisplay} */ (chrome.system.display);
return {
systemDisplayApi: systemDisplayApi,
};
});
Polymer({
is: 'settings-display',
behaviors: [
I18nBehavior,
PrefsBehavior,
],
properties: {
/**
* @type {!chrome.settingsPrivate.PrefObject}
* @private
*/
selectedModePref_: {
type: Object,
value: function() {
return {
key: 'fakeDisplaySliderPref',
type: chrome.settingsPrivate.PrefType.NUMBER,
value: 0,
};
},
},
/**
* Array of displays.
* @type {!Array<!chrome.system.display.DisplayUnitInfo>}
*/
displays: Array,
/**
* Array of display layouts.
* @type {!Array<!chrome.system.display.DisplayLayout>}
*/
layouts: Array,
/**
* String listing the ids in displays. Used to observe changes to the
* display configuration (i.e. when a display is added or removed).
*/
displayIds: {type: String, observer: 'onDisplayIdsChanged_'},
/** Primary display id */
primaryDisplayId: String,
/** @type {!chrome.system.display.DisplayUnitInfo|undefined} */
selectedDisplay: Object,
/** Id passed to the overscan dialog. */
overscanDisplayId: {
type: String,
notify: true,
},
/** Ids for mirroring destination displays. */
mirroringDestinationIds: Array,
/** @private {!Array<number>} Mode index values for slider. */
modeValues_: Array,
/** @private */
unifiedDesktopAvailable_: {
type: Boolean,
value: function() {
return loadTimeData.getBoolean('unifiedDesktopAvailable');
}
},
/** @private */
multiMirroringAvailable_: {
type: Boolean,
value: function() {
return loadTimeData.getBoolean('multiMirroringAvailable');
}
},
/** @private */
nightLightFeatureEnabled_: {
type: Boolean,
value: function() {
return loadTimeData.getBoolean('nightLightFeatureEnabled');
}
},
/** @private */
unifiedDesktopMode_: {
type: Boolean,
value: false,
},
/** @private */
scheduleTypesList_: {
type: Array,
value: function() {
return [
{
name: loadTimeData.getString('displayNightLightScheduleNever'),
value: NightLightScheduleType.NEVER
},
{
name: loadTimeData.getString(
'displayNightLightScheduleSunsetToSunRise'),
value: NightLightScheduleType.SUNSET_TO_SUNRISE
},
{
name: loadTimeData.getString('displayNightLightScheduleCustom'),
value: NightLightScheduleType.CUSTOM
}
];
},
},
/** @private */
shouldOpenCustomScheduleCollapse_: {
type: Boolean,
value: false,
},
/** @private */
nightLightScheduleSubLabel_: String,
},
observers: [
'updateNightLightScheduleSettings_(prefs.ash.night_light.schedule_type.*,' +
' prefs.ash.night_light.enabled.*)',
],
/** @private {number} Selected mode index received from chrome. */
currentSelectedModeIndex_: -1,
/**
* Listener for chrome.system.display.onDisplayChanged events.
* @type {function(void)|undefined}
* @private
*/
displayChangedListener_: undefined,
/** @override */
attached: function() {
this.displayChangedListener_ =
this.displayChangedListener_ || this.getDisplayInfo_.bind(this);
settings.display.systemDisplayApi.onDisplayChanged.addListener(
this.displayChangedListener_);
this.getDisplayInfo_();
},
/** @override */
detached: function() {
settings.display.systemDisplayApi.onDisplayChanged.removeListener(
assert(this.displayChangedListener_));
this.currentSelectedModeIndex_ = -1;
},
/**
* Shows or hides the overscan dialog.
* @param {boolean} showOverscan
* @private
*/
showOverscanDialog_: function(showOverscan) {
if (showOverscan) {
this.$.displayOverscan.open();
this.$.displayOverscan.focus();
} else {
this.$.displayOverscan.close();
}
},
/** @private */
onDisplayIdsChanged_: function() {
// Close any overscan dialog (which will cancel any overscan operation)
// if displayIds changes.
this.showOverscanDialog_(false);
},
/** @private */
getDisplayInfo_: function() {
/** @type {chrome.system.display.GetInfoFlags} */ const flags = {
singleUnified: true
};
settings.display.systemDisplayApi.getInfo(
flags, this.displayInfoFetched_.bind(this));
},
/**
* @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
* @private
*/
displayInfoFetched_: function(displays) {
if (!displays.length)
return;
settings.display.systemDisplayApi.getDisplayLayout(
this.displayLayoutFetched_.bind(this, displays));
if (this.isMirrored_(displays))
this.mirroringDestinationIds = displays[0].mirroringDestinationIds;
else
this.mirroringDestinationIds = [];
},
/**
* @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
* @param {!Array<!chrome.system.display.DisplayLayout>} layouts
* @private
*/
displayLayoutFetched_: function(displays, layouts) {
this.layouts = layouts;
this.displays = displays;
this.updateDisplayInfo_();
},
/**
* @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
* @return {number}
* @private
*/
getSelectedModeIndex_: function(selectedDisplay) {
for (let i = 0; i < selectedDisplay.modes.length; ++i) {
if (selectedDisplay.modes[i].isSelected)
return i;
}
return 0;
},
/**
* We need to call this explicitly rather than relying on change events
* so that we can control the update order.
* @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
* @private
*/
setSelectedDisplay_: function(selectedDisplay) {
// Set |currentSelectedModeIndex_| and |modeValues_| first since these
// are not used directly in data binding.
const numModes = selectedDisplay.modes.length;
if (numModes == 0) {
this.modeValues_ = [];
this.currentSelectedModeIndex_ = 0;
} else {
this.modeValues_ = Array.from(Array(numModes).keys());
this.currentSelectedModeIndex_ =
this.getSelectedModeIndex_(selectedDisplay);
}
// Set |selectedDisplay| first since only the resolution slider depends
// on |selectedModePref_|.
this.selectedDisplay = selectedDisplay;
this.set('selectedModePref_.value', this.currentSelectedModeIndex_);
},
/**
* Returns true if external touch devices are connected and the current
* display is not an internal display. If the feature is not enabled via the
* switch, this will return false.
* @param {!chrome.system.display.DisplayUnitInfo} display Display being
* checked for touch support.
* @return {boolean}
* @private
*/
showTouchCalibrationSetting_: function(display) {
return !display.isInternal &&
loadTimeData.getBoolean('hasExternalTouchDevice') &&
loadTimeData.getBoolean('enableTouchCalibrationSetting');
},
/**
* Returns true if the overscan setting should be shown for |display|.
* @param {!chrome.system.display.DisplayUnitInfo} display
* @return {boolean}
* @private
*/
showOverscanSetting_: function(display) {
return !display.isInternal;
},
/**
* @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
* @return {boolean}
* @private
*/
hasMultipleDisplays_: function(displays) {
return displays.length > 1;
},
/**
* Returns false if the display select menu has to be hidden.
* @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
* @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
* @return {boolean}
* @private
*/
showDisplaySelectMenu_: function(displays, selectedDisplay) {
return displays.length > 1 && !selectedDisplay.isPrimary;
},
/**
* Returns the select menu index indicating whether the display currently is
* primary or extended.
* @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
* @param {string} primaryDisplayId
* @return {number} Returns 0 if the display is primary else returns 1.
* @private
*/
getDisplaySelectMenuIndex_: function(selectedDisplay, primaryDisplayId) {
if (selectedDisplay && selectedDisplay.id == primaryDisplayId)
return 0;
return 1;
},
/**
* Returns the i18n string for the text to be used for mirroring settings.
* @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
* @return {string} i18n string for mirroring settings text.
* @private
*/
getDisplayMirrorText_: function(displays) {
return this.i18n('displayMirror', displays[0].name);
},
/**
* @param {boolean} unifiedDesktopAvailable
* @param {boolean} unifiedDesktopMode
* @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
* @return {boolean}
* @private
*/
showUnifiedDesktop_: function(
unifiedDesktopAvailable, unifiedDesktopMode, displays) {
return unifiedDesktopMode ||
(unifiedDesktopAvailable && displays.length > 1 &&
!this.isMirrored_(displays));
},
/**
* @param {boolean} unifiedDesktopMode
* @return {string}
* @private
*/
getUnifiedDesktopText_: function(unifiedDesktopMode) {
return this.i18n(unifiedDesktopMode ? 'toggleOn' : 'toggleOff');
},
/**
* @param {boolean} unifiedDesktopMode
* @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
* @return {boolean}
* @private
*/
showMirror_: function(unifiedDesktopMode, displays) {
return this.isMirrored_(displays) ||
(!unifiedDesktopMode &&
((this.multiMirroringAvailable_ && displays.length > 1) ||
displays.length == 2));
},
/**
* @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
* @return {boolean}
* @private
*/
isMirrored_: function(displays) {
return displays.length > 0 && !!displays[0].mirroringSourceId;
},
/**
* @param {!chrome.system.display.DisplayUnitInfo} display
* @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
* @return {boolean}
* @private
*/
isSelected_: function(display, selectedDisplay) {
return display.id == selectedDisplay.id;
},
/**
* @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
* @return {boolean}
* @private
*/
enableSetResolution_: function(selectedDisplay) {
return selectedDisplay.modes.length > 1;
},
/**
* @return {string}
* @private
*/
getResolutionText_: function() {
if (this.selectedDisplay.modes.length == 0 ||
this.currentSelectedModeIndex_ == -1) {
// If currentSelectedModeIndex_ == -1, selectedDisplay and
// |selectedModePref_.value| are not in sync.
return this.i18n(
'displayResolutionText', this.selectedDisplay.bounds.width.toString(),
this.selectedDisplay.bounds.height.toString());
}
const mode = this.selectedDisplay.modes[
/** @type {number} */ (this.selectedModePref_.value)];
assert(mode);
const best =
this.selectedDisplay.isInternal ? mode.uiScale == 1.0 : mode.isNative;
const widthStr = mode.width.toString();
const heightStr = mode.height.toString();
if (best)
return this.i18n('displayResolutionTextBest', widthStr, heightStr);
else if (mode.isNative)
return this.i18n('displayResolutionTextNative', widthStr, heightStr);
return this.i18n('displayResolutionText', widthStr, heightStr);
},
/**
* @param {!{detail: string}} e |e.detail| is the id of the selected display.
* @private
*/
onSelectDisplay_: function(e) {
const id = e.detail;
for (let i = 0; i < this.displays.length; ++i) {
const display = this.displays[i];
if (id == display.id) {
if (this.selectedDisplay != display)
this.setSelectedDisplay_(display);
return;
}
}
},
/**
* Handles event when a display tab is selected.
* @param {!{detail: !{item: !{displayId: string}}}} e
* @private
*/
onSelectDisplayTab_: function(e) {
this.onSelectDisplay_({detail: e.detail.item.displayId});
},
/**
* Handles event when a touch calibration option is selected.
* @param {!Event} e
* @private
*/
onTouchCalibrationTap_: function(e) {
settings.display.systemDisplayApi.showNativeTouchCalibration(
this.selectedDisplay.id);
},
/**
* Handles the event when an option from display select menu is selected.
* @param {!{target: !HTMLSelectElement}} e
* @private
*/
updatePrimaryDisplay_: function(e) {
/** @type {number} */ const PRIMARY_DISP_IDX = 0;
if (!this.selectedDisplay)
return;
if (this.selectedDisplay.id == this.primaryDisplayId)
return;
if (e.target.value != PRIMARY_DISP_IDX)
return;
/** @type {!chrome.system.display.DisplayProperties} */ const properties = {
isPrimary: true
};
settings.display.systemDisplayApi.setDisplayProperties(
this.selectedDisplay.id, properties,
this.setPropertiesCallback_.bind(this));
},
/**
* Triggered when the 'change' event for the selected mode slider is
* triggered. This only occurs when the value is committed (i.e. not while
* the slider is being dragged).
* @private
*/
onSelectedModeChange_: function() {
if (this.currentSelectedModeIndex_ == -1 ||
this.currentSelectedModeIndex_ == this.selectedModePref_.value) {
// Don't change the selected display mode until we have received an update
// from Chrome and the mode differs from the current mode.
return;
}
/** @type {!chrome.system.display.DisplayProperties} */ const properties = {
displayMode: this.selectedDisplay.modes[
/** @type {number} */ (this.selectedModePref_.value)]
};
settings.display.systemDisplayApi.setDisplayProperties(
this.selectedDisplay.id, properties,
this.setPropertiesCallback_.bind(this));
},
/**
* @param {!Event} event
* @private
*/
onOrientationChange_: function(event) {
const target = /** @type {!HTMLSelectElement} */ (event.target);
/** @type {!chrome.system.display.DisplayProperties} */ const properties = {
rotation: parseInt(target.value, 10)
};
settings.display.systemDisplayApi.setDisplayProperties(
this.selectedDisplay.id, properties,
this.setPropertiesCallback_.bind(this));
},
/** @private */
onMirroredTap_: function(event) {
// Blur the control so that when the transition animation completes and the
// UI is focused, the control does not receive focus. crbug.com/785070
event.target.blur();
let id = '';
/** @type {!chrome.system.display.DisplayProperties} */
const properties = {};
if (this.isMirrored_(this.displays)) {
id = this.primaryDisplayId;
properties.mirroringSourceId = '';
} else {
// Set the mirroringSourceId of the secondary (first non-primary) display.
for (let i = 0; i < this.displays.length; ++i) {
const display = this.displays[i];
if (display.id != this.primaryDisplayId) {
id = display.id;
break;
}
}
properties.mirroringSourceId = this.primaryDisplayId;
}
settings.display.systemDisplayApi.setDisplayProperties(
id, properties, this.setPropertiesCallback_.bind(this));
},
/** @private */
onUnifiedDesktopTap_: function() {
/** @type {!chrome.system.display.DisplayProperties} */ const properties = {
isUnified: !this.unifiedDesktopMode_,
};
settings.display.systemDisplayApi.setDisplayProperties(
this.primaryDisplayId, properties,
this.setPropertiesCallback_.bind(this));
},
/**
* @param {!Event} e
* @private
*/
onOverscanTap_: function(e) {
e.preventDefault();
this.overscanDisplayId = this.selectedDisplay.id;
this.showOverscanDialog_(true);
},
/** @private */
onCloseOverscanDialog_: function() {
cr.ui.focusWithoutInk(assert(this.$$('#overscan button')));
},
/** @private */
updateDisplayInfo_: function() {
let displayIds = '';
let primaryDisplay = undefined;
let selectedDisplay = undefined;
for (let i = 0; i < this.displays.length; ++i) {
const display = this.displays[i];
if (displayIds)
displayIds += ',';
displayIds += display.id;
if (display.isPrimary && !primaryDisplay)
primaryDisplay = display;
if (this.selectedDisplay && display.id == this.selectedDisplay.id)
selectedDisplay = display;
}
this.displayIds = displayIds;
this.primaryDisplayId = (primaryDisplay && primaryDisplay.id) || '';
selectedDisplay = selectedDisplay || primaryDisplay ||
(this.displays && this.displays[0]);
this.setSelectedDisplay_(selectedDisplay);
this.unifiedDesktopMode_ = !!primaryDisplay && primaryDisplay.isUnified;
this.$.displayLayout.updateDisplays(
this.displays, this.layouts, this.mirroringDestinationIds);
},
/** @private */
setPropertiesCallback_: function() {
if (chrome.runtime.lastError) {
console.error(
'setDisplayProperties Error: ' + chrome.runtime.lastError.message);
}
},
/**
* Invoked when the status of Night Light or its schedule type are changed, in
* order to update the schedule settings, such as whether to show the custom
* schedule slider, and the schedule sub label.
* @private
*/
updateNightLightScheduleSettings_: function() {
const scheduleType = this.getPref('ash.night_light.schedule_type').value;
this.shouldOpenCustomScheduleCollapse_ =
scheduleType == NightLightScheduleType.CUSTOM;
if (scheduleType == NightLightScheduleType.SUNSET_TO_SUNRISE) {
const nightLightStatus = this.getPref('ash.night_light.enabled').value;
this.nightLightScheduleSubLabel_ = nightLightStatus ?
this.i18n('displayNightLightOffAtSunrise') :
this.i18n('displayNightLightOnAtSunset');
} else {
this.nightLightScheduleSubLabel_ = '';
}
},
});